diff --git a/main.go b/main.go index aab3b37..d120108 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ import ( _ "embed" ) -const VERSION = "v0.1.5"; +const VERSION = "v0.2.1"; const LISTEN_HOST = "0.0.0.0"; const DB_SCHEMA = 6 @@ -496,7 +496,7 @@ func nodeDownload(w http.ResponseWriter, r *http.Request) {// {{{ } w.Header().Add("Content-Type", files[0].MIME) - w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, files[0].Filename)) + w.Header().Add("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, files[0].Filename)) w.Header().Add("Content-Length", strconv.Itoa(int(finfo.Size()))) read := 1 diff --git a/static/css/main.css b/static/css/main.css index 0d06d80..88eb3d9 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -16,13 +16,61 @@ body { font-family: 'Liberation Mono', monospace; font-size: 14pt; background-color: #fff; + height: 100%; } h1 { color: #abc837; } +.layout-crumbs { + grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank"; + grid-template-columns: 1fr; + grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr; + /* blank */ +} +.layout-crumbs #tree { + display: none; +} +.layout-tree { + display: grid; + grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree files" "tree blank"; + grid-template-columns: min-content 1fr; + grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr; + /* blank */ + color: #fff; + height: 100%; +} +.layout-tree-only { + display: grid; + grid-template-areas: "header" "tree"; + grid-template-columns: 1fr; + grid-template-rows: min-content /* header */ 1fr; + /* blank */ + color: #fff; + height: 100%; +} +.layout-tree-only #crumbs { + display: none; +} +.layout-tree-only .child-nodes { + display: none; +} +.layout-tree-only .node-name { + display: none; +} +.layout-tree-only .node-content { + display: none; +} +.layout-tree-only #file-section { + display: none; +} #app { display: grid; + grid-template-areas: "header header" "tree crumbs" "tree child-nodes" "tree name" "tree content" "tree files" "tree blank"; + grid-template-columns: min-content 1fr; + grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr; + /* blank */ color: #fff; + height: 100%; } #blackout { position: absolute; @@ -109,16 +157,31 @@ h1 { } header { display: grid; - grid-template-columns: 1fr min-content min-content; + grid-area: header; + grid-template-columns: min-content 1fr min-content min-content; align-items: center; - background: #abc837; padding: 0px; color: #333c11; + background: linear-gradient(to right, #009fff, #ec2f4b); + background: linear-gradient(to right, #f5af19, #f12711); + background: linear-gradient(to right, #fdc830, #f37335); + background: linear-gradient(to right, #8a2387, #e94057, #f27121); + background: linear-gradient(to right, #659999, #f4791f); + background: linear-gradient(to right, #3e5151, #decba4); + color: #fff; } header.modified { background: #c84a37; color: #faedeb; } +header .tree { + padding-left: 16px; +} +header .tree img { + display: block; + height: 24px; + width: 24px; +} header .name { font-weight: bold; padding-left: 16px; @@ -135,12 +198,54 @@ header .menu { user-select: none; -webkit-tap-highlight-color: transparent; } +#tree { + grid-area: tree; + padding: 16px; + background-color: #333; + color: #ddd; + font-size: 0.85em; +} +#tree .node { + display: grid; + grid-template-columns: 24px min-content; + grid-template-rows: min-content 1fr; + margin-top: 12px; +} +#tree .node .expand-toggle img { + width: 16px; + height: 16px; +} +#tree .node .name { + white-space: nowrap; + cursor: pointer; +} +#tree .node .name:hover { + color: #ecbf00; +} +#tree .node .name.selected { + color: #ecbf00; + font-weight: bold; +} +#tree .node .children { + padding-left: 24px; + margin-left: 8px; + border-left: 1px solid #555; + grid-column: 1 / -1; +} +#tree .node .children.collapsed { + display: none; +} +#crumbs { + grid-area: crumbs; + background: linear-gradient(to right, #fdc830, #f37335); +} .crumbs { display: flex; flex-wrap: wrap; - padding: 16px; - background: #333; + padding: 8px 16px; + background: #505050; color: #fff; + box-shadow: 0px 5px 8px 0px rgba(128, 128, 128, 0.5); } .crumbs .crumb { margin-right: 8px; @@ -162,15 +267,15 @@ header .menu { margin-left: 0px; } .child-nodes { + grid-area: child-nodes; display: flex; flex-wrap: wrap; padding: 16px 16px 0px 16px; - background-color: #505050; } .child-nodes .child-node { padding: 8px; border-radius: 8px; - background-color: #2f2f2f; + background-color: #333; margin-right: 12px; margin-bottom: 16px; white-space: nowrap; @@ -180,33 +285,78 @@ header .menu { -webkit-tap-highlight-color: transparent; } .node-name { - margin: 32px 0 16px 0; + grid-area: name; background: #fff; color: #000; text-align: center; font-weight: bold; + margin-top: 32px; + margin-bottom: 32px; } .node-content { + grid-area: content; justify-self: center; - padding: 16px 32px; word-wrap: break-word; font-family: monospace; font-size: 0.85em; color: #333; - width: 900px; + width: calc(100% - 32px); + max-width: 900px; resize: none; border: none; outline: none; } .node-content:invalid { background: #f5f5f5; + padding-top: 16px; } -#file-section { +/* ============================================================= * + * Textarea replicates the height of an element expanding height * + * ============================================================= */ +.grow-wrap { + /* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */ + display: grid; + grid-area: content; +} +.grow-wrap::after { + /* Note the weird space! Needed to preventy jumpy behavior */ + content: attr(data-replicated-value) " "; + /* This is how textarea text behaves */ + width: calc(100% - 32px); + max-width: 900px; + white-space: pre-wrap; + word-wrap: break-word; + color: #f0f; + background: rgba(0, 255, 255, 0.5); justify-self: center; - width: 900px; - margin-top: 16px; + /* Hidden from view, clicks, and screen readers */ + visibility: hidden; +} +.grow-wrap > textarea { + /* You could leave this, but after a user resizes, then it ruins the auto sizing */ + resize: none; + /* Firefox shows scrollbar on growth, you can hide like this. */ + overflow: hidden; +} +.grow-wrap > textarea, +.grow-wrap::after { + /* Identical styling required!! */ + padding: 0.5rem; + font: inherit; + /* Place on top of each other */ + grid-area: 1 / 1 / 2 / 2; +} +/* ============================================================= */ +#file-section { + grid-area: files; + justify-self: center; + width: calc(100% - 32px); + max-width: 900px; padding: 32px; background: #f5f5f5; + border-radius: 8px; + margin-top: 32px; + margin-bottom: 32px; } #file-section .header { font-weight: bold; @@ -233,12 +383,17 @@ header .menu { white-space: nowrap; text-align: right; } -.tree { - padding: 16px; -} @media only screen and (max-width: 932px) { + #app { + grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank"; + grid-template-columns: 1fr; + grid-template-rows: min-content /* header */ min-content /* crumbs */ min-content /* child-nodes */ min-content /* name */ min-content /* content */ min-content /* files */ 1fr; + /* blank */ + } + #app #tree { + display: none; + } .node-content { - width: calc(100% - 32px); margin-left: 16px; padding: 16px; justify-self: start; diff --git a/static/images/collapsed.svg b/static/images/collapsed.svg new file mode 100644 index 0000000..8bd376f --- /dev/null +++ b/static/images/collapsed.svg @@ -0,0 +1,74 @@ + + + + diff --git a/static/images/expanded.svg b/static/images/expanded.svg new file mode 100644 index 0000000..e1a6f66 --- /dev/null +++ b/static/images/expanded.svg @@ -0,0 +1,65 @@ + + + + diff --git a/static/images/leaf.svg b/static/images/leaf.svg new file mode 100644 index 0000000..ed44541 --- /dev/null +++ b/static/images/leaf.svg @@ -0,0 +1,57 @@ + + + + diff --git a/static/js/app.mjs b/static/js/app.mjs index a5ba406..c78b2d2 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -3,7 +3,7 @@ import { signal } from 'preact/signals' import { h, Component, render, createRef } from 'preact' import htm from 'htm' import { Session } from 'session' -import { NodeUI } from 'node' +import { Node, NodeUI } from 'node' const html = htm.bind(h) class App extends Component { @@ -19,10 +19,16 @@ class App extends Component { this.session = new Session(this) this.session.initialize() this.login = createRef() + this.tree = null this.nodeUI = createRef() this.nodeModified = signal(false) + + this.startNode = null + + this.setStartNode() }//}}} render() {//{{{ + console.log('render', 'app') if(!this.session.initialized) { return html`