new CollapsibleSectionContainer WIP

This commit is contained in:
zadam 2020-10-31 22:47:15 +01:00
parent 435200ec5a
commit 2c028f7c45
11 changed files with 233 additions and 317 deletions

6
package-lock.json generated
View File

@ -4549,9 +4549,9 @@
}
},
"jasmine": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.2.tgz",
"integrity": "sha512-Uc0o2MRnC8TS1MjDrB8jE1umKEo2mflzGvdg0Ncs+yuLtOJ+uz/Wz8VmGsNGtuASr8+E0LDgPkOpvdoC76m5WQ==",
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.3.tgz",
"integrity": "sha512-Th91zHsbsALWjDUIiU5d/W5zaYQsZFMPTdeNmi8GivZPmAaUAK8MblSG3yQI4VMGC/abF2us7ex60NH1AAIMTA==",
"dev": true,
"requires": {
"glob": "^7.1.6",

View File

@ -81,7 +81,7 @@
"electron-packager": "15.1.0",
"electron-rebuild": "2.3.2",
"esm": "3.2.25",
"jasmine": "3.6.2",
"jasmine": "3.6.3",
"jsdoc": "3.6.6",
"lorem-ipsum": "2.0.3",
"rcedit": "2.2.0",

View File

@ -9,7 +9,7 @@ import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "../widgets/note_actions.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import AttributeListWidget from "../widgets/attribute_list.js";
import OwnedAttributeListWidget from "../widgets/owned_attribute_list.js";
export default class DesktopExtraWindowLayout {
constructor(customWidgets) {
@ -39,7 +39,7 @@ export default class DesktopExtraWindowLayout {
.child(new NoteTypeWidget().hideInZenMode())
.child(new NoteActionsWidget().hideInZenMode())
)
.child(new TabCachingWidget(() => new AttributeListWidget()))
.child(new TabCachingWidget(() => new OwnedAttributeListWidget()))
.child(new TabCachingWidget(() => new NoteDetailWidget()))
.child(...this.customWidgets.get('center-pane'))
)

View File

@ -11,7 +11,7 @@ import NoteTreeWidget from "../widgets/note_tree.js";
import TabCachingWidget from "../widgets/tab_caching_widget.js";
import NotePathsWidget from "../widgets/note_paths.js";
import NoteTitleWidget from "../widgets/note_title.js";
import AttributeListWidget from "../widgets/attribute_list.js";
import OwnedAttributeListWidget from "../widgets/owned_attribute_list.js";
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "../widgets/note_actions.js";
@ -26,6 +26,7 @@ import SidePaneToggles from "../widgets/side_pane_toggles.js";
import EditedNotesWidget from "../widgets/collapsible_widgets/edited_notes.js";
import CollapsibleSectionContainer from "../widgets/collapsible_section_container.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import InheritedAttributesWidget from "../widgets/inherited_attribute_list.js";
const RIGHT_PANE_CSS = `
<style>
@ -158,7 +159,8 @@ export default class DesktopMainWindowLayout {
.child(
new TabCachingWidget(() => new CollapsibleSectionContainer()
.child(new PromotedAttributesWidget())
.child(new AttributeListWidget())
.child(new OwnedAttributeListWidget())
.child(new InheritedAttributesWidget())
)
)
.child(new TabCachingWidget(() => new NoteDetailWidget()))

View File

@ -1,30 +0,0 @@
import BasicWidget from "./basic_widget.js";
export default class AbstractContainer extends BasicWidget {
constructor() {
super();
this.children = [];
this.positionCounter = 10;
}
child(...components) {
if (!components) {
return this;
}
super.child(...components);
for (const component of components) {
if (!component.position) {
component.position = this.positionCounter;
this.positionCounter += 10;
}
}
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
return this;
}
}

View File

@ -17,7 +17,7 @@ const HELP_TEXT = `
<p>Alternatively you can add label and relation using the <code>+</code> button on the right side.</p>`;
const TPL = `
<div style="position: relative">
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
<style>
.attribute-list-editor {
border: 0 !important;

View File

@ -1,273 +0,0 @@
import TabAwareWidget from "./tab_aware_widget.js";
import AttributeDetailWidget from "./attribute_detail.js";
import attributeRenderer from "../services/attribute_renderer.js";
import AttributeEditorWidget from "./attribute_editor.js";
import options from '../services/options.js';
const TPL = `
<div class="attribute-list">
<style>
.attribute-list {
margin-left: 7px;
margin-right: 7px;
position: relative;
}
.inherited-attributes-wrapper {
color: var(--muted-text-color);
max-height: 200px;
overflow: auto;
padding-bottom: 5px;
padding-left: 7px;
}
.attribute-list-editor p {
margin: 0 !important;
}
.attribute-list.error .attribute-list-editor {
border-color: red !important;
}
.area-expander {
display: flex;
flex-direction: row;
color: var(--muted-text-color);
font-size: 90%;
margin: 3px 0 3px 0;
}
.attribute-list hr {
height: 1px;
border-color: var(--main-border-color);
position: relative;
top: 4px;
margin-top: 5px;
margin-bottom: 0;
}
.area-expander-text {
padding-left: 20px;
padding-right: 20px;
white-space: nowrap;
}
.area-expander:hover {
cursor: pointer;
}
.area-expander:hover hr {
border-color: var(--main-text-color);
}
.area-expander:hover .area-expander-text {
color: var(--main-text-color);
}
</style>
<div class="area-expander attr-promoted-expander">
<hr class="w-100">
<div class="area-expander-text">Promoted attributes</div>
<hr class="w-100">
</div>
<div class="all-attr-wrapper">
<div class="promoted-attributes-placeholder"></div>
<div class="area-expander attr-owned-and-inherited-expander">
<hr class="w-100">
<div class="area-expander-text"></div>
<hr class="w-100">
</div>
<div class="owned-and-inherited-wrapper">
<div class="attr-editor-placeholder"></div>
<hr class="w-100 attr-inherited-empty-expander" style="margin-bottom: 10px;">
<div class="area-expander attr-inherited-expander">
<hr class="w-100">
<div class="area-expander-text"></div>
<hr class="w-100">
</div>
<div class="inherited-attributes-wrapper"></div>
</div>
</div>
</div>
`;
export default class AttributeListWidget extends TabAwareWidget {
constructor() {
super();
this.attributeDetailWidget = new AttributeDetailWidget().setParent(this);
this.attributeEditorWidget = new AttributeEditorWidget(this.attributeDetailWidget).setParent(this);
this.child(this.attributeEditorWidget, this.attributeDetailWidget);
}
renderTitle() {
this.$title = $('<div>').text('Attribute list');
return this.$title;
}
doRender() {
this.$widget = $(TPL);
this.overflowing();
this.$promotedExpander = this.$widget.find('.attr-promoted-expander');
this.$allAttrWrapper = this.$widget.find('.all-attr-wrapper');
this.$promotedExpander.on('click', async () => {
const collapse = this.$allAttrWrapper.is(":visible");
await options.save('promotedAttributesExpanded', !collapse);
this.triggerEvent(`promotedAttributesCollapsedStateChanged`, {collapse});
});
this.$ownedAndInheritedWrapper = this.$widget.find('.owned-and-inherited-wrapper');
this.$ownedExpander = this.$widget.find('.attr-owned-and-inherited-expander');
this.$ownedExpander.on('click', async () => {
const collapse = this.$ownedAndInheritedWrapper.is(":visible");
await options.save('attributeListExpanded', !collapse);
this.triggerEvent(`attributeListCollapsedStateChanged`, {collapse});
});
this.$ownedExpanderText = this.$ownedExpander.find('.area-expander-text');
this.$inheritedAttributesWrapper = this.$widget.find('.inherited-attributes-wrapper');
this.$inheritedExpander = this.$widget.find('.attr-inherited-expander');
this.$inheritedExpander.on('click', () => {
if (this.$inheritedAttributesWrapper.is(":visible")) {
this.$inheritedAttributesWrapper.slideUp(200);
}
else {
this.$inheritedAttributesWrapper.slideDown(200);
}
});
this.$inheritedExpanderText = this.$inheritedExpander.find('.area-expander-text');
this.$inheritedEmptyExpander = this.$widget.find('.attr-inherited-empty-expander');
this.$widget.find('.attr-editor-placeholder').replaceWith(this.attributeEditorWidget.render());
this.$widget.append(this.attributeDetailWidget.render());
}
async refreshWithNote(note, updateOnly = false) {
if (!updateOnly) {
// const hasPromotedAttrs = this.promotedAttributesWidget.getPromotedDefinitionAttributes().length > 0;
//
// if (hasPromotedAttrs) {
// this.$promotedExpander.show();
// this.$allAttrWrapper.toggle(options.is('promotedAttributesExpanded'));
// this.$ownedAndInheritedWrapper.hide();
// this.$inheritedAttributesWrapper.hide();
// } else {
// this.$promotedExpander.hide();
// this.$allAttrWrapper.show();
// this.$ownedAndInheritedWrapper.toggle(options.is('attributeListExpanded'));
// }
}
const ownedAttributes = note.getOwnedAttributes().filter(attr => !attr.isAutoLink);
this.$ownedExpanderText.text(ownedAttributes.length + ' owned ' + this.attrPlural(ownedAttributes.length));
const inheritedAttributes = note.getAttributes().filter(attr => attr.noteId !== this.noteId);
if (inheritedAttributes.length === 0) {
this.$inheritedExpander.hide();
this.$inheritedEmptyExpander.show();
}
else {
this.$inheritedExpander.show();
this.$inheritedEmptyExpander.hide();
}
this.$inheritedExpanderText.text(inheritedAttributes.length + ' inherited ' + this.attrPlural(inheritedAttributes.length));
this.$inheritedAttributesWrapper.empty();
await this.renderInheritedAttributes(inheritedAttributes, this.$inheritedAttributesWrapper);
}
attrPlural(number) {
return 'attribute' + (number === 1 ? '' : 's');
}
async renderInheritedAttributes(attributes, $container) {
for (const attribute of attributes) {
const $attr = (await attributeRenderer.renderAttribute(attribute, false))
.on('click', e => this.attributeDetailWidget.showAttributeDetail({
attribute: {
noteId: attribute.noteId,
type: attribute.type,
name: attribute.name,
value: attribute.value
},
isOwned: false,
x: e.pageX,
y: e.pageY
}));
$container
.append($attr)
.append(" ");
}
}
async saveAttributesCommand() {
await this.attributeEditorWidget.save();
}
async reloadAttributesCommand() {
await this.attributeEditorWidget.refresh();
}
async updateAttributeListCommand({attributes}) {
await this.attributeEditorWidget.updateAttributeList(attributes);
}
/**
* This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
* separately but should behave uniformly for the user.
*/
attributeListCollapsedStateChangedEvent({collapse}) {
if (collapse) {
this.$ownedAndInheritedWrapper.slideUp(200);
} else {
this.$ownedAndInheritedWrapper.slideDown(200);
}
}
/**
* This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
* separately but should behave uniformly for the user.
*/
promotedAttributesCollapsedStateChangedEvent({collapse}) {
if (collapse) {
this.$allAttrWrapper.slideUp(200);
} else {
this.$allAttrWrapper.slideDown(200);
}
}
entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) {
this.refreshWithNote(this.note, true);
}
}
}

View File

@ -1,8 +1,12 @@
import AbstractContainer from "./abstract_container.js";
import TabAwareWidget from "./tab_aware_widget.js";
const TPL = `
<div class="section-container">
<style>
.section-container {
margin-bottom: 10px;
}
.section-title-container {
display: flex;
flex-direction: row;
@ -17,6 +21,11 @@ const TPL = `
border-bottom: 1px solid var(--main-border-color);
}
.section-title.active {
color: var(--main-text-color);
border-bottom: 1px solid var(--main-text-color);
}
.section-title:hover {
cursor: pointer;
}
@ -29,13 +38,49 @@ const TPL = `
flex-shrink: 1;
flex-grow: 1;
}
.section-body {
display: none;
border-bottom: 1px solid var(--main-border-color);
}
.section-body.active {
display: block;
}
</style>
<div class="section-title-container"></div>
<div class="section-body-container"></div>
</div>`;
export default class CollapsibleSectionContainer extends AbstractContainer {
export default class CollapsibleSectionContainer extends TabAwareWidget {
constructor() {
super();
this.children = [];
this.positionCounter = 10;
}
child(...components) {
if (!components) {
return this;
}
super.child(...components);
for (const component of components) {
if (!component.position) {
component.position = this.positionCounter;
this.positionCounter += 10;
}
}
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
return this;
}
doRender() {
this.$widget = $(TPL);
this.contentSized();
@ -47,7 +92,8 @@ export default class CollapsibleSectionContainer extends AbstractContainer {
for (const widget of this.children) {
this.$titleContainer.append(
$('<div class="section-title">')
$('<div class="section-title section-title-real">')
.attr('data-section-component-id', widget.componentId)
.append(widget.renderTitle())
);
@ -55,8 +101,27 @@ export default class CollapsibleSectionContainer extends AbstractContainer {
this.$bodyContainer.append(
$('<div class="section-body">')
.attr('data-section-component-id', widget.componentId)
.append(widget.render())
);
}
this.$titleContainer.on('click', '.section-title-real', e => {
const $sectionTitle = $(e.target).closest('.section-title-real');
const activate = !$sectionTitle.hasClass("active");
this.$titleContainer.find('.section-title-real').removeClass("active");
this.$bodyContainer.find('.section-body').removeClass("active");
if (activate) {
this.$titleContainer.find(`.section-title-real[data-section-component-id="${$sectionTitle.attr('data-section-component-id')}"]`).addClass("active");
this.$bodyContainer.find(`.section-body[data-section-component-id="${$sectionTitle.attr('data-section-component-id')}"]`).addClass("active");
}
});
}
async refreshWithNote(note) {
this.$titleContainer.find('.section-title-real:first').trigger('click');
}
}

View File

@ -1,6 +1,6 @@
import AbstractContainer from "./abstract_container.js";
import BasicWidget from "./basic_widget.js";
export default class FlexContainer extends AbstractContainer {
export default class FlexContainer extends BasicWidget {
constructor(direction) {
super();
@ -9,6 +9,29 @@ export default class FlexContainer extends AbstractContainer {
}
this.attrs.style = `display: flex; flex-direction: ${direction};`;
this.children = [];
this.positionCounter = 10;
}
child(...components) {
if (!components) {
return this;
}
super.child(...components);
for (const component of components) {
if (!component.position) {
component.position = this.positionCounter;
this.positionCounter += 10;
}
}
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
return this;
}
doRender() {

View File

@ -0,0 +1,66 @@
import TabAwareWidget from "./tab_aware_widget.js";
import AttributeDetailWidget from "./attribute_detail.js";
import attributeRenderer from "../services/attribute_renderer.js";
const TPL = `
<div class="inherited-attributes-widget">
<style>
.inherited-attributes-container {
color: var(--muted-text-color);
max-height: 200px;
overflow: auto;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 7px;
}
</style>
<div class="inherited-attributes-container"></div>
</div>`
export default class InheritedAttributesWidget extends TabAwareWidget {
constructor() {
super();
this.attributeDetailWidget = new AttributeDetailWidget().setParent(this);
this.child(this.attributeDetailWidget);
}
renderTitle() {
this.$title = $('<div>').text('Inherited attributes');
return this.$title;
}
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$container = this.$widget.find('.inherited-attributes-container');
this.$widget.append(this.attributeDetailWidget.render());
}
async refreshWithNote(note) {
this.$container.empty();
const inheritedAttributes = note.getAttributes().filter(attr => attr.noteId !== this.noteId);
for (const attribute of inheritedAttributes) {
const $attr = (await attributeRenderer.renderAttribute(attribute, false))
.on('click', e => this.attributeDetailWidget.showAttributeDetail({
attribute: {
noteId: attribute.noteId,
type: attribute.type,
name: attribute.name,
value: attribute.value
},
isOwned: false,
x: e.pageX,
y: e.pageY
}));
this.$container
.append($attr)
.append(" ");
}
}
}

View File

@ -0,0 +1,63 @@
import TabAwareWidget from "./tab_aware_widget.js";
import AttributeDetailWidget from "./attribute_detail.js";
import AttributeEditorWidget from "./attribute_editor.js";
const TPL = `
<div class="attribute-list">
<style>
.attribute-list {
margin-left: 7px;
margin-right: 7px;
position: relative;
}
.attribute-list-editor p {
margin: 0 !important;
}
</style>
<div class="attr-editor-placeholder"></div>
</div>
`;
export default class OwnedAttributeListWidget extends TabAwareWidget {
constructor() {
super();
this.attributeDetailWidget = new AttributeDetailWidget().setParent(this);
this.attributeEditorWidget = new AttributeEditorWidget(this.attributeDetailWidget).setParent(this);
this.child(this.attributeEditorWidget, this.attributeDetailWidget);
}
renderTitle() {
this.$title = $('<div>').text('Owned attributes');
return this.$title;
}
doRender() {
this.$widget = $(TPL);
this.overflowing();
this.$widget.find('.attr-editor-placeholder').replaceWith(this.attributeEditorWidget.render());
this.$widget.append(this.attributeDetailWidget.render());
}
async saveAttributesCommand() {
await this.attributeEditorWidget.save();
}
async reloadAttributesCommand() {
await this.attributeEditorWidget.refresh();
}
async updateAttributeListCommand({attributes}) {
await this.attributeEditorWidget.updateAttributeList(attributes);
}
entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) {
this.refreshWithNote(this.note, true);
}
}
}