@maplat/ui 0.11.1 → 0.11.3

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.
@@ -7,3 +7,4 @@ export declare function isMaplatSource(source: unknown): source is {
7
7
  };
8
8
  };
9
9
  export declare function isBasemap(source: unknown): boolean;
10
+ export declare function encBytes(bytes: number): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maplat/ui",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "Maplat is the cool Historical Map/Illustrated Map Viewer.\nIt can transform each map coordinates with nonlinear but homeomorphic projection and makes possible that the maps can collaborate with GPS/accurate maps, without distorting original maps.",
5
5
  "type": "module",
6
6
  "main": "dist/maplat-ui.umd.js",
@@ -36,15 +36,13 @@
36
36
  "dependencies": {
37
37
  "@c4h/chuci": "0.2.4",
38
38
  "@c4h/weiwudi": "^0.2.0",
39
- "@maplat/core": "0.12.1",
40
- "@maplat/transform": "^0.4.0",
39
+ "@maplat/core": "0.12.2",
41
40
  "@turf/turf": "^7.3.1",
42
41
  "@types/page": "^1.11.9",
43
42
  "bootstrap.native": "^5.1.6",
44
43
  "page": "^1.11.6",
45
44
  "qrcode": "^1.5.4",
46
45
  "swiper": "^6.8.4",
47
- "tiny-emitter": "^2.1.0",
48
46
  "workbox-core": "^7.4.0",
49
47
  "workbox-expiration": "^7.4.0",
50
48
  "workbox-precaching": "^7.4.0",
@@ -72,25 +70,13 @@
72
70
  "ol": "^10.7.0",
73
71
  "prettier": "^3.7.4",
74
72
  "typescript": "^5.9.3",
75
- "typescript-eslint": "^8.50.1",
73
+ "typescript-eslint": "^8.51.0",
76
74
  "vite": "^6.4.1",
77
75
  "vite-plugin-dts": "^4.5.4",
78
76
  "vitest": "^3.2.4",
79
77
  "workbox-build": "^7.4.0",
80
78
  "workbox-window": "^7.4.0"
81
79
  },
82
- "overrides": {
83
- "@babel/traverse": "7.23.2",
84
- "glob-parent": "5.1.2",
85
- "axios": "1.6.0",
86
- "minimist": "1.2.8",
87
- "glob": "^11.0.0",
88
- "rimraf": "^6.0.0",
89
- "inflight": "npm:inflight-purged@^2.0.0",
90
- "@types/minimatch": "^5.1.2",
91
- "source-map": "^0.7.4",
92
- "sourcemap-codec": "^1.4.8"
93
- },
94
80
  "scripts": {
95
81
  "dev": "concurrently \"pnpm watch:sw\" \"vite --host\"",
96
82
  "build:sw": "esbuild src/service-worker/index.ts --bundle --outfile=dist/service-worker.js --format=iife --define:self.__WB_MANIFEST=[] && node -e \"require('fs').copyFileSync('dist/service-worker.js', 'public/service-worker.js')\"",
package/src/ui_init.ts CHANGED
@@ -21,7 +21,7 @@ import ContextMenu from "./contextmenu";
21
21
  import Weiwudi from "@c4h/weiwudi";
22
22
  import absoluteUrl from "./absolute_url";
23
23
  import * as QRCode from "qrcode";
24
- import { ellips, isBasemap } from "./ui_utils";
24
+ import { ellips, isBasemap, encBytes } from "./ui_utils";
25
25
 
26
26
  import { poiWebControl } from "./ui_marker";
27
27
 
@@ -1117,6 +1117,7 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1117
1117
  });
1118
1118
  }
1119
1119
  });
1120
+ modal.show();
1120
1121
  } else if (control === "copyright") {
1121
1122
  ui.modalSetting("map");
1122
1123
  const mapData = ui.core!.from!;
@@ -1156,6 +1157,207 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1156
1157
  }
1157
1158
  }
1158
1159
  });
