diff --git a/build/index.html b/build/index.html index 3997893..fe38608 100644 --- a/build/index.html +++ b/build/index.html @@ -2,10 +2,10 @@ - + - + Carbon diff --git a/build/static/directs.svg b/build/static/directs.svg index cb3558a..b1ed08b 100644 --- a/build/static/directs.svg +++ b/build/static/directs.svg @@ -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"> @@ -61,7 +64,7 @@ transform="translate(36.739286,-225.97828)"> diff --git a/build/static/main.css b/build/static/main.css index 177caf9..fa3314b 100644 --- a/build/static/main.css +++ b/build/static/main.css @@ -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; diff --git a/build/static/room-picker.js b/build/static/room-picker.js index aa35136..9099473 100644 --- a/build/static/room-picker.js +++ b/build/static/room-picker.js @@ -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()) diff --git a/build/static/store/Subscribable.js b/build/static/store/Subscribable.js new file mode 100644 index 0000000..d205b79 --- /dev/null +++ b/build/static/store/Subscribable.js @@ -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} diff --git a/build/static/store/SubscribeMapList.js b/build/static/store/SubscribeMapList.js new file mode 100644 index 0000000..fb93574 --- /dev/null +++ b/build/static/store/SubscribeMapList.js @@ -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} diff --git a/build/static/store/SubscribeValue.js b/build/static/store/SubscribeValue.js new file mode 100644 index 0000000..16e5b88 --- /dev/null +++ b/build/static/store/SubscribeValue.js @@ -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} diff --git a/build/static/store/store.js b/build/static/store/store.js new file mode 100644 index 0000000..a4479ca --- /dev/null +++ b/build/static/store/store.js @@ -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} diff --git a/src/assets/icons/directs.svg b/src/assets/icons/directs.svg index cb3558a..b1ed08b 100644 --- a/src/assets/icons/directs.svg +++ b/src/assets/icons/directs.svg @@ -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"> @@ -61,7 +64,7 @@ transform="translate(36.739286,-225.97828)"> diff --git a/src/js/room-picker.js b/src/js/room-picker.js index aa35136..9099473 100644 --- a/src/js/room-picker.js +++ b/src/js/room-picker.js @@ -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()) diff --git a/src/js/store/Subscribable.js b/src/js/store/Subscribable.js new file mode 100644 index 0000000..d205b79 --- /dev/null +++ b/src/js/store/Subscribable.js @@ -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} diff --git a/src/js/store/SubscribeMap.js b/src/js/store/SubscribeMap.js new file mode 100644 index 0000000..562d70f --- /dev/null +++ b/src/js/store/SubscribeMap.js @@ -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} diff --git a/src/js/store/SubscribeMapList.js b/src/js/store/SubscribeMapList.js new file mode 100644 index 0000000..fb93574 --- /dev/null +++ b/src/js/store/SubscribeMapList.js @@ -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} diff --git a/src/js/store/SubscribeValue.js b/src/js/store/SubscribeValue.js new file mode 100644 index 0000000..16e5b88 --- /dev/null +++ b/src/js/store/SubscribeValue.js @@ -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} diff --git a/src/js/store/store.js b/src/js/store/store.js new file mode 100644 index 0000000..a4479ca --- /dev/null +++ b/src/js/store/store.js @@ -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} diff --git a/src/sass/components/groups.sass b/src/sass/components/groups.sass index ee4c27a..10571dc 100644 --- a/src/sass/components/groups.sass +++ b/src/sass/components/groups.sass @@ -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 diff --git a/src/sass/components/rooms.sass b/src/sass/components/rooms.sass index f176d1f..dc0f0e2 100644 --- a/src/sass/components/rooms.sass +++ b/src/sass/components/rooms.sass @@ -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