Skip to content

Commit ca4247d

Browse files
committed
feat(document-details): show general file type with matching icon
1 parent e46dfea commit ca4247d

File tree

13 files changed

+224
-10
lines changed

13 files changed

+224
-10
lines changed

addon/components/single-document-details.hbs

+9
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@
142142
{{t "alexandria.document-details.metadata"}}
143143
</p>
144144
<ul class="uk-list uk-list-collapse uk-margin-remove">
145+
<li data-test-file-type>
146+
<FaIcon
147+
@icon={{@document.latestFile.value.fileTypeInfo.icon}}
148+
@fixedWidth={{true}}
149+
class="uk-margin-small-right"
150+
{{uk-tooltip (t "alexandria.document-details.file-type") pos="left"}}
151+
/>
152+
{{@document.latestFile.value.fileTypeInfo.label}}
153+
</li>
145154
<li data-test-created-at>
146155
<FaIcon
147156
@icon="clock"

addon/models/file.js

+24
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import { service } from "@ember/service";
12
import Model, { attr, belongsTo, hasMany } from "@ember-data/model";
23
import { task } from "ember-concurrency";
34

45
import { isDownloadUrlExpired } from "ember-alexandria/utils/download";
56
import { ErrorHandler } from "ember-alexandria/utils/error-handler";
7+
import { getFileType, getIcon } from "ember-alexandria/utils/file-type";
68

79
export default class FileModel extends Model {
10+
@service("alexandria-config") config;
11+
@service intl;
12+
813
@attr variant;
914
@attr name;
1015
@attr downloadUrl;
@@ -41,4 +46,23 @@ export default class FileModel extends Model {
4146
new ErrorHandler(this, error).notify("alexandria.errors.save-file");
4247
}
4348
});
49+
50+
get fileTypeInfo() {
51+
const fileType = getFileType(
52+
this.mimeType,
53+
this.config.additionalFileTypes,
54+
);
55+
let label = fileType
56+
? this.intl.t(`alexandria.document-details.file-types.${fileType}`)
57+
: this.mimeType;
58+
59+
if (Object.keys(this.config.additionalFileTypes).includes(fileType)) {
60+
label = this.config.additionalFileTypes[fileType].label;
61+
}
62+
63+
return {
64+
icon: getIcon(fileType, this.config.additionalFileTypes),
65+
label,
66+
};
67+
}
4468
}

addon/services/alexandria-config.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default class AlexandriaConfigService extends Service {
1212
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1313
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1414
];
15+
additionalFileTypes = {};
1516

1617
markIcons = {};
1718

addon/utils/file-type.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export const FILE_TYPES = {
2+
// Specific types that need to match a fixed set of mime types
3+
pdf: { icon: "file-pdf", mimeTypes: ["application/pdf"] },
4+
word: {
5+
icon: "file-word",
6+
mimeTypes: [
7+
"application/msword",
8+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
9+
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
10+
"application/vnd.ms-word.document.macroEnabled.12",
11+
"application/vnd.ms-word.template.macroEnabled.12",
12+
],
13+
},
14+
excel: {
15+
icon: "file-excel",
16+
mimeTypes: [
17+
"application/vnd.ms-excel",
18+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
19+
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
20+
"application/vnd.ms-excel.sheet.macroEnabled.12",
21+
"application/vnd.ms-excel.template.macroEnabled.12",
22+
"application/vnd.ms-excel.addin.macroEnabled.12",
23+
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
24+
],
25+
},
26+
powerpoint: {
27+
icon: "file-powerpoint",
28+
mimeTypes: [
29+
"application/vnd.ms-powerpoint",
30+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
31+
"application/vnd.openxmlformats-officedocument.presentationml.template",
32+
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
33+
"application/vnd.ms-powerpoint.addin.macroEnabled.12",
34+
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
35+
"application/vnd.ms-powerpoint.template.macroEnabled.12",
36+
"application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
37+
],
38+
},
39+
// Fallback types taken from the first part of the mime type
40+
image: { icon: "file-image", match: /image\// },
41+
video: { icon: "file-video", match: /video\// },
42+
text: { icon: "file-lines", match: /text\// },
43+
audio: { icon: "file-audio", match: /audio\// },
44+
};
45+
46+
export function getIcon(fileType, additionalFileTypes = {}) {
47+
const allFileTypes = { ...additionalFileTypes, ...FILE_TYPES };
48+
49+
return allFileTypes[fileType]?.icon ?? "file";
50+
}
51+
52+
export function getFileType(mimeType, additionalFileTypes = {}) {
53+
if (!mimeType) {
54+
return null;
55+
}
56+
57+
const allFileTypes = { ...additionalFileTypes, ...FILE_TYPES };
58+
59+
const match = Object.entries(allFileTypes).find(([, config]) => {
60+
if (config.mimeTypes) {
61+
return config.mimeTypes?.includes(mimeType);
62+
} else if (config.match) {
63+
return mimeType.search(config.match) > -1;
64+
}
65+
66+
return false;
67+
});
68+
69+
return match?.[0] ?? null;
70+
}

config/icons.js

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ module.exports = function () {
1313
"chevron-right",
1414
"chevron-left",
1515
"link",
16+
"file",
17+
"file-audio",
18+
"file-excel",
19+
"file-image",
20+
"file-lines",
21+
"file-pdf",
22+
"file-powerpoint",
23+
"file-video",
24+
"file-word",
1625
],
1726
"free-regular-svg-icons": [
1827
"folder",

tests/dummy/app/services/alexandria-config.js

+7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ export default class CustomAlexandriaConfigService extends AlexandriaConfigServi
99

1010
enablePDFConversion = true;
1111
enableWebDAV = true;
12+
additionalFileTypes = {
13+
json: {
14+
icon: "file-code",
15+
label: "JSON",
16+
mimeTypes: ["application/json"],
17+
},
18+
};
1219

1320
markIcons = {
1421
decision: "stamp",

tests/dummy/config/icons.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
module.exports = function () {
22
return {
3-
"free-solid-svg-icons": ["stamp", "bullhorn", "heart", "dollar-sign"],
3+
"free-solid-svg-icons": [
4+
"stamp",
5+
"bullhorn",
6+
"heart",
7+
"dollar-sign",
8+
"file-code",
9+
],
410
};
511
};

tests/dummy/mirage/factories/file.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { faker } from "@faker-js/faker";
2+
import mime from "mime";
23
import { Factory } from "miragejs";
34

45
export default Factory.extend({
@@ -8,4 +9,8 @@ export default Factory.extend({
89
name: () => faker.system.fileName(),
910
variant: "original",
1011
downloadUrl: () => faker.internet.url(),
12+
13+
afterCreate(file) {
14+
file.update({ mimeType: mime.getType(file.name) });
15+
},
1116
});

tests/integration/components/single-document-details-test.js

+16-9
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,26 @@ module("Integration | Component | single-document-details", function (hooks) {
1212
setupMirage(hooks);
1313

1414
test("it renders document information", async function (assert) {
15+
const file = {
16+
variant: "original",
17+
name: "some-file.pdf",
18+
createdByUser: null,
19+
downloadUrl: "http://test.com",
20+
download: { perform: fake() },
21+
fileTypeInfo: {
22+
label: "PDF",
23+
icon: "file-pdf",
24+
},
25+
};
26+
1527
this.selectedDocument = {
1628
title: "Test",
1729
category: { color: "#F00" },
1830
createdAt: new Date(1998, 11, 11),
1931
createdByUser: "user1",
2032
createdByGroup: "group1",
21-
files: [
22-
{
23-
variant: "original",
24-
name: "some-file.pdf",
25-
createdByUser: null,
26-
downloadUrl: "http://test.com",
27-
download: { perform: fake() },
28-
},
29-
],
33+
latestFile: { value: file },
34+
files: [file],
3035
};
3136

3237
await render(
@@ -38,6 +43,8 @@ module("Integration | Component | single-document-details", function (hooks) {
3843
assert.dom("[data-test-title-icon]").hasStyle({ color: "rgb(255, 0, 0)" });
3944

4045
assert.dom("[data-test-title]").hasText(this.selectedDocument.title);
46+
assert.dom("[data-test-file-type]").hasText("PDF");
47+
assert.dom("[data-test-file-type] svg[data-icon='file-pdf']").exists();
4148
assert.dom("[data-test-created-at]").hasText("12/11/1998, 12:00 AM");
4249
assert
4350
.dom("[data-test-created-by-user]")

tests/unit/utils/file-type-test.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { module, test } from "qunit";
2+
3+
import { getIcon, getFileType } from "ember-alexandria/utils/file-type";
4+
5+
module("Unit | Utility | file-type", function () {
6+
test.each(
7+
"determines file type and icon by mime type",
8+
[
9+
["image/png", "image", "file-image"],
10+
["video/mp4", "video", "file-video"],
11+
["text/plain", "text", "file-lines"],
12+
["audio/mpeg", "audio", "file-audio"],
13+
["application/pdf", "pdf", "file-pdf"],
14+
[
15+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
16+
"word",
17+
"file-word",
18+
],
19+
[
20+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
21+
"excel",
22+
"file-excel",
23+
],
24+
[
25+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
26+
"powerpoint",
27+
"file-powerpoint",
28+
],
29+
// Custom definition
30+
["application/json", "json", "file-code"],
31+
],
32+
function (assert, [mimeType, expectedFileType, expectedIcon]) {
33+
const additionalFileTypes = {
34+
json: {
35+
icon: "file-code",
36+
mimeTypes: ["application/json"],
37+
},
38+
};
39+
40+
const fileType = getFileType(mimeType, additionalFileTypes);
41+
42+
assert.strictEqual(fileType, expectedFileType);
43+
assert.strictEqual(getIcon(fileType, additionalFileTypes), expectedIcon);
44+
},
45+
);
46+
});

translations/de.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ alexandria:
8888
version:
8989
filename: "Dateiname"
9090
checksum: "Checksumme"
91+
file-type: "Dateityp"
92+
file-types:
93+
audio: "Audio"
94+
excel: "Excel"
95+
image: "Bild"
96+
pdf: "PDF"
97+
powerpoint: "PowerPoint"
98+
text: "Text"
99+
video: "Video"
100+
word: "Word"
91101

92102
document-download:
93103
button: "{numDocs, plural, =1 {Herunterladen} other {Auswahl herunterladen}}"

translations/en.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ alexandria:
8989
version:
9090
filename: "Filename"
9191
checksum: "Checksum"
92+
file-type: "Filetype"
93+
file-types:
94+
audio: "Audio"
95+
excel: "Excel"
96+
image: "Image"
97+
pdf: "PDF"
98+
powerpoint: "PowerPoint"
99+
text: "Text"
100+
video: "Video"
101+
word: "Word"
92102

93103
document-download:
94104
button: "{numDocs, plural, =1 {Download} other {Download selection}}"

translations/it.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ alexandria:
8787
version:
8888
filename: "Nome del file"
8989
checksum: "Checksum"
90+
file-type: "Tipo di file"
91+
file-types:
92+
audio: "Audio"
93+
excel: "Excel"
94+
image: "Immagine"
95+
pdf: "PDF"
96+
powerpoint: "PowerPoint"
97+
text: "Testo"
98+
video: "Video"
99+
word: "Word"
90100

91101
document-download:
92102
button: "{numDocs, plural, =1 {Scarica} other {Scarica elementi selezionati}}"

0 commit comments

Comments
 (0)