From fb64db0a284c55e9d7cb8dccc9c7134a2d5837a7 Mon Sep 17 00:00:00 2001
From: Eugen Ciur <eugen@papermerge.com>
Date: Tue, 9 Nov 2021 20:29:05 +0100
Subject: [PATCH] pretty good selection + rename functionality

---
 app/components/button/upload.hbs            |  4 +-
 app/components/commander/action_buttons.hbs | 53 ++++++++++--
 app/components/commander/action_buttons.js  | 33 +++++++
 app/components/commander/index.hbs          | 12 ++-
 app/components/commander/index.js           | 36 ++++++++
 app/components/document.hbs                 | 20 +++--
 app/components/document.js                  |  6 ++
 app/components/documents.js                 |  6 --
 app/components/folder.hbs                   |  6 +-
 app/components/folder.js                    |  5 +-
 app/components/modal/rename_node.hbs        | 18 ++++
 app/components/modal/rename_node.js         | 52 +++++++++++
 app/components/node.js                      | 20 +++++
 app/styles/node.scss                        | 96 +++++++++++++++------
 14 files changed, 315 insertions(+), 52 deletions(-)
 create mode 100644 app/components/commander/action_buttons.js
 create mode 100644 app/components/document.js
 delete mode 100644 app/components/documents.js
 create mode 100644 app/components/modal/rename_node.hbs
 create mode 100644 app/components/modal/rename_node.js
 create mode 100644 app/components/node.js

diff --git a/app/components/button/upload.hbs b/app/components/button/upload.hbs
index 543d2a8..1f1f858 100644
--- a/app/components/button/upload.hbs
+++ b/app/components/button/upload.hbs
@@ -5,8 +5,8 @@
   {{on "change" this.onUploadChange }} />
 
 <button
-  class="btn btn-bordered btn-light btn-flat"
+  class="btn btn-success"
   type="button" {{on "click" this.onClickProxyUpload }}>
-    <i class="fa fa-upload mr-1 text-success"></i>
+    <i class="fa fa-upload mr-1"></i>
     Upload
 </button>
\ No newline at end of file
diff --git a/app/components/commander/action_buttons.hbs b/app/components/commander/action_buttons.hbs
index dbe6b71..330d1dd 100644
--- a/app/components/commander/action_buttons.hbs
+++ b/app/components/commander/action_buttons.hbs
@@ -1,10 +1,49 @@
 <div>
-  <Button::Upload @node={{@node}} />
+  {{#if this.one_node_selected}}
+    <button
+      class="btn btn-success"
+      type="button"
+      {{on "click" this.onRename}}>
+        <i class="fa fa-edit"></i>
+      Rename
+    </button>
+    <button
+      class="btn btn-success"
+      type="button">
+        <i class="fa fa-download"></i>
+      Download
+    </button>
+    <button
+      class="btn btn-danger mx-5"
+      type="button"
+      {{on "click" this.onDelete}}>
+        <i class="fa fa-times"></i>
+      Delete
+    </button>
+  {{else if this.multiple_nodes_selected}}
+    <button
+      class="btn btn-success"
+      type="button">
+        <i class="fa fa-download"></i>
+      Download
+    </button>
+    <button
+      class="btn btn-danger mx-5"
+      type="button"
+      {{on "click" this.onDelete}}>
+        <i class="fa fa-times"></i>
+      Delete
+    </button>
+  {{else}}
+    {{! No nodes are currently selected }}
+    <Button::Upload
+      @node={{@node}} />
 
-  <button
-    class="btn btn-bordered btn-light btn-flat"
-    type="button" {{on "click" @openNewFolderModal }}>
-      <i class="fa fa-plus mr-1 text-success"></i>
-    New Folder
-  </button>
+    <button
+      class="btn btn-success"
+      type="button" {{on "click" @openNewFolderModal }}>
+        <i class="fa fa-plus mr-1"></i>
+      New Folder
+    </button>
+  {{/if}}
 </div>
\ No newline at end of file
diff --git a/app/components/commander/action_buttons.js b/app/components/commander/action_buttons.js
new file mode 100644
index 0000000..bbec4e2
--- /dev/null
+++ b/app/components/commander/action_buttons.js
@@ -0,0 +1,33 @@
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+
+
+export default class ActionButtonsComponent 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
+  */
+  get one_node_selected() {
+    return this.args.selectedNodes.length === 1;
+  }
+
+  get multiple_nodes_selected() {
+    return this.args.selectedNodes.length > 1;
+  }
+
+  @action
+  onRename() {
+    this.args.openRenameModal(
+      this.args.selectedNodes[0]
+    );
+  }
+
+  @action
+  onDelete() {
+
+  }
+}
diff --git a/app/components/commander/index.hbs b/app/components/commander/index.hbs
index d95c971..812a1a9 100644
--- a/app/components/commander/index.hbs
+++ b/app/components/commander/index.hbs
@@ -2,6 +2,8 @@
   <div class="d-flex justify-content-between">
     <Commander::ActionButtons
       @openNewFolderModal={{this.openNewFolderModal}}
+      @openRenameModal={{this.openRenameModal}}
+      @selectedNodes={{this.selected_nodes}}
       @node={{@node}} />
 
     <Commander::ActionModes
@@ -16,6 +18,12 @@
     @onClose={{this.closeNewFolderModal}}
     {{show-when this.show_new_folder_modal}} />
 
+  <Modal::RenameNode
+    id="rename-node"
+    @selectedNodes={{this.selected_nodes}}
+    @onClose={{this.closeRenameModal}}
+    {{show-when this.show_rename_node_modal}} />
+
   <Breadcrumb
     @node={{@node}}
     @extranode={{@extranode}}
@@ -25,7 +33,9 @@
     {{#each @node.children as |node|}}
       {{#let (component node.nodeType) as |NodeType|}}
         {{! NodeType is either <Folder /> or <Document />}}
-        <NodeType @model={{node}}>
+        <NodeType
+          @model={{node}}
+          @onCheckboxChange={{this.onCheckboxChange}} >
           {{#if (is_equal @hint "left")}}
             <Commander::LinkToLeft
               @node={{node}}
diff --git a/app/components/commander/index.js b/app/components/commander/index.js
index 64f348d..d2de83a 100644
--- a/app/components/commander/index.js
+++ b/app/components/commander/index.js
@@ -1,19 +1,36 @@
 import Component from '@glimmer/component';
 import { tracked } from '@glimmer/tracking';
+import { A } from '@ember/array';
 import { action } from '@ember/object';
 
 
 export default class CommanderComponent extends Component {
   // show create new folder modal dialog?
   @tracked show_new_folder_modal = false;
+
+  // show rename node modal dialog?
+  @tracked show_rename_node_modal = false;
+
   // nodes are displayed as list or as grid?
   @tracked view_mode = 'list';
 
+  @tracked selected_nodes = A([]);
+
   @action
   openNewFolderModal() {
     this.show_new_folder_modal = true;
   }
 
+  @action
+  openRenameModal() {
+    this.show_rename_node_modal = true;
+  }
+
+  @action
+  closeRenameModal() {
+    this.show_rename_node_modal = false;
+  }
+
   @action
   closeNewFolderModal() {
     this.show_new_folder_modal = false;
@@ -23,4 +40,23 @@ export default class CommanderComponent extends Component {
   onViewModeChange(new_view_mode) {
     this.view_mode = new_view_mode;
   }
+
+  @action
+  onCheckboxChange({node, is_selected}) {
+    /**
+    Triggered whenever node's checkbox changes.
+
+    `node` is an instance of Node model of whose
+    selection state changed.
+
+    `is_selected` - new selection state i.e. if user checked
+    the checkbox then `is_selected` is true; if user unchecked
+    the checkbox  then `is_selected` is false;
+    */
+    if (is_selected) {
+      this.selected_nodes.pushObject(node);
+    } else {
+      this.selected_nodes.removeObject(node);
+    }
+  }
 }
diff --git a/app/components/document.hbs b/app/components/document.hbs
index 9d3de59..e2ee59e 100644
--- a/app/components/document.hbs
+++ b/app/components/document.hbs
@@ -1,9 +1,13 @@
-<div class="node document">
-    <Input @type="checkbox" />
-    <div class="icon document"></div>
-    <div class="title">
-        <LinkTo @route="authenticated.document" @model={{@model}}>
-            {{@model.title}}
-        </LinkTo>
-    </div>
+<div class="node document {{if this.is_selected "checked"}}">
+  <Input
+    @type="checkbox"
+    {{on "change" this.onCheckboxChange}} />
+  <div class="icon document"></div>
+  <div class="title">
+    <LinkTo
+      @route="authenticated.document"
+      @model={{@model}}>
+      {{@model.title}}
+    </LinkTo>
+  </div>
 </div>
\ No newline at end of file
diff --git a/app/components/document.js b/app/components/document.js
new file mode 100644
index 0000000..c2c1868
--- /dev/null
+++ b/app/components/document.js
@@ -0,0 +1,6 @@
+import NodeComponent from "./node";
+
+
+export default class DocumentComponent extends NodeComponent {
+
+}
diff --git a/app/components/documents.js b/app/components/documents.js
deleted file mode 100644
index 386f372..0000000
--- a/app/components/documents.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import Component from '@glimmer/component';
-import { tracked } from '@glimmer/tracking';
-
-export default class DocumentsComponent extends Component {
-  @tracked query = '';
-}
diff --git a/app/components/folder.hbs b/app/components/folder.hbs
index a63727c..efc14be 100644
--- a/app/components/folder.hbs
+++ b/app/components/folder.hbs
@@ -1,5 +1,7 @@
-<div class="node folder">
-  <Input @type="checkbox" />
+<div class="node folder {{if this.is_selected "checked"}}">
+  <Input
+    @type="checkbox"
+    {{on "change" this.onCheckboxChange}} />
   <div class="icon folder"></div>
   <div class="title">
     {{yield}}
diff --git a/app/components/folder.js b/app/components/folder.js
index e8f8a27..0fd871d 100644
--- a/app/components/folder.js
+++ b/app/components/folder.js
@@ -1,7 +1,7 @@
-import Component from '@glimmer/component';
+import NodeComponent from "./node";
 
 
-export default class FolderComponent extends Component {
+export default class FolderComponent extends NodeComponent {
 
   get query() {
     if (this.args.extranode) {
@@ -16,4 +16,5 @@ export default class FolderComponent extends Component {
   get model() {
     return this.args.model;
   }
+
 }
diff --git a/app/components/modal/rename_node.hbs b/app/components/modal/rename_node.hbs
new file mode 100644
index 0000000..a7efb36
--- /dev/null
+++ b/app/components/modal/rename_node.hbs
@@ -0,0 +1,18 @@
+
+<Modal::Base
+  @title="Rename"
+  @actionTitle="Rename"
+  @onClose={{@onClose}}
+  @onSubmit={{this.onSubmit}}
+  @onCancel={{this.onCancel}}
+  ...attributes
+>
+  {{this.title}}
+  <label for="node-title" class="form-label">New title:</label>
+  <Input
+    id="node-title"
+    class="form-control"
+    @type="text"
+    @value={{this.new_title}}
+  />
+</Modal::Base>
\ No newline at end of file
diff --git a/app/components/modal/rename_node.js b/app/components/modal/rename_node.js
new file mode 100644
index 0000000..0ab2cb0
--- /dev/null
+++ b/app/components/modal/rename_node.js
@@ -0,0 +1,52 @@
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { inject as service } from '@ember/service';
+
+
+export default class RenameNodeComponent extends Component {
+  @service store;
+  @service currentUser;
+
+
+  get new_title() {
+    let selected_nodes;
+
+    if (this.title) {
+      return this.title;
+    }
+
+    selected_nodes = this.args.selectedNodes;
+    if (selected_nodes && selected_nodes[0]) {
+      return selected_nodes[0].title;
+    }
+
+    return '';
+  }
+
+  set new_title(value) {
+    this.title = value;
+  }
+
+  get node() {
+    return this.args.selectedNodes[0];
+  }
+
+  @action
+  onSubmit() {
+
+    this.store.findRecord('node', this.node.id).then(
+      (node) => {
+        node.title = this.title;
+        node.save();
+    });
+
+    this.args.onClose();
+  }
+
+  @action
+  onCancel() {
+    this.args.onClose();
+    this.title = '';
+  }
+
+}
diff --git a/app/components/node.js b/app/components/node.js
new file mode 100644
index 0000000..144bada
--- /dev/null
+++ b/app/components/node.js
@@ -0,0 +1,20 @@
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { action } from '@ember/object';
+
+
+export default class NodeComponent extends Component {
+  @tracked is_selected = false;
+
+  @action
+  onCheckboxChange(event) {
+    let is_checked = event.target.checked;
+
+    this.is_selected = is_checked;
+
+    this.args.onCheckboxChange({
+      node: this.args.model,
+      is_selected: is_checked
+    });
+  }
+}
diff --git a/app/styles/node.scss b/app/styles/node.scss
index 3dc95a8..912d8c5 100644
--- a/app/styles/node.scss
+++ b/app/styles/node.scss
@@ -1,39 +1,87 @@
 .node {
+  display: flex;
+  align-items: center;
 
+  .icon {
+    width: 4rem;
+    height: 3rem;
+  }
+
+  .icon.folder {
+    background-image: url("/assets/images/folder.svg");
+    background-size: 100% 88%;
+  }
+
+  .icon.document {
+    background-image: url("/assets/images/document.svg");
+    background-size: 100% 100%;
+  }
+
+  &.checked {
+    background-color:  #e6f5ff;
+    outline: 1px solid #A6DAFF;
+  }
+
+  &:hover {
+    background-color: #8ed2fe66;
+    outline: 1px solid #6fc5ff;
+       //border: 1px solid $result_list_hover_border_color;
+  }
+  .title {
+    a {
+        text-decoration: none;
+    }
+    text-align: center;
+    opacity: 1;
+    z-index: 0;
+ }
+}
+
+
+.view-mode-grid {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+
+  .node {
+
+    width: 8rem;
+    height: 8rem;
     display: flex;
-    align-items: center;
+    flex-direction: column;
+
+    margin: 0.75rem;
+    padding: 0.5rem;
 
     .icon {
-        width: 4rem;
-        height: 3rem;
+      width: 6rem;
+      height: 5rem;
     }
 
-    .icon.folder {
-        background-image: url("/assets/images/folder.svg");
-        background-size: 100% 88%;
+    input[type=checkbox] {
+      align-self: start;
+      visibility: hidden;
     }
 
-    .icon.document {
-        background-image: url("/assets/images/document.svg");
-        background-size: 100% 100%;
+    &:hover {
+      input[type=checkbox] {
+        visibility: visible;
+      }
     }
 
     &.checked {
-        background-color:  #e6f5ff;
-        outline: 1px solid #A6DAFF;
+      input[type=checkbox] {
+        visibility: visible;
+      }
     }
+  }
+}
 
-    &:hover {
-        background-color: #8ed2fe66;
-        outline: 1px solid #6fc5ff;
-         //border: 1px solid $result_list_hover_border_color;
-    }
-    .title {
-        a {
-            text-decoration: none;
-        }
-        text-align: center;
-        opacity: 1;
-        z-index: 0;
-   }
+.view-mode-list {
+  display: flex;
+  flex-direction: column;
+
+  .node {
+    padding: 0.25rem;
+  }
 }
\ No newline at end of file
-- 
GitLab