@fiduswriter/image-manager 0.1.5 → 0.1.7
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/README.md +53 -3
- package/dist/overview/index.d.ts +4 -4
- package/dist/overview/index.d.ts.map +1 -1
- package/dist/overview/index.js +41 -55
- 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/package.json +93 -80
- package/src/global.d.ts +9 -28
- package/src/overview/index.ts +448 -458
- 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,269 @@ 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
|
-
|
|
295
|
-
|
|
265
|
+
})
|
|
266
|
+
.filter((rowIndex): rowIndex is number => rowIndex !== false);
|
|
296
267
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.overviewTable = null
|
|
302
|
-
}
|
|
303
|
-
this.table = null
|
|
304
|
-
this.dtBulk = null
|
|
268
|
+
if (existingRows.length) {
|
|
269
|
+
this.table.rows.remove(existingRows);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
305
272
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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;
|
|
288
|
+
|
|
289
|
+
const contentsEl = this.dom.querySelector(
|
|
290
|
+
".fw-contents",
|
|
291
|
+
) as HTMLElement | null;
|
|
292
|
+
if (!contentsEl) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
contentsEl.innerHTML = "";
|
|
311
296
|
|
|
312
|
-
|
|
297
|
+
const hiddenCols: number[] = [0];
|
|
313
298
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
299
|
+
if (window.innerWidth < 500) {
|
|
300
|
+
hiddenCols.push(1);
|
|
301
|
+
}
|
|
317
302
|
|
|
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
|
-
|
|
303
|
+
this.overviewTable = new OverviewDataTable({
|
|
304
|
+
dom: contentsEl,
|
|
305
|
+
classes: ["fw-data-table", "fw-large"],
|
|
306
|
+
columns: [
|
|
307
|
+
{
|
|
308
|
+
select: 0,
|
|
309
|
+
type: "number",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
select: 1,
|
|
313
|
+
type: "boolean",
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
select: hiddenCols,
|
|
317
|
+
hidden: true,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
select: [1, 3, 5],
|
|
321
|
+
sortable: false,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
select: [this.lastSort.column],
|
|
325
|
+
sort: this.lastSort.dir,
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
data: ids.map((id) => this.createTableRow(Number.parseInt(id))),
|
|
329
|
+
idColumn: 0,
|
|
330
|
+
checkboxColumn: 1,
|
|
331
|
+
bulkMenu: bulkMenuModel(),
|
|
332
|
+
bulkMenuPage: this as Record<string, unknown>,
|
|
333
|
+
searchable: true,
|
|
334
|
+
scrollY: `${Math.max(window.innerHeight - 360, 100)}px`,
|
|
335
|
+
tabIndex: 1,
|
|
336
|
+
labels: {
|
|
337
|
+
noRows: gettext("No images available"), // Message shown when there are no images
|
|
338
|
+
noResults: gettext("No images found"), // Message shown when no images are found after search
|
|
339
|
+
},
|
|
340
|
+
headings: [
|
|
341
|
+
"",
|
|
342
|
+
"",
|
|
343
|
+
gettext("File"),
|
|
344
|
+
gettext("Size (px)"),
|
|
345
|
+
gettext("Added"),
|
|
346
|
+
"",
|
|
347
|
+
],
|
|
348
|
+
template: (
|
|
349
|
+
options: {
|
|
350
|
+
classes: Record<string, string>;
|
|
351
|
+
scrollY: string;
|
|
352
|
+
paging?: boolean;
|
|
353
|
+
},
|
|
354
|
+
_dom,
|
|
355
|
+
) =>
|
|
356
|
+
`<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>
|
|
365
357
|
<div class='${options.classes.bottom}'>
|
|
366
358
|
${
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
359
|
+
options.paging
|
|
360
|
+
? `<div class='${options.classes.info}'></div>`
|
|
361
|
+
: ""
|
|
370
362
|
}
|
|
371
363
|
<nav class='${options.classes.pagination}'></nav>
|
|
372
364
|
</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
|
-
}
|
|
365
|
+
rowRender: (row, tr, _index) => {
|
|
366
|
+
const id = row.cells[0].data as number;
|
|
367
|
+
const inputNode: VirtualNode = {
|
|
368
|
+
nodeName: "input",
|
|
369
|
+
attributes: {
|
|
370
|
+
type: "checkbox",
|
|
371
|
+
class: "entry-select fw-check",
|
|
372
|
+
"data-id": String(id),
|
|
373
|
+
id: `doc-img-${id}`,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
if (row.cells[1].data) {
|
|
377
|
+
inputNode.attributes!.checked = "checked";
|
|
378
|
+
}
|
|
379
|
+
const trNode = tr as {
|
|
380
|
+
childNodes: { childNodes: VirtualNode[] }[];
|
|
381
|
+
};
|
|
382
|
+
trNode.childNodes[0].childNodes = [
|
|
383
|
+
inputNode,
|
|
384
|
+
{
|
|
385
|
+
nodeName: "label",
|
|
386
|
+
attributes: {
|
|
387
|
+
for: `doc-img-${id}`,
|
|
418
388
|
},
|
|
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
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
},
|
|
392
|
+
onEnter: (row, _event) => {
|
|
393
|
+
if (this.getSelected().length > 0) {
|
|
394
|
+
return;
|
|
455
395
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
396
|
+
if (!this.table) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const rowIndex = this.table.data.data.findIndex(
|
|
400
|
+
(dataRow) =>
|
|
401
|
+
(dataRow as unknown as DataTableRow).cells[0].data ===
|
|
402
|
+
row.cells[0].data,
|
|
403
|
+
);
|
|
404
|
+
const button = this.table.dom.querySelector(
|
|
405
|
+
`tr[data-index="${rowIndex}"] span.edit-image`,
|
|
406
|
+
);
|
|
407
|
+
if (button) {
|
|
408
|
+
(button as HTMLElement).click();
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
onDelete: (row) => {
|
|
412
|
+
const imageId = row.cells[0].data as number;
|
|
413
|
+
this.deleteImageDialog([imageId]);
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
this.overviewTable.init();
|
|
417
|
+
this.table = this.overviewTable.table!;
|
|
418
|
+
(this.table as unknown as { id: string }).id = "imagelist";
|
|
419
|
+
this.dtBulk = this.overviewTable.dtBulk || null;
|
|
420
|
+
|
|
421
|
+
this.table.on("datatable.sort", (column, dir) => {
|
|
422
|
+
this.lastSort = { column: column as number, dir: dir as "asc" | "desc" };
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
this.table.dom.focus();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// get IDs of selected bib entries
|
|
429
|
+
getSelected(): number[] {
|
|
430
|
+
return Array.from(
|
|
431
|
+
this.dom.querySelectorAll(".entry-select:checked:not(:disabled)"),
|
|
432
|
+
).map((el) => Number.parseInt(el.getAttribute("data-id") || "0"));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
bindEvents(): void {
|
|
436
|
+
this.dom.addEventListener("click", (event) => this.handleActivation(event));
|
|
437
|
+
this.dom.addEventListener("keydown", (event) =>
|
|
438
|
+
this.handleActivation(event),
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
handleActivation(event: Event): void {
|
|
443
|
+
if (!isActivationEvent(event)) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const el: { target?: Element | null } = {};
|
|
447
|
+
switch (true) {
|
|
448
|
+
case findTarget(event, ".delete-image", el): {
|
|
449
|
+
const imageId = (el.target as HTMLElement | null)?.dataset.id;
|
|
450
|
+
this.deleteImageDialog([imageId || ""]);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case findTarget(event, ".edit-image", el): {
|
|
454
|
+
const imageId = (el.target as HTMLElement | null)?.dataset.id;
|
|
455
|
+
import("../edit_dialog/index.js").then(({ ImageEditDialog }) => {
|
|
456
|
+
const dialog = new ImageEditDialog(
|
|
457
|
+
this.app.imageDB,
|
|
458
|
+
imageId ? Number.parseInt(imageId) : false,
|
|
459
|
+
this as unknown as ImageManagerPage,
|
|
460
|
+
);
|
|
461
|
+
dialog.init().then(() => {
|
|
462
|
+
if (imageId) {
|
|
463
|
+
this.updateTable([Number.parseInt(imageId)]);
|
|
480
464
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case findTarget(event, ".fw-add-input", el): {
|
|
470
|
+
const itemEl = (el.target as HTMLElement | null)?.closest(
|
|
471
|
+
".fw-list-input",
|
|
472
|
+
) as HTMLElement;
|
|
473
|
+
if (!itemEl.nextElementSibling) {
|
|
474
|
+
itemEl.insertAdjacentHTML(
|
|
475
|
+
"afterend",
|
|
476
|
+
`<tr class="fw-list-input">
|
|
487
477
|
<td>
|
|
488
478
|
<input type="text" class="category-form">
|
|
489
479
|
<span class="fw-add-input icon-addremove" tabindex="0"></span>
|
|
490
480
|
</td>
|
|
491
|
-
</tr
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
break
|
|
497
|
-
}
|
|
498
|
-
default:
|
|
499
|
-
break
|
|
481
|
+
</tr>`,
|
|
482
|
+
);
|
|
483
|
+
} else {
|
|
484
|
+
itemEl.parentElement!.removeChild(itemEl);
|
|
500
485
|
}
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
default:
|
|
489
|
+
break;
|
|
501
490
|
}
|
|
491
|
+
}
|
|
502
492
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
493
|
+
close(): void {
|
|
494
|
+
if (this.table) {
|
|
495
|
+
this.table.destroy();
|
|
496
|
+
this.table = null;
|
|
497
|
+
}
|
|
498
|
+
if (this.dtBulk) {
|
|
499
|
+
this.dtBulk.destroy();
|
|
500
|
+
this.dtBulk = null;
|
|
501
|
+
}
|
|
502
|
+
if (this.menu) {
|
|
503
|
+
this.menu.destroy();
|
|
504
|
+
this.menu = null as unknown as OverviewMenuView;
|
|
516
505
|
}
|
|
506
|
+
}
|
|
517
507
|
}
|