1160
+
1161
+ const cacheDiv = modalRoot.querySelector(
1162
+ ".modal_cache_content"
1163
+ ) as HTMLElement;
1164
+ const cacheSize = cacheDiv.querySelector(".cache_size") as HTMLElement;
1165
+ let cacheFetch = cacheDiv.querySelector(
1166
+ ".cache_fetch"
1167
+ ) as HTMLButtonElement;
1168
+ let cacheDelete = cacheDiv.querySelector(
1169
+ ".cache_delete"
1170
+ ) as HTMLButtonElement;
1171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1172
+ const weiwudi = (mapData as any).weiwudi;
1173
+ if (
1174
+ ui.core!.enableCache &&
1175
+ weiwudi &&
1176
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1177
+ !(mapData as any).vector
1178
+ ) {
1179
+ cacheDiv.style.display = "block";
1180
+ cacheFetch.style.display = "none";
1181
+ cacheDelete.style.display = "none";
1182
+ const totalTile = weiwudi.totalTile;
1183
+ let isFetching = false;
1184
+
1185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1186
+ let currentStats: any = undefined;
1187
+
1188
+ const updateButtons = () => {
1189
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1190
+ const coreAny = ui.core as any;
1191
+ const t = coreAny.t
1192
+ ? coreAny.t.bind(ui.core)
1193
+ : ui.core!.translate.bind(ui.core);
1194
+
1195
+ if (totalTile) {
1196
+ cacheFetch.style.display = "inline-block";
1197
+ if (isFetching) {
1198
+ cacheFetch.innerHTML =
1199
+ t("html.cache_cancel") || "Cancel download";
1200
+ cacheFetch.classList.remove("btn-default");
1201
+ cacheFetch.classList.add("btn-danger");
1202
+ if (!cacheFetch.classList.contains("btn-default"))
1203
+ cacheFetch.classList.add("btn-default");
1204
+ cacheFetch.disabled = false;
1205
+ } else {
1206
+ cacheFetch.innerHTML = t("html.cache_fetch") || "Bulk download";
1207
+ if (!cacheFetch.classList.contains("btn-default"))
1208
+ cacheFetch.classList.add("btn-default");
1209
+ cacheFetch.classList.remove("btn-danger");
1210
+
1211
+ // Disable if 100%
1212
+ if (currentStats && currentStats.count === currentStats.total) {
1213
+ cacheFetch.disabled = true;
1214
+ // User requested disabled, not hidden
1215
+ cacheFetch.style.display = "inline-block";
1216
+ } else {
1217
+ cacheFetch.disabled = false;
1218
+ cacheFetch.style.display = "inline-block";
1219
+ }
1220
+ }
1221
+ } else {
1222
+ cacheFetch.style.display = "none";
1223
+ }
1224
+
1225
+ if (currentStats && currentStats.size > 0) {
1226
+ cacheDelete.style.display = "inline-block";
1227
+ // Disable if fetching
1228
+ cacheDelete.disabled = isFetching;
1229
+ } else {
1230
+ // User requested disabled, not hidden
1231
+ cacheDelete.style.display = "inline-block";
1232
+ cacheDelete.disabled = true;
1233
+ }
1234
+ if (isFetching) cacheDelete.disabled = true; // Double ensure
1235
+ };
1236
+
1237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1238
+ const showStats = async (stats: any | undefined = undefined) => {
1239
+ if (!stats) stats = await weiwudi.stats();
1240
+ currentStats = stats;
1241
+
1242
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1243
+ const coreAny = ui.core as any;
1244
+ const t = coreAny.t
1245
+ ? coreAny.t.bind(ui.core)
1246
+ : ui.core!.translate.bind(ui.core);
1247
+
1248
+ const sizeStr = isFetching
1249
+ ? t("html.cache_processing") || "Calculating..."
1250
+ : encBytes(stats.size || 0);
1251
+
1252
+ if (totalTile) {
1253
+ const count = stats.count || 0;
1254
+ const percent = Math.floor((1000 * count) / totalTile);
1255
+ cacheSize.innerText = `${sizeStr} (${
1256
+ count
1257
+ } / ${totalTile} tiles [${percent / 10}%])`;
1258
+ } else {
1259
+ cacheSize.innerText = `${sizeStr} (${stats.count || 0} tiles)`;
1260
+ }
1261
+ updateButtons();
1262
+ };
1263
+ showStats();
1264
+
1265
+ let updateFrame = 0;
1266
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1267
+ let latestStats: any = null;
1268
+
1269
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1270
+ const fetchHandler = (evt: any) => {
1271
+ if (evt.type === "proceed") {
1272
+ isFetching = true;
1273
+ latestStats = {
1274
+ size: evt.detail.size || currentStats?.size || 0,
1275
+ count: evt.detail.processed || evt.detail.count || 0,
1276
+ total: evt.detail.total || totalTile || 0
1277
+ };
1278
+
1279
+ if (!updateFrame) {
1280
+ updateFrame = requestAnimationFrame(() => {
1281
+ updateFrame = 0;
1282
+ try {
1283
+ if (latestStats) showStats(latestStats);
1284
+ } catch (e) {
1285
+ console.error("Error in showStats:", e);
1286
+ }
1287
+ });
1288
+ }
1289
+ } else if (
1290
+ evt.type === "finish" ||
1291
+ evt.type === "stop" ||
1292
+ evt.type === "canceled"
1293
+ ) {
1294
+ if (updateFrame) {
1295
+ cancelAnimationFrame(updateFrame);
1296
+ updateFrame = 0;
1297
+ }
1298
+ isFetching = false;
1299
+ latestStats = null;
1300
+ showStats();
1301
+ }
1302
+ };
1303
+ // Weiwudi might dispatch 'proceed', 'finish', 'stop', 'canceled'
1304
+ weiwudi.addEventListener("proceed", fetchHandler);
1305
+ weiwudi.addEventListener("finish", fetchHandler);
1306
+ weiwudi.addEventListener("stop", fetchHandler);
1307
+ // Check if weiwudi dispatches 'canceled'. Based on my read, it does dispatch e.data.type
1308
+ // And weiwudi_gw_logic sends type: 'canceled'.
1309
+ weiwudi.addEventListener("canceled", fetchHandler);
1310
+
1311
+ const modalEl = modalRoot.querySelector(".modalBase") as HTMLElement;
1312
+ const removeListeners = () => {
1313
+ weiwudi.removeEventListener("proceed", fetchHandler);
1314
+ weiwudi.removeEventListener("finish", fetchHandler);
1315
+ weiwudi.removeEventListener("stop", fetchHandler);
1316
+ weiwudi.removeEventListener("canceled", fetchHandler);
1317
+ modalEl.removeEventListener("hidden.bs.modal", removeListeners);
1318
+ };
1319
+ modalEl.addEventListener("hidden.bs.modal", removeListeners);
1320
+
1321
+ const newElem = cacheFetch.cloneNode(true);
1322
+ cacheFetch.parentNode!.replaceChild(newElem, cacheFetch);
1323
+ // Initial update handled by showStats -> updateButtons
1324
+ cacheFetch = newElem as HTMLButtonElement;
1325
+
1326
+ cacheFetch.addEventListener("click", async () => {
1327
+ if (isFetching) {
1328
+ await weiwudi.cancel();
1329
+ } else {
1330
+ isFetching = true; // Set immediately to update UI
1331
+ updateButtons();
1332
+ try {
1333
+ await weiwudi.fetchAll();
1334
+ } catch {
1335
+ isFetching = false;
1336
+ updateButtons();
1337
+ }
1338
+ }
1339
+ await showStats();
1340
+ });
1341
+
1342
+ const newElem2 = cacheDelete.cloneNode(true);
1343
+ cacheDelete.parentNode!.replaceChild(newElem2, cacheDelete);
1344
+ cacheDelete = newElem2 as HTMLButtonElement;
1345
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1346
+ const t = (ui.core as any).t;
1347
+ cacheDelete.innerHTML =
1348
+ (t ? t.call(ui.core, "html.cache_delete") : undefined) ||
1349
+ ui.core!.translate("html.cache_delete") ||
1350
+ "Clear";
1351
+
1352
+ cacheDelete.addEventListener("click", async () => {
1353
+ if (isFetching) return; // Should be disabled, but safety check
1354
+ await weiwudi.clean();
1355
+ await showStats();
1356
+ });
1357
+ } else {
1358
+ cacheDiv.style.display = "none";
1359
+ }
1360
+
1159
1361
  modal.show();
1160
1362
  } else if (control === "border") {
1161
1363
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
package/src/ui_utils.ts CHANGED
@@ -85,3 +85,14 @@ export function isBasemap(source: unknown): boolean {
85
85
  if (source.constructor && source.constructor.isBasemap_ === true) return true;
86
86
  return true;
87
87
  }
88
+
89
+ export function encBytes(bytes: number): string {
90
+ const suffixes = ["Bytes", "KBytes", "MBytes", "GBytes"];
91
+ let suffix = "Bytes";
92
+ for (let i = 0; i < suffixes.length; i++) {
93
+ suffix = suffixes[i];
94
+ if (bytes < 1000) break;
95
+ bytes = bytes / 1000;
96
+ }
97
+ return `${Math.floor(bytes * 10) / 10} ${suffix}`;
98
+ }