Skip to content

Commit

Permalink
feat(document): allow moving of a document per drag & drop
Browse files Browse the repository at this point in the history
  • Loading branch information
anehx committed Jan 4, 2024
1 parent 7601fb5 commit 7ba1835
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 45 deletions.
9 changes: 8 additions & 1 deletion addon/components/category-nav/category.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
<li class="category-nav__category" ...attributes>
<li
class="category-nav__category {{if this.isDragOver "category-nav__category--dragover"}}"
...attributes
{{on "dragenter" this.onDragEnter}}
{{on "dragleave" this.onDragLeave}}
{{on "dragover" this.onDragOver}}
{{on "drop" (perform this.onDrop)}}
>
<div
class="uk-link-reset {{if this.isActive "active"}}"
tabindex="0"
Expand Down
99 changes: 98 additions & 1 deletion addon/components/category-nav/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { dropTask } from "ember-concurrency";

export default class CategoryNavCategoryComponent extends Component {
@service("alexandria-documents") documents;
@service notification;
@service router;
@service store;
@service intl;

dragCounter = 0;

@tracked collapseChildren = false;
@tracked isDragOver = false;

get isActive() {
if (!this.args.category.id) {
Expand All @@ -27,7 +34,7 @@ export default class CategoryNavCategoryComponent extends Component {
}

get expandChildren() {
return this.isOpen && !this.collapseChildren;
return this.isDragOver || (this.isOpen && !this.collapseChildren);
}

get controllerInstance() {
Expand All @@ -52,4 +59,94 @@ export default class CategoryNavCategoryComponent extends Component {
},
});
}

@action onDragEnter() {
if (!this.args.category.id) return;

this.dragCounter++;
this.isDragOver = true;
}

@action onDragLeave() {
if (!this.args.category.id) return;

this.dragCounter--;
this.isDragOver = this.dragCounter > 0;
}

@action onDragOver(event) {
if (!this.args.category.id) return;

event.preventDefault();
event.stopPropagation();
}

@dropTask
*onDrop(event) {
event.preventDefault();

if (!this.args.category.id) return;

const documentIds = event.dataTransfer.getData("text").split(",");

const success = yield Promise.all(
documentIds.map(async (id) => {
const document = this.store.peekRecord("document", id);

if (document.category.id === this.args.category.id) {
return true;
}

const previousCategory = this.store.peekRecord(
"category",
document.category.id,
);

try {
document.category = this.args.category;
await document.save();
return true;
} catch (error) {
document.category = previousCategory;

if (error.errors[0].status === "403") {
this.notification.danger(
this.intl.t("alexandria.errors.no-permission"),
);
}

return false;
}
}),
);

const failCount = success.filter((i) => i === false).length;
const successCount = success.filter((i) => i === true).length;

if (failCount) {
this.notification.danger(
this.intl.t("alexandria.errors.move-document", {
count: failCount,
}),
);
}

if (successCount) {
this.notification.success(
this.intl.t("alexandria.success.move-document", {
count: successCount,
}),
);

this.router.transitionTo(this.router.currentRouteName, {
queryParams: {
category: this.args.category.id,
search: undefined,
document: documentIds.join(","),
tags: [],
marks: [],
},
});
}
}
}
2 changes: 2 additions & 0 deletions addon/components/document-grid.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
data-test-document
@document={{document}}
@isSelected={{includes document.id (map-by "id" @selectedDocuments)}}
draggable="true"
{{on "click" (fn @onClickDocument document)}}
{{on "dblclick" (fn @onDoubleClickDocument document)}}
{{on "dragstart" (fn @onDragStart document)}}
/>
</div>
{{else}}
Expand Down
2 changes: 2 additions & 0 deletions addon/components/document-list-item.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
data-test-document-list-item
data-test-document-list-item-id={{@document.id}}
tabindex="0"
draggable="true"
{{on "click" (fn @onClickDocument @document)}}
{{on "dblclick" (fn @onDoubleClickDocument @document)}}
{{on "dragstart" (fn @onDragStart @document)}}
>
<td class="uk-preserve-width">
<FaIcon
Expand Down
1 change: 1 addition & 0 deletions addon/components/document-list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
@isSelected={{includes document.id (map-by "id" @selectedDocuments)}}
@onClickDocument={{@onClickDocument}}
@onDoubleClickDocument={{@onDoubleClickDocument}}
@onDragStart={{@onDragStart}}
/>
{{/each}}
</tbody>
Expand Down
9 changes: 9 additions & 0 deletions addon/components/document-view.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
@selectedDocuments={{this.documents.selectedDocuments}}
@onClickDocument={{this.handleDocumentSelection}}
@onDoubleClickDocument={{this.openDocument}}
@onDragStart={{this.dragDocument}}
/>
{{else}}
<DocumentGrid
Expand All @@ -64,6 +65,7 @@
@selectedDocuments={{this.documents.selectedDocuments}}
@onClickDocument={{this.handleDocumentSelection}}
@onDoubleClickDocument={{this.openDocument}}
@onDragStart={{this.dragDocument}}
/>
{{/if}}
</div>
Expand All @@ -85,4 +87,11 @@
</div>
</div>
<DocumentsSidePanel @selectedDocuments={{this.documents.selectedDocuments}} />
</div>

<div
class="drag-info uk-background-default uk-box-shadow-medium uk-border-rounded uk-width-auto"
{{did-insert this.registerDragInfo}}
>
{{t "alexandria.move-document" count=this.documents.selectedDocuments.length}}
</div>
24 changes: 23 additions & 1 deletion addon/components/document-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default class DocumentViewComponent extends Component {
// Needed for ember-resource
@tracked uploadedDocuments = 0;

dragElement = null;

constructor(parent, args) {
super(parent, args);
/* Adds a key down event listener to enable Ctrl+A document selection of all docs
Expand Down Expand Up @@ -105,7 +107,9 @@ export default class DocumentViewComponent extends Component {
}

onDrop = dropTask(async (event) => {
if (!this.args.filters.category) {
if (!this.args.filters.category || !event.dataTransfer.files.length) {
this.dragCounter = 0;
this.isDragOver = false;
return;
}

Expand All @@ -132,6 +136,7 @@ export default class DocumentViewComponent extends Component {
);
}

this.dragCounter = 0;
this.isDragOver = false;
});

Expand Down Expand Up @@ -208,4 +213,21 @@ export default class DocumentViewComponent extends Component {
afterUpload() {
this.uploadedDocuments++;
}

@action registerDragInfo(element) {
this.dragInfo = element;
}

@action dragDocument(document, event) {
if (!this.documents.selectedDocuments.includes(document)) {
this.handleDocumentSelection(document, event);
}

event.dataTransfer.clearData();
event.dataTransfer.setData(
"text/plain",
this.documents.selectedDocuments.map((d) => d.id).join(","),
);
event.dataTransfer.setDragImage(this.dragInfo, -20, 0);
}
}
11 changes: 11 additions & 0 deletions app/styles/components/_drop.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@use "sass:math";

.drop {
width: 200px;

Expand All @@ -21,3 +23,12 @@
}
}
}

.drag-info {
padding: math.div($padding-small-padding, 3) $padding-small-padding;
border-color: $global-primary-background;
position: fixed;
top: 0;
right: 0;
transform: translate(100%, -100%);
}
39 changes: 25 additions & 14 deletions app/styles/components/category-nav/_category.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@
background: $background-default-background;
}

.uk-nav .category-nav__category > div {
transition-property: background, box-shadow, color;
transition-duration: $animation-duration;
padding: 10px 0 10px $global-margin;
cursor: pointer;
.uk-nav .category-nav__category {
&--dragover {
outline: 1px solid $alert-primary-color;
outline-offset: -1px;

&.active {
@include active;
> div {
background-color: rgba($alert-primary-color, 0.25) !important;
}
}

&:hover {
@include active;
@extend .uk-box-shadow-small;
}
> div {
transition-property: background, box-shadow, color;
transition-duration: $animation-duration;
padding: 10px 0 10px $global-margin;
cursor: pointer;

&.active {
@include active;
}

&:hover {
@include active;
@extend .uk-box-shadow-small;
}

.skeleton-text {
height: 15px;
width: 70%;
.skeleton-text {
height: 15px;
width: 70%;
}
}
}
19 changes: 4 additions & 15 deletions tests/dummy/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,10 @@ export default function makeServer(config) {
this.namespace = "/api/v1";
this.timing = 400;

this.get("/categories");
this.get("/categories/:id");

this.get("/documents");
this.get("/documents/:id");
this.patch("/documents/:id");
this.post("/documents");
this.delete("/documents/:id");

this.get("/tags");
this.get("/tags/:id");
this.post("/tags");
this.patch("/tags/:id");

this.get("/marks");
this.resource("categories", { only: ["index", "show"] });
this.resource("documents");
this.resource("tags", { except: ["delete"] });
this.resource("marks", { only: ["index"] });

this.post("/files", function (schema) {
const attrs = this.normalizedRequestAttrs();
Expand Down
4 changes: 3 additions & 1 deletion tests/dummy/mirage/serializers/application.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { JSONAPISerializer } from "miragejs";

export default JSONAPISerializer.extend({});
export default JSONAPISerializer.extend({
alwaysIncludeLinkageData: true,
});
Loading

0 comments on commit 7ba1835

Please sign in to comment.