Skip to content

Commit 3cdfb5d

Browse files
committed
feat(document): add copy button
1 parent a8b9133 commit 3cdfb5d

11 files changed

+228
-12
lines changed

addon/components/documents-side-panel.hbs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
{{else}}
1616
<MultiDocumentDetails
1717
@selectedDocuments={{@selectedDocuments}}
18+
@refreshDocumentList={{@refreshDocumentList}}
1819
data-test-multi-doc-details
1920
/>
2021
{{/if}}

addon/components/multi-document-details.hbs

+24-10
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,35 @@
2323
{{#if @selectedDocuments.length}}
2424
<MarkManager @documents={{@selectedDocuments}} class="uk-margin" />
2525

26-
<div class="uk-margin">
27-
<DocumentDeleteButton
28-
@docsToDelete={{@selectedDocuments}}
29-
as |showDialog|
30-
>
26+
<div class="uk-grid uk-grid-small uk-child-width-1-2" uk-grid>
27+
<div>
3128
<UkButton
32-
data-test-delete
3329
@size="small"
34-
@color="danger"
30+
@onClick={{this.copyDocuments.perform}}
31+
@loading={{this.copyDocuments.isLoading}}
3532
class="uk-width-1"
36-
{{on "click" showDialog}}
33+
data-test-copy
3734
>
38-
{{t "alexandria.delete"}}
35+
{{t "alexandria.copy"}}
3936
</UkButton>
40-
</DocumentDeleteButton>
37+
</div>
38+
39+
<div>
40+
<DocumentDeleteButton
41+
@docsToDelete={{@selectedDocuments}}
42+
as |showDialog|
43+
>
44+
<UkButton
45+
data-test-delete
46+
@size="small"
47+
@color="danger"
48+
class="uk-width-1"
49+
{{on "click" showDialog}}
50+
>
51+
{{t "alexandria.delete"}}
52+
</UkButton>
53+
</DocumentDeleteButton>
54+
</div>
4155
</div>
4256

4357
<hr>

addon/components/multi-document-details.js

+26
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { inject as service } from "@ember/service";
22
import Component from "@glimmer/component";
3+
import { task } from "ember-concurrency";
4+
5+
import { ErrorHandler } from "ember-alexandria/utils/error-handler";
6+
37
export default class MultiDocumentDetailsComponent extends Component {
48
@service("alexandria-side-panel") sidePanel;
9+
@service("alexandria-documents") documents;
10+
@service notification;
11+
@service intl;
512

613
get mergedTags() {
714
const tags = [];
@@ -23,4 +30,23 @@ export default class MultiDocumentDetailsComponent extends Component {
2330

2431
return tags;
2532
}
33+
34+
copyDocuments = task({ drop: true }, async (event) => {
35+
event?.preventDefault();
36+
try {
37+
await this.documents.copy(
38+
this.args.selectedDocuments.map((doc) => doc.id),
39+
);
40+
await this.args.refreshDocumentList();
41+
this.notification.success(
42+
this.intl.t("alexandria.success.copy-document", {
43+
count: this.args.selectedDocuments.length,
44+
}),
45+
);
46+
} catch (error) {
47+
new ErrorHandler(this, error).notify("alexandria.errors.copy-document", {
48+
count: this.args.selectedDocuments.length,
49+
});
50+
}
51+
});
2652
}

addon/components/single-document-details.hbs

+13-1
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@
223223
</div>
224224
{{/if}}
225225

226+
<div>
227+
<UkButton
228+
@size="small"
229+
@onClick={{this.copyDocument.perform}}
230+
@loading={{this.copyDocument.isRunning}}
231+
class="uk-width-1"
232+
data-test-copy
233+
>
234+
{{t "alexandria.copy"}}
235+
</UkButton>
236+
</div>
237+
226238
<div uk-form-custom>
227239
<input
228240
data-test-replace
@@ -241,7 +253,7 @@
241253
</button>
242254
</div>
243255

244-
<div>
256+
<div class="uk-width-1-1">
245257
<DocumentDeleteButton @docsToDelete={{@document}} as |showDialog|>
246258
<UkButton
247259
data-test-delete

addon/components/single-document-details.js

+15
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,19 @@ export default class SingleDocumentDetailsComponent extends Component {
146146
new ErrorHandler(this, error).notify("alexandria.errors.open-webdav");
147147
}
148148
});
149+
150+
copyDocument = task({ drop: true }, async (event) => {
151+
event?.preventDefault();
152+
try {
153+
await this.documents.copy([this.args.document.id]);
154+
await this.args.refreshDocumentList();
155+
this.notification.success(
156+
this.intl.t("alexandria.success.copy-document", { count: 1 }),
157+
);
158+
} catch (error) {
159+
new ErrorHandler(this, error).notify("alexandria.errors.copy-document", {
160+
count: 1,
161+
});
162+
}
163+
});
149164
}

addon/services/alexandria-documents.js

+70
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,76 @@ export default class AlexandriaDocumentsService extends Service {
218218
return states;
219219
}
220220

221+
/**
222+
* Copies one or multiple files.
223+
*
224+
* @param {Array<Number>} documentIds.
225+
* @param {Object} category category instance.
226+
*/
227+
async copy(documentIds, category = null) {
228+
const INVALID_FILE_TYPE = "invalid-file-type";
229+
230+
const states = await Promise.all(
231+
documentIds.map(async (id) => {
232+
const originalDocument = this.store.peekRecord("document", id);
233+
if (!originalDocument) {
234+
return true;
235+
}
236+
237+
const files = (await originalDocument.files) ?? [];
238+
if (
239+
category &&
240+
files
241+
.filter((f) => f.variant === "original")
242+
.some((file) => !fileHasValidMimeType(file, category))
243+
) {
244+
return "invalid-file-type";
245+
}
246+
247+
const adapter = this.store.adapterFor("document");
248+
let url = adapter.buildURL("document", originalDocument.id);
249+
url += "/copy";
250+
251+
const data = {
252+
type: "documents",
253+
id: originalDocument.id,
254+
relationships: {},
255+
};
256+
257+
if (category) {
258+
data.relationships.category = {
259+
data: {
260+
id: category.id,
261+
type: "categories",
262+
},
263+
};
264+
}
265+
266+
try {
267+
const res = await this.fetch.fetch(url, {
268+
method: "POST",
269+
body: JSON.stringify({ data }),
270+
});
271+
272+
return (await res.json()).data.id;
273+
} catch (error) {
274+
new ErrorHandler(this, error).notify();
275+
276+
return false;
277+
}
278+
}),
279+
);
280+
281+
if (states.includes(INVALID_FILE_TYPE)) {
282+
this.mimeTypeErrorNotification(category);
283+
return states.map((state) =>
284+
state === INVALID_FILE_TYPE ? false : state,
285+
);
286+
}
287+
288+
return states;
289+
}
290+
221291
/**
222292
* Clears the document selection
223293
*/

tests/acceptance/documents-test.js

+50
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,56 @@ module("Acceptance | documents", function (hooks) {
150150
assert.dom("[data-test-document]").doesNotExist();
151151
});
152152

153+
test("copy single document", async function (assert) {
154+
const document = this.server.create("document");
155+
156+
await visit(`/`);
157+
158+
assert.dom("[data-test-document-list-item]").exists({ count: 1 });
159+
await click("[data-test-document-list-item]:nth-of-type(1)");
160+
161+
this.assertRequest("POST", "/api/v1/documents/:id/copy", (request) => {
162+
assert.strictEqual(
163+
request.params.id,
164+
document.id,
165+
"copying the correct document",
166+
);
167+
});
168+
169+
await click("[data-test-single-doc-details] [data-test-copy]");
170+
assert.dom("[data-test-document-list-item]").exists({ count: 2 });
171+
});
172+
173+
test("copy multiple documents", async function (assert) {
174+
const documents = this.server.createList("document", 2);
175+
176+
await visit(`/`);
177+
178+
assert.dom("[data-test-document-list-item]").exists({ count: 2 });
179+
180+
await click("[data-test-document-list-item]:nth-of-type(1)");
181+
await click("[data-test-document-list-item]:nth-of-type(2)", {
182+
shiftKey: true,
183+
});
184+
185+
const assertFn = (document) => (request) => {
186+
assert.strictEqual(
187+
request.params.id,
188+
document.id,
189+
"copying the correct document",
190+
);
191+
};
192+
193+
this.assertRequests(
194+
"POST",
195+
"/api/v1/documents/:id/copy",
196+
documents.map(assertFn),
197+
);
198+
199+
await click("[data-test-multi-doc-details] [data-test-copy]");
200+
assert.dom("[data-test-document-list-item]").exists({ count: 4 });
201+
});
202+
153203
test("upload file", async function (assert) {
154204
this.server.create("category");
155205

tests/dummy/mirage/config.js

+19
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@ export default function makeServer(config) {
1414
this.post("/documents", function (schema) {
1515
return schema.documents.create();
1616
});
17+
this.post("/documents/:id/copy", function (schema, request) {
18+
const originalDocument = schema.documents.find(request.params.id);
19+
const payload = JSON.parse(request.requestBody || "{}");
20+
const payloadCategoryId =
21+
payload?.data?.relationships?.category?.data?.id;
22+
const category = payloadCategoryId
23+
? schema.categories.find(payloadCategoryId)
24+
: originalDocument.category;
25+
26+
const input = {
27+
...originalDocument.attrs,
28+
};
29+
delete input.id;
30+
31+
return schema.documents.create({
32+
...input,
33+
category,
34+
});
35+
});
1736
this.resource("tags", { except: ["delete"] });
1837
this.resource("marks", { only: ["index", "show"] });
1938

translations/de.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ alexandria:
77
upload-file: "Datei hochladen"
88
download: "Download"
99
move-document: "{count} {count, plural, one {Dokument} other {Dokumente}} verschieben"
10+
copy: "Kopieren"
1011

1112
errors:
1213
save-file: "Beim Herunterladen der Datei ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut."
@@ -21,6 +22,7 @@ alexandria:
2122
update: "Änderungen konnten nicht gespeichert werden. Versuchen Sie es erneut."
2223
no-permission: "Sie haben keine Berechtigung, diese Aktion auszuführen."
2324
move-document: "Beim Verschieben {count, plural, one {des Dokumentes} other {von # Dokumenten}} ist ein Fehler aufgetreten"
25+
copy-document: "Beim Kopieren {count, plural, one {des Dokumentes} other {von # Dokumenten}} ist ein Fehler aufgetreten"
2426
convert-pdf: "Während dem Umwandeln ist ein Fehler aufgetreten."
2527
invalid-file-type: 'In der Kategorie "{category}" können nur {types} hochgeladen werden.'
2628
file-too-large: "Die hochgeladene Datei ist zu gross."
@@ -35,6 +37,7 @@ alexandria:
3537
} erfolgreich hochgeladen.
3638
update: "Änderungen wurden gespeichert."
3739
move-document: "{count, plural, one {Das Dokument wurde} other {# Dokumente wurden}} erfolgreich verschoben"
40+
copy-document: "{count, plural, one {Das Dokument wurde} other {# Dokumente wurden}} erfolgreich kopiert"
3841
convert-pdf: "Dokument wurde erfolgreich umgewandelt."
3942

4043
category-nav:

translations/en.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ alexandria:
77
upload-file: "Upload file"
88
download: "Download"
99
move-document: "Move {count} {count, plural, one {document} other {documents}}"
10+
copy: "Copy"
1011

1112
errors:
1213
save-file: "While downloading the document, an error occured. Please try again."
@@ -21,6 +22,7 @@ alexandria:
2122
update: "Your changes could not be saved. Please try again."
2223
no-permission: "You don't have permission to perform this action."
2324
move-document: "While moving {count, plural, one {the document} other {# documents}}, an error occured. Please try again."
25+
copy-document: "While copying {count, plural, one {the document} other {# documents}}, an error occured. Please try again."
2426
convert-pdf: "While converting, an error occured. Please try again."
2527
invalid-file-type: 'In category "{category}" only {types} can be uploaded.'
2628
file-too-large: "The uploaded file is too large."
@@ -35,6 +37,7 @@ alexandria:
3537
} uploaded successfully.
3638
update: "Changes saved."
3739
move-document: "{count, plural, one {Document} other {# documents}} moved successfully"
40+
copy-document: "{count, plural, one {Document} other {# documents}} copied successfully"
3841
convert-pdf: "Document converted successfully."
3942

4043
category-nav:

translations/it.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ alexandria:
77
upload-file: "Carica file"
88
download: "Download"
99
move-document: "Sposta {count} {count, plural, one {documento} other {documenti}}"
10+
copy: "Copia"
1011

1112
errors:
1213
save-file: "Errore nel tentativo di scaricare il file. Riprova"
1314
delete-document: "Errore nel tentativo di eliminare il documento. Riprova"
1415
fetch-categories: "Errore nel tentativo di caricare le categorie."
1516
replace-document: "Errore nel tentativo di sostituire il documento."
1617
upload-document: |-
17-
Errore nel tentativo di caricare
18+
Errore nel tentativo di caricare
1819
{count, plural, one {il documento}
1920
other {i documenti}}.
2021
update: "Non è stato possibile salvare le modifiche. Riprova"
2122
no-permission: "Non dispone dei diritti per eseguire questa operazione."
2223
move-document: "Errore nel tentativo di spostare {count, plural, one {il documento} other {i documenti}}"
24+
copy-document: "Errore nel tentativo di copiare {count, plural, one {il documento} other {i documenti}}"
2325
convert-pdf: "Errore nel tentativo di convertire il documento."
2426
invalid-file-type: 'Nella categoria "{category}" è possibile caricare solo {types}.'
2527
file-too-large: "Il file caricato è troppo grande."
@@ -34,6 +36,7 @@ alexandria:
3436
} con successo.
3537
update: "Le modifiche sono state salvate."
3638
move-document: "{count, plural, one {Il documento è stato spostato} other {# I documenti sono stati spostati}} con successo"
39+
copy-document: "{count, plural, one {Il documento è stato copiato} other {# I documenti sono stati copiati}} con successo"
3740
convert-pdf: "Il documento è stato convertito con successo."
3841

3942
category-nav:

0 commit comments

Comments
 (0)