Groups and rooms list first draft
This commit is contained in:
parent
36430999f0
commit
da1f63fc8d
16 changed files with 917 additions and 0 deletions
231
build.js
Normal file
231
build.js
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
const pug = require("pug")
|
||||||
|
const sass = require("sass")
|
||||||
|
const fs = require("fs").promises
|
||||||
|
const os = require("os")
|
||||||
|
const crypto = require("crypto")
|
||||||
|
const path = require("path")
|
||||||
|
const pj = path.join
|
||||||
|
const babel = require("@babel/core")
|
||||||
|
const fetch = require("node-fetch")
|
||||||
|
const chalk = require("chalk")
|
||||||
|
const hint = require("jshint").JSHINT
|
||||||
|
|
||||||
|
process.chdir(pj(__dirname, "src"))
|
||||||
|
|
||||||
|
const buildDir = "../build"
|
||||||
|
|
||||||
|
const validationQueue = []
|
||||||
|
const validationHost = os.hostname() === "future" ? "http://localhost:8888/" : "http://validator.w3.org/nu/"
|
||||||
|
const static = new Map()
|
||||||
|
const links = new Map()
|
||||||
|
const pugLocals = {static, links}
|
||||||
|
|
||||||
|
const spec = require("./spec.js")
|
||||||
|
|
||||||
|
function hash(buffer) {
|
||||||
|
return crypto.createHash("sha256").update(buffer).digest("hex").slice(0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(filename, body, type) {
|
||||||
|
const promise = fetch(validationHost+"?out=json", {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
headers: {
|
||||||
|
"content-type": `text/${type}; charset=UTF-8`
|
||||||
|
}
|
||||||
|
}).then(res => res.json()).then(root => {
|
||||||
|
return function cont() {
|
||||||
|
let concerningMessages = 0
|
||||||
|
for (const message of root.messages) {
|
||||||
|
if (message.hiliteStart) {
|
||||||
|
let type = message.type
|
||||||
|
if (message.type === "error") {
|
||||||
|
type = chalk.red("error")
|
||||||
|
} else if (message.type === "warning") {
|
||||||
|
type = chalk.yellow("warning")
|
||||||
|
} else {
|
||||||
|
continue // don't care about info
|
||||||
|
}
|
||||||
|
concerningMessages++
|
||||||
|
console.log(`validation: ${type} in ${filename}`)
|
||||||
|
console.log(` ${message.message}`)
|
||||||
|
const text = message.extract.replace(/\n/g, "⏎").replace(/\t/g, " ")
|
||||||
|
console.log(chalk.grey(
|
||||||
|
" "
|
||||||
|
+ text.slice(0, message.hiliteStart)
|
||||||
|
+ chalk.inverse(text.substr(message.hiliteStart, message.hiliteLength))
|
||||||
|
+ text.slice(message.hiliteStart+message.hiliteLength)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!concerningMessages) {
|
||||||
|
console.log(`validation: ${chalk.green("ok")} for ${filename}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
validationQueue.push(promise)
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
function runHint(filename, source) {
|
||||||
|
hint(source, {
|
||||||
|
esversion: 9,
|
||||||
|
undef: true,
|
||||||
|
unused: true,
|
||||||
|
loopfunc: true,
|
||||||
|
globals: ["require", "console", "URLSearchParams", "L"],
|
||||||
|
strict: "global",
|
||||||
|
browser: true
|
||||||
|
})
|
||||||
|
const result = hint.data()
|
||||||
|
if (result.errors && result.errors.length) {
|
||||||
|
for (const error of result.errors) {
|
||||||
|
if (error.evidence) {
|
||||||
|
const text = error.evidence.replace(/\t/g, " ")
|
||||||
|
let type = error.code.startsWith("W") ? chalk.yellow("warning") : chalk.red("error")
|
||||||
|
console.log(`hint: ${type} in ${filename}`)
|
||||||
|
console.log(` ${error.line}:${error.character}: ${error.reason} (${error.code})`)
|
||||||
|
console.log(chalk.gray(
|
||||||
|
" "
|
||||||
|
+ text.slice(0, error.character)
|
||||||
|
+ chalk.inverse(text.substr(error.character, 1))
|
||||||
|
+ text.slice(error.character+1)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`hint: ${chalk.cyan(result.errors.length+" problems")} in ${filename}`)
|
||||||
|
} else {
|
||||||
|
console.log(`hint: ${chalk.green("ok")} for ${filename}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFile(sourcePath, targetPath) {
|
||||||
|
const contents = await fs.readFile(pj(".", sourcePath), {encoding: null})
|
||||||
|
static.set(sourcePath, `${targetPath}?static=${hash(contents)}`)
|
||||||
|
fs.writeFile(pj(buildDir, targetPath), contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addJS(sourcePath, targetPath) {
|
||||||
|
const source = await fs.readFile(pj(".", sourcePath), {encoding: "utf8"})
|
||||||
|
static.set(sourcePath, `${targetPath}?static=${hash(source)}`)
|
||||||
|
runHint(sourcePath, source);
|
||||||
|
fs.writeFile(pj(buildDir, targetPath), source)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addSass(sourcePath, targetPath) {
|
||||||
|
const renderedCSS = sass.renderSync({
|
||||||
|
file: pj(".", sourcePath),
|
||||||
|
outputStyle: "expanded",
|
||||||
|
indentType: "tab",
|
||||||
|
indentWidth: 1,
|
||||||
|
functions: {
|
||||||
|
"static($name)": function(name) {
|
||||||
|
if (!(name instanceof sass.types.String)) {
|
||||||
|
throw "$name: expected a string"
|
||||||
|
}
|
||||||
|
const result = static.get(name.getValue())
|
||||||
|
if (typeof result === "string") {
|
||||||
|
return new sass.types.String(result)
|
||||||
|
} else {
|
||||||
|
throw new Error("static file '"+name.getValue()+"' does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).css
|
||||||
|
static.set(sourcePath, `${targetPath}?static=${hash(renderedCSS)}`)
|
||||||
|
validate(sourcePath, renderedCSS, "css")
|
||||||
|
await fs.writeFile(pj(buildDir, targetPath), renderedCSS)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addPug(sourcePath, targetPath) {
|
||||||
|
function getRelative(staticTarget) {
|
||||||
|
const pathLayer = (path.dirname(targetPath).replace(/\/$/, "").match(/\//g) || []).length
|
||||||
|
const prefix = Array(pathLayer).fill("../").join("")
|
||||||
|
const result = prefix + staticTarget.replace(/^\//, "")
|
||||||
|
if (result) return result
|
||||||
|
else return "./"
|
||||||
|
}
|
||||||
|
function getStatic(target) {
|
||||||
|
return getRelative(static.get(target))
|
||||||
|
}
|
||||||
|
function getStaticName(target) {
|
||||||
|
return getRelative(static.get(target)).replace(/\?.*$/, "")
|
||||||
|
}
|
||||||
|
function getLink(target) {
|
||||||
|
return getRelative(links.get(target))
|
||||||
|
}
|
||||||
|
const renderedHTML = pug.compileFile(pj(".", sourcePath), {pretty: true})({getStatic, getStaticName, getLink, ...pugLocals})
|
||||||
|
let renderedWithoutPHP = renderedHTML.replace(/<\?(?:php|=).*?\?>/gsm, "")
|
||||||
|
validate(sourcePath, renderedWithoutPHP, "html")
|
||||||
|
await fs.writeFile(pj(buildDir, targetPath), renderedHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addBabel(sourcePath, targetPath) {
|
||||||
|
const originalCode = await fs.readFile(pj(".", sourcePath), "utf8")
|
||||||
|
|
||||||
|
const compiled = babel.transformSync(originalCode, {
|
||||||
|
sourceMaps: false,
|
||||||
|
sourceType: "script",
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
"@babel/env", {
|
||||||
|
targets: {
|
||||||
|
"ie": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
generatorOpts: {
|
||||||
|
comments: false,
|
||||||
|
minified: false,
|
||||||
|
sourceMaps: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const filenameWithQuery = `${targetPath}?static=${hash(compiled.code)}`
|
||||||
|
|
||||||
|
static.set(sourcePath, filenameWithQuery)
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
fs.writeFile(pj(buildDir, targetPath), originalCode),
|
||||||
|
fs.writeFile(pj(buildDir, minFilename), compiled.code),
|
||||||
|
fs.writeFile(pj(buildDir, mapFilename), JSON.stringify(compiled.map))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
// Stage 1: Register
|
||||||
|
for (const item of spec) {
|
||||||
|
if (item.type === "pug") {
|
||||||
|
links.set(item.source, item.target.replace(/index.html$/, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage 2: Build
|
||||||
|
for (const item of spec) {
|
||||||
|
if (item.type === "file") {
|
||||||
|
await addFile(item.source, item.target)
|
||||||
|
} else if (item.type === "js") {
|
||||||
|
await addJS(item.source, item.target)
|
||||||
|
} else if (item.type === "sass") {
|
||||||
|
await addSass(item.source, item.target)
|
||||||
|
} else if (item.type === "babel") {
|
||||||
|
await addBabel(item.source, item.target)
|
||||||
|
} else if (item.type === "pug") {
|
||||||
|
await addPug(item.source, item.target)
|
||||||
|
} else {
|
||||||
|
throw new Error("Unknown item type: "+item.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.green("All files emitted."))
|
||||||
|
|
||||||
|
await Promise.all(validationQueue).then(v => {
|
||||||
|
console.log(`validation: using host ${chalk.cyan(validationHost)}`)
|
||||||
|
v.forEach(cont => cont())
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(chalk.green("Build complete.") + "\n\n------------\n")
|
||||||
|
})()
|
78
build/index.html
Normal file
78
build/index.html
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" type="text/css" href="static/main.css?static=13628a6e3d">
|
||||||
|
<script type="module" src="static/groups.js?static=18b23cbd17"></script>
|
||||||
|
<title>Carbon</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="main">
|
||||||
|
<div class="c-groups">
|
||||||
|
<div class="c-groups__display" id="c-groups">
|
||||||
|
<div class="c-groups__container">
|
||||||
|
<div class="c-group">
|
||||||
|
<div class="c-group__icon"></div>
|
||||||
|
<div class="c-group__name">Directs</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-group">
|
||||||
|
<div class="c-group__icon"></div>
|
||||||
|
<div class="c-group__name">Channels</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-group">
|
||||||
|
<div class="c-group__icon"></div>
|
||||||
|
<div class="c-group__name">Fediverse Drama Museum</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-group">
|
||||||
|
<div class="c-group__icon"></div>
|
||||||
|
<div class="c-group__name">Epicord</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-group">
|
||||||
|
<div class="c-group__icon"></div>
|
||||||
|
<div class="c-group__name">Invidious</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-rooms" id="c-rooms">
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">Carbon brainstorming</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">riley</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">BadAtNames</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">lepton</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">cockandball</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">Bibliogram</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">Monsters Inc Debate Hall</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">DRB clan</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-room">
|
||||||
|
<div class="c-room__icon"></div>
|
||||||
|
<div class="c-room__name">mettaton simp zone</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-chat"></div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
148
build/static/basic.js
Normal file
148
build/static/basic.js
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
* Shortcut for querySelector.
|
||||||
|
* @template {HTMLElement} T
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
const q = s => document.querySelector(s);
|
||||||
|
/**
|
||||||
|
* Shortcut for querySelectorAll.
|
||||||
|
* @template {HTMLElement} T
|
||||||
|
* @returns {T[]}
|
||||||
|
*/
|
||||||
|
const qa = s => document.querySelectorAll(s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An easier, chainable, object-oriented way to create and update elements
|
||||||
|
* and children according to related data. Subclass ElemJS to create useful,
|
||||||
|
* advanced data managers, or just use it inline to quickly make a custom element.
|
||||||
|
* Created by Cadence Ember in 2018.
|
||||||
|
*/
|
||||||
|
class ElemJS {
|
||||||
|
constructor(type) {
|
||||||
|
if (type instanceof HTMLElement) {
|
||||||
|
// If passed an existing element, bind to it
|
||||||
|
this.bind(type);
|
||||||
|
} else {
|
||||||
|
// Otherwise, create a new detached element to bind to
|
||||||
|
this.bind(document.createElement(type));
|
||||||
|
}
|
||||||
|
this.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bind this construct to an existing element on the page. */
|
||||||
|
bind(element) {
|
||||||
|
this.element = element;
|
||||||
|
this.element.js = this;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a class. */
|
||||||
|
class() {
|
||||||
|
for (let name of arguments) if (name) this.element.classList.add(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove a class. */
|
||||||
|
removeClass() {
|
||||||
|
for (let name of arguments) if (name) this.element.classList.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set a JS property on the element. */
|
||||||
|
direct(name, value) {
|
||||||
|
if (name) this.element[name] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set an attribute on the element. */
|
||||||
|
attribute(name, value) {
|
||||||
|
if (name) this.element.setAttribute(name, value != undefined ? value : "");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set a style on the element. */
|
||||||
|
style(name, value) {
|
||||||
|
if (name) this.element.style[name] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the element's ID. */
|
||||||
|
id(name) {
|
||||||
|
if (name) this.element.id = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attach a callback function to an event on the element. */
|
||||||
|
on(name, callback) {
|
||||||
|
this.element.addEventListener(name, callback);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the element's text. */
|
||||||
|
text(name) {
|
||||||
|
this.element.innerText = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a text node and add it to the element. */
|
||||||
|
addText(name) {
|
||||||
|
const node = document.createTextNode(name);
|
||||||
|
this.element.appendChild(node);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the element's HTML content. */
|
||||||
|
html(name) {
|
||||||
|
this.element.innerHTML = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add children to the element.
|
||||||
|
* Children can either be an instance of ElemJS, in
|
||||||
|
* which case the element will be appended as a child,
|
||||||
|
* or a string, in which case the string will be added as a text node.
|
||||||
|
* Each child should be a parameter to this method.
|
||||||
|
*/
|
||||||
|
child(...children) {
|
||||||
|
for (const toAdd of children) {
|
||||||
|
if (typeof toAdd === "object" && toAdd !== null) {
|
||||||
|
// Should be an instance of ElemJS, so append as child
|
||||||
|
toAdd.parent = this;
|
||||||
|
this.element.appendChild(toAdd.element);
|
||||||
|
this.children.push(toAdd);
|
||||||
|
} else if (typeof toAdd === "string") {
|
||||||
|
// Is a string, so add as text node
|
||||||
|
this.addText(toAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all children from the element.
|
||||||
|
*/
|
||||||
|
clearChildren() {
|
||||||
|
this.children.length = 0;
|
||||||
|
while (this.element.lastChild) this.element.removeChild(this.element.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this element.
|
||||||
|
*/
|
||||||
|
remove() {
|
||||||
|
let index;
|
||||||
|
if (this.parent && (index = this.parent.children.indexOf(this)) !== -1) {
|
||||||
|
this.parent.children.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.parent = null;
|
||||||
|
this.element.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shortcut for `new ElemJS`. */
|
||||||
|
function ejs(tag) {
|
||||||
|
return new ElemJS(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {q, qa, ElemJS, ejs}
|
15
build/static/groups.js
Normal file
15
build/static/groups.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {q} from "./basic.js"
|
||||||
|
|
||||||
|
let state = "CLOSED"
|
||||||
|
|
||||||
|
const groups = q("#c-groups")
|
||||||
|
const rooms = q("#c-rooms")
|
||||||
|
|
||||||
|
groups.addEventListener("click", () => {
|
||||||
|
console.log("hello", groups)
|
||||||
|
groups.classList.add("c-groups__display--closed")
|
||||||
|
})
|
||||||
|
|
||||||
|
rooms.addEventListener("mouseout", () => {
|
||||||
|
groups.classList.remove("c-groups__display--closed")
|
||||||
|
})
|
99
build/static/main.css
Normal file
99
build/static/main.css
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: Whitney;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url(/static/whitney-500.woff?static=ba33ed18fe) format("woff2");
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #36393e;
|
||||||
|
color: #ddd;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: Whitney;
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-rooms {
|
||||||
|
background-color: #2f3135;
|
||||||
|
padding: 8px;
|
||||||
|
width: 240px;
|
||||||
|
font-size: 20px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #42454a #2f3135;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-room {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.c-room:hover {
|
||||||
|
background-color: #393c42;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.c-room__icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background-color: #bbb;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.c-room__name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-groups {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
.c-groups__display {
|
||||||
|
background-color: #202224;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 80px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.c-groups__display:not(.c-groups__display--closed):hover {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.c-groups__container {
|
||||||
|
width: 300px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.c-group:hover {
|
||||||
|
background-color: #2f3135;
|
||||||
|
}
|
||||||
|
.c-group__icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background-color: #999;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.c-group__name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
BIN
build/static/whitney-500.woff
Normal file
BIN
build/static/whitney-500.woff
Normal file
Binary file not shown.
32
spec.js
Normal file
32
spec.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
source: "/assets/fonts/whitney-500.woff",
|
||||||
|
target: "/static/whitney-500.woff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
source: "/js/basic.js",
|
||||||
|
target: "/static/basic.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
source: "/js/groups.js",
|
||||||
|
target: "/static/groups.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
source: "/assets/fonts/whitney-500.woff",
|
||||||
|
target: "/static/whitney-500.woff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "sass",
|
||||||
|
source: "/sass/main.sass",
|
||||||
|
target: "/static/main.css"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "pug",
|
||||||
|
source: "/home.pug",
|
||||||
|
target: "/index.html"
|
||||||
|
}
|
||||||
|
]
|
BIN
src/assets/fonts/whitney-500.woff
Normal file
BIN
src/assets/fonts/whitney-500.woff
Normal file
Binary file not shown.
38
src/home.pug
Normal file
38
src/home.pug
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
mixin group(name)
|
||||||
|
.c-group
|
||||||
|
.c-group__icon
|
||||||
|
.c-group__name= name
|
||||||
|
|
||||||
|
mixin room(name)
|
||||||
|
.c-room
|
||||||
|
.c-room__icon
|
||||||
|
.c-room__name= name
|
||||||
|
|
||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
meta(charset="utf-8")
|
||||||
|
link(rel="stylesheet" type="text/css" href=getStatic("/sass/main.sass"))
|
||||||
|
script(type="module" src=getStatic("/js/groups.js"))
|
||||||
|
title Carbon
|
||||||
|
body
|
||||||
|
main.main
|
||||||
|
.c-groups
|
||||||
|
.c-groups__display#c-groups
|
||||||
|
.c-groups__container
|
||||||
|
+group("Directs")
|
||||||
|
+group("Channels")
|
||||||
|
+group("Fediverse Drama Museum")
|
||||||
|
+group("Epicord")
|
||||||
|
+group("Invidious")
|
||||||
|
.c-rooms#c-rooms
|
||||||
|
+room("Carbon brainstorming")
|
||||||
|
+room("riley")
|
||||||
|
+room("BadAtNames")
|
||||||
|
+room("lepton")
|
||||||
|
+room("cockandball")
|
||||||
|
+room("Bibliogram")
|
||||||
|
+room("Monsters Inc Debate Hall")
|
||||||
|
+room("DRB clan")
|
||||||
|
+room("mettaton simp zone")
|
||||||
|
.c-chat
|
148
src/js/basic.js
Normal file
148
src/js/basic.js
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
* Shortcut for querySelector.
|
||||||
|
* @template {HTMLElement} T
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
const q = s => document.querySelector(s);
|
||||||
|
/**
|
||||||
|
* Shortcut for querySelectorAll.
|
||||||
|
* @template {HTMLElement} T
|
||||||
|
* @returns {T[]}
|
||||||
|
*/
|
||||||
|
const qa = s => document.querySelectorAll(s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An easier, chainable, object-oriented way to create and update elements
|
||||||
|
* and children according to related data. Subclass ElemJS to create useful,
|
||||||
|
* advanced data managers, or just use it inline to quickly make a custom element.
|
||||||
|
* Created by Cadence Ember in 2018.
|
||||||
|
*/
|
||||||
|
class ElemJS {
|
||||||
|
constructor(type) {
|
||||||
|
if (type instanceof HTMLElement) {
|
||||||
|
// If passed an existing element, bind to it
|
||||||
|
this.bind(type);
|
||||||
|
} else {
|
||||||
|
// Otherwise, create a new detached element to bind to
|
||||||
|
this.bind(document.createElement(type));
|
||||||
|
}
|
||||||
|
this.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bind this construct to an existing element on the page. */
|
||||||
|
bind(element) {
|
||||||
|
this.element = element;
|
||||||
|
this.element.js = this;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a class. */
|
||||||
|
class() {
|
||||||
|
for (let name of arguments) if (name) this.element.classList.add(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove a class. */
|
||||||
|
removeClass() {
|
||||||
|
for (let name of arguments) if (name) this.element.classList.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set a JS property on the element. */
|
||||||
|
direct(name, value) {
|
||||||
|
if (name) this.element[name] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set an attribute on the element. */
|
||||||
|
attribute(name, value) {
|
||||||
|
if (name) this.element.setAttribute(name, value != undefined ? value : "");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set a style on the element. */
|
||||||
|
style(name, value) {
|
||||||
|
if (name) this.element.style[name] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the element's ID. */
|
||||||
|
id(name) {
|
||||||
|
if (name) this.element.id = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attach a callback function to an event on the element. */
|
||||||
|
on(name, callback) {
|
||||||
|
this.element.addEventListener(name, callback);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the element's text. */
|
||||||
|
text(name) {
|
||||||
|
this.element.innerText = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a text node and add it to the element. */
|
||||||
|
addText(name) {
|
||||||
|
const node = document.createTextNode(name);
|
||||||
|
this.element.appendChild(node);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the element's HTML content. */
|
||||||
|
html(name) {
|
||||||
|
this.element.innerHTML = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add children to the element.
|
||||||
|
* Children can either be an instance of ElemJS, in
|
||||||
|
* which case the element will be appended as a child,
|
||||||
|
* or a string, in which case the string will be added as a text node.
|
||||||
|
* Each child should be a parameter to this method.
|
||||||
|
*/
|
||||||
|
child(...children) {
|
||||||
|
for (const toAdd of children) {
|
||||||
|
if (typeof toAdd === "object" && toAdd !== null) {
|
||||||
|
// Should be an instance of ElemJS, so append as child
|
||||||
|
toAdd.parent = this;
|
||||||
|
this.element.appendChild(toAdd.element);
|
||||||
|
this.children.push(toAdd);
|
||||||
|
} else if (typeof toAdd === "string") {
|
||||||
|
// Is a string, so add as text node
|
||||||
|
this.addText(toAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all children from the element.
|
||||||
|
*/
|
||||||
|
clearChildren() {
|
||||||
|
this.children.length = 0;
|
||||||
|
while (this.element.lastChild) this.element.removeChild(this.element.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this element.
|
||||||
|
*/
|
||||||
|
remove() {
|
||||||
|
let index;
|
||||||
|
if (this.parent && (index = this.parent.children.indexOf(this)) !== -1) {
|
||||||
|
this.parent.children.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.parent = null;
|
||||||
|
this.element.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shortcut for `new ElemJS`. */
|
||||||
|
function ejs(tag) {
|
||||||
|
return new ElemJS(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {q, qa, ElemJS, ejs}
|
15
src/js/groups.js
Normal file
15
src/js/groups.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {q} from "./basic.js"
|
||||||
|
|
||||||
|
let state = "CLOSED"
|
||||||
|
|
||||||
|
const groups = q("#c-groups")
|
||||||
|
const rooms = q("#c-rooms")
|
||||||
|
|
||||||
|
groups.addEventListener("click", () => {
|
||||||
|
console.log("hello", groups)
|
||||||
|
groups.classList.add("c-groups__display--closed")
|
||||||
|
})
|
||||||
|
|
||||||
|
rooms.addEventListener("mouseout", () => {
|
||||||
|
groups.classList.remove("c-groups__display--closed")
|
||||||
|
})
|
17
src/sass/base.sass
Normal file
17
src/sass/base.sass
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@font-face
|
||||||
|
font-family: Whitney
|
||||||
|
font-weight: 500
|
||||||
|
src: url(static("/assets/fonts/whitney-500.woff")) format("woff2")
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family: sans-serif
|
||||||
|
background-color: #36393e
|
||||||
|
color: #ddd
|
||||||
|
font-size: 24px
|
||||||
|
font-family: Whitney
|
||||||
|
margin: 0
|
||||||
|
height: 100vh
|
||||||
|
|
||||||
|
.main
|
||||||
|
height: 100vh
|
||||||
|
display: flex
|
5
src/sass/colors.sass
Normal file
5
src/sass/colors.sass
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
$dark: #36393e
|
||||||
|
$darker: #2f3135
|
||||||
|
$darkest: #202224
|
||||||
|
$mild: #393c42
|
||||||
|
$milder: #42454a
|
51
src/sass/components/groups.sass
Normal file
51
src/sass/components/groups.sass
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
@use "../colors" as c
|
||||||
|
@use "./rooms" as rooms
|
||||||
|
|
||||||
|
$icon-size: 48px
|
||||||
|
$icon-padding: 8px
|
||||||
|
$base-width: $icon-size + $icon_padding * 4
|
||||||
|
$out-width: $base-width + rooms.$list-width - 20px
|
||||||
|
|
||||||
|
.c-groups
|
||||||
|
position: relative
|
||||||
|
width: $base-width
|
||||||
|
|
||||||
|
&__display
|
||||||
|
background-color: c.$darkest
|
||||||
|
overflow: hidden
|
||||||
|
width: $base-width
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
top: 0
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
|
||||||
|
&:not(&--closed):hover
|
||||||
|
width: $out-width
|
||||||
|
|
||||||
|
&__container
|
||||||
|
width: $out-width
|
||||||
|
padding: $icon-padding
|
||||||
|
|
||||||
|
.c-group
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
padding: $icon-padding / 2 $icon-padding
|
||||||
|
cursor: pointer
|
||||||
|
border-radius: 8px
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: c.$darker
|
||||||
|
|
||||||
|
&__icon
|
||||||
|
width: $icon-size
|
||||||
|
height: $icon-size
|
||||||
|
background-color: #999
|
||||||
|
border-radius: 50%
|
||||||
|
margin-right: $icon-padding * 2
|
||||||
|
flex-shrink: 0
|
||||||
|
|
||||||
|
&__name
|
||||||
|
white-space: nowrap
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
37
src/sass/components/rooms.sass
Normal file
37
src/sass/components/rooms.sass
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
@use "../colors" as c
|
||||||
|
|
||||||
|
$list-width: 240px
|
||||||
|
$icon-size: 36px
|
||||||
|
$icon-padding: 8px
|
||||||
|
|
||||||
|
.c-rooms
|
||||||
|
background-color: c.$darker
|
||||||
|
padding: $icon-padding
|
||||||
|
width: $list-width
|
||||||
|
font-size: 20px
|
||||||
|
overflow-y: scroll
|
||||||
|
scrollbar-width: thin
|
||||||
|
scrollbar-color: c.$milder c.$darker
|
||||||
|
|
||||||
|
.c-room
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
padding: $icon-padding
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: c.$mild
|
||||||
|
border-radius: 8px
|
||||||
|
|
||||||
|
&__icon
|
||||||
|
width: $icon-size
|
||||||
|
height: $icon-size
|
||||||
|
background-color: #bbb
|
||||||
|
margin-right: $icon-padding
|
||||||
|
border-radius: 50%
|
||||||
|
flex-shrink: 0
|
||||||
|
|
||||||
|
&__name
|
||||||
|
white-space: nowrap
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
3
src/sass/main.sass
Normal file
3
src/sass/main.sass
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@use "./base"
|
||||||
|
@use "./components/groups"
|
||||||
|
@use "./components/rooms"
|
Loading…
Reference in a new issue