component => type widget refactoring

This commit is contained in:
zadam 2020-01-19 11:03:34 +01:00
parent a99c016818
commit 746181689f
25 changed files with 114 additions and 126 deletions

View File

@ -141,7 +141,7 @@ async function printActiveNote() {
await libraryLoader.requireLibrary(libraryLoader.PRINT_THIS); await libraryLoader.requireLibrary(libraryLoader.PRINT_THIS);
$tabContext.$tabContent.find('.note-detail-component:visible').printThis({ $tabContext.$tabContent.find('.note-detail-printable:visible').printThis({
header: $("<h2>").text($tabContext.note && $tabContext.note.title).prop('outerHTML') , header: $("<h2>").text($tabContext.note && $tabContext.note.title).prop('outerHTML') ,
importCSS: false, importCSS: false,
loadCSS: [ loadCSS: [

View File

@ -60,6 +60,11 @@ class TabContext extends Component {
bundleService.executeRelationBundles(this.note, 'runOnNoteView', this); bundleService.executeRelationBundles(this.note, 'runOnNoteView', this);
if (this.note.isProtected && protectedSessionHolder.isProtectedSessionAvailable()) {
// FIXME: there are probably more places where this should be done
protectedSessionHolder.touchProtectedSession();
}
this.trigger('tabNoteSwitched', {tabId: this.tabId}); this.trigger('tabNoteSwitched', {tabId: this.tabId});
} }

View File

@ -16,25 +16,25 @@ const TPL = `
</div> </div>
`; `;
const componentClasses = { const typeWidgetClasses = {
'empty': "./detail/note_detail_empty.js", 'empty': "./type_widgets/note_detail_empty.js",
'text': "./detail/note_detail_text.js", 'text': "./type_widgets/text.js",
'code': "./detail/note_detail_code.js", 'code': "./type_widgets/code.js",
'file': "./detail/note_detail_file.js", 'file': "./type_widgets/file.js",
'image': "./detail/note_detail_image.js", 'image': "./type_widgets/note_detail_image.js",
'search': "./detail/note_detail_search.js", 'search': "./type_widgets/note_detail_search.js",
'render': "./detail/note_detail_render.js", 'render': "./type_widgets/note_detail_render.js",
'relation-map': "./detail/note_detail_relation_map.js", 'relation-map': "./type_widgets/note_detail_relation_map.js",
'protected-session': "./detail/note_detail_protected_session.js", 'protected-session': "./type_widgets/note_detail_protected_session.js",
'book': "./detail/note_detail_book.js" 'book': "./type_widgets/note_detail_book.js"
}; };
export default class NoteDetailWidget extends TabAwareWidget { export default class NoteDetailWidget extends TabAwareWidget {
constructor(appContext) { constructor(appContext) {
super(appContext); super(appContext);
this.components = {}; this.typeWidgets = {};
this.componentPromises = {}; this.typeWidgetPromises = {};
} }
doRender() { doRender() {
@ -43,18 +43,23 @@ export default class NoteDetailWidget extends TabAwareWidget {
return this.$widget; return this.$widget;
} }
async noteSwitched() { async refresh() {
await this.initComponent(/**disableAutoBook*/); this.type = this.getWidgetType(/*disableAutoBook*/);
for (const componentType in this.components) { if (!(this.type in this.typeWidgetPromises)) {
if (componentType !== this.type) { this.typeWidgetPromises[this.type] = this.initWidgetType(this.type);
this.components[componentType].cleanup(); }
await this.typeWidgetPromises[this.type];
for (const typeWidget of Object.values(this.typeWidgets)) {
if (typeWidget.constructor.getType() !== this.type) {
typeWidget.cleanup();
typeWidget.toggle(false);
} }
} }
this.$widget.find('.note-detail-component').hide(); this.getTypeWidget().toggle(true);
this.getComponent().show();
this.setupClasses(); this.setupClasses();
} }
@ -75,36 +80,26 @@ export default class NoteDetailWidget extends TabAwareWidget {
this.$widget.toggleClass("protected", note.isProtected); this.$widget.toggleClass("protected", note.isProtected);
} }
getComponent() { getTypeWidget() {
if (!this.components[this.type]) { if (!this.typeWidgets[this.type]) {
throw new Error("Could not find component for type: " + this.type); throw new Error("Could not find typeWidget for type: " + this.type);
} }
return this.components[this.type]; return this.typeWidgets[this.type];
}
async initComponent(disableAutoBook = false) {
this.type = this.getComponentType(disableAutoBook);
if (!(this.type in this.componentPromises)) {
this.componentPromises[this.type] = this.reallyInitComponent(this.type);
}
await this.componentPromises[this.type];
} }
async reallyInitComponent(type) { async initWidgetType(type) {
const clazz = await import(componentClasses[type]); const clazz = await import(typeWidgetClasses[type]);
this.components[this.type] = new clazz.default(this.appContext); this.typeWidgets[this.type] = new clazz.default(this.appContext);
this.children.push(this.components[this.type]); this.children.push(this.typeWidgets[this.type]);
this.components[this.type].renderTo(this.$widget); this.typeWidgets[this.type].renderTo(this.$widget);
this.components[this.type].eventReceived('setTabContext', {tabContext: this.tabContext}); this.typeWidgets[this.type].eventReceived('setTabContext', {tabContext: this.tabContext});
} }
getComponentType(disableAutoBook) { getWidgetType(disableAutoBook) {
const note = this.tabContext.note; const note = this.tabContext.note;
if (!note) { if (!note) {
@ -113,20 +108,15 @@ export default class NoteDetailWidget extends TabAwareWidget {
let type = note.type; let type = note.type;
if (type === 'text' && !disableAutoBook && utils.isHtmlEmpty(note.content) && note.hasChildren()) { if (type === 'text' && !disableAutoBook
&& utils.isHtmlEmpty(note.content)
&& note.hasChildren()) {
type = 'book'; type = 'book';
} }
if (note.isProtected) { if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
if (protectedSessionHolder.isProtectedSessionAvailable()) { type = 'protected-session';
protectedSessionHolder.touchProtectedSession();
} else {
type = 'protected-session';
// FIXME
// user shouldn't be able to edit note title
//this.$noteTitle.prop("readonly", true);
}
} }
return type; return type;

View File

@ -135,6 +135,10 @@ export default class NoteTitleWidget extends TabAwareWidget {
this.$noteTitle.val(note.title); this.$noteTitle.val(note.title);
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
this.$noteTitle.prop("readonly", true);
}
this.$protectButton.toggleClass("active", note.isProtected); this.$protectButton.toggleClass("active", note.isProtected);
this.$protectButton.prop("disabled", note.isProtected); this.$protectButton.prop("disabled", note.isProtected);
this.$unprotectButton.toggleClass("active", !note.isProtected); this.$unprotectButton.toggleClass("active", !note.isProtected);

View File

@ -4,14 +4,16 @@ import toastService from "../../services/toast.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import noteDetailService from "../../services/note_detail.js"; import noteDetailService from "../../services/note_detail.js";
import keyboardActionService from "../../services/keyboard_actions.js"; import keyboardActionService from "../../services/keyboard_actions.js";
import TabAwareWidget from "../tab_aware_widget.js"; import TypeWidget from "./type_widget.js";
const TPL = ` const TPL = `
<div class="note-detail-code note-detail-component"> <div class="note-detail-code note-detail-printable">
<div class="note-detail-code-editor"></div> <div class="note-detail-code-editor"></div>
</div>`; </div>`;
class NoteDetailCode extends TabAwareWidget { class CodeTypeWidget extends TypeWidget {
static getType() { return "code"; }
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$editor = this.$widget.find('.note-detail-code-editor'); this.$editor = this.$widget.find('.note-detail-code-editor');
@ -58,22 +60,19 @@ class NoteDetailCode extends TabAwareWidget {
//this.onNoteChange(() => this.tabContext.noteChanged()); //this.onNoteChange(() => this.tabContext.noteChanged());
} }
refresh() { doRefresh() {
// lazy loading above can take time and tab might have been already switched to another note // CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
if (this.tabContext.note && this.tabContext.note.type === 'code') { // we provide fallback
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check) this.codeEditor.setValue(this.tabContext.note.content || "");
// we provide fallback
this.codeEditor.setValue(this.tabContext.note.content || "");
const info = CodeMirror.findModeByMIME(this.tabContext.note.mime); const info = CodeMirror.findModeByMIME(this.tabContext.note.mime);
if (info) { if (info) {
this.codeEditor.setOption("mode", info.mime); this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode); CodeMirror.autoLoadMode(this.codeEditor, info.mode);
}
this.show();
} }
this.show();
} }
show() { show() {
@ -127,4 +126,4 @@ class NoteDetailCode extends TabAwareWidget {
} }
} }
export default NoteDetailCode; export default CodeTypeWidget;

View File

@ -2,10 +2,10 @@ import utils from "../../services/utils.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import toastService from "../../services/toast.js"; import toastService from "../../services/toast.js";
import noteDetailService from "../../services/note_detail.js"; import noteDetailService from "../../services/note_detail.js";
import TabAwareWidget from "../tab_aware_widget.js"; import TypeWidget from "./type_widget.js";
const TPL = ` const TPL = `
<div class="note-detail-file note-detail-component"> <div class="note-detail-file note-detail-printable">
<style> <style>
.note-detail-file { .note-detail-file {
padding: 10px; padding: 10px;
@ -55,7 +55,9 @@ const TPL = `
<input type="file" class="file-upload-new-revision-input" style="display: none"> <input type="file" class="file-upload-new-revision-input" style="display: none">
</div>`; </div>`;
class NoteDetailFile extends TabAwareWidget{ class FileTypeWidget extends TypeWidget {
static getType() { return "file"; }
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$fileNoteId = this.$widget.find(".file-note-id"); this.$fileNoteId = this.$widget.find(".file-note-id");
@ -115,7 +117,7 @@ class NoteDetailFile extends TabAwareWidget{
return this.$widget; return this.$widget;
} }
async refresh() { async doRefresh() {
const attributes = await server.get('notes/' + this.tabContext.note.noteId + '/attributes'); const attributes = await server.get('notes/' + this.tabContext.note.noteId + '/attributes');
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
@ -155,4 +157,4 @@ class NoteDetailFile extends TabAwareWidget{
scrollToTop() {} scrollToTop() {}
} }
export default NoteDetailFile; export default FileTypeWidget;

View File

@ -3,6 +3,7 @@ import treeService from '../../services/tree.js';
import noteAutocompleteService from '../../services/note_autocomplete.js'; import noteAutocompleteService from '../../services/note_autocomplete.js';
import mimeTypesService from '../../services/mime_types.js'; import mimeTypesService from '../../services/mime_types.js';
import TabAwareWidget from "../tab_aware_widget.js"; import TabAwareWidget from "../tab_aware_widget.js";
import TypeWidget from "./type_widget.js";
const ENABLE_INSPECTOR = false; const ENABLE_INSPECTOR = false;
@ -41,7 +42,7 @@ const mentionSetup = {
}; };
const TPL = ` const TPL = `
<div class="note-detail-text note-detail-component"> <div class="note-detail-text note-detail-printable">
<style> <style>
.note-detail-text h1 { font-size: 2.0em; } .note-detail-text h1 { font-size: 2.0em; }
.note-detail-text h2 { font-size: 1.8em; } .note-detail-text h2 { font-size: 1.8em; }
@ -72,7 +73,9 @@ const TPL = `
</div> </div>
`; `;
class NoteDetailText extends TabAwareWidget { class TextTypeWidget extends TypeWidget {
static getType() { return "text"; }
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$editor = this.$widget.find('.note-detail-text-editor'); this.$editor = this.$widget.find('.note-detail-text-editor');
@ -130,15 +133,12 @@ class NoteDetailText extends TabAwareWidget {
} }
} }
async refresh() { async doRefresh() {
// lazy loading above can take time and tab might have been already switched to another note this.textEditor.isReadOnly = await this.isReadOnly();
if (this.tabContext.note && this.tabContext.note.type === 'text') {
this.textEditor.isReadOnly = await this.isReadOnly();
this.$widget.show(); this.$widget.show();
this.textEditor.setData(this.tabContext.note.content); this.textEditor.setData(this.tabContext.note.content);
}
} }
getContent() { getContent() {
@ -188,4 +188,4 @@ class NoteDetailText extends TabAwareWidget {
} }
} }
export default NoteDetailText; export default TextTypeWidget;

View File

@ -0,0 +1,18 @@
import TabAwareWidget from "../tab_aware_widget.js";
export default class TypeWidget extends TabAwareWidget {
// for overriding
static getType() {}
doRefresh() {}
refresh() {
if (!this.tabContext.note || this.tabContext.note.type !== this.constructor.getType()) {
this.toggle(false);
return;
}
this.doRefresh();
}
}

View File

@ -104,15 +104,4 @@ span.fancytree-expander {
margin-top: 4px; margin-top: 4px;
border-width: 2px; border-width: 2px;
border-style: solid; border-style: solid;
}
.note-detail-component-wrapper {
position: relative;
overflow: auto;
flex-direction: column;
/* for some reason detail overflows a little bit so we subtract few pixels */
height: calc(100% - 25px);
/* large left padding is necessary for ckeditor gutter in detail-only (smartphone) layout */
padding-left: 35px;
padding-top: 10px;
} }

View File

@ -100,25 +100,6 @@ span.fancytree-node.muted { opacity: 0.6; }
width: 100%; width: 100%;
} }
.note-detail-component-wrapper {
flex-grow: 100;
position: relative;
overflow: auto;
flex-basis: content;
height: 100%;
display: flex;
flex-direction: column;
min-height: 500px;
overflow-wrap: break-word; /* otherwise CKEditor fails miserably on super long lines */
font-family: var(--detail-font-family);
font-size: var(--detail-font-size);
}
.note-detail-component {
flex-grow: 100;
display: none;
}
/** we disable shield background when in zen mode because I couldn't get it to stay static /** we disable shield background when in zen mode because I couldn't get it to stay static
(it kept growing with content) */ (it kept growing with content) */
#container:not(.zen-mode) .note-tab-content.protected { #container:not(.zen-mode) .note-tab-content.protected {

View File

@ -1,4 +1,4 @@
<div class="note-detail-book note-detail-component"> <div class="note-detail-book note-detail-printable">
<div class="btn-group floating-button" style="right: 20px; top: 20px;"> <div class="btn-group floating-button" style="right: 20px; top: 20px;">
<button type="button" <button type="button"
class="expand-children-button btn icon-button bx bx-move-vertical" class="expand-children-button btn icon-button bx bx-move-vertical"

View File

@ -1,4 +1,4 @@
<div class="note-detail-empty note-detail-component"> <div class="note-detail-empty note-detail-printable">
<div class="form-group"> <div class="form-group">
<label>Open note by typing note's title into input below or choose a note in the tree.</label> <label>Open note by typing note's title into input below or choose a note in the tree.</label>
<div class="input-group"> <div class="input-group">

View File

@ -1,4 +1,4 @@
<div class="note-detail-image note-detail-component"> <div class="note-detail-image note-detail-printable">
<div style="display: flex; justify-content: space-evenly; margin: 10px;"> <div style="display: flex; justify-content: space-evenly; margin: 10px;">
<button class="image-download btn btn-sm btn-primary" type="button">Download</button> <button class="image-download btn btn-sm btn-primary" type="button">Download</button>

View File

@ -1,4 +1,4 @@
<div class="protected-session-password-component note-detail-component"> <div class="protected-session-password-component note-detail-printable">
<form class="protected-session-password-form"> <form class="protected-session-password-form">
<div class="form-group"> <div class="form-group">
<label for="protected-session-password-in-detail">Showing protected note requires entering your password:</label> <label for="protected-session-password-in-detail">Showing protected note requires entering your password:</label>

View File

@ -1,4 +1,4 @@
<div class="note-detail-relation-map note-detail-component"> <div class="note-detail-relation-map note-detail-printable">
<button class="relation-map-create-child-note btn btn-sm floating-button" type="button" <button class="relation-map-create-child-note btn btn-sm floating-button" type="button"
title="Create new child note and add it into this relation map"> title="Create new child note and add it into this relation map">
<span class="bx bx-folder-plus"></span> <span class="bx bx-folder-plus"></span>

View File

@ -1,4 +1,4 @@
<div class="note-detail-render note-detail-component"> <div class="note-detail-render note-detail-printable">
<div class="note-detail-render-help alert alert-warning"> <div class="note-detail-render-help alert alert-warning">
<p><strong>This help note is shown because this note of type Render HTML doesn't have required relation to function properly.</strong></p> <p><strong>This help note is shown because this note of type Render HTML doesn't have required relation to function properly.</strong></p>

View File

@ -1,4 +1,4 @@
<div class="note-detail-search note-detail-component"> <div class="note-detail-search note-detail-printable">
<div style="display: flex; align-items: center; margin-right: 20px; margin-top: 15px;"> <div style="display: flex; align-items: center; margin-right: 20px; margin-top: 15px;">
<strong>Search string: &nbsp; &nbsp;</strong> <strong>Search string: &nbsp; &nbsp;</strong>
<textarea rows="4" style="width: auto !important; flex-grow: 4" class="search-string form-control"></textarea> <textarea rows="4" style="width: auto !important; flex-grow: 4" class="search-string form-control"></textarea>

View File

@ -53,12 +53,12 @@
</button> </button>
</div> </div>
<div class="note-detail-component-wrapper"> <div class="note-detail-printable-wrapper">
<div class="note-detail-text note-detail-component" tabindex="10000"> <div class="note-detail-text note-detail-printable" tabindex="10000">
<div class="note-detail-text-editor"></div> <div class="note-detail-text-editor"></div>
</div> </div>
<div class="note-detail-code note-detail-component"> <div class="note-detail-code note-detail-printable">
<div class="note-detail-code-editor"></div> <div class="note-detail-code-editor"></div>
</div> </div>