This commit is contained in:
Magnus Åhall 2023-06-18 20:13:35 +02:00
parent c255b58335
commit 1812873e33
10 changed files with 342 additions and 61 deletions

View file

@ -11,7 +11,7 @@
border-bottom: 1px solid #fff;
font-size: 18pt;
color: #fff;
background-color: #494949;
background-color: #fff;
}
#login input:focus {
outline: none;
@ -19,7 +19,7 @@
#login button {
max-width: 300px;
border: 1px solid #666;
background: #494949;
background: #fff;
color: #fff;
padding: 16px 32px;
font-size: 0.8em;

View file

@ -1,34 +1,68 @@
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
[onClick] {
cursor: pointer;
}
html,
body {
margin: 0px;
padding: 0px;
font-family: 'Liberation Mono', monospace;
font-size: 14pt;
background-color: #494949;
background-color: #fff;
}
h1 {
color: #ecbf00;
}
#app {
display: grid;
color: #fff;
}
header {
display: grid;
grid-template-columns: 1fr min-content;
align-items: center;
background: #ecbf00;
padding: 0px;
color: #3a2f00;
}
header.modified {
background: #c84a37;
color: #faedeb;
}
header .name {
font-weight: bold;
padding-left: 16px;
}
header .add {
font-size: 2em;
padding-right: 16px;
cursor: pointer;
}
.crumbs {
display: flex;
flex-wrap: wrap;
padding: 8px 16px;
background: #ecbf00;
color: #000;
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.4);
padding: 16px;
background: #333;
color: #fff;
}
.crumbs .crumb {
margin-right: 8px;
font-size: 0.8em;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.crumbs .crumb:after {
content: ">";
content: "";
margin-left: 8px;
color: #a08100;
color: #ecbf00;
}
.crumbs .crumb:last-child {
margin-right: 0;
@ -41,22 +75,41 @@ h1 {
display: flex;
flex-wrap: wrap;
padding: 16px 16px 0px 16px;
background-color: #3c3c3c;
box-shadow: 0px 0px 16px -4px rgba(0, 0, 0, 0.55) inset;
background-color: #505050;
}
.child-nodes .child-node {
padding: 8px;
border-radius: 8px;
background-color: #292929;
margin-right: 16px;
background-color: #2f2f2f;
margin-right: 12px;
margin-bottom: 16px;
white-space: nowrap;
font-size: 0.8em;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.node-name {
padding: 16px;
padding: 32px;
background: #fff;
color: #000;
text-align: center;
font-weight: bold;
}
.node-content {
padding: 32px;
justify-self: center;
padding: 0px 32px 32px 32px;
white-space: pre-wrap;
font-size: 0.85em;
color: #333;
width: 100ex;
}
.node-content[contenteditable] {
outline: 0px solid transparent;
}
@media only screen and (max-width: 100ex) {
.node-content {
width: initial;
justify-self: start;
}
}

View file

@ -9,6 +9,7 @@ const html = htm.bind(h)
class App extends Component {
constructor() {//{{{
super()
this.websocket_uri = `wss://notes.ahall.se/ws`
this.websocket = null
this.websocket_int_ping = null
this.websocket_int_reconnect = null
@ -19,6 +20,7 @@ class App extends Component {
this.session.initialize()
this.login = createRef()
this.nodeUI = createRef()
this.nodeModified = signal(false)
}//}}}
render() {//{{{
if(!this.session.initialized) {
@ -41,7 +43,7 @@ class App extends Component {
}, 1000)
}//}}}
wsConnect() {//{{{
this.websocket = new WebSocket(`ws://192.168.11.60:1371/ws`)
this.websocket = new WebSocket(this.websocket_uri)
this.websocket.onopen = evt=>this.wsOpen(evt)
this.websocket.onmessage = evt=>this.wsMessage(evt)
this.websocket.onerror = evt=>this.wsError(evt)
@ -164,28 +166,6 @@ class Login extends Component {
}
// Init{{{
//let urlParams = new URLSearchParams(window.location.search)
/*
async function debug(type, context, data) {
await fetch("https://msg.kon-it.se/log", {
method: 'POST',
body: JSON.stringify({
systemId: 12,
type,
context,
data: JSON.stringify(data, null, 4),
}),
})
}
window.onerror = (event, source, lineno, colon, error) => {
debug('Notes', 'error', {
event, source, lineno, colon, error
})
}
*/
window._app = createRef()
window._resourceModels = []
render(html`<${App} ref=${window._app} />`, document.getElementById('app'))

View file

@ -1,4 +1,4 @@
import { h, Component } from 'preact'
import { h, Component, createRef } from 'preact'
import htm from 'htm'
import { signal } from 'preact/signals'
const html = htm.bind(h)
@ -7,6 +7,7 @@ export class NodeUI extends Component {
constructor() {//{{{
super()
this.node = signal(null)
this.nodeContent = createRef()
window.addEventListener('popstate', evt=>{
if(evt.state && evt.state.hasOwnProperty('nodeID'))
this.goToNode(evt.state.nodeID, true)
@ -24,7 +25,7 @@ export class NodeUI extends Component {
html`<div class="crumb" onclick=${()=>this.goToNode(0)}>Start</div>`
]
crumbs = crumbs.concat(node.Crumbs.slice(1).map(node=>
crumbs = crumbs.concat(node.Crumbs.slice(0).map(node=>
html`<div class="crumb" onclick=${()=>this.goToNode(node.ID)}>${node.Name}</div>`
).reverse())
@ -36,27 +37,83 @@ export class NodeUI extends Component {
<div class="child-node" onclick=${()=>this.goToNode(child.ID)}>${child.Name}</div>
`)
let modified = ''
if(this.props.app.nodeModified.value)
modified = 'modified';
return html`
${node.ID > 0 ? html`<div class="crumbs">${crumbs}</crumbs>` : html``}
<div class="node-name">${node.Name}</div>
<header class="${modified}" onclick=${()=>this.saveNode()}>
<div class="name">Notes</div>
<div class="add" onclick=${()=>this.createNode()}>+</div>
</header>
<div class="crumbs">${crumbs}</crumbs>
${children.length > 0 ? html`<div class="child-nodes">${children}</div>` : html``}
<div class="node-content">${node.Content}</div>
<div class="node-name">${node.Name}</div>
<${NodeContent} key=${node.ID} content=${node.Content} ref=${this.nodeContent} />
`
}//}}}
componentDidMount() {//{{{
let root = new Node(this.props.app, 0)
let urlParams = new URLSearchParams(window.location.search)
let nodeID = urlParams.get('node')
let root = new Node(this.props.app, nodeID ? parseInt(nodeID) : 0)
root.retrieve(node=>{
this.node.value = node
})
}//}}}
goToNode(nodeID, dontPush) {//{{{
if(this.props.app.nodeModified.value) {
if(!confirm("Changes not saved. Do you want to discard changes?"))
return
}
if(!dontPush)
history.pushState({ nodeID }, '', `#${nodeID}`)
history.pushState({ nodeID }, '', `/?node=${nodeID}`)
let node = new Node(this.props.app, nodeID)
node.retrieve(node=>{
this.props.app.nodeModified.value = false
this.node.value = node
})
}//}}}
createNode() {//{{{
let name = prompt("Name")
if(!name)
return
this.props.app.request('/node/create', {
Name: name.trim(),
ParentID: this.node.value.ID,
})
.then(res=>{
this.goToNode(res.Node.ID)
})
.catch(this.props.app.responseError)
}//}}}
saveNode() {
let content = this.nodeContent.current.contentDiv.current.innerText
this.props.app.request('/node/update', {
NodeID: this.node.value.ID,
Content: content,
})
.then(res=>{
this.props.app.nodeModified.value = false
})
.catch(this.props.app.responseError)
}
}
class NodeContent extends Component {
constructor(props) {//{{{
super(props)
this.contentDiv = createRef()
this.state = {
modified: false,
//content: props.content,
}
}//}}}
render({ content }) {//{{{
return html`<div class="node-content" ref=${this.contentDiv} contenteditable=true oninput=${()=>window._app.current.nodeModified.value = true}>${content}</div>`
}//}}}
}
class Node {

View file

@ -1,5 +1,17 @@
@import "theme.less";
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
[onClick] {
cursor: pointer;
}
html, body {
margin: 0px;
padding: 0px;
@ -14,27 +26,54 @@ h1 {
}
#app {
display: grid;
color: #fff;
}
header {
display: grid;
grid-template-columns: 1fr min-content;
align-items: center;
background: @accent_1;
padding: 0px;
color: darken(@accent_1, 35%);
&.modified {
background: @accent_3;
color: lighten(@accent_3, 45%);
}
.name {
font-weight: bold;
padding-left: 16px;
}
.add {
font-size: 2em;
padding-right: 16px;
cursor: pointer;
}
}
.crumbs {
display: flex;
flex-wrap: wrap;
padding: 8px 16px;
background: @accent_1;
color: #000;
box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.4);
padding: 16px;
background: #333;
color: #fff;
.crumb {
margin-right: 8px;
font-size: 0.8em;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.crumb:after {
content: ">";
content: "";
margin-left: 8px;
color: darken(@accent_1, 15%);
color: @accent_1;
}
.crumb:last-child {
@ -52,25 +91,46 @@ h1 {
flex-wrap: wrap;
padding: 16px 16px 0px 16px;
background-color: darken(@background, 5%);
box-shadow: 0px 0px 16px -4px rgba(0,0,0,0.55) inset;
background-color: #505050;
.child-node {
padding: 8px;
border-radius: 8px;
background-color: darken(@background, 12.5%);
margin-right: 16px;
background-color: #2f2f2f;
margin-right: 12px;
margin-bottom: 16px;
white-space: nowrap;
font-size: 0.8em;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
}
.node-name {
padding: 16px;
padding: 32px;
background: #fff;
color: #000;
text-align: center;
font-weight: bold;
}
.node-content {
padding: 32px;
justify-self: center;
padding: 0px 32px 32px 32px;
white-space: pre-wrap;
font-size: 0.85em;
color: #333;
width: 100ex;
&[contenteditable] {
outline: 0px solid transparent;
}
}
@media only screen and (max-width: 100ex) {
.node-content {
width: initial;
justify-self: start;
}
}

View file

@ -1,4 +1,5 @@
@fontsize: 14pt;
@background: #494949;
@background: #fff;
@accent_1: #ecbf00;
@accent_2: #abc837;
@accent_3: #c84a37;