refactored access to options on frontend

This commit is contained in:
zadam 2020-02-05 22:08:45 +01:00
parent 786bbbc160
commit 42017fde5f
32 changed files with 267 additions and 311 deletions

18
package-lock.json generated
View File

@ -2157,9 +2157,9 @@
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI="
}, },
"dayjs": { "dayjs": {
"version": "1.8.19", "version": "1.8.20",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.19.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.20.tgz",
"integrity": "sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg==" "integrity": "sha512-mH0MCDxw6UCGJYxVN78h8ugWycZAO8thkj3bW6vApL5tS0hQplIDdAQcmbvl7n35H0AKdCJQaArTrIQw2xt4Qg=="
}, },
"debug": { "debug": {
"version": "4.1.1", "version": "4.1.1",
@ -3782,9 +3782,9 @@
} }
}, },
"file-type": { "file-type": {
"version": "14.0.0", "version": "14.1.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-14.0.0.tgz", "resolved": "https://registry.npmjs.org/file-type/-/file-type-14.1.0.tgz",
"integrity": "sha512-+gxNvurlwHfTohZC6gqf0ybMl+cXYB9f1x++kw9AgKItdFx20J0fV9wCVR38a5/jphL5EUcusJ9tLYkPRtGHaw==", "integrity": "sha512-HfxnzrPH+LLClSAsno88/0frRtamu1XfqEP4IP/8RqBmqQnBQkemv3Udde0t53wZmrdOtc70aaR9WUHyQhjCUQ==",
"requires": { "requires": {
"readable-web-to-node-stream": "^2.0.0", "readable-web-to-node-stream": "^2.0.0",
"strtok3": "^6.0.0", "strtok3": "^6.0.0",
@ -6859,9 +6859,9 @@
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
}, },
"node-abi": { "node-abi": {
"version": "2.13.0", "version": "2.14.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.13.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.14.0.tgz",
"integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", "integrity": "sha512-y54KGgEOHnRHlGQi7E5UiryRkH8bmksmQLj/9iLAjoje743YS+KaKB/sDYXgqtT0J16JT3c3AYJZNI98aU/kYg==",
"requires": { "requires": {
"semver": "^5.4.1" "semver": "^5.4.1"
}, },

View File

@ -37,7 +37,7 @@
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"express": "4.17.1", "express": "4.17.1",
"express-session": "1.17.0", "express-session": "1.17.0",
"file-type": "14.0.0", "file-type": "14.1.0",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"helmet": "3.21.2", "helmet": "3.21.2",
"html": "1.0.0", "html": "1.0.0",
@ -53,7 +53,7 @@
"jimp": "0.9.3", "jimp": "0.9.3",
"mime-types": "2.1.26", "mime-types": "2.1.26",
"multer": "1.4.2", "multer": "1.4.2",
"node-abi": "2.13.0", "node-abi": "2.14.0",
"open": "7.0.2", "open": "7.0.2",
"pngjs": "3.4.0", "pngjs": "3.4.0",
"portscanner": "2.2.0", "portscanner": "2.2.0",

View File

@ -28,7 +28,7 @@ import dateNoteService from './services/date_notes.js';
import importService from './services/import.js'; import importService from './services/import.js';
import keyboardActionService from "./services/keyboard_actions.js"; import keyboardActionService from "./services/keyboard_actions.js";
import splitService from "./services/split.js"; import splitService from "./services/split.js";
import optionService from "./services/options.js"; import options from "./services/options.js";
import noteContentRenderer from "./services/note_content_renderer.js"; import noteContentRenderer from "./services/note_content_renderer.js";
import appContext from "./services/app_context.js"; import appContext from "./services/app_context.js";
@ -140,42 +140,3 @@ appContext.start();
noteTooltipService.setupGlobalTooltip(); noteTooltipService.setupGlobalTooltip();
noteAutocompleteService.init(); noteAutocompleteService.init();
if (utils.isElectron()) {
import("./services/spell_check.js").then(spellCheckService => spellCheckService.initSpellCheck());
}
optionService.waitForOptions().then(options => {
toggleSidebar('left', options.is('leftPaneVisible'));
toggleSidebar('right', options.is('rightPaneVisible'));
splitService.setupSplit(paneVisible.left, paneVisible.right);
});
const paneVisible = {};
function toggleSidebar(side, show) {
$(`#${side}-pane`).toggle(show);
$(`#show-${side}-pane-button`).toggle(!show);
$(`#hide-${side}-pane-button`).toggle(show);
paneVisible[side] = show;
}
async function toggleAndSave(side, show) {
toggleSidebar(side, show);
await server.put(`options/${side}PaneVisible/` + show.toString());
await optionService.reloadOptions();
splitService.setupSplit(paneVisible.left, paneVisible.right);
appContext.trigger('sidebarVisibilityChanged', {side, show});
}
$("#show-right-pane-button").on('click', () => toggleAndSave('right', true));
$("#hide-right-pane-button").on('click', () => toggleAndSave('right', false));
$("#show-left-pane-button").on('click', () => toggleAndSave('left', true));
$("#hide-left-pane-button").on('click', () => toggleAndSave('left', false));

View File

@ -3,6 +3,7 @@ import utils from "../../services/utils.js";
import cssLoader from "../../services/css_loader.js"; import cssLoader from "../../services/css_loader.js";
import zoomService from "../../services/zoom.js"; import zoomService from "../../services/zoom.js";
import optionsService from "../../services/options.js"; import optionsService from "../../services/options.js";
import appContext from "../../services/app_context.js";
const TPL = ` const TPL = `
<p><strong>Settings on this options tab are saved automatically after each change.</strong></p> <p><strong>Settings on this options tab are saved automatically after each change.</strong></p>
@ -107,7 +108,7 @@ export default class ApperanceOptions {
server.put('options/theme/' + newTheme); server.put('options/theme/' + newTheme);
}); });
this.$zoomFactorSelect.on('change', () => { zoomService.setZoomFactorAndSave(this.$zoomFactorSelect.val()); }); this.$zoomFactorSelect.on('change', () => { appContext.trigger('setZoomFactorAndSave', {zoomFactor: this.$zoomFactorSelect.val()}); });
this.$nativeTitleBarSelect.on('change', () => { this.$nativeTitleBarSelect.on('change', () => {
const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false'; const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false';

View File

@ -1,6 +1,5 @@
import server from "../../services/server.js";
import mimeTypesService from "../../services/mime_types.js"; import mimeTypesService from "../../services/mime_types.js";
import optionsService from "../../services/options.js"; import options from "../../services/options.js";
const TPL = ` const TPL = `
<h4>Available MIME types in the dropdown</h4> <h4>Available MIME types in the dropdown</h4>
@ -14,7 +13,7 @@ export default class CodeNotesOptions {
this.$mimeTypes = $("#options-mime-types"); this.$mimeTypes = $("#options-mime-types");
} }
async optionsLoaded(options) { async optionsLoaded() {
this.$mimeTypes.empty(); this.$mimeTypes.empty();
let idCtr = 1; let idCtr = 1;
@ -42,10 +41,8 @@ export default class CodeNotesOptions {
this.$mimeTypes.find("input:checked").each( this.$mimeTypes.find("input:checked").each(
(i, el) => enabledMimeTypes.push($(el).attr("data-mime-type"))); (i, el) => enabledMimeTypes.push($(el).attr("data-mime-type")));
const opts = { codeNotesMimeTypes: JSON.stringify(enabledMimeTypes) }; await options.save('codeNotesMimeTypes', JSON.stringify(enabledMimeTypes));
await server.put('options', opts); mimeTypesService.loadMimeTypes();
await optionsService.reloadOptions();
} }
} }

View File

@ -101,8 +101,6 @@ export default class ProtectedSessionOptions {
const eraseNotesAfterTimeInSeconds = this.$eraseNotesAfterTimeInSeconds.val(); const eraseNotesAfterTimeInSeconds = this.$eraseNotesAfterTimeInSeconds.val();
server.put('options', { 'eraseNotesAfterTimeInSeconds': eraseNotesAfterTimeInSeconds }).then(() => { server.put('options', { 'eraseNotesAfterTimeInSeconds': eraseNotesAfterTimeInSeconds }).then(() => {
optionsService.reloadOptions();
toastService.showMessage("Options change have been saved."); toastService.showMessage("Options change have been saved.");
}); });
@ -115,8 +113,6 @@ export default class ProtectedSessionOptions {
const protectedSessionTimeout = this.$protectedSessionTimeout.val(); const protectedSessionTimeout = this.$protectedSessionTimeout.val();
server.put('options', { 'protectedSessionTimeout': protectedSessionTimeout }).then(() => { server.put('options', { 'protectedSessionTimeout': protectedSessionTimeout }).then(() => {
optionsService.reloadOptions();
toastService.showMessage("Options change have been saved."); toastService.showMessage("Options change have been saved.");
}); });

View File

@ -104,8 +104,6 @@ export default class SidebarOptions {
}); });
await server.put('options', opts); await server.put('options', opts);
optionsService.reloadOptions();
} }
parseJsonSafely(str) { parseJsonSafely(str) {

View File

@ -29,10 +29,12 @@ import bundleService from "./bundle.js";
import DialogEventComponent from "./dialog_events.js"; import DialogEventComponent from "./dialog_events.js";
import Entrypoints from "./entrypoints.js"; import Entrypoints from "./entrypoints.js";
import CalendarWidget from "../widgets/calendar.js"; import CalendarWidget from "../widgets/calendar.js";
import optionsService from "./options.js"; import options from "./options.js";
import utils from "./utils.js"; import utils from "./utils.js";
import treeService from "./tree.js"; import treeService from "./tree.js";
import SidePaneContainer from "../widgets/side_pane_container.js"; import SidePaneContainer from "../widgets/side_pane_container.js";
import ZoomService from "./zoom.js";
import SidebarToggle from "../widgets/sidebar_toggle.js";
class AppContext { class AppContext {
constructor() { constructor() {
@ -45,7 +47,9 @@ class AppContext {
this.activeTabId = null; this.activeTabId = null;
} }
start() { async start() {
options.load(await server.get('options'));
this.showWidgets(); this.showWidgets();
this.loadTabs(); this.loadTabs();
@ -54,8 +58,6 @@ class AppContext {
} }
async loadTabs() { async loadTabs() {
const options = await optionsService.waitForOptions();
const openTabs = options.getJson('openTabs') || []; const openTabs = options.getJson('openTabs') || [];
await treeCache.initializedPromise; await treeCache.initializedPromise;
@ -186,14 +188,25 @@ class AppContext {
$centerPane.after(rightPaneContainer.render()); $centerPane.after(rightPaneContainer.render());
const sidebarToggleWidget = new SidebarToggle(this);
$centerPane.after(sidebarToggleWidget.render());
this.components = [ this.components = [
new Entrypoints(), new Entrypoints(),
new DialogEventComponent(this), new DialogEventComponent(this),
...topPaneWidgets, ...topPaneWidgets,
leftPaneContainer, leftPaneContainer,
...centerPaneWidgets, ...centerPaneWidgets,
rightPaneContainer rightPaneContainer,
sidebarToggleWidget
]; ];
if (utils.isElectron()) {
this.components.push(new ZoomService(this));
import("./spell_check.js").then(spellCheckService => spellCheckService.initSpellCheck());
}
} }
trigger(name, data, sync = false) { trigger(name, data, sync = false) {

View File

@ -70,7 +70,7 @@ function copy(nodes) {
function cut(nodes) { function cut(nodes) {
clipboardBranchIds = nodes clipboardBranchIds = nodes
.filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteId())
.filter(node => node.getParent().data.noteType !== 'search') .filter(node => node.getParent().data.noteType !== 'search')
.map(node => node.key); .map(node => node.key);

View File

@ -57,13 +57,7 @@ export default class Entrypoints extends Component {
}); });
} }
zoomOutListener() {
zoomService.decreaseZoomFactor();
}
zoomInListener() {
zoomService.increaseZoomFactor();
}
async createNoteIntoDayNoteListener() { async createNoteIntoDayNoteListener() {
const todayNote = await dateNoteService.getTodayNote(); const todayNote = await dateNoteService.getTodayNote();

View File

@ -1,30 +1,16 @@
import optionsService from './options.js'; import options from './options.js';
import server from "./server.js";
import appContext from "./app_context.js"; import appContext from "./app_context.js";
import treeService from "./tree.js"; import treeService from "./tree.js";
let hoistedNoteId = 'root'; function getHoistedNoteId() {
return options.get('hoistedNoteId');
optionsService.waitForOptions().then(options => {
hoistedNoteId = options.get('hoistedNoteId');
});
function getHoistedNoteNoPromise() {
return hoistedNoteId;
}
async function getHoistedNoteId() {
await optionsService.waitForOptions();
return hoistedNoteId;
} }
async function setHoistedNoteId(noteId) { async function setHoistedNoteId(noteId) {
hoistedNoteId = noteId; await options.save('hoistedNoteId', noteId);
await server.put('options/hoistedNoteId/' + noteId); // FIXME - just use option load event
appContext.trigger('hoistedNoteChanged', {noteId});
appContext.trigger('hoistedNoteChanged', {hoistedNoteId});
} }
async function unhoist() { async function unhoist() {
@ -69,7 +55,6 @@ async function checkNoteAccess(notePath) {
export default { export default {
getHoistedNoteId, getHoistedNoteId,
getHoistedNoteNoPromise,
setHoistedNoteId, setHoistedNoteId,
unhoist, unhoist,
isTopLevelNode, isTopLevelNode,

View File

@ -14,6 +14,8 @@ export class LoadResults {
this.noteRevisions = []; this.noteRevisions = [];
this.contentNoteIdToSourceId = []; this.contentNoteIdToSourceId = [];
this.options = [];
} }
addNote(noteId, sourceId) { addNote(noteId, sourceId) {
@ -90,4 +92,12 @@ export class LoadResults {
return this.contentNoteIdToSourceId.find(l => l.noteId === noteId && l.sourceId !== sourceId); return this.contentNoteIdToSourceId.find(l => l.noteId === noteId && l.sourceId !== sourceId);
} }
addOption(name) {
this.options.push(name);
}
isOptionReloaded(name) {
this.options.includes(name);
}
} }

View File

@ -1,4 +1,4 @@
import optionsService from "./options.js"; import options from "./options.js";
const MIME_TYPES_DICT = [ const MIME_TYPES_DICT = [
{ default: true, title: "Plain text", mime: "text/plain" }, { default: true, title: "Plain text", mime: "text/plain" },
@ -161,7 +161,7 @@ const MIME_TYPES_DICT = [
let mimeTypes = null; let mimeTypes = null;
function loadMimeTypes(options) { function loadMimeTypes() {
mimeTypes = JSON.parse(JSON.stringify(MIME_TYPES_DICT)); // clone mimeTypes = JSON.parse(JSON.stringify(MIME_TYPES_DICT)); // clone
const enabledMimeTypes = options.getJson('codeNotesMimeTypes') const enabledMimeTypes = options.getJson('codeNotesMimeTypes')
@ -172,16 +172,15 @@ function loadMimeTypes(options) {
} }
} }
optionsService.addLoadListener(loadMimeTypes);
async function getMimeTypes() { async function getMimeTypes() {
if (mimeTypes === null) { if (mimeTypes === null) {
loadMimeTypes(await options.waitForOptions()); loadMimeTypes();
} }
return mimeTypes; return mimeTypes;
} }
export default { export default {
getMimeTypes getMimeTypes,
loadMimeTypes
} }

View File

@ -1,11 +1,9 @@
import server from "./server.js"; import server from "./server.js";
let optionsReady;
const loadListeners = []; const loadListeners = [];
class Options { class Options {
constructor(arr) { load(arr) {
this.arr = arr; this.arr = arr;
} }
@ -37,45 +35,21 @@ class Options {
is(key) { is(key) {
return this.arr[key] === 'true'; return this.arr[key] === 'true';
} }
}
function reloadOptions() { set(key, value) {
optionsReady = new Promise((resolve, reject) => { this.arr[key] = value;
server.get('options').then(optionArr => {
const options = new Options(optionArr);
resolve(options);
for (const listener of loadListeners) {
listener(options);
} }
});
});
return optionsReady; async save(key, value) {
this.set(key, value);
const payload = {};
payload[key] = value;
await server.put(`options`, payload);
}
} }
/** const options = new Options();
* just waits for some options without triggering reload
*
* @return {Options}
*/
async function waitForOptions() {
return await optionsReady;
}
reloadOptions(); // initial load export default options;
function addLoadListener(listener) {
loadListeners.push(listener);
// useful when listener has been added after the promise resolved, but can cause double emit if not yet
// that should not be an issue though
optionsReady.then(listener);
}
export default {
addLoadListener,
reloadOptions,
waitForOptions
}

View File

@ -1,23 +1,19 @@
import utils from "./utils.js"; import utils from "./utils.js";
import optionsService from './options.js'; import options from './options.js';
const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; const PROTECTED_SESSION_ID_KEY = 'protectedSessionId';
let lastProtectedSessionOperationDate = null; let lastProtectedSessionOperationDate = 0;
let protectedSessionTimeout = null;
optionsService.addLoadListener(options => setProtectedSessionTimeout(options.getInt('protectedSessionTimeout')));
setInterval(() => { setInterval(() => {
if (lastProtectedSessionOperationDate !== null && Date.now() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { const protectedSessionTimeout = options.getInt('protectedSessionTimeout');
if (lastProtectedSessionOperationDate
&& Date.now() - lastProtectedSessionOperationDate > protectedSessionTimeout * 1000) {
resetProtectedSession(); resetProtectedSession();
} }
}, 5000); }, 5000);
function setProtectedSessionTimeout(encSessTimeout) {
protectedSessionTimeout = encSessTimeout;
}
function setProtectedSessionId(id) { function setProtectedSessionId(id) {
// using session cookie so that it disappears after browser/tab is closed // using session cookie so that it disappears after browser/tab is closed
utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, id); utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, id);
@ -37,7 +33,7 @@ function isProtectedSessionAvailable() {
function touchProtectedSession() { function touchProtectedSession() {
if (isProtectedSessionAvailable()) { if (isProtectedSessionAvailable()) {
lastProtectedSessionOperationDate = new Date(); lastProtectedSessionOperationDate = Date.now();
setProtectedSessionId(utils.getCookie(PROTECTED_SESSION_ID_KEY)); setProtectedSessionId(utils.getCookie(PROTECTED_SESSION_ID_KEY));
} }
@ -47,6 +43,5 @@ export default {
setProtectedSessionId, setProtectedSessionId,
resetProtectedSession, resetProtectedSession,
isProtectedSessionAvailable, isProtectedSessionAvailable,
setProtectedSessionTimeout,
touchProtectedSession touchProtectedSession
}; };

View File

@ -1,9 +1,21 @@
import treeService from './tree.js';
import treeCache from "./tree_cache.js";
import server from './server.js';
import toastService from "./toast.js"; import toastService from "./toast.js";
import appContext from "./app_context.js"; import appContext from "./app_context.js";
const helpText = `
<strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="Search">complete help on search</button>
<p>
<ul>
<li>Just enter any text for full text search</li>
<li><code>@abc</code> - returns notes with label abc</li>
<li><code>@year=2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
<li><code>@rock @pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
<li><code>@rock or @pop</code> - only one of the labels must be present</li>
<li><code>@year&lt;=2000</code> - numerical comparison (also &gt;, &gt;=, &lt;).</li>
<li><code>@dateCreated>=MONTH-1</code> - notes created in the last month</li>
<li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li>
</ul>
</p>`;
async function refreshSearch() { async function refreshSearch() {
const activeNode = appContext.getMainNoteTree().getActiveNode(); const activeNode = appContext.getMainNoteTree().getActiveNode();
@ -23,10 +35,6 @@ function init() {
} }
export default { export default {
// toggleSearch,
// resetSearch,
// showSearch,
// doSearch,
refreshSearch, refreshSearch,
init, init,
getHelpText: () => helpText getHelpText: () => helpText

View File

@ -1,8 +1,6 @@
import optionsService from "./options.js"; import options from "./options.js";
export async function initSpellCheck() { export async function initSpellCheck() {
const options = await optionsService.waitForOptions();
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker'); const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
const {remote, shell} = require('electron'); const {remote, shell} = require('electron');

View File

@ -1,17 +1,7 @@
import server from "./server.js"; import options from "./options.js";
import optionService from "./options.js";
let instance; let instance;
async function getPaneWidths() {
const options = await optionService.waitForOptions();
return {
leftPaneWidth: options.getInt('leftPaneWidth'),
rightPaneWidth: options.getInt('rightPaneWidth')
};
}
async function setupSplit(left, right) { async function setupSplit(left, right) {
if (instance) { if (instance) {
instance.destroy(); instance.destroy();
@ -24,15 +14,16 @@ async function setupSplit(left, right) {
return; return;
} }
const {leftPaneWidth, rightPaneWidth} = await getPaneWidths(); const leftPaneWidth = options.getInt('leftPaneWidth');
const rightPaneWidth = options.getInt('rightPaneWidth');
if (left && right) { if (left && right) {
instance = Split(['#left-pane', '#center-pane', '#right-pane'], { instance = Split(['#left-pane', '#center-pane', '#right-pane'], {
sizes: [leftPaneWidth, 100 - leftPaneWidth - rightPaneWidth, rightPaneWidth], sizes: [leftPaneWidth, 100 - leftPaneWidth - rightPaneWidth, rightPaneWidth],
gutterSize: 5, gutterSize: 5,
onDragEnd: sizes => { onDragEnd: sizes => {
server.put('options/leftPaneWidth/' + Math.round(sizes[0])); options.save('leftPaneWidth', Math.round(sizes[0]));
server.put('options/rightPaneWidth/' + Math.round(sizes[2])); options.save('rightPaneWidth', Math.round(sizes[2]));
} }
}); });
} }
@ -41,7 +32,7 @@ async function setupSplit(left, right) {
sizes: [leftPaneWidth, 100 - leftPaneWidth], sizes: [leftPaneWidth, 100 - leftPaneWidth],
gutterSize: 5, gutterSize: 5,
onDragEnd: sizes => { onDragEnd: sizes => {
server.put('options/leftPaneWidth/' + Math.round(sizes[0])); options.save('leftPaneWidth', Math.round(sizes[0]));
} }
}); });
} }
@ -50,7 +41,7 @@ async function setupSplit(left, right) {
sizes: [100 - rightPaneWidth, rightPaneWidth], sizes: [100 - rightPaneWidth, rightPaneWidth],
gutterSize: 5, gutterSize: 5,
onDragEnd: sizes => { onDragEnd: sizes => {
server.put('options/rightPaneWidth/' + Math.round(sizes[1])); options.save('rightPaneWidth', Math.round(sizes[1]));
} }
}); });
} }

View File

@ -9,12 +9,6 @@ import Component from "../widgets/component.js";
import treeCache from "./tree_cache.js"; import treeCache from "./tree_cache.js";
import hoistedNoteService from "./hoisted_note.js"; import hoistedNoteService from "./hoisted_note.js";
let showSidebarInNewTab = true;
optionsService.addLoadListener(options => {
showSidebarInNewTab = options.is('showSidebarInNewTab');
});
class TabContext extends Component { class TabContext extends Component {
/** /**
* @param {AppContext} appContext * @param {AppContext} appContext

View File

@ -5,6 +5,7 @@ import server from "./server.js";
import {LoadResults} from "./load_results.js"; import {LoadResults} from "./load_results.js";
import NoteComplement from "../entities/note_complement.js"; import NoteComplement from "../entities/note_complement.js";
import appContext from "./app_context.js"; import appContext from "./app_context.js";
import options from "./options.js";
/** /**
* TreeCache keeps a read only cache of note tree structure in frontend's memory. * TreeCache keeps a read only cache of note tree structure in frontend's memory.
@ -351,6 +352,12 @@ class TreeCache {
loadResults.addNoteRevision(sync.entityId, sync.noteId, sync.sourceId); loadResults.addNoteRevision(sync.entityId, sync.noteId, sync.sourceId);
}); });
syncRows.filter(sync => sync.entityName === 'options').forEach(sync => {
options.set(sync.entity.name, sync.entity.value);
loadResults.addOption(sync.entity.name);
});
appContext.trigger('entitiesReloaded', {loadResults}); appContext.trigger('entitiesReloaded', {loadResults});
} }
} }

View File

@ -130,7 +130,7 @@ async function consumeSyncData() {
await treeCache.processSyncRows(allSyncData); await treeCache.processSyncRows(allSyncData);
} }
catch (e) { catch (e) {
logError(`Encountered error ${e.message}, reloading frontend.`); logError(`Encountered error ${e.message}: ${e.stack}, reloading frontend.`);
// if there's an error in updating the frontend then the easy option to recover is to reload the frontend completely // if there's an error in updating the frontend then the easy option to recover is to reload the frontend completely
utils.reloadApp(); utils.reloadApp();

View File

@ -1,51 +1,47 @@
import server from "./server.js"; import options from "./options.js";
import utils from "./utils.js"; import Component from "../widgets/component.js";
import optionsService from "./options.js";
const MIN_ZOOM = 0.5; const MIN_ZOOM = 0.5;
const MAX_ZOOM = 2.0; const MAX_ZOOM = 2.0;
async function decreaseZoomFactor() { export default class ZoomService extends Component {
await setZoomFactorAndSave(getCurrentZoom() - 0.1); constructor(appContext) {
} super(appContext);
async function increaseZoomFactor() { this.setZoomFactor(options.getFloat('zoomFactor'));
await setZoomFactorAndSave(getCurrentZoom() + 0.1); }
}
function setZoomFactor(zoomFactor) { setZoomFactor(zoomFactor) {
zoomFactor = parseFloat(zoomFactor); zoomFactor = parseFloat(zoomFactor);
const webFrame = require('electron').webFrame; const webFrame = require('electron').webFrame;
webFrame.setZoomFactor(zoomFactor); webFrame.setZoomFactor(zoomFactor);
}
async function setZoomFactorAndSave(zoomFactor) {
if (!utils.isElectron()) {
return;
} }
async setZoomFactorAndSave(zoomFactor) {
if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) { if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) {
setZoomFactor(zoomFactor); this.setZoomFactor(zoomFactor);
await server.put('options/zoomFactor/' + zoomFactor); await options.save('zoomFactor', zoomFactor);
} }
else { else {
console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`); console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`);
} }
} }
function getCurrentZoom() { getCurrentZoom() {
return require('electron').webFrame.getZoomFactor(); return require('electron').webFrame.getZoomFactor();
} }
if (utils.isElectron()) { zoomOutListener() {
optionsService.addLoadListener(options => setZoomFactor(options.getFloat('zoomFactor'))) this.setZoomFactorAndSave(this.getCurrentZoom() - 0.1);
} }
export default { zoomInListener() {
decreaseZoomFactor, this.setZoomFactorAndSave(this.getCurrentZoom() + 0.1);
increaseZoomFactor, }
setZoomFactor,
setZoomFactorAndSave setZoomFactorAndSaveListener({zoomFactor}) {
this.setZoomFactorAndSave(zoomFactor);
}
} }

View File

@ -118,7 +118,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
autoExpandMS: 600, autoExpandMS: 600,
dragStart: (node, data) => { dragStart: (node, data) => {
// don't allow dragging root node // don't allow dragging root node
if (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() if (node.data.noteId === hoistedNoteService.getHoistedNoteId()
|| node.getParent().data.noteType === 'search') { || node.getParent().data.noteType === 'search') {
return false; return false;
} }
@ -141,7 +141,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
dragDrop: async (node, data) => { dragDrop: async (node, data) => {
if ((data.hitMode === 'over' && node.data.noteType === 'search') || if ((data.hitMode === 'over' && node.data.noteType === 'search') ||
(['after', 'before'].includes(data.hitMode) (['after', 'before'].includes(data.hitMode)
&& (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() || node.getParent().data.noteType === 'search'))) { && (node.data.noteId === hoistedNoteService.getHoistedNoteId() || node.getParent().data.noteType === 'search'))) {
const infoDialog = await import('../dialogs/info.js'); const infoDialog = await import('../dialogs/info.js');

View File

@ -1,25 +1,10 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "./basic_widget.js";
import treeService from "../services/tree.js"; import searchService from "../services/search_notes.js";
import treeCache from "../services/tree_cache.js"; import treeCache from "../services/tree_cache.js";
import toastService from "../services/toast.js"; import toastService from "../services/toast.js";
import appContext from "../services/app_context.js"; import appContext from "../services/app_context.js";
import noteCreateService from "../services/note_create.js"; import noteCreateService from "../services/note_create.js";
const helpText = `
<strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="Search">complete help on search</button>
<p>
<ul>
<li>Just enter any text for full text search</li>
<li><code>@abc</code> - returns notes with label abc</li>
<li><code>@year=2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
<li><code>@rock @pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
<li><code>@rock or @pop</code> - only one of the labels must be present</li>
<li><code>@year&lt;=2000</code> - numerical comparison (also &gt;, &gt;=, &lt;).</li>
<li><code>@dateCreated>=MONTH-1</code> - notes created in the last month</li>
<li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li>
</ul>
</p>`;
const TPL = ` const TPL = `
<div class="search-box"> <div class="search-box">
<style> <style>
@ -145,7 +130,7 @@ export default class SearchBoxWidget extends BasicWidget {
this.$searchBox.tooltip({ this.$searchBox.tooltip({
trigger: 'focus', trigger: 'focus',
html: true, html: true,
title: helpText, title: searchService.getHelpText(),
placement: 'right', placement: 'right',
delay: { delay: {
show: 500, // necessary because sliding out may cause wrong position show: 500, // necessary because sliding out may cause wrong position

View File

@ -1,5 +1,5 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "./basic_widget.js";
import optionService from "../services/options.js"; import options from "../services/options.js";
export default class SidePaneContainer extends BasicWidget { export default class SidePaneContainer extends BasicWidget {
constructor(appContext, side, widgets) { constructor(appContext, side, widgets) {
@ -19,9 +19,7 @@ export default class SidePaneContainer extends BasicWidget {
return this.$widget; return this.$widget;
} }
async eventReceived(name, data, sync = false) { eventReceived(name, data, sync = false) {
const options = await optionService.waitForOptions();
if (options.is(this.side + 'PaneVisible')) { if (options.is(this.side + 'PaneVisible')) {
super.eventReceived(name, data, sync); super.eventReceived(name, data, sync);
} }

View File

@ -0,0 +1,72 @@
import options from "../services/options.js";
import splitService from "../services/split.js";
import BasicWidget from "./basic_widget.js";
const TPL = `
<div>
<style>
#hide-right-pane-button, #show-right-pane-button {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 1000;
}
#hide-left-pane-button, #show-left-pane-button {
position: fixed;
bottom: 10px;
left: 10px;
z-index: 1000;
}
</style>
<button id="hide-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button>
<button id="show-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button>
<button id="hide-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button>
<button id="show-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button>
</div>
`;
export default class SidebarToggle extends BasicWidget {
constructor(appContext) {
super(appContext);
this.paneVisible = {};
}
doRender() {
this.$widget = $(TPL);
this.toggleSidebar('left', options.is('leftPaneVisible'));
this.toggleSidebar('right', options.is('rightPaneVisible'));
$("#show-right-pane-button").on('click', () => toggleAndSave('right', true));
$("#hide-right-pane-button").on('click', () => toggleAndSave('right', false));
$("#show-left-pane-button").on('click', () => toggleAndSave('left', true));
$("#hide-left-pane-button").on('click', () => toggleAndSave('left', false));
splitService.setupSplit(this.paneVisible.left, this.paneVisible.right);
return this.$widget;
}
toggleSidebar(side, show) {
$(`#${side}-pane`).toggle(show);
$(`#show-${side}-pane-button`).toggle(!show);
$(`#hide-${side}-pane-button`).toggle(show);
this.paneVisible[side] = show;
}
async toggleAndSave(side, show) {
this.toggleSidebar(side, show);
await options.save(`${side}PaneVisible`, show.toString());
splitService.setupSplit(this.paneVisible.left, this.paneVisible.right);
this.trigger('sidebarVisibilityChanged', {side, show});
}
}

View File

@ -1,5 +1,5 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "./basic_widget.js";
import optionService from "../services/options.js"; import options from "../services/options.js";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
const TPL = ` const TPL = `
@ -23,10 +23,8 @@ export default class TitleBarButtonsWidget extends BasicWidget {
return; return;
} }
this.$widget = $(TPL);
optionService.waitForOptions().then(options => {
if (!options.is('nativeTitleBarVisible')) { if (!options.is('nativeTitleBarVisible')) {
this.$widget = $(TPL);
this.$widget.show(); this.$widget.show();
const $minimizeBtn = this.$widget.find(".minimize-btn"); const $minimizeBtn = this.$widget.find(".minimize-btn");
@ -57,7 +55,9 @@ export default class TitleBarButtonsWidget extends BasicWidget {
remote.BrowserWindow.getFocusedWindow().close(); remote.BrowserWindow.getFocusedWindow().close();
}); });
} }
}); else {
this.$widget = $('<div>');
}
return this.$widget; return this.$widget;
} }

View File

@ -146,20 +146,6 @@ body {
border-color: var(--button-border-color); border-color: var(--button-border-color);
} }
#hide-right-pane-button, #show-right-pane-button {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 1000;
}
#hide-left-pane-button, #show-left-pane-button {
position: fixed;
bottom: 10px;
left: 10px;
z-index: 1000;
}
#right-pane { #right-pane {
overflow: auto; overflow: auto;
padding-top: 4px; padding-top: 4px;

View File

@ -110,9 +110,7 @@ async function updateEntity(entity) {
const primaryKey = entity[primaryKeyName]; const primaryKey = entity[primaryKeyName];
if (entity.isChanged) { if (entity.isChanged) {
if (entityName !== 'options' || entity.isSynced) {
await syncTableService.addEntitySync(entityName, primaryKey); await syncTableService.addEntitySync(entityName, primaryKey);
}
if (!cls.isEntityEventsDisabled()) { if (!cls.isEntityEventsDisabled()) {
const eventPayload = { const eventPayload = {

View File

@ -315,10 +315,13 @@ async function getSyncRecords(syncs) {
let length = 0; let length = 0;
for (const sync of syncs) { for (const sync of syncs) {
const record = { const entity = await getEntityRow(sync.entityName, sync.entityId);
sync: sync,
entity: await getEntityRow(sync.entityName, sync.entityId) if (sync.entityName === 'options' && !entity.isSynced) {
}; continue;
}
const record = { sync, entity };
records.push(record); records.push(record);

View File

@ -92,6 +92,9 @@ async function fillInAdditionalProperties(sync) {
} else if (sync.entityName === 'note_reordering') { } else if (sync.entityName === 'note_reordering') {
sync.positions = await sql.getMap(`SELECT branchId, notePosition FROM branches WHERE isDeleted = 0 AND parentNoteId = ?`, [sync.entityId]); sync.positions = await sql.getMap(`SELECT branchId, notePosition FROM branches WHERE isDeleted = 0 AND parentNoteId = ?`, [sync.entityId]);
} }
else if (sync.entityName === 'options') {
sync.entity = await sql.getRow(`SELECT * FROM options WHERE name = ?`, [sync.entityId]);
}
} }
async function sendPing(client) { async function sendPing(client) {

View File

@ -14,13 +14,7 @@
<div id="top-pane"></div> <div id="top-pane"></div>
<div id="main-pane" style="display: flex; flex-grow: 1; flex-shrink: 1; min-height: 0;"> <div id="main-pane" style="display: flex; flex-grow: 1; flex-shrink: 1; min-height: 0;">
<button id="hide-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button>
<button id="show-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button>
<div id="center-pane"></div> <div id="center-pane"></div>
<button id="hide-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button>
<button id="show-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button>
</div> </div>
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div> <div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>