Small refactor for user preferences

This commit is contained in:
Magnus Åhall 2026-06-18 09:21:23 +02:00
parent 1a712fb7a9
commit 81d02b82dc
13 changed files with 202 additions and 112 deletions

View file

@ -73,9 +73,10 @@ button {
1fr;
}
&.page-history {
/* The other pages just gets the whole page without dividing it up. */
&:not(.page-node) {
grid-template-areas:
"tree-expander tree pad1 n2-pagehistory pad2"
"tree-expander tree pad1 n2-page pad2"
;
grid-template-columns:
@ -245,7 +246,6 @@ button {
#notes2 {
&.page-node {
#page-root {
display: none;
}
@ -260,7 +260,7 @@ button {
display: contents;
n2-pagestorage {
grid-area: content;
grid-area: n2-page;
}
}
}
@ -268,9 +268,14 @@ button {
&.page-history {
#page-history {
display: grid;
grid-area: n2-pagehistory;
grid-area: n2-page;
}
}
n2-pagehistory {}
&.page-preferences {
#page-preferences {
display: grid !important;
grid-area: n2-page;
}
}
@ -282,7 +287,6 @@ button {
#page-root {
display: contents !important;
}
}
}

View file

@ -2,18 +2,18 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="26.666645"
height="24"
viewBox="0 0 7.0555498 6.35"
width="12"
height="23.999981"
viewBox="0 0 3.1750001 6.349995"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e94, 2025-05-08)"
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
sodipodi:docname="icon_menu.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
@ -23,29 +23,34 @@
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="2.096401"
inkscape:cx="10.255672"
inkscape:cy="9.0631517"
inkscape:window-width="1916"
inkscape:window-height="1041"
inkscape:window-x="1920"
inkscape:window-y="1080"
inkscape:zoom="11.859035"
inkscape:cx="8.6010372"
inkscape:cy="17.32856"
inkscape:window-width="2190"
inkscape:window-height="1401"
inkscape:window-x="1463"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-146.57917,-92.339583)">
<title
id="title1">menu</title>
<title
id="title1-6">hamburger</title>
<path
d="m 153.63472,95.867362 c 0,0.391582 -0.31398,0.705554 -0.70555,0.705554 h -5.64445 c -0.38806,0 -0.70555,-0.313972 -0.70555,-0.705554 0,-0.391584 0.31749,-0.705556 0.70555,-0.705556 h 3.175 l 0.88194,0.705556 0.88195,-0.705556 h 0.70556 c 0.39157,0 0.70555,0.3175 0.70555,0.705556 m -3.52778,-3.527779 c -3.175,0 -3.175,2.116667 -3.175,2.116667 h 6.35 c 0,0 0,-2.116667 -3.175,-2.116667 m -3.175,5.291667 c 0,0.585612 0.47272,1.058333 1.05834,1.058333 h 4.23333 c 0.58561,0 1.05833,-0.472721 1.05833,-1.058333 v -0.352778 h -6.35 z"
id="path1-2"
style="stroke-width:0.352777" />
</g>
</svg>
transform="translate(-147.15925,-92.339586)"><title
id="title1">menu</title><title
id="title1-6">hamburger</title><circle
style="fill:#000000;stroke:none;stroke-width:0.264583"
id="path3"
cx="149.55338"
cy="93.120461"
r="0.78087437" /><circle
style="fill:#000000;stroke:none;stroke-width:0.264583"
id="circle4"
cx="149.55338"
cy="97.908707"
r="0.78087437" /><circle
style="fill:#000000;stroke:none;stroke-width:0.264583"
id="circle5"
cx="149.55338"
cy="95.514587"
r="0.78087437" /></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
export class API {
// query resolves into the JSON data produced by the application, or an exception with 'type' and 'error' properties.
static async query(method, path, request) {
return new Promise((resolve, reject) => {
try {
const body = JSON.stringify(request)
const headers = {}
@ -12,33 +12,22 @@ export class API {
headers.Authorization = `Bearer ${token}`
}
fetch(path, { method, headers, body })
.then(response => {
// An HTTP communication level error occured.
if (!response.ok || response.status != 200)
return reject({
type: 'http',
error: response,
})
return response.json()
})
.then(json => {
// Application level response are handled here.
if (!json.OK)
return reject({
type: 'application',
error: json.Error,
application: json,
})
resolve(json)
})
.catch(err =>
// Catch any other errors from fetch.
reject({
type: 'http',
error: err,
}))
})
const res = await fetch(path, { method, headers, body })
// An HTTP communication level error occured.
if (!res.ok || res.status != 200)
throw new Error('HTTP error', { cause: { type: 'http', error: res, }})
// Application level response are handled here.
const json = await res.json()
if (!json.OK)
throw new Error(json.Error, { cause: { type: 'application', application: json, }})
return json
} catch (err) {
// Catch any other errors from fetch.
throw new Error(err.message, { cause: { type: 'http', error: err, }})
}
}
static hasAuthenticationToken() {//{{{

View file

@ -0,0 +1,32 @@
import { CustomHTMLElement } from "./lib/custom_html_element.mjs"
import { API } from './api.mjs'
export class N2PagePreferences extends CustomHTMLElement {
static {// {{{
this.tmpl = document.createElement('template')
this.tmpl.innerHTML = `
<h1>Preferences</h1>
`
}// }}}
constructor() {// {{{
super()
window._mbus.subscribe('SHOW_PAGE', event => {
if (event.detail.data?.page == 'preferences')
this.render()
})
}// }}}
async render() {// {{{
}// }}}
getPreferences() {
API.query('GET', '/user/preferences')
}
}
customElements.define('n2-pagepreferences', N2PagePreferences)
// Preferences is a set of preferences, of which there can be many named.
class Preferences {
constructor(name, data) {
this.name = name
this.data = data
}
}

View file

@ -13,7 +13,10 @@ export class N2PageStorage extends CustomHTMLElement {
constructor() {
super()
window._mbus.subscribe('SHOW_PAGE', () => this.render())
window._mbus.subscribe('SHOW_PAGE', event => {
if (event.detail.data?.page == 'storage')
this.render()
})
}
async render() {
const countNodes = await globalThis.nodeStore.nodeCount()

View file

@ -128,6 +128,7 @@ export class N2Sidebar extends CustomHTMLElement {
this.elSearch.addEventListener('click', () => _mbus.dispatch('op-search'))
this.elSync.addEventListener('click', () => _sync.run())
this.elLogo.addEventListener('click', () => _app.goToNode(ROOT_NODE, false, false))
this.elSettings.addEventListener('click', ()=> _mbus.dispatch('SHOW_PAGE', { page: 'preferences' }))
this.elHideTree.addEventListener('click', event => {
event.stopPropagation()
_mbus.dispatch('TREE_EXPANSION', { expand: false })

View file

@ -90,6 +90,7 @@ export class Sync {
nodeStore.setAppState('latest_sync_node', currMax)
} catch (e) {
console.error('sync node tree', e)
alert(e.message)
} finally {
syncEnd = Date.now()
const duration = (syncEnd - syncStart) / 1000
@ -157,8 +158,8 @@ export class Sync {
_mbus.dispatch('SYNC_UPLOADED', { count: nodesToSend.length })
} catch (e) {
console.trace(e)
alert(e.error)
console.error(e)
alert(e.message)
return
}
}