forked from cadence/Carbon
		
	Reactive state for groups and rooms
This commit is contained in:
		
							parent
							
								
									f42ea1493b
								
							
						
					
					
						commit
						dd0b14720e
					
				
					 17 changed files with 671 additions and 162 deletions
				
			
		| 
						 | 
				
			
			@ -2,10 +2,10 @@
 | 
			
		|||
<html>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="static/main.css?static=d352e5de1f">
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="static/main.css?static=79b6afceb8">
 | 
			
		||||
    <script type="module" src="static/groups.js?static=2cc7f0daf8"></script>
 | 
			
		||||
    <script type="module" src="static/chat-input.js?static=a90499fdac"></script>
 | 
			
		||||
    <script type="module" src="static/room-picker.js?static=c7bdd4a2f3"></script>
 | 
			
		||||
    <script type="module" src="static/room-picker.js?static=596e719ff8"></script>
 | 
			
		||||
    <title>Carbon</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,19 +25,22 @@
 | 
			
		|||
     borderopacity="1.0"
 | 
			
		||||
     inkscape:pageopacity="0.0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:zoom="5.6568542"
 | 
			
		||||
     inkscape:cx="30.795644"
 | 
			
		||||
     inkscape:cy="43.802047"
 | 
			
		||||
     inkscape:zoom="22.627417"
 | 
			
		||||
     inkscape:cx="27.665561"
 | 
			
		||||
     inkscape:cy="33.324951"
 | 
			
		||||
     inkscape:document-units="px"
 | 
			
		||||
     inkscape:current-layer="layer1"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     showgrid="true"
 | 
			
		||||
     units="px"
 | 
			
		||||
     inkscape:window-width="1440"
 | 
			
		||||
     inkscape:window-height="879"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="0"
 | 
			
		||||
     inkscape:window-maximized="1"
 | 
			
		||||
     showborder="false">
 | 
			
		||||
     showborder="true"
 | 
			
		||||
     inkscape:snap-bbox="true"
 | 
			
		||||
     inkscape:bbox-nodes="true"
 | 
			
		||||
     inkscape:snap-grids="true">
 | 
			
		||||
    <inkscape:grid
 | 
			
		||||
       type="xygrid"
 | 
			
		||||
       id="grid824" />
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +64,7 @@
 | 
			
		|||
     transform="translate(36.739286,-225.97828)">
 | 
			
		||||
    <path
 | 
			
		||||
       style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
			
		||||
       d="m -33.035119,229.41786 h 5.067509 c 0.868277,0 1.567287,0.69901 1.567287,1.56728 v 1.88109 c 0,0.86828 -0.69901,1.56729 -1.567287,1.56729 h -2.157093 l -1.322916,1.86351 -1.322917,-1.86351 h -0.264583 c -0.868277,0 -1.567287,-0.69901 -1.567287,-1.56729 v -1.88109 c 0,-0.86827 0.69901,-1.56728 1.567287,-1.56728 z"
 | 
			
		||||
       d="m -33.035119,228.8887 h 5.067509 c 0.868277,0 1.567287,0.69901 1.567287,1.56728 v 2.66473 c 0,0.86828 -0.69901,1.56729 -1.567287,1.56729 h -2.157093 l -1.322916,1.86351 -1.322917,-1.86351 h -0.264583 c -0.868277,0 -1.567287,-0.69901 -1.567287,-1.56729 v -2.66473 c 0,-0.86827 0.69901,-1.56728 1.567287,-1.56728 z"
 | 
			
		||||
       id="rect820"
 | 
			
		||||
       inkscape:connector-curvature="0"
 | 
			
		||||
       sodipodi:nodetypes="ssssscccssss" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
		 Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB  | 
| 
						 | 
				
			
			@ -31,7 +31,7 @@ body {
 | 
			
		|||
	width: 240px;
 | 
			
		||||
	font-size: 20px;
 | 
			
		||||
	font-weight: 500;
 | 
			
		||||
	overflow-y: scroll;
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
	scrollbar-width: thin;
 | 
			
		||||
	scrollbar-color: #202224 #2f3135;
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -40,16 +40,20 @@ body {
 | 
			
		|||
.c-room {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
	padding: 6px 8px;
 | 
			
		||||
	margin: 2px 0;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.c-room:hover {
 | 
			
		||||
	background-color: #393c42;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
.c-room:not(.c-room--active):hover {
 | 
			
		||||
	background-color: #393c42;
 | 
			
		||||
}
 | 
			
		||||
.c-room--active {
 | 
			
		||||
	background-color: #42454a;
 | 
			
		||||
}
 | 
			
		||||
.c-room__icon {
 | 
			
		||||
	width: 36px;
 | 
			
		||||
	height: 36px;
 | 
			
		||||
	width: 32px;
 | 
			
		||||
	height: 32px;
 | 
			
		||||
	background-color: #bbb;
 | 
			
		||||
	margin-right: 8px;
 | 
			
		||||
	border-radius: 50%;
 | 
			
		||||
| 
						 | 
				
			
			@ -121,8 +125,9 @@ body {
 | 
			
		|||
.c-group-marker {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 5px;
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: translateY(8px);
 | 
			
		||||
	transition: transform ease 0.12s;
 | 
			
		||||
	transition: transform ease 0.12s, opacity ease-out 0.12s;
 | 
			
		||||
	height: 46px;
 | 
			
		||||
	width: 6px;
 | 
			
		||||
	background-color: #ccc;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,25 +1,30 @@
 | 
			
		|||
import {q, ElemJS, ejs} from "./basic.js"
 | 
			
		||||
import {store} from "./store/store.js"
 | 
			
		||||
 | 
			
		||||
class ActiveGroupMarker extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super(q("#c-group-marker"))
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	follow(group) {
 | 
			
		||||
		this.style("transform", `translateY(${group.element.offsetTop}px)`)
 | 
			
		||||
	render() {
 | 
			
		||||
		if (store.activeGroup.exists()) {
 | 
			
		||||
			const group = store.activeGroup.value()
 | 
			
		||||
			this.style("opacity", 1)
 | 
			
		||||
			this.style("transform", `translateY(${group.element.offsetTop}px)`)
 | 
			
		||||
		} else {
 | 
			
		||||
			this.style("opacity", 0)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const activeGroupMarker = new ActiveGroupMarker()
 | 
			
		||||
 | 
			
		||||
class Group extends ElemJS {
 | 
			
		||||
	constructor(groups, data) {
 | 
			
		||||
	constructor(key, data) {
 | 
			
		||||
		super("div")
 | 
			
		||||
 | 
			
		||||
		this.groups = groups
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.active = false
 | 
			
		||||
 | 
			
		||||
		this.on("click", this.onClick.bind(this))
 | 
			
		||||
 | 
			
		||||
		this.class("c-group")
 | 
			
		||||
		this.child(
 | 
			
		||||
| 
						 | 
				
			
			@ -29,29 +34,45 @@ class Group extends ElemJS {
 | 
			
		|||
			),
 | 
			
		||||
			ejs("div").class("c-group__name").text(this.data.name)
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		this.on("click", this.onClick.bind(this))
 | 
			
		||||
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setActive(active) {
 | 
			
		||||
		this.active = active
 | 
			
		||||
	render() {
 | 
			
		||||
		const active = store.activeGroup.value() === this
 | 
			
		||||
		this.element.classList[active ? "add" : "remove"]("c-group--active")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClick() {
 | 
			
		||||
		this.groups.setGroup(this)
 | 
			
		||||
		store.activeGroup.set(this)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Room extends ElemJS {
 | 
			
		||||
	constructor(name) {
 | 
			
		||||
	constructor(key, data) {
 | 
			
		||||
		super("div")
 | 
			
		||||
 | 
			
		||||
		this.name = name
 | 
			
		||||
		this.data = data
 | 
			
		||||
 | 
			
		||||
		this.class("c-room")
 | 
			
		||||
		this.child(
 | 
			
		||||
			ejs("div").class("c-room__icon"),
 | 
			
		||||
			ejs("div").class("c-room__name").text(this.name)
 | 
			
		||||
			ejs("div").class("c-room__name").text(this.data.name)
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		this.on("click", this.onClick.bind(this))
 | 
			
		||||
		store.activeRoom.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClick() {
 | 
			
		||||
		store.activeRoom.set(this)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		const active = store.activeRoom.value() === this
 | 
			
		||||
		this.element.classList[active ? "add" : "remove"]("c-room--active")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,20 +80,38 @@ class Rooms extends ElemJS {
 | 
			
		|||
	constructor() {
 | 
			
		||||
		super(q("#c-rooms"))
 | 
			
		||||
 | 
			
		||||
		this.roomData = []
 | 
			
		||||
		this.rooms = []
 | 
			
		||||
 | 
			
		||||
		store.rooms.subscribe("askAdd", this.askAdd.bind(this))
 | 
			
		||||
		store.rooms.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setRooms(rooms) {
 | 
			
		||||
		this.rooms = rooms
 | 
			
		||||
		this.render()
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const room = new Room(key, data)
 | 
			
		||||
		store.rooms.addEnd(key, room)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		this.clearChildren()
 | 
			
		||||
		for (const room of this.rooms) {
 | 
			
		||||
			this.child(new Room(room.name))
 | 
			
		||||
		let first = null
 | 
			
		||||
		// set room list
 | 
			
		||||
		store.rooms.forEach((id, room) => {
 | 
			
		||||
			if (room.value().data.group === store.activeGroup.value()) {
 | 
			
		||||
				if (!first) first = room.value()
 | 
			
		||||
				this.child(room.value())
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
		// if needed, change the active room to be an item in the room list
 | 
			
		||||
		if (!store.activeRoom.exists() || store.activeRoom.value().data.group !== store.activeGroup.value()) {
 | 
			
		||||
			if (first) {
 | 
			
		||||
				store.activeRoom.set(first)
 | 
			
		||||
			} else {
 | 
			
		||||
				store.activeRoom.delete()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -81,59 +120,73 @@ const rooms = new Rooms()
 | 
			
		|||
class Groups extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super(q("#c-groups-list"))
 | 
			
		||||
		this.groupData = [
 | 
			
		||||
			{name: "Directs", icon: "/static/directs.svg", rooms: [
 | 
			
		||||
				{name: "riley"},
 | 
			
		||||
				{name: "BadAtNames"},
 | 
			
		||||
				{name: "lynxano"},
 | 
			
		||||
				{name: "quarky"},
 | 
			
		||||
				{name: "lepton"},
 | 
			
		||||
				{name: "ash"},
 | 
			
		||||
				{name: "mewmew"},
 | 
			
		||||
				{name: "Toniob"},
 | 
			
		||||
				{name: "cockandball"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Channels", icon: "/static/channels.svg", rooms: [
 | 
			
		||||
				{name: "Carbon brainstorming"},
 | 
			
		||||
				{name: "Bibliogram"},
 | 
			
		||||
				{name: "Monsters Inc Debate Hall"},
 | 
			
		||||
				{name: "DRB clan"},
 | 
			
		||||
				{name: "mettaton simp zone"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Fediverse Drama Museum", rooms: [
 | 
			
		||||
				{name: "witches"},
 | 
			
		||||
				{name: "snouts"},
 | 
			
		||||
				{name: "monads"},
 | 
			
		||||
				{name: "radical"},
 | 
			
		||||
				{name: "blobcat"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Epicord", rooms: [
 | 
			
		||||
				{name: "main"},
 | 
			
		||||
				{name: "gaming"},
 | 
			
		||||
				{name: "inhalers"},
 | 
			
		||||
				{name: "minecraft"},
 | 
			
		||||
				{name: "osu"},
 | 
			
		||||
				{name: "covid"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Invidious", rooms: [
 | 
			
		||||
			]}
 | 
			
		||||
		]
 | 
			
		||||
		this.groups = []
 | 
			
		||||
		this.render()
 | 
			
		||||
		this.setGroup(this.children[0])
 | 
			
		||||
 | 
			
		||||
		store.groups.subscribe("askAdd", this.askAdd.bind(this))
 | 
			
		||||
		store.groups.subscribe("addItem", this.addItem.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setGroup(group) {
 | 
			
		||||
		rooms.setRooms(group.data.rooms)
 | 
			
		||||
		this.groups.forEach(g => g.setActive(g === group))
 | 
			
		||||
		activeGroupMarker.follow(group)
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const group = new Group(key, data)
 | 
			
		||||
		store.groups.addEnd(key, group)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		this.groups = this.groupData.map(data => new Group(this, data))
 | 
			
		||||
		for (const group of this.groups) {
 | 
			
		||||
			this.child(group)
 | 
			
		||||
		}
 | 
			
		||||
	addItem(event, key) {
 | 
			
		||||
		this.child(store.groups.get(key).value())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
const groups = new Groups()
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{
 | 
			
		||||
		id: "directs",
 | 
			
		||||
		name: "Directs",
 | 
			
		||||
		icon: "/static/directs.svg"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "channels",
 | 
			
		||||
		name: "Channels",
 | 
			
		||||
		icon: "/static/channels.svg"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "123",
 | 
			
		||||
		name: "Fediverse Drama Museum"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "456",
 | 
			
		||||
		name: "Epicord"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "789",
 | 
			
		||||
		name: "Invidious"
 | 
			
		||||
	}
 | 
			
		||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{id: "001", name: "riley", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "002", name: "BadAtNames", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "003", name: "lynxano", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "004", name: "quarky", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "005", name: "lepton", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "006", name: "ash", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "007", name: "mewmew", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "008", name: "Toniob", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "009", name: "cockandball", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "010", name: "Carbon brainstorming", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "011", name: "Bibliogram", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "012", name: "Monsters Inc Debate Hall", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "013", name: "DRB clan", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "014", name: "mettaton simp zone", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "015", name: "witches", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "016", name: "snouts", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "017", name: "monads", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "018", name: "radical", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "019", name: "blobcat", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "020", name: "main", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "021", name: "gaming", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "022", name: "inhalers", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "023", name: "minecraft", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "024", name: "osu", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "025", name: "covid", group: store.groups.get("456").value()}
 | 
			
		||||
].forEach(data => store.rooms.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										36
									
								
								build/static/store/Subscribable.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								build/static/store/Subscribable.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
class Subscribable {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.events = {
 | 
			
		||||
			addSelf: [],
 | 
			
		||||
			editSelf: [],
 | 
			
		||||
			removeSelf: [],
 | 
			
		||||
			changeSelf: []
 | 
			
		||||
		}
 | 
			
		||||
		this.eventDeps = {
 | 
			
		||||
			addSelf: ["changeSelf"],
 | 
			
		||||
			editSelf: ["changeSelf"],
 | 
			
		||||
			removeSelf: ["changeSelf"],
 | 
			
		||||
			changeSelf: []
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subscribe(event, callback) {
 | 
			
		||||
		if (this.events[event]) {
 | 
			
		||||
			this.events[event].push(callback)
 | 
			
		||||
		} else {
 | 
			
		||||
			throw new Error(`Cannot subscribe to non-existent event ${event}, available events are: ${Object.keys(this.events).join(", ")}`)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsubscribe(event, callback) {
 | 
			
		||||
		this.events[event].push(callback)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	broadcast(event, data) {
 | 
			
		||||
		this.eventDeps[event].concat(event).forEach(eventName => {
 | 
			
		||||
			this.events[eventName].forEach(f => f(event, data))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {Subscribable}
 | 
			
		||||
							
								
								
									
										77
									
								
								build/static/store/SubscribeMapList.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								build/static/store/SubscribeMapList.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
 | 
			
		||||
class SubscribeMapList extends Subscribable {
 | 
			
		||||
	constructor(inner) {
 | 
			
		||||
		super()
 | 
			
		||||
		this.inner = inner
 | 
			
		||||
		Object.assign(this.events, {
 | 
			
		||||
			addItem: [],
 | 
			
		||||
			removeItem: [],
 | 
			
		||||
			editItem: [],
 | 
			
		||||
			changeItem: [],
 | 
			
		||||
			askAdd: []
 | 
			
		||||
		})
 | 
			
		||||
		Object.assign(this.eventDeps, {
 | 
			
		||||
			addItem: ["changeItem"],
 | 
			
		||||
			removeItem: ["changeItem"],
 | 
			
		||||
			editItem: ["changeItem"],
 | 
			
		||||
			changeItem: [],
 | 
			
		||||
			askAdd: []
 | 
			
		||||
		})
 | 
			
		||||
		this.map = new Map()
 | 
			
		||||
		this.list = []
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	has(key) {
 | 
			
		||||
		return this.map.has(key) && this.map.get(key).exists()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get(key) {
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
			return this.map.get(key)
 | 
			
		||||
		} else {
 | 
			
		||||
			const item = new this.inner()
 | 
			
		||||
			this.map.set(key, item)
 | 
			
		||||
			return item
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forEach(f) {
 | 
			
		||||
		this.list.forEach(key => f(key, this.get(key)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	askAdd(key, data) {
 | 
			
		||||
		this.broadcast("askAdd", {key, data})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addStart(key, value) {
 | 
			
		||||
		this._add(key, value, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addEnd(key, value) {
 | 
			
		||||
		this._add(key, value, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_add(key, value, start) {
 | 
			
		||||
		let s
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
			const exists = this.map.get(key).exists()
 | 
			
		||||
			s = this.map.get(key).set(value)
 | 
			
		||||
			if (exists) {
 | 
			
		||||
				this.broadcast("changeItem", key)
 | 
			
		||||
			} else {
 | 
			
		||||
				this.broadcast("addItem", key)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			s = new this.inner().set(value)
 | 
			
		||||
			this.map.set(key, s)
 | 
			
		||||
			if (start) this.list.unshift(key)
 | 
			
		||||
			else this.list.push(key)
 | 
			
		||||
			this.broadcast("addItem", key)
 | 
			
		||||
		}
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {SubscribeMapList}
 | 
			
		||||
							
								
								
									
										47
									
								
								build/static/store/SubscribeValue.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								build/static/store/SubscribeValue.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
 | 
			
		||||
class SubscribeValue extends Subscribable {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super()
 | 
			
		||||
		this.hasData = false
 | 
			
		||||
		this.data = null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exists() {
 | 
			
		||||
		return this.hasData
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value() {
 | 
			
		||||
		if (this.hasData) return this.data
 | 
			
		||||
		else return null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set(data) {
 | 
			
		||||
		const exists = this.exists()
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.hasData = true
 | 
			
		||||
		if (exists) {
 | 
			
		||||
			this.broadcast("editSelf", this.data)
 | 
			
		||||
		} else {
 | 
			
		||||
			this.broadcast("addSelf", this.data)
 | 
			
		||||
		}
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	edit(f) {
 | 
			
		||||
		if (this.exists()) {
 | 
			
		||||
			f(this.data)
 | 
			
		||||
			this.set(this.data)
 | 
			
		||||
		} else {
 | 
			
		||||
			throw new Error("Tried to edit a SubscribeValue that had no value")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete() {
 | 
			
		||||
		this.hasData = false
 | 
			
		||||
		this.broadcast("removeSelf")
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {SubscribeValue}
 | 
			
		||||
							
								
								
									
										13
									
								
								build/static/store/store.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								build/static/store/store.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
import {SubscribeMapList} from "./SubscribeMapList.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
 | 
			
		||||
const store = {
 | 
			
		||||
	groups: new SubscribeMapList(SubscribeValue),
 | 
			
		||||
	rooms: new SubscribeMapList(SubscribeValue),
 | 
			
		||||
	activeGroup: new SubscribeValue(),
 | 
			
		||||
	activeRoom: new SubscribeValue()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.store = store
 | 
			
		||||
 | 
			
		||||
export {store}
 | 
			
		||||
| 
						 | 
				
			
			@ -25,19 +25,22 @@
 | 
			
		|||
     borderopacity="1.0"
 | 
			
		||||
     inkscape:pageopacity="0.0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:zoom="5.6568542"
 | 
			
		||||
     inkscape:cx="30.795644"
 | 
			
		||||
     inkscape:cy="43.802047"
 | 
			
		||||
     inkscape:zoom="22.627417"
 | 
			
		||||
     inkscape:cx="27.665561"
 | 
			
		||||
     inkscape:cy="33.324951"
 | 
			
		||||
     inkscape:document-units="px"
 | 
			
		||||
     inkscape:current-layer="layer1"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     showgrid="true"
 | 
			
		||||
     units="px"
 | 
			
		||||
     inkscape:window-width="1440"
 | 
			
		||||
     inkscape:window-height="879"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="0"
 | 
			
		||||
     inkscape:window-maximized="1"
 | 
			
		||||
     showborder="false">
 | 
			
		||||
     showborder="true"
 | 
			
		||||
     inkscape:snap-bbox="true"
 | 
			
		||||
     inkscape:bbox-nodes="true"
 | 
			
		||||
     inkscape:snap-grids="true">
 | 
			
		||||
    <inkscape:grid
 | 
			
		||||
       type="xygrid"
 | 
			
		||||
       id="grid824" />
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +64,7 @@
 | 
			
		|||
     transform="translate(36.739286,-225.97828)">
 | 
			
		||||
    <path
 | 
			
		||||
       style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
			
		||||
       d="m -33.035119,229.41786 h 5.067509 c 0.868277,0 1.567287,0.69901 1.567287,1.56728 v 1.88109 c 0,0.86828 -0.69901,1.56729 -1.567287,1.56729 h -2.157093 l -1.322916,1.86351 -1.322917,-1.86351 h -0.264583 c -0.868277,0 -1.567287,-0.69901 -1.567287,-1.56729 v -1.88109 c 0,-0.86827 0.69901,-1.56728 1.567287,-1.56728 z"
 | 
			
		||||
       d="m -33.035119,228.8887 h 5.067509 c 0.868277,0 1.567287,0.69901 1.567287,1.56728 v 2.66473 c 0,0.86828 -0.69901,1.56729 -1.567287,1.56729 h -2.157093 l -1.322916,1.86351 -1.322917,-1.86351 h -0.264583 c -0.868277,0 -1.567287,-0.69901 -1.567287,-1.56729 v -2.66473 c 0,-0.86827 0.69901,-1.56728 1.567287,-1.56728 z"
 | 
			
		||||
       id="rect820"
 | 
			
		||||
       inkscape:connector-curvature="0"
 | 
			
		||||
       sodipodi:nodetypes="ssssscccssss" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
		 Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB  | 
| 
						 | 
				
			
			@ -1,25 +1,30 @@
 | 
			
		|||
import {q, ElemJS, ejs} from "./basic.js"
 | 
			
		||||
import {store} from "./store/store.js"
 | 
			
		||||
 | 
			
		||||
class ActiveGroupMarker extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super(q("#c-group-marker"))
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	follow(group) {
 | 
			
		||||
		this.style("transform", `translateY(${group.element.offsetTop}px)`)
 | 
			
		||||
	render() {
 | 
			
		||||
		if (store.activeGroup.exists()) {
 | 
			
		||||
			const group = store.activeGroup.value()
 | 
			
		||||
			this.style("opacity", 1)
 | 
			
		||||
			this.style("transform", `translateY(${group.element.offsetTop}px)`)
 | 
			
		||||
		} else {
 | 
			
		||||
			this.style("opacity", 0)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const activeGroupMarker = new ActiveGroupMarker()
 | 
			
		||||
 | 
			
		||||
class Group extends ElemJS {
 | 
			
		||||
	constructor(groups, data) {
 | 
			
		||||
	constructor(key, data) {
 | 
			
		||||
		super("div")
 | 
			
		||||
 | 
			
		||||
		this.groups = groups
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.active = false
 | 
			
		||||
 | 
			
		||||
		this.on("click", this.onClick.bind(this))
 | 
			
		||||
 | 
			
		||||
		this.class("c-group")
 | 
			
		||||
		this.child(
 | 
			
		||||
| 
						 | 
				
			
			@ -29,29 +34,45 @@ class Group extends ElemJS {
 | 
			
		|||
			),
 | 
			
		||||
			ejs("div").class("c-group__name").text(this.data.name)
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		this.on("click", this.onClick.bind(this))
 | 
			
		||||
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setActive(active) {
 | 
			
		||||
		this.active = active
 | 
			
		||||
	render() {
 | 
			
		||||
		const active = store.activeGroup.value() === this
 | 
			
		||||
		this.element.classList[active ? "add" : "remove"]("c-group--active")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClick() {
 | 
			
		||||
		this.groups.setGroup(this)
 | 
			
		||||
		store.activeGroup.set(this)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Room extends ElemJS {
 | 
			
		||||
	constructor(name) {
 | 
			
		||||
	constructor(key, data) {
 | 
			
		||||
		super("div")
 | 
			
		||||
 | 
			
		||||
		this.name = name
 | 
			
		||||
		this.data = data
 | 
			
		||||
 | 
			
		||||
		this.class("c-room")
 | 
			
		||||
		this.child(
 | 
			
		||||
			ejs("div").class("c-room__icon"),
 | 
			
		||||
			ejs("div").class("c-room__name").text(this.name)
 | 
			
		||||
			ejs("div").class("c-room__name").text(this.data.name)
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		this.on("click", this.onClick.bind(this))
 | 
			
		||||
		store.activeRoom.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClick() {
 | 
			
		||||
		store.activeRoom.set(this)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		const active = store.activeRoom.value() === this
 | 
			
		||||
		this.element.classList[active ? "add" : "remove"]("c-room--active")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,20 +80,38 @@ class Rooms extends ElemJS {
 | 
			
		|||
	constructor() {
 | 
			
		||||
		super(q("#c-rooms"))
 | 
			
		||||
 | 
			
		||||
		this.roomData = []
 | 
			
		||||
		this.rooms = []
 | 
			
		||||
 | 
			
		||||
		store.rooms.subscribe("askAdd", this.askAdd.bind(this))
 | 
			
		||||
		store.rooms.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setRooms(rooms) {
 | 
			
		||||
		this.rooms = rooms
 | 
			
		||||
		this.render()
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const room = new Room(key, data)
 | 
			
		||||
		store.rooms.addEnd(key, room)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		this.clearChildren()
 | 
			
		||||
		for (const room of this.rooms) {
 | 
			
		||||
			this.child(new Room(room.name))
 | 
			
		||||
		let first = null
 | 
			
		||||
		// set room list
 | 
			
		||||
		store.rooms.forEach((id, room) => {
 | 
			
		||||
			if (room.value().data.group === store.activeGroup.value()) {
 | 
			
		||||
				if (!first) first = room.value()
 | 
			
		||||
				this.child(room.value())
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
		// if needed, change the active room to be an item in the room list
 | 
			
		||||
		if (!store.activeRoom.exists() || store.activeRoom.value().data.group !== store.activeGroup.value()) {
 | 
			
		||||
			if (first) {
 | 
			
		||||
				store.activeRoom.set(first)
 | 
			
		||||
			} else {
 | 
			
		||||
				store.activeRoom.delete()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -81,59 +120,73 @@ const rooms = new Rooms()
 | 
			
		|||
class Groups extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super(q("#c-groups-list"))
 | 
			
		||||
		this.groupData = [
 | 
			
		||||
			{name: "Directs", icon: "/static/directs.svg", rooms: [
 | 
			
		||||
				{name: "riley"},
 | 
			
		||||
				{name: "BadAtNames"},
 | 
			
		||||
				{name: "lynxano"},
 | 
			
		||||
				{name: "quarky"},
 | 
			
		||||
				{name: "lepton"},
 | 
			
		||||
				{name: "ash"},
 | 
			
		||||
				{name: "mewmew"},
 | 
			
		||||
				{name: "Toniob"},
 | 
			
		||||
				{name: "cockandball"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Channels", icon: "/static/channels.svg", rooms: [
 | 
			
		||||
				{name: "Carbon brainstorming"},
 | 
			
		||||
				{name: "Bibliogram"},
 | 
			
		||||
				{name: "Monsters Inc Debate Hall"},
 | 
			
		||||
				{name: "DRB clan"},
 | 
			
		||||
				{name: "mettaton simp zone"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Fediverse Drama Museum", rooms: [
 | 
			
		||||
				{name: "witches"},
 | 
			
		||||
				{name: "snouts"},
 | 
			
		||||
				{name: "monads"},
 | 
			
		||||
				{name: "radical"},
 | 
			
		||||
				{name: "blobcat"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Epicord", rooms: [
 | 
			
		||||
				{name: "main"},
 | 
			
		||||
				{name: "gaming"},
 | 
			
		||||
				{name: "inhalers"},
 | 
			
		||||
				{name: "minecraft"},
 | 
			
		||||
				{name: "osu"},
 | 
			
		||||
				{name: "covid"}
 | 
			
		||||
			]},
 | 
			
		||||
			{name: "Invidious", rooms: [
 | 
			
		||||
			]}
 | 
			
		||||
		]
 | 
			
		||||
		this.groups = []
 | 
			
		||||
		this.render()
 | 
			
		||||
		this.setGroup(this.children[0])
 | 
			
		||||
 | 
			
		||||
		store.groups.subscribe("askAdd", this.askAdd.bind(this))
 | 
			
		||||
		store.groups.subscribe("addItem", this.addItem.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setGroup(group) {
 | 
			
		||||
		rooms.setRooms(group.data.rooms)
 | 
			
		||||
		this.groups.forEach(g => g.setActive(g === group))
 | 
			
		||||
		activeGroupMarker.follow(group)
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const group = new Group(key, data)
 | 
			
		||||
		store.groups.addEnd(key, group)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		this.groups = this.groupData.map(data => new Group(this, data))
 | 
			
		||||
		for (const group of this.groups) {
 | 
			
		||||
			this.child(group)
 | 
			
		||||
		}
 | 
			
		||||
	addItem(event, key) {
 | 
			
		||||
		this.child(store.groups.get(key).value())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
const groups = new Groups()
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{
 | 
			
		||||
		id: "directs",
 | 
			
		||||
		name: "Directs",
 | 
			
		||||
		icon: "/static/directs.svg"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "channels",
 | 
			
		||||
		name: "Channels",
 | 
			
		||||
		icon: "/static/channels.svg"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "123",
 | 
			
		||||
		name: "Fediverse Drama Museum"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "456",
 | 
			
		||||
		name: "Epicord"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "789",
 | 
			
		||||
		name: "Invidious"
 | 
			
		||||
	}
 | 
			
		||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{id: "001", name: "riley", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "002", name: "BadAtNames", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "003", name: "lynxano", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "004", name: "quarky", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "005", name: "lepton", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "006", name: "ash", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "007", name: "mewmew", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "008", name: "Toniob", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "009", name: "cockandball", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "010", name: "Carbon brainstorming", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "011", name: "Bibliogram", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "012", name: "Monsters Inc Debate Hall", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "013", name: "DRB clan", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "014", name: "mettaton simp zone", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "015", name: "witches", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "016", name: "snouts", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "017", name: "monads", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "018", name: "radical", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "019", name: "blobcat", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "020", name: "main", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "021", name: "gaming", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "022", name: "inhalers", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "023", name: "minecraft", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "024", name: "osu", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "025", name: "covid", group: store.groups.get("456").value()}
 | 
			
		||||
].forEach(data => store.rooms.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										36
									
								
								src/js/store/Subscribable.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/js/store/Subscribable.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
class Subscribable {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.events = {
 | 
			
		||||
			addSelf: [],
 | 
			
		||||
			editSelf: [],
 | 
			
		||||
			removeSelf: [],
 | 
			
		||||
			changeSelf: []
 | 
			
		||||
		}
 | 
			
		||||
		this.eventDeps = {
 | 
			
		||||
			addSelf: ["changeSelf"],
 | 
			
		||||
			editSelf: ["changeSelf"],
 | 
			
		||||
			removeSelf: ["changeSelf"],
 | 
			
		||||
			changeSelf: []
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subscribe(event, callback) {
 | 
			
		||||
		if (this.events[event]) {
 | 
			
		||||
			this.events[event].push(callback)
 | 
			
		||||
		} else {
 | 
			
		||||
			throw new Error(`Cannot subscribe to non-existent event ${event}, available events are: ${Object.keys(this.events).join(", ")}`)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsubscribe(event, callback) {
 | 
			
		||||
		this.events[event].push(callback)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	broadcast(event, data) {
 | 
			
		||||
		this.eventDeps[event].concat(event).forEach(eventName => {
 | 
			
		||||
			this.events[eventName].forEach(f => f(event, data))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {Subscribable}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/js/store/SubscribeMap.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/js/store/SubscribeMap.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
 | 
			
		||||
class SubscribeMap extends Subscribable {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super()
 | 
			
		||||
		Object.assign(this.events, {
 | 
			
		||||
			addItem: [],
 | 
			
		||||
			changeItem: [],
 | 
			
		||||
			removeItem: []
 | 
			
		||||
		})
 | 
			
		||||
		this.map = new Map()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	has(key) {
 | 
			
		||||
		return this.map.has(key) && this.map.get(key).exists()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get(key) {
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
			return this.map.get(key)
 | 
			
		||||
		} else {
 | 
			
		||||
			this.map.set(key, new SubscribeValue())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set(key, value) {
 | 
			
		||||
		let s
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
			s = this.map.get(key).set(value)
 | 
			
		||||
			this.broadcast("changeItem", key)
 | 
			
		||||
		} else {
 | 
			
		||||
			s = new SubscribeValue().set(value)
 | 
			
		||||
			this.map.set(key, s)
 | 
			
		||||
			this.broadcast("addItem", key)
 | 
			
		||||
		}
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {SubscribeMap}
 | 
			
		||||
							
								
								
									
										77
									
								
								src/js/store/SubscribeMapList.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/js/store/SubscribeMapList.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
 | 
			
		||||
class SubscribeMapList extends Subscribable {
 | 
			
		||||
	constructor(inner) {
 | 
			
		||||
		super()
 | 
			
		||||
		this.inner = inner
 | 
			
		||||
		Object.assign(this.events, {
 | 
			
		||||
			addItem: [],
 | 
			
		||||
			removeItem: [],
 | 
			
		||||
			editItem: [],
 | 
			
		||||
			changeItem: [],
 | 
			
		||||
			askAdd: []
 | 
			
		||||
		})
 | 
			
		||||
		Object.assign(this.eventDeps, {
 | 
			
		||||
			addItem: ["changeItem"],
 | 
			
		||||
			removeItem: ["changeItem"],
 | 
			
		||||
			editItem: ["changeItem"],
 | 
			
		||||
			changeItem: [],
 | 
			
		||||
			askAdd: []
 | 
			
		||||
		})
 | 
			
		||||
		this.map = new Map()
 | 
			
		||||
		this.list = []
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	has(key) {
 | 
			
		||||
		return this.map.has(key) && this.map.get(key).exists()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get(key) {
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
			return this.map.get(key)
 | 
			
		||||
		} else {
 | 
			
		||||
			const item = new this.inner()
 | 
			
		||||
			this.map.set(key, item)
 | 
			
		||||
			return item
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forEach(f) {
 | 
			
		||||
		this.list.forEach(key => f(key, this.get(key)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	askAdd(key, data) {
 | 
			
		||||
		this.broadcast("askAdd", {key, data})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addStart(key, value) {
 | 
			
		||||
		this._add(key, value, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addEnd(key, value) {
 | 
			
		||||
		this._add(key, value, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_add(key, value, start) {
 | 
			
		||||
		let s
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
			const exists = this.map.get(key).exists()
 | 
			
		||||
			s = this.map.get(key).set(value)
 | 
			
		||||
			if (exists) {
 | 
			
		||||
				this.broadcast("changeItem", key)
 | 
			
		||||
			} else {
 | 
			
		||||
				this.broadcast("addItem", key)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			s = new this.inner().set(value)
 | 
			
		||||
			this.map.set(key, s)
 | 
			
		||||
			if (start) this.list.unshift(key)
 | 
			
		||||
			else this.list.push(key)
 | 
			
		||||
			this.broadcast("addItem", key)
 | 
			
		||||
		}
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {SubscribeMapList}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/js/store/SubscribeValue.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/js/store/SubscribeValue.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
 | 
			
		||||
class SubscribeValue extends Subscribable {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super()
 | 
			
		||||
		this.hasData = false
 | 
			
		||||
		this.data = null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exists() {
 | 
			
		||||
		return this.hasData
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value() {
 | 
			
		||||
		if (this.hasData) return this.data
 | 
			
		||||
		else return null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set(data) {
 | 
			
		||||
		const exists = this.exists()
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.hasData = true
 | 
			
		||||
		if (exists) {
 | 
			
		||||
			this.broadcast("editSelf", this.data)
 | 
			
		||||
		} else {
 | 
			
		||||
			this.broadcast("addSelf", this.data)
 | 
			
		||||
		}
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	edit(f) {
 | 
			
		||||
		if (this.exists()) {
 | 
			
		||||
			f(this.data)
 | 
			
		||||
			this.set(this.data)
 | 
			
		||||
		} else {
 | 
			
		||||
			throw new Error("Tried to edit a SubscribeValue that had no value")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete() {
 | 
			
		||||
		this.hasData = false
 | 
			
		||||
		this.broadcast("removeSelf")
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {SubscribeValue}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/js/store/store.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/js/store/store.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
import {SubscribeMapList} from "./SubscribeMapList.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
 | 
			
		||||
const store = {
 | 
			
		||||
	groups: new SubscribeMapList(SubscribeValue),
 | 
			
		||||
	rooms: new SubscribeMapList(SubscribeValue),
 | 
			
		||||
	activeGroup: new SubscribeValue(),
 | 
			
		||||
	activeRoom: new SubscribeValue()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.store = store
 | 
			
		||||
 | 
			
		||||
export {store}
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +64,9 @@ $out-width: $base-width + rooms.$list-width
 | 
			
		|||
.c-group-marker
 | 
			
		||||
  position: absolute
 | 
			
		||||
  top: 5px
 | 
			
		||||
  opacity: 0
 | 
			
		||||
  transform: translateY(8px)
 | 
			
		||||
  transition: transform ease 0.12s
 | 
			
		||||
  transition: transform ease 0.12s, opacity ease-out 0.12s
 | 
			
		||||
  height: $icon-size - 2px
 | 
			
		||||
  width: 6px
 | 
			
		||||
  background-color: #ccc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
@use "../colors" as c
 | 
			
		||||
 | 
			
		||||
$list-width: 240px
 | 
			
		||||
$icon-size: 36px
 | 
			
		||||
$icon-size: 32px
 | 
			
		||||
$icon-padding: 8px
 | 
			
		||||
 | 
			
		||||
.c-rooms
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ $icon-padding: 8px
 | 
			
		|||
  width: $list-width
 | 
			
		||||
  font-size: 20px
 | 
			
		||||
  font-weight: 500
 | 
			
		||||
  overflow-y: scroll
 | 
			
		||||
  overflow-y: auto
 | 
			
		||||
  scrollbar-width: thin
 | 
			
		||||
  scrollbar-color: c.$darkest c.$darker
 | 
			
		||||
  flex-shrink: 0
 | 
			
		||||
| 
						 | 
				
			
			@ -18,12 +18,16 @@ $icon-padding: 8px
 | 
			
		|||
.c-room
 | 
			
		||||
  display: flex
 | 
			
		||||
  align-items: center
 | 
			
		||||
  padding: $icon-padding
 | 
			
		||||
  padding: $icon-padding * 0.75 $icon-padding
 | 
			
		||||
  margin: $icon-padding * 0.25 0
 | 
			
		||||
  cursor: pointer
 | 
			
		||||
  border-radius: 8px
 | 
			
		||||
 | 
			
		||||
  &:hover
 | 
			
		||||
  &:not(&--active):hover
 | 
			
		||||
    background-color: c.$mild
 | 
			
		||||
    border-radius: 8px
 | 
			
		||||
 | 
			
		||||
  &--active
 | 
			
		||||
    background-color: c.$milder
 | 
			
		||||
 | 
			
		||||
  &__icon
 | 
			
		||||
    width: $icon-size
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue