@fiduswriter/image-manager 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/overview/index.d.ts +4 -4
- package/dist/overview/index.d.ts.map +1 -1
- package/dist/overview/index.js +42 -59
- package/dist/overview/index.js.map +1 -1
- package/dist/overview/templates.d.ts +7 -1
- package/dist/overview/templates.d.ts.map +1 -1
- package/dist/overview/templates.js +15 -3
- package/dist/overview/templates.js.map +1 -1
- package/logo.svg +10 -0
- package/package.json +3 -3
- package/src/global.d.ts +9 -28
- package/src/overview/index.ts +442 -457
- package/src/overview/templates.ts +30 -17
package/src/overview/index.ts
CHANGED
|
@@ -1,251 +1,236 @@
|
|
|
1
|
-
import {baseBodyTemplate} from "@fiduswriter/common/common"
|
|
2
|
-
import {FeedbackTab} from "@fiduswriter/common/feedback"
|
|
3
|
-
import {SiteMenu} from "@fiduswriter/common/menu"
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "fwtoolkit"
|
|
22
|
-
import type {DataTable} from "simple-datatables"
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
OverviewDataTable,
|
|
4
|
+
OverviewMenuView,
|
|
5
|
+
activateWait,
|
|
6
|
+
addAlert,
|
|
7
|
+
deactivateWait,
|
|
8
|
+
ensureCSS,
|
|
9
|
+
escapeText,
|
|
10
|
+
findTarget,
|
|
11
|
+
gettext,
|
|
12
|
+
isActivationEvent,
|
|
13
|
+
localizeDate,
|
|
14
|
+
post,
|
|
15
|
+
setDocTitle,
|
|
16
|
+
staticUrl,
|
|
17
|
+
whenReady,
|
|
18
|
+
} from "fwtoolkit";
|
|
19
|
+
import type { DataTable } from "simple-datatables";
|
|
20
|
+
|
|
21
|
+
import { imageOverviewTemplate } from "./templates.js";
|
|
22
|
+
import { ImageOverviewCategories } from "./categories.js";
|
|
23
|
+
import { bulkMenuModel, menuModel } from "./menu.js";
|
|
26
24
|
import type {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from "../types.js"
|
|
25
|
+
ImageManagerApp,
|
|
26
|
+
ImageManagerPage,
|
|
27
|
+
ImageOverviewPlugin,
|
|
28
|
+
ImageOverviewPluginConstructor,
|
|
29
|
+
ImageTableRow,
|
|
30
|
+
} from "../types.js";
|
|
33
31
|
|
|
34
32
|
interface DataTableRow {
|
|
35
|
-
|
|
33
|
+
cells: { data: unknown; text?: string }[];
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
interface VirtualNode {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
nodeName: string;
|
|
38
|
+
attributes?: Record<string, string | boolean>;
|
|
39
|
+
childNodes?: VirtualNode[];
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
/** Helper functions for user added images/SVGs.*/
|
|
45
43
|
|
|
46
44
|
export class ImageOverview {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
45
|
+
app: ImageManagerApp;
|
|
46
|
+
|
|
47
|
+
container: HTMLElement;
|
|
48
|
+
|
|
49
|
+
plugins: [string, Record<string, ImageOverviewPluginConstructor>][];
|
|
50
|
+
|
|
51
|
+
mod: {
|
|
52
|
+
categories?: ImageOverviewCategories;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
lastSort: {
|
|
56
|
+
column: number;
|
|
57
|
+
dir: "asc" | "desc";
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
pluginsActivated = false;
|
|
61
|
+
|
|
62
|
+
dom!: HTMLElement;
|
|
63
|
+
|
|
64
|
+
menu!: OverviewMenuView;
|
|
65
|
+
|
|
66
|
+
overviewTable: OverviewDataTable | null = null;
|
|
67
|
+
|
|
68
|
+
table: DataTable | null = null;
|
|
69
|
+
|
|
70
|
+
dtBulk: import("fwtoolkit").DatatableBulk | null = null;
|
|
71
|
+
|
|
72
|
+
constructor({
|
|
73
|
+
app,
|
|
74
|
+
container,
|
|
75
|
+
plugins = [],
|
|
76
|
+
}: {
|
|
77
|
+
app: ImageManagerApp;
|
|
78
|
+
container: HTMLElement;
|
|
79
|
+
plugins?: [string, Record<string, ImageOverviewPluginConstructor>][];
|
|
80
|
+
}) {
|
|
81
|
+
this.app = app;
|
|
82
|
+
this.container = container;
|
|
83
|
+
this.plugins = plugins;
|
|
84
|
+
this.mod = {};
|
|
85
|
+
|
|
86
|
+
this.lastSort = { column: 0, dir: "asc" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
init(): Promise<void> {
|
|
90
|
+
ensureCSS([
|
|
91
|
+
staticUrl("css/dialog_usermedia.css"),
|
|
92
|
+
staticUrl("css/dot_menu.css"),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
return whenReady().then(() => {
|
|
96
|
+
this.render();
|
|
97
|
+
new ImageOverviewCategories(this);
|
|
98
|
+
this.menu = new OverviewMenuView(this, menuModel);
|
|
99
|
+
this.menu.init();
|
|
100
|
+
this.activatePlugins();
|
|
101
|
+
this.bindEvents();
|
|
102
|
+
this.mod.categories!.setImageCategoryList(this.app.imageDB.cats);
|
|
103
|
+
this.initTable(Object.keys(this.app.imageDB.db));
|
|
104
|
+
// Reset scroll position to top to prevent Safari from auto-scrolling
|
|
105
|
+
// to the focused table element, which would hide the header/menu
|
|
106
|
+
window.scrollTo(0, 0);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
render(): void {
|
|
111
|
+
this.dom = this.container;
|
|
112
|
+
this.dom.innerHTML = imageOverviewTemplate();
|
|
113
|
+
ensureCSS([staticUrl("css/cropper.min.css")]);
|
|
114
|
+
setDocTitle(gettext("Media Manager"), this.app);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
activatePlugins(): Promise<void> {
|
|
118
|
+
if (this.pluginsActivated) {
|
|
119
|
+
// Plugins have been activated already
|
|
120
|
+
return Promise.resolve();
|
|
89
121
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.render()
|
|
99
|
-
new ImageOverviewCategories(this)
|
|
100
|
-
const smenu = new SiteMenu(this.app, "images")
|
|
101
|
-
smenu.init()
|
|
102
|
-
this.menu = new OverviewMenuView(this, menuModel)
|
|
103
|
-
this.menu.init()
|
|
104
|
-
this.activatePlugins()
|
|
105
|
-
this.bindEvents()
|
|
106
|
-
this.mod.categories!.setImageCategoryList(this.app.imageDB.cats)
|
|
107
|
-
this.initTable(Object.keys(this.app.imageDB.db))
|
|
108
|
-
// Reset scroll position to top to prevent Safari from auto-scrolling
|
|
109
|
-
// to the focused table element, which would hide the header/menu
|
|
110
|
-
window.scrollTo(0, 0)
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
render(): void {
|
|
115
|
-
this.dom = document.createElement("body")
|
|
116
|
-
this.dom.innerHTML = baseBodyTemplate({
|
|
117
|
-
contents: "",
|
|
118
|
-
user: this.user,
|
|
119
|
-
hasOverview: true,
|
|
120
|
-
app: this.app
|
|
121
|
-
})
|
|
122
|
-
document.body = this.dom
|
|
123
|
-
ensureCSS([staticUrl("css/cropper.min.css")])
|
|
124
|
-
setDocTitle(gettext("Media Manager"), this.app)
|
|
125
|
-
const feedbackTab = new FeedbackTab()
|
|
126
|
-
feedbackTab.init()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
activatePlugins(): Promise<void> {
|
|
130
|
-
if (this.pluginsActivated) {
|
|
131
|
-
// Plugins have been activated already
|
|
132
|
-
return Promise.resolve()
|
|
122
|
+
this.pluginsActivated = true;
|
|
123
|
+
// Add plugins.
|
|
124
|
+
const pluginInstances: Record<string, ImageOverviewPlugin> = {};
|
|
125
|
+
|
|
126
|
+
return Promise.all(
|
|
127
|
+
this.plugins.map(([app, plugin]) => {
|
|
128
|
+
if (!this.app.settings.APPS.includes(app)) {
|
|
129
|
+
return Promise.resolve();
|
|
133
130
|
}
|
|
134
|
-
this.pluginsActivated = true
|
|
135
|
-
// Add plugins.
|
|
136
|
-
const pluginInstances: Record<string, ImageOverviewPlugin> = {}
|
|
137
|
-
|
|
138
131
|
return Promise.all(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Object.values(plugin).map(pluginExport => {
|
|
145
|
-
if (typeof pluginExport === "function") {
|
|
146
|
-
const Plugin = pluginExport as ImageOverviewPluginConstructor
|
|
147
|
-
pluginInstances[Plugin.name] = new Plugin(this)
|
|
148
|
-
return (
|
|
149
|
-
pluginInstances[Plugin.name].init?.() ||
|
|
150
|
-
Promise.resolve()
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
return Promise.resolve()
|
|
154
|
-
})
|
|
155
|
-
)
|
|
156
|
-
})
|
|
157
|
-
).then(() => undefined)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
//delete image
|
|
161
|
-
deleteImage(ids: (string | number)[]): void {
|
|
162
|
-
const numericIds = ids.map(id => Number.parseInt(String(id)))
|
|
163
|
-
if (this.app.isOffline()) {
|
|
164
|
-
addAlert(
|
|
165
|
-
"error",
|
|
166
|
-
gettext(
|
|
167
|
-
"You are currently offline. Please try again when you are back online."
|
|
168
|
-
)
|
|
169
|
-
)
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
activateWait()
|
|
173
|
-
post("/api/usermedia/delete/", {ids: numericIds})
|
|
174
|
-
.catch(error => {
|
|
175
|
-
addAlert("error", gettext("The image(s) could not be deleted"))
|
|
176
|
-
deactivateWait()
|
|
177
|
-
if (this.app.isOffline()) {
|
|
178
|
-
addAlert(
|
|
179
|
-
"error",
|
|
180
|
-
gettext(
|
|
181
|
-
"You are currently offline. Please try again when you are back online."
|
|
182
|
-
)
|
|
183
|
-
)
|
|
184
|
-
} else {
|
|
185
|
-
throw error
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
.then(() => {
|
|
189
|
-
numericIds.forEach(id => delete this.app.imageDB.db[id])
|
|
190
|
-
this.removeTableRows(numericIds)
|
|
191
|
-
addAlert("success", gettext("The image(s) have been deleted"))
|
|
192
|
-
})
|
|
193
|
-
.then(() => deactivateWait())
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
deleteImageDialog(ids: (string | number)[]): void {
|
|
197
|
-
const buttons = [
|
|
198
|
-
{
|
|
199
|
-
text: gettext("Delete"),
|
|
200
|
-
classes: "fw-dark",
|
|
201
|
-
click: () => {
|
|
202
|
-
this.deleteImage(ids)
|
|
203
|
-
dialog.close()
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
type: "cancel" as const
|
|
132
|
+
Object.values(plugin).map((pluginExport) => {
|
|
133
|
+
if (typeof pluginExport === "function") {
|
|
134
|
+
const Plugin = pluginExport as ImageOverviewPluginConstructor;
|
|
135
|
+
pluginInstances[Plugin.name] = new Plugin(this);
|
|
136
|
+
return pluginInstances[Plugin.name].init?.() || Promise.resolve();
|
|
208
137
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
138
|
+
return Promise.resolve();
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
}),
|
|
142
|
+
).then(() => undefined);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//delete image
|
|
146
|
+
deleteImage(ids: (string | number)[]): void {
|
|
147
|
+
const numericIds = ids.map((id) => Number.parseInt(String(id)));
|
|
148
|
+
if (this.app.isOffline()) {
|
|
149
|
+
addAlert(
|
|
150
|
+
"error",
|
|
151
|
+
gettext(
|
|
152
|
+
"You are currently offline. Please try again when you are back online.",
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
return;
|
|
218
156
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
157
|
+
activateWait();
|
|
158
|
+
post("/api/usermedia/delete/", { ids: numericIds })
|
|
159
|
+
.catch((error) => {
|
|
160
|
+
addAlert("error", gettext("The image(s) could not be deleted"));
|
|
161
|
+
deactivateWait();
|
|
162
|
+
if (this.app.isOffline()) {
|
|
163
|
+
addAlert(
|
|
164
|
+
"error",
|
|
165
|
+
gettext(
|
|
166
|
+
"You are currently offline. Please try again when you are back online.",
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
throw error;
|
|
229
171
|
}
|
|
172
|
+
})
|
|
173
|
+
.then(() => {
|
|
174
|
+
numericIds.forEach((id) => delete this.app.imageDB.db[id]);
|
|
175
|
+
this.removeTableRows(numericIds);
|
|
176
|
+
addAlert("success", gettext("The image(s) have been deleted"));
|
|
177
|
+
})
|
|
178
|
+
.then(() => deactivateWait());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
deleteImageDialog(ids: (string | number)[]): void {
|
|
182
|
+
const buttons = [
|
|
183
|
+
{
|
|
184
|
+
text: gettext("Delete"),
|
|
185
|
+
classes: "fw-dark",
|
|
186
|
+
click: () => {
|
|
187
|
+
this.deleteImage(ids);
|
|
188
|
+
dialog.close();
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
type: "cancel" as const,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
const dialog = new Dialog({
|
|
196
|
+
id: "confirmdeletion",
|
|
197
|
+
icon: "exclamation-triangle",
|
|
198
|
+
title: gettext("Confirm deletion"),
|
|
199
|
+
body: `<p>${gettext("Delete the image(s)")}?</p>`,
|
|
200
|
+
buttons,
|
|
201
|
+
});
|
|
202
|
+
dialog.open();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
updateTable(ids: (string | number)[]): void {
|
|
206
|
+
// Remove items that already exist
|
|
207
|
+
this.removeTableRows(ids);
|
|
208
|
+
if (this.table) {
|
|
209
|
+
this.table.insert({
|
|
210
|
+
data: ids.map((id) => this.createTableRow(Number(id))),
|
|
211
|
+
});
|
|
212
|
+
// Redo last sort
|
|
213
|
+
this.table.columns.sort(this.lastSort.column, this.lastSort.dir);
|
|
230
214
|
}
|
|
215
|
+
}
|
|
231
216
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
217
|
+
createTableRow(id: number): ImageTableRow {
|
|
218
|
+
const image = this.app.imageDB.db[id];
|
|
219
|
+
const cats = image.cats.map((cat) => `cat_${cat}`);
|
|
235
220
|
|
|
236
|
-
|
|
221
|
+
const fileTypeParts = image.file_type.split("/");
|
|
237
222
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
223
|
+
let fileType: string;
|
|
224
|
+
if (1 < fileTypeParts.length) {
|
|
225
|
+
fileType = fileTypeParts[1].toUpperCase();
|
|
226
|
+
} else {
|
|
227
|
+
fileType = fileTypeParts[0].toUpperCase();
|
|
228
|
+
}
|
|
244
229
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
230
|
+
return [
|
|
231
|
+
id,
|
|
232
|
+
false, // checkbox
|
|
233
|
+
`<span class="fw-usermedia-image ${cats.join(" ")}">
|
|
249
234
|
<img src="${image.thumbnail ? image.thumbnail : image.image}">
|
|
250
235
|
</span>
|
|
251
236
|
<span class="fw-usermedia-title">
|
|
@@ -254,264 +239,264 @@ export class ImageOverview {
|
|
|
254
239
|
</span>
|
|
255
240
|
<span class="fw-usermedia-type">${fileType}</span>
|
|
256
241
|
</span>`,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
242
|
+
`<span>${image.width} x ${image.height}</span>`,
|
|
243
|
+
`<span class="fw-date">${localizeDate(image.added, "sortable-date")}</span>`,
|
|
244
|
+
`<span class="delete-image fw-link-text" data-id="${id}">
|
|
260
245
|
<i class="fa fa-trash-alt"></i>
|
|
261
|
-
</span
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
removeTableRows(ids: (string | number)[]): void {
|
|
266
|
-
const numericIds = ids.map(id => Number.parseInt(String(id)))
|
|
267
|
-
|
|
268
|
-
if (!this.table) {
|
|
269
|
-
return
|
|
270
|
-
}
|
|
246
|
+
</span>`,
|
|
247
|
+
];
|
|
248
|
+
}
|
|
271
249
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const id = Number(row.cells[0].data)
|
|
275
|
-
if (numericIds.includes(id)) {
|
|
276
|
-
return index
|
|
277
|
-
} else {
|
|
278
|
-
return false
|
|
279
|
-
}
|
|
280
|
-
})
|
|
281
|
-
.filter(
|
|
282
|
-
(rowIndex): rowIndex is number => rowIndex !== false
|
|
283
|
-
)
|
|
250
|
+
removeTableRows(ids: (string | number)[]): void {
|
|
251
|
+
const numericIds = ids.map((id) => Number.parseInt(String(id)));
|
|
284
252
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
253
|
+
if (!this.table) {
|
|
254
|
+
return;
|
|
288
255
|
}
|
|
289
256
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
257
|
+
const existingRows = this.table.data.data
|
|
258
|
+
.map((row: DataTableRow, index: number) => {
|
|
259
|
+
const id = Number(row.cells[0].data);
|
|
260
|
+
if (numericIds.includes(id)) {
|
|
261
|
+
return index;
|
|
262
|
+
} else {
|
|
263
|
+
return false;
|
|
293
264
|
}
|
|
294
|
-
|
|
265
|
+
})
|
|
266
|
+
.filter((rowIndex): rowIndex is number => rowIndex !== false);
|
|
267
|
+
|
|
268
|
+
if (existingRows.length) {
|
|
269
|
+
this.table.rows.remove(existingRows);
|
|
295
270
|
}
|
|
271
|
+
}
|
|
296
272
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
273
|
+
onResize(): void {
|
|
274
|
+
if (!this.table) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.initTable(Object.keys(this.app.imageDB.db));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Initialize the overview table */
|
|
281
|
+
initTable(ids: string[]): void {
|
|
282
|
+
if (this.overviewTable) {
|
|
283
|
+
this.overviewTable.destroy();
|
|
284
|
+
this.overviewTable = null;
|
|
285
|
+
}
|
|
286
|
+
this.table = null;
|
|
287
|
+
this.dtBulk = null;
|
|
305
288
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return
|
|
309
|
-
}
|
|
310
|
-
contentsEl.innerHTML = ""
|
|
289
|
+
const contentsEl = this.dom as HTMLElement;
|
|
290
|
+
contentsEl.innerHTML = "";
|
|
311
291
|
|
|
312
|
-
|
|
292
|
+
const hiddenCols: number[] = [0];
|
|
313
293
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
294
|
+
if (window.innerWidth < 500) {
|
|
295
|
+
hiddenCols.push(1);
|
|
296
|
+
}
|
|
317
297
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
298
|
+
this.overviewTable = new OverviewDataTable({
|
|
299
|
+
dom: contentsEl,
|
|
300
|
+
classes: ["fw-data-table", "fw-large"],
|
|
301
|
+
columns: [
|
|
302
|
+
{
|
|
303
|
+
select: 0,
|
|
304
|
+
type: "number",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
select: 1,
|
|
308
|
+
type: "boolean",
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
select: hiddenCols,
|
|
312
|
+
hidden: true,
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
select: [1, 3, 5],
|
|
316
|
+
sortable: false,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
select: [this.lastSort.column],
|
|
320
|
+
sort: this.lastSort.dir,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
data: ids.map((id) => this.createTableRow(Number.parseInt(id))),
|
|
324
|
+
idColumn: 0,
|
|
325
|
+
checkboxColumn: 1,
|
|
326
|
+
bulkMenu: bulkMenuModel(),
|
|
327
|
+
bulkMenuPage: this as Record<string, unknown>,
|
|
328
|
+
searchable: true,
|
|
329
|
+
scrollY: `${Math.max(window.innerHeight - 360, 100)}px`,
|
|
330
|
+
tabIndex: 1,
|
|
331
|
+
labels: {
|
|
332
|
+
noRows: gettext("No images available"), // Message shown when there are no images
|
|
333
|
+
noResults: gettext("No images found"), // Message shown when no images are found after search
|
|
334
|
+
},
|
|
335
|
+
headings: [
|
|
336
|
+
"",
|
|
337
|
+
"",
|
|
338
|
+
gettext("File"),
|
|
339
|
+
gettext("Size (px)"),
|
|
340
|
+
gettext("Added"),
|
|
341
|
+
"",
|
|
342
|
+
],
|
|
343
|
+
template: (
|
|
344
|
+
options: {
|
|
345
|
+
classes: Record<string, string>;
|
|
346
|
+
scrollY: string;
|
|
347
|
+
paging?: boolean;
|
|
348
|
+
},
|
|
349
|
+
_dom,
|
|
350
|
+
) =>
|
|
351
|
+
`<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>
|
|
365
352
|
<div class='${options.classes.bottom}'>
|
|
366
353
|
${
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
354
|
+
options.paging
|
|
355
|
+
? `<div class='${options.classes.info}'></div>`
|
|
356
|
+
: ""
|
|
370
357
|
}
|
|
371
358
|
<nav class='${options.classes.pagination}'></nav>
|
|
372
359
|
</div>`,
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
},
|
|
400
|
-
onEnter: (row, _event) => {
|
|
401
|
-
if (this.getSelected().length > 0) {
|
|
402
|
-
return
|
|
403
|
-
}
|
|
404
|
-
if (!this.table) {
|
|
405
|
-
return
|
|
406
|
-
}
|
|
407
|
-
const rowIndex = this.table.data.data.findIndex(
|
|
408
|
-
dataRow =>
|
|
409
|
-
(dataRow as unknown as DataTableRow).cells[0].data ===
|
|
410
|
-
row.cells[0].data
|
|
411
|
-
)
|
|
412
|
-
const button = this.table.dom.querySelector(
|
|
413
|
-
`tr[data-index="${rowIndex}"] span.edit-image`
|
|
414
|
-
)
|
|
415
|
-
if (button) {
|
|
416
|
-
;(button as HTMLElement).click()
|
|
417
|
-
}
|
|
360
|
+
rowRender: (row, tr, _index) => {
|
|
361
|
+
const id = row.cells[0].data as number;
|
|
362
|
+
const inputNode: VirtualNode = {
|
|
363
|
+
nodeName: "input",
|
|
364
|
+
attributes: {
|
|
365
|
+
type: "checkbox",
|
|
366
|
+
class: "entry-select fw-check",
|
|
367
|
+
"data-id": String(id),
|
|
368
|
+
id: `doc-img-${id}`,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
if (row.cells[1].data) {
|
|
372
|
+
inputNode.attributes!.checked = "checked";
|
|
373
|
+
}
|
|
374
|
+
const trNode = tr as {
|
|
375
|
+
childNodes: { childNodes: VirtualNode[] }[];
|
|
376
|
+
};
|
|
377
|
+
trNode.childNodes[0].childNodes = [
|
|
378
|
+
inputNode,
|
|
379
|
+
{
|
|
380
|
+
nodeName: "label",
|
|
381
|
+
attributes: {
|
|
382
|
+
for: `doc-img-${id}`,
|
|
418
383
|
},
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
this.table = this.overviewTable.table!
|
|
426
|
-
;(this.table as unknown as {id: string}).id = "imagelist"
|
|
427
|
-
this.dtBulk = this.overviewTable.dtBulk || null
|
|
428
|
-
|
|
429
|
-
this.table.on("datatable.sort", (column, dir) => {
|
|
430
|
-
this.lastSort = {column: column as number, dir: dir as "asc" | "desc"}
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
this.table.dom.focus()
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// get IDs of selected bib entries
|
|
437
|
-
getSelected(): number[] {
|
|
438
|
-
return Array.from(
|
|
439
|
-
this.dom.querySelectorAll(".entry-select:checked:not(:disabled)")
|
|
440
|
-
).map(el => Number.parseInt(el.getAttribute("data-id") || "0"))
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
bindEvents(): void {
|
|
444
|
-
this.dom.addEventListener("click", event =>
|
|
445
|
-
this.handleActivation(event)
|
|
446
|
-
)
|
|
447
|
-
this.dom.addEventListener("keydown", event =>
|
|
448
|
-
this.handleActivation(event)
|
|
449
|
-
)
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
handleActivation(event: Event): void {
|
|
453
|
-
if (!isActivationEvent(event)) {
|
|
454
|
-
return
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
},
|
|
387
|
+
onEnter: (row, _event) => {
|
|
388
|
+
if (this.getSelected().length > 0) {
|
|
389
|
+
return;
|
|
455
390
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
391
|
+
if (!this.table) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const rowIndex = this.table.data.data.findIndex(
|
|
395
|
+
(dataRow) =>
|
|
396
|
+
(dataRow as unknown as DataTableRow).cells[0].data ===
|
|
397
|
+
row.cells[0].data,
|
|
398
|
+
);
|
|
399
|
+
const button = this.table.dom.querySelector(
|
|
400
|
+
`tr[data-index="${rowIndex}"] span.edit-image`,
|
|
401
|
+
);
|
|
402
|
+
if (button) {
|
|
403
|
+
(button as HTMLElement).click();
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
onDelete: (row) => {
|
|
407
|
+
const imageId = row.cells[0].data as number;
|
|
408
|
+
this.deleteImageDialog([imageId]);
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
this.overviewTable.init();
|
|
412
|
+
this.table = this.overviewTable.table!;
|
|
413
|
+
(this.table as unknown as { id: string }).id = "imagelist";
|
|
414
|
+
this.dtBulk = this.overviewTable.dtBulk || null;
|
|
415
|
+
|
|
416
|
+
this.table.on("datatable.sort", (column, dir) => {
|
|
417
|
+
this.lastSort = { column: column as number, dir: dir as "asc" | "desc" };
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
this.table.dom.focus();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// get IDs of selected bib entries
|
|
424
|
+
getSelected(): number[] {
|
|
425
|
+
return Array.from(
|
|
426
|
+
this.dom.querySelectorAll(".entry-select:checked:not(:disabled)"),
|
|
427
|
+
).map((el) => Number.parseInt(el.getAttribute("data-id") || "0"));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
bindEvents(): void {
|
|
431
|
+
this.dom.addEventListener("click", (event) => this.handleActivation(event));
|
|
432
|
+
this.dom.addEventListener("keydown", (event) =>
|
|
433
|
+
this.handleActivation(event),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
handleActivation(event: Event): void {
|
|
438
|
+
if (!isActivationEvent(event)) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const el: { target?: Element | null } = {};
|
|
442
|
+
switch (true) {
|
|
443
|
+
case findTarget(event, ".delete-image", el): {
|
|
444
|
+
const imageId = (el.target as HTMLElement | null)?.dataset.id;
|
|
445
|
+
this.deleteImageDialog([imageId || ""]);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case findTarget(event, ".edit-image", el): {
|
|
449
|
+
const imageId = (el.target as HTMLElement | null)?.dataset.id;
|
|
450
|
+
import("../edit_dialog/index.js").then(({ ImageEditDialog }) => {
|
|
451
|
+
const dialog = new ImageEditDialog(
|
|
452
|
+
this.app.imageDB,
|
|
453
|
+
imageId ? Number.parseInt(imageId) : false,
|
|
454
|
+
this as unknown as ImageManagerPage,
|
|
455
|
+
);
|
|
456
|
+
dialog.init().then(() => {
|
|
457
|
+
if (imageId) {
|
|
458
|
+
this.updateTable([Number.parseInt(imageId)]);
|
|
480
459
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case findTarget(event, ".fw-add-input", el): {
|
|
465
|
+
const itemEl = (el.target as HTMLElement | null)?.closest(
|
|
466
|
+
".fw-list-input",
|
|
467
|
+
) as HTMLElement;
|
|
468
|
+
if (!itemEl.nextElementSibling) {
|
|
469
|
+
itemEl.insertAdjacentHTML(
|
|
470
|
+
"afterend",
|
|
471
|
+
`<tr class="fw-list-input">
|
|
487
472
|
<td>
|
|
488
473
|
<input type="text" class="category-form">
|
|
489
474
|
<span class="fw-add-input icon-addremove" tabindex="0"></span>
|
|
490
475
|
</td>
|
|
491
|
-
</tr
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
break
|
|
497
|
-
}
|
|
498
|
-
default:
|
|
499
|
-
break
|
|
476
|
+
</tr>`,
|
|
477
|
+
);
|
|
478
|
+
} else {
|
|
479
|
+
itemEl.parentElement!.removeChild(itemEl);
|
|
500
480
|
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
default:
|
|
484
|
+
break;
|
|
501
485
|
}
|
|
486
|
+
}
|
|
502
487
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
488
|
+
close(): void {
|
|
489
|
+
if (this.table) {
|
|
490
|
+
this.table.destroy();
|
|
491
|
+
this.table = null;
|
|
492
|
+
}
|
|
493
|
+
if (this.dtBulk) {
|
|
494
|
+
this.dtBulk.destroy();
|
|
495
|
+
this.dtBulk = null;
|
|
496
|
+
}
|
|
497
|
+
if (this.menu) {
|
|
498
|
+
this.menu.destroy();
|
|
499
|
+
this.menu = null as unknown as OverviewMenuView;
|
|
516
500
|
}
|
|
501
|
+
}
|
|
517
502
|
}
|