From db1b3aef8cd6fab78767a03e59e6660d1ed76ea8 Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Thu, 7 Dec 2017 19:43:46 +0000 Subject: [PATCH] WIP content linker UX --- gui/app/components/document/content-linker.js | 98 ++++++----- .../components/section/base-editor-inline.js | 94 ++++------- gui/app/mixins/modal.js | 60 ++++++- gui/app/styles/view/document/all.scss | 3 - .../styles/view/document/content-linker.scss | 29 ---- gui/app/styles/view/document/document.scss | 1 + .../styles/view/document/inline-editor.scss | 41 ----- .../styles/view/document/section-editor.scss | 51 +++++- gui/app/styles/widget/widget-tabnav.scss | 12 +- .../components/document/content-linker.hbs | 158 +++++++++++------- .../components/section/base-editor-inline.hbs | 116 ++++++------- 11 files changed, 338 insertions(+), 325 deletions(-) delete mode 100644 gui/app/styles/view/document/content-linker.scss delete mode 100644 gui/app/styles/view/document/inline-editor.scss diff --git a/gui/app/components/document/content-linker.js b/gui/app/components/document/content-linker.js index 5a74bbb1..a1e3a9b5 100644 --- a/gui/app/components/document/content-linker.js +++ b/gui/app/components/document/content-linker.js @@ -11,48 +11,45 @@ import { debounce } from '@ember/runloop'; import { computed, set } from '@ember/object'; -import Component from '@ember/component'; import { inject as service } from '@ember/service'; +import Component from '@ember/component'; import TooltipMixin from '../../mixins/tooltip'; +import ModalMixin from '../../mixins/modal'; -export default Component.extend(TooltipMixin, { +export default Component.extend(ModalMixin, TooltipMixin, { link: service(), linkName: '', - keywords: '', selection: null, + + tab1Selected: true, + tab2Selected: false, + tab3Selected: false, + showSections: computed('tab1Selected', function() { return this.get('tab1Selected'); }), + showAttachments: computed('tab2Selected', function() { return this.get('tab2Selected'); }), + showSearch: computed('tab3Selected', function() { return this.get('tab3Selected'); }), + + keywords: '', matches: { documents: [], pages: [], attachments: [] }, - tabs: [ - { label: 'Section', selected: true }, - { label: 'Attachment', selected: false }, - { label: 'Search', selected: false } - ], - contentLinkerButtonId: computed('page', function () { - let page = this.get('page'); - return `content-linker-button-${page.id}`; - }), - - showSections: computed('tabs.@each.selected', function () { - return this.get('tabs').findBy('label', 'Section').selected; - }), - showAttachments: computed('tabs.@each.selected', function () { - return this.get('tabs').findBy('label', 'Attachment').selected; - }), - showSearch: computed('tabs.@each.selected', function () { - return this.get('tabs').findBy('label', 'Search').selected; - }), hasMatches: computed('matches', function () { let m = this.get('matches'); return m.documents.length || m.pages.length || m.attachments.length; }), - init() { - this._super(...arguments); - let self = this; + modalId: computed('page', function() { return '#content-linker-modal-' + this.get('page.id'); }), + showModal: false, + onToggle: function() { + let modalId = this.get('modalId'); + if (!this.get('showModal')) { + this.modalClose(modalId); + return; + } + + let self = this; let folderId = this.get('folder.id'); let documentId = this.get('document.id'); let pageId = this.get('page.id'); @@ -62,18 +59,25 @@ export default Component.extend(TooltipMixin, { self.set('hasSections', is.not.null(candidates.pages) && candidates.pages.length); self.set('hasAttachments', is.not.null(candidates.attachments) && candidates.attachments.length); }); - }, + + this.modalOpen(modalId, {show: true}); + }.observes('showModal'), didRender() { - this.addTooltip(document.getElementById("content-linker-button")); - this.addTooltip(document.getElementById("content-counter-button")); + this._super(...arguments); + + this.renderTooltips(); }, willDestroyElement() { - this.destroyTooltips(); + this._super(...arguments); + + this.removeTooltips(); + + this.modalClose(this.get('modalId')); }, - onKeywordChange: function () { + onKeywordChange: function() { debounce(this, this.fetch, 750); }.observes('keywords'), @@ -98,25 +102,15 @@ export default Component.extend(TooltipMixin, { this.set('selection', i); - candidates.pages.forEach(c => { - set(c, 'selected', c.id === i.id); - }); + candidates.pages.forEach(c => { set(c, 'selected', c.id === i.id); }); + candidates.attachments.forEach(c => { set(c, 'selected', c.id === i.id); }); + matches.documents.forEach(c => { set(c, 'selected', c.id === i.id); }); + matches.pages.forEach(c => { set(c, 'selected', c.id === i.id); }); + matches.attachments.forEach(c => { set(c, 'selected', c.id === i.id); }); + }, - candidates.attachments.forEach(c => { - set(c, 'selected', c.id === i.id); - }); - - matches.documents.forEach(c => { - set(c, 'selected', c.id === i.id); - }); - - matches.pages.forEach(c => { - set(c, 'selected', c.id === i.id); - }); - - matches.attachments.forEach(c => { - set(c, 'selected', c.id === i.id); - }); + onCancel() { + this.set('showModal', false); }, onInsertLink() { @@ -126,11 +120,13 @@ export default Component.extend(TooltipMixin, { return; } - return this.get('onInsertLink')(selection); + this.get('onInsertLink')(selection); }, - onTabSelect(tabs) { - this.set('tabs', tabs); + onTabSelect(id) { + this.set('tab1Selected', id === 1); + this.set('tab2Selected', id === 2); + this.set('tab3Selected', id === 3); } } }); diff --git a/gui/app/components/section/base-editor-inline.js b/gui/app/components/section/base-editor-inline.js index 00b59c37..f12578d8 100644 --- a/gui/app/components/section/base-editor-inline.js +++ b/gui/app/components/section/base-editor-inline.js @@ -10,43 +10,23 @@ // https://documize.com import { empty } from '@ember/object/computed'; - import { computed } from '@ember/object'; +import TooltipMixin from '../../mixins/tooltip'; +import ModalMixin from '../../mixins/modal'; import Component from '@ember/component'; - -export default Component.extend({ - drop: null, +export default Component.extend(TooltipMixin, ModalMixin, { busy: false, mousetrap: null, + showLinkModal: false, hasNameError: empty('page.title'), - containerId: computed('page', function () { - let page = this.get('page'); - return `base-editor-inline-container-${page.id}`; - }), pageId: computed('page', function () { let page = this.get('page'); return `page-editor-${page.id}`; }), - cancelId: computed('page', function () { - let page = this.get('page'); - return `cancel-edits-button-${page.id}`; - }), - dialogId: computed('page', function () { - let page = this.get('page'); - return `discard-edits-dialog-${page.id}`; - }), - contentLinkerButtonId: computed('page', function () { - let page = this.get('page'); - return `content-linker-button-${page.id}`; - }), - previewButtonId: computed('page', function () { - let page = this.get('page'); - return `content-preview-button-${page.id}`; - }), didRender() { - let msContainer = document.getElementById(this.get('containerId')); + let msContainer = document.getElementById('section-editor-' + this.get('containerId')); let mousetrap = this.get('mousetrap'); if (is.null(mousetrap)) { @@ -67,13 +47,14 @@ export default Component.extend({ $('#' + this.get('pageId')).focus(function() { $(this).select(); }); + + this.renderTooltips(); }, willDestroyElement() { - let drop = this.get('drop'); - if (is.not.null(drop)) { - drop.destroy(); - } + this._super(...arguments); + + this.removeTooltips(); let mousetrap = this.get('mousetrap'); if (is.not.null(mousetrap)) { @@ -83,33 +64,6 @@ export default Component.extend({ }, actions: { - onCancel() { - if (this.attrs.isDirty() !== null && this.attrs.isDirty()) { - $('#' + this.get('dialogId')).css("display", "block"); - - let drop = new Drop({ - target: $('#' + this.get('cancelId'))[0], - content: $('#' + this.get('dialogId'))[0], - classes: 'drop-theme-basic', - position: "bottom right", - openOn: "always", - constrainToWindow: true, - constrainToScrollParent: false, - tetherOptions: { - offset: "5px 0", - targetOffset: "10px 0" - }, - remove: false - }); - - this.set('drop', drop); - - return; - } - - this.attrs.onCancel(); - }, - onAction() { if (this.get('busy') || is.empty(this.get('page.title'))) { return; @@ -122,21 +76,31 @@ export default Component.extend({ this.attrs.onAction(this.get('page.title')); }, - keepEditing() { - let drop = this.get('drop'); - drop.close(); - }, + onCancel() { + if (this.attrs.isDirty() !== null && this.attrs.isDirty()) { + this.modalOpen('#discard-modal-' + this.get('page.id'), {show: true}); + return; + } - discardEdits() { this.attrs.onCancel(); }, - onInsertLink(selection) { - return this.get('onInsertLink')(selection); - }, + onDiscard() { + this.modalClose('#discard-modal-' + this.get('page.id')); + this.attrs.onCancel(); + }, onPreview() { return this.get('onPreview')(); - }, + }, + + onShowLinkModal() { + this.set('showLinkModal', true); + }, + + onInsertLink(selection) { + this.set('showLinkModal', false); + return this.get('onInsertLink')(selection); + } } }); diff --git a/gui/app/mixins/modal.js b/gui/app/mixins/modal.js index 9c2dfa48..3fbef5ba 100644 --- a/gui/app/mixins/modal.js +++ b/gui/app/mixins/modal.js @@ -12,8 +12,16 @@ import Mixin from '@ember/object/mixin'; import { schedule } from '@ember/runloop'; +// ID values expected format: +// modal: #document-template-modal +// element: #new-template-name +// See https://getbootstrap.com/docs/4.0/components/modal/#via-javascript export default Mixin.create({ - // e.g. #document-template-modal, #new-template-name + modalOpen(modalId, options) { + $(modalId).modal('dispose'); + $(modalId).modal(options); + }, + modalInputFocus(modalId, inputId) { $(modalId).on('show.bs.modal', function(event) { // eslint-disable-line no-unused-vars schedule('afterRender', () => { @@ -22,15 +30,55 @@ export default Mixin.create({ }); }, - // e.g. #document-template-modal + // Destroys the element’s modal. + modalDispose(modalId) { + $(modalId).modal('dispose'); + }, + + // Manually hides a modal. Returns to the caller before the modal has actually + // been hidden (i.e. before the hidden.bs.modal event occurs). + modalHide(modalId) { + $(modalId).modal('hide'); + }, + + // Manually hides a modal. Returns to the caller before the modal + // has actually been hidden (i.e. before the hidden.bs.modal event occurs). + // Then destroys the element's modal. modalClose(modalId) { $(modalId).modal('hide'); $(modalId).modal('dispose'); }, - // e.g. #document-template-modal - modalOpen(modalId, options) { - $(modalId).modal('dispose'); - $(modalId).modal(options); + // This event fires immediately when the show instance method is called. + // If caused by a click, the clicked element is available as the relatedTarget + // property of the event. + modalOnShow(modalId, callback) { + $(modalId).on('show.bs.modal', function(e) { + callback(e); + }); + }, + + // This event is fired when the modal has been made visible to the user + // (will wait for CSS transitions to complete). If caused by a click, + // the clicked element is available as the relatedTarget property of the event. + modalOnShown(modalId, callback) { + $(modalId).on('shown.bs.modal', function(e) { + callback(e); + }); + }, + + // This event is fired immediately when the hide instance method has been called. + modalOnHide(modalId, callback) { + $(modalId).on('hide.bs.modal', function(e) { + callback(e); + }); + }, + + // This event is fired when the modal has finished being hidden from the user + // (will wait for CSS transitions to complete). + modalOnHidden(modalId, callback) { + $(modalId).on('hidden.bs.modal', function(e) { + callback(e); + }); } }); diff --git a/gui/app/styles/view/document/all.scss b/gui/app/styles/view/document/all.scss index f0cd3166..f67dd6d3 100644 --- a/gui/app/styles/view/document/all.scss +++ b/gui/app/styles/view/document/all.scss @@ -1,7 +1,4 @@ -@import "content-linker.scss"; @import "history.scss"; -@import "inline-editor.scss"; -@import "section-editor.scss"; @import "activity.scss"; @import "attachments.scss"; @import "toc.scss"; diff --git a/gui/app/styles/view/document/content-linker.scss b/gui/app/styles/view/document/content-linker.scss deleted file mode 100644 index c9b68ec8..00000000 --- a/gui/app/styles/view/document/content-linker.scss +++ /dev/null @@ -1,29 +0,0 @@ -.content-counter-dialog { - width: 200px; - height: 200px; -} - -.content-linker-dialog { - width: 350px; - height: 450px; - overflow-y: auto; - - .link-list { - margin: 0; - padding: 0; - - .link-item { - margin: 0; - padding: 0; - font-size: 0.9rem; - color: $color-gray; - cursor: pointer; - - .icon { - margin-right: 5px; - height: 15px; - width: 15px; - } - } - } -} diff --git a/gui/app/styles/view/document/document.scss b/gui/app/styles/view/document/document.scss index f0349630..c53b188c 100644 --- a/gui/app/styles/view/document/document.scss +++ b/gui/app/styles/view/document/document.scss @@ -1,3 +1,4 @@ @import "doc-meta.scss"; @import "doc-structure.scss"; +@import "section-editor.scss"; @import "wysiwyg.scss"; diff --git a/gui/app/styles/view/document/inline-editor.scss b/gui/app/styles/view/document/inline-editor.scss deleted file mode 100644 index d5e0d4f1..00000000 --- a/gui/app/styles/view/document/inline-editor.scss +++ /dev/null @@ -1,41 +0,0 @@ -.document-editor { - position: relative; - - > .toolbar { - width: 100%; - padding: 0; - - > .edit-title { - width: 70%; - - > input { - font-weight: bold; - font-size: 1.5rem; - margin: 16px 0 10px 0; - color: $color-wysiwyg; - } - } - - > .buttons { - margin-top: 17px; - } - } - - > .canvas { - padding: 0; - } - - .cancel-edits-dialog { - display: none; - } -} - -.document-editor-full { - @extend .transition-all; - @include border-radius(2px); - @include ease-in(); - position: relative; - padding: 25px 50px; - box-shadow: 0 0 0 0.75pt $color-stroke,0 0 3pt 0.75pt $color-stroke; - background-color: $color-white; -} diff --git a/gui/app/styles/view/document/section-editor.scss b/gui/app/styles/view/document/section-editor.scss index dbbf873f..49712823 100644 --- a/gui/app/styles/view/document/section-editor.scss +++ b/gui/app/styles/view/document/section-editor.scss @@ -1,3 +1,50 @@ +.section-editor { + > .edit-title { + margin: 16px 0; + } + + > .canvas { + margin: 34px 0 0 0; + } +} + +.content-linker-modal-container { + height: 500px; + overflow-y: auto; + + .link-list { + margin: 0; + padding: 0; + + .link-item { + margin: 0; + padding: 0; + font-size: 0.9rem; + color: $color-gray; + cursor: pointer; + + .icon { + margin-right: 5px; + height: 15px; + width: 15px; + } + } + } +} + + + + +.section-editor-fullscreen { + @extend .transition-all; + @include border-radius(2px); + @include ease-in(); + position: relative; + padding: 25px 50px; + box-shadow: 0 0 0 0.75pt $color-stroke,0 0 3pt 0.75pt $color-stroke; + background-color: $color-white; +} + .zone-section-editor { margin-left: 60px; padding: 20px 60px; @@ -18,10 +65,6 @@ float: right; } - > .canvas { - padding: 0; - } - .cancel-edits-dialog { display: none; } diff --git a/gui/app/styles/widget/widget-tabnav.scss b/gui/app/styles/widget/widget-tabnav.scss index 1ea3eba1..fe1bffd5 100644 --- a/gui/app/styles/widget/widget-tabnav.scss +++ b/gui/app/styles/widget/widget-tabnav.scss @@ -1,6 +1,7 @@ .tabnav-control { - padding: 0; - margin: 0; + padding: 0 !important; + margin: 0 !important; + font-size: 0; > .tab { @include ease-in(); @@ -9,12 +10,15 @@ padding: 5px 15px; background-color: $color-white; color: $color-primary; + border: 1px solid $color-border; font-weight: bold; font-size: 1.1rem; text-align: center; cursor: pointer; - border: 1px solid $color-border; - margin: -1px 0 0 -5px; // handles border overlap when tabs wrap onto 2nd line + line-height: 26px; + list-style-type: none; + // margin: -1px 0 0 -5px; // handles border overlap when tabs wrap onto 2nd line + margin-left: -4px; // remove whitespace inline block &:first-of-type { @include border-radius-left(3px); diff --git a/gui/app/templates/components/document/content-linker.hbs b/gui/app/templates/components/document/content-linker.hbs index 06644e6c..353ca13f 100644 --- a/gui/app/templates/components/document/content-linker.hbs +++ b/gui/app/templates/components/document/content-linker.hbs @@ -1,72 +1,100 @@ -{{#dropdown-dialog target=contentLinkerButtonId position="bottom right" button="Insert" color="flat-blue" onAction=(action 'onInsertLink')}} -
-
- {{ui/ui-tab tabs=tabs onTabSelect=(action 'onTabSelect')}} +