diff --git a/app/components/commander/index.hbs b/app/components/commander/index.hbs index c7be9e2b9d555b6fbc06d71fa7057594dd800e9c..9f35210e120b2b621d5eda57434366a22cb64a82 100644 --- a/app/components/commander/index.hbs +++ b/app/components/commander/index.hbs @@ -3,9 +3,18 @@ onDrop=this.onDrop onDragOver=this.onDragOver }} - {{uiSelect}}> + {{uiSelect}} + {{contextMenu}}> <UiSelect /> + + <ContextMenu + @openNewFolderModal={{this.openNewFolderModal}} + @openConfirmDeletionModal={{this.openConfirmDeletionModal}} + @selectedNodes={{this.selected_nodes}} + @onDownloadNodes={{this.onDownloadNodes}} + @openRenameModal={{this.openRenameModal}} /> + <div class="d-flex justify-content-between"> <Commander::ActionButtons @openNewFolderModal={{this.openNewFolderModal}} diff --git a/app/components/context_menu/index.hbs b/app/components/context_menu/index.hbs new file mode 100644 index 0000000000000000000000000000000000000000..e426b63f5d7dc4701dafc828e07a6846331af182 --- /dev/null +++ b/app/components/context_menu/index.hbs @@ -0,0 +1,52 @@ +<ul id="context-menu" class="dropdown-menu"> + <li> + <a class="dropdown-item" {{on "click" @openNewFolderModal }}> + <i class="fa fa-plus mx-2"></i>New Folder + </a> + </li> + <li class="dropdown dropdown-submenu dropend"> + <div class="dropdown"> + <a class="dropdown-item dropdown-toggle" data-toggle="dropdown"> + <i class="fa fa-expand mx-2"></i> + Selection + </a> + <ul class="dropdown-menu dropdown-menu-left"> + <li class="dropdown-item" + {{on "click" this.onSelectAll}}>All</li> + <li class="dropdown-item" + {{on "click" this.onSelectFolders}}>Folders</li> + <li class="dropdown-item" + {{on "click" this.onSelectDocuments}}>Documents</li> + <li class="dropdown-item" + {{on "click" this.onSelectNone}}>None</li> + <li class="dropdown-item" + {{on "click" this.onInvertSelection}}>Invert Selection</li> + </ul> + </div> + </li> + {{#if this.one_node_selected}} + <li> + <a class="dropdown-item" {{on "click" this.onRename }}> + <i class="fa fa-edit mx-2"></i>Rename + </a> + </li> + {{/if}} + {{#if this.one_or_multiple_nodes_selected}} + <li> + <a class="dropdown-item" {{on "click" this.onDownloadNodes}}> + {{#if this.download_in_progress}} + <Spinner @inProgress={{this.download_in_progress}} /> + {{else}} + <i class="fa fa-download mx-2"></i> + {{/if}} + Download + </a> + </li> + <li><hr class="dropdown-divider"></li> + <li> + <a class="dropdown-item" {{on "click" @openConfirmDeletionModal}}> + <i class="fa fa-times mx-2"></i>Delete + </a> + </li> + {{/if}} +</ul> diff --git a/app/components/context_menu/index.js b/app/components/context_menu/index.js new file mode 100644 index 0000000000000000000000000000000000000000..71443df888a5fab64751e412065adfc1fb80eb93 --- /dev/null +++ b/app/components/context_menu/index.js @@ -0,0 +1,108 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + + +export default class ContextMenuComponent extends Component { + /* + Arguments: + `openNewFolderModal` - action invoked when new folder button + is clicked + `node` - current node i.e. current + folder whose content is currenlty being displayed + `selectedNodes` - array of selected nodes + */ + @tracked download_in_progress = false; + + + get one_node_selected() { + return this.args.selectedNodes.length === 1; + } + + get one_or_multiple_nodes_selected() { + return this.args.selectedNodes.length >= 1; + } + + get multiple_nodes_selected() { + return this.args.selectedNodes.length > 1; + } + + @action + onSelectAll() { + let nodes_arr = Array.from( + document.getElementsByClassName('node') + ); + + this.select_nodes(nodes_arr); + } + + @action + onSelectFolders() { + let nodes_arr = Array.from( + document.querySelectorAll('.node.folder') + ); + + this.select_nodes(nodes_arr); + } + + @action + onSelectDocuments() { + let nodes_arr = Array.from( + document.querySelectorAll('.node.document') + ); + + this.select_nodes(nodes_arr); + } + + @action + onInvertSelection() { + console.log('Invert Selection'); + } + + @action + onSelectNone() { + let nodes_arr = Array.from( + document.getElementsByClassName('node') + ); + + this.deselect_all_nodes(nodes_arr); + } + + @action + onRename() { + this.args.openRenameModal( + this.args.selectedNodes[0] + ); + } + + @action + async onDownloadNodes() { + this.download_in_progress = true; + await this.args.onDownloadNodes( + this.args.selectedNodes + ); + this.download_in_progress = false; + } + + select_nodes(elements) { + let input_el; + + elements.forEach(element => { + input_el = element.querySelector('input'); + if (input_el && !input_el.checked) { + input_el.click(); + } + }); + } + + deselect_all_nodes(nodes_arr) { + let input_el; + + nodes_arr.forEach(element => { + input_el = element.querySelector('input'); + if (input_el && input_el.checked) { + input_el.click(); + } + }); + } +} diff --git a/app/modifiers/context_menu.js b/app/modifiers/context_menu.js new file mode 100644 index 0000000000000000000000000000000000000000..c457717e4a0f3d0915f7ba58436da6be82eaa43a --- /dev/null +++ b/app/modifiers/context_menu.js @@ -0,0 +1,107 @@ +import { action } from '@ember/object'; +import Modifier from 'ember-modifier'; + + +class ContextMenu { + + constructor(dom_element) { + this._dom_el = dom_element; + } + + show(x, y) { + this.display = 'block'; + this.top = `${x}px`; + this.left = `${y}px`; + } + + hide() { + this.display = 'None'; + } + + toggle(x, y) { + if (!this.is_visible()) { + this.show(x, y); + } else { + this.hide(); + } + } + + is_visible() { + return this._dom_el.style.display == 'block'; + } + + set display(value) { + if (!this._dom_el) { + return; + } + this._dom_el.style.display = value; + } + + set top(value) { + if (!this._dom_el) { + return; + } + this._dom_el.style.top = value; + } + + set left(value) { + if (!this._dom_el) { + return; + } + this._dom_el.style.left = value; + } +} + + +export default class ContextMenuModifier extends Modifier { + + context_menu = undefined; + + addEventListener() { + this.element.addEventListener('contextmenu', this.onContextMenu); + this.element.addEventListener('click', this.onClick); + } + + removeEventListener() { + this.element.removeEventListener('contextmenu', this.onMouseDown); + this.element.removeEventListener('click', this.onClick); + } + + // lifecycle hooks + didReceiveArguments() { + let dom_el = document.getElementById('context-menu'); + + if (dom_el) { + this.removeEventListener(); + this.addEventListener(); + + this.context_menu = new ContextMenu(dom_el); + } else { + console.error(`#context-menu element not found`); + } + } + + willDestroy() { + this.removeEventListener(); + } + + @action + onContextMenu(event) { + event.preventDefault(); + + if (this.context_menu) { + this.context_menu.toggle(event.pageY, event.pageX); + } + } + + @action + onClick() { + this.hide(); + } + + hide() { + if (this.context_menu) { + this.context_menu.hide(); + } + } +} diff --git a/app/modifiers/ui_select.js b/app/modifiers/ui_select.js index 6c787807a3bd703fe591339c6f339455479e9e73..77fe62ce26f2fdd98fdd8437f0eed552b37c9b70 100644 --- a/app/modifiers/ui_select.js +++ b/app/modifiers/ui_select.js @@ -126,7 +126,6 @@ class UISelect { }); } - deselect_all_nodes() { let input_el; diff --git a/app/styles/app.scss b/app/styles/app.scss index f5fc50bd8fff0db4ed6cc6d2bcf7aff1ea13ee89..28733cb9f2d60e4d2781f1d46fc95870576d2b05 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -9,6 +9,7 @@ @import "./search.scss"; @import "./preferences.scss"; @import "./ui_select.scss"; +@import "./context_menu.scss"; body { diff --git a/app/styles/context_menu.scss b/app/styles/context_menu.scss new file mode 100644 index 0000000000000000000000000000000000000000..41222129b7ab6dfa0b01f1039ca27ca3bd184223 --- /dev/null +++ b/app/styles/context_menu.scss @@ -0,0 +1,24 @@ +#context-menu { + position: absolute; + display: hidden; +} + +#context-menu { + .dropdown-menu-left { + left: 100%; + top: 10%; + } + .dropdown-submenu { + position: relative; + z-index: 5000; + } + + .dropdown-submenu:hover .dropdown-menu, .dropdown-submenu:focus .dropdown-menu{ + display: flex; + flex-direction: column; + position: absolute !important; + } + +} + +