map-gl-offline 0.9.0 → 0.9.1

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/index.esm.js CHANGED
@@ -3739,7 +3739,7 @@ class ModelService {
3739
3739
  */
3740
3740
  async downloadModels(models, styleId, options = {}) {
3741
3741
  const db = await this.db;
3742
- const { onProgress, batchSize = 4, maxRetries = 3, skipExisting = true, timeoutMs = 30000, } = options;
3742
+ const { onProgress, batchSize = 4, maxRetries = 3, skipExisting = true, timeoutMs = 30000, signal, } = options;
3743
3743
  const entries = Object.entries(models);
3744
3744
  const progressTracker = createProgressTracker(entries.length);
3745
3745
  const result = {
@@ -3763,6 +3763,9 @@ class ModelService {
3763
3763
  }
3764
3764
  }
3765
3765
  await processBatch(entries, async ([modelName, url]) => {
3766
+ // Cancelled: let the remaining models go by without fetching them.
3767
+ if (signal?.aborted)
3768
+ return;
3766
3769
  const key = modelKey(styleId, modelName);
3767
3770
  const label = `${styleId}::${modelName}`;
3768
3771
  if (skipExisting && existingKeys.has(key)) {
@@ -4564,7 +4567,7 @@ class SpriteService {
4564
4567
  */
4565
4568
  async downloadSprites(spriteUrls, styleName, options = {}) {
4566
4569
  const db = await this.db;
4567
- const { onProgress, batchSize = 10, maxRetries = 3, skipExisting = true, timeoutMs = 15000, storageQuotaCheck = true, prioritySprites = [], bandwidthLimit, enableValidation = true, namePrefix, } = options;
4570
+ const { onProgress, batchSize = 10, maxRetries = 3, skipExisting = true, timeoutMs = 15000, storageQuotaCheck = true, prioritySprites = [], bandwidthLimit, enableValidation = true, namePrefix, signal, } = options;
4568
4571
  const startTime = Date.now();
4569
4572
  let totalSize = 0;
4570
4573
  let downloadedSprites = 0;
@@ -4636,6 +4639,9 @@ class SpriteService {
4636
4639
  spriteLogger.debug(` ${url} -> ${key}`);
4637
4640
  });
4638
4641
  await processBatch(urlsToDownload, async (spriteUrl) => {
4642
+ // Cancelled: let the remaining sprites go by without fetching them.
4643
+ if (signal?.aborted)
4644
+ return;
4639
4645
  try {
4640
4646
  const spriteName = namePrefix
4641
4647
  ? this.buildSpriteName(spriteUrl, namePrefix)
@@ -6439,7 +6445,8 @@ class RegionService {
6439
6445
  await (await dbPromise).put('styles', styleEntry);
6440
6446
  // 2. Sprites
6441
6447
  const spriteResults = [];
6442
- if (!skipSprites) {
6448
+ // After a cancel, later phases don't start; the tile step then cleans up.
6449
+ if (!skipSprites && !signal?.aborted) {
6443
6450
  const spriteSources = normalizeSpriteProperty(originalSpriteUrl);
6444
6451
  if (spriteSources.length > 0) {
6445
6452
  const { downloadSprites } = await Promise.resolve().then(function () { return spriteService$1; });
@@ -6481,6 +6488,7 @@ class RegionService {
6481
6488
  });
6482
6489
  try {
6483
6490
  const result = await downloadSprites(spriteUrls, styleId, {
6491
+ signal,
6484
6492
  enableValidation: true,
6485
6493
  skipExisting: false,
6486
6494
  namePrefix: source.id !== 'sprite' ? source.id : undefined,
@@ -6500,7 +6508,10 @@ class RegionService {
6500
6508
  }
6501
6509
  // 3. Glyphs
6502
6510
  let glyphResult;
6503
- if (!skipGlyphs && originalGlyphsUrl && !originalGlyphsUrl.startsWith('idb://')) {
6511
+ if (!skipGlyphs &&
6512
+ !signal?.aborted &&
6513
+ originalGlyphsUrl &&
6514
+ !originalGlyphsUrl.startsWith('idb://')) {
6504
6515
  const fontFamilies = new Set(extractAllFontNames(storedStyle));
6505
6516
  if (fontFamilies.size > 0) {
6506
6517
  const { downloadGlyphs } = await Promise.resolve().then(function () { return glyphService$1; });
@@ -6514,6 +6525,7 @@ class RegionService {
6514
6525
  // jump when the real total came in).
6515
6526
  try {
6516
6527
  glyphResult = await downloadGlyphs(glyphsUrl, Array.from(fontFamilies), styleId, ranges, {
6528
+ signal,
6517
6529
  onProgress: (progress) => {
6518
6530
  emit('glyphs', progress.completed, progress.total, 'Downloading glyphs');
6519
6531
  },
@@ -6528,7 +6540,7 @@ class RegionService {
6528
6540
  // turbine .glb assets. Two value shapes exist in the wild
6529
6541
  // (plain string or `{ uri }`) — we accept both.
6530
6542
  let modelResult;
6531
- if (!skipModels && storedStyle.models) {
6543
+ if (!skipModels && !signal?.aborted && storedStyle.models) {
6532
6544
  // Upstream URLs, from before patching, so models that failed last time are retried.
6533
6545
  const rawModels = (styleEntry.originalModels ?? storedStyle.models);
6534
6546
  const alreadyStored = await storedModelNames(styleId);
@@ -6551,6 +6563,7 @@ class RegionService {
6551
6563
  emit('models', 0, Object.keys(resolved).length, 'Downloading 3D models');
6552
6564
  try {
6553
6565
  modelResult = await downloadModels(resolved, styleId, {
6566
+ signal,
6554
6567
  onProgress: (progress) => {
6555
6568
  emit('models', progress.completed, progress.total, 'Downloading 3D models');
6556
6569
  },
@@ -6564,7 +6577,8 @@ class RegionService {
6564
6577
  // Renderer code fetched from api.mapbox.com at render time (3D decoders,
6565
6578
  // RTL text, procedural buildings). Never throws; see cacheMapboxRuntimeAssets.
6566
6579
  let runtimeAssetsCached;
6567
- if (/mapbox:\/\/|api\.mapbox\.com/.test(`${region.styleUrl ?? ''} ${originalGlyphsUrl ?? ''}`)) {
6580
+ if (!signal?.aborted &&
6581
+ /mapbox:\/\/|api\.mapbox\.com/.test(`${region.styleUrl ?? ''} ${originalGlyphsUrl ?? ''}`)) {
6568
6582
  const { cacheMapboxRuntimeAssets } = await Promise.resolve().then(function () { return runtimeAssetService; });
6569
6583
  runtimeAssetsCached = await cacheMapboxRuntimeAssets();
6570
6584
  }
@@ -8200,7 +8214,7 @@ class GlyphService {
8200
8214
  db = dbPromise;
8201
8215
  async downloadGlyphs(glyphUrl, fontstacks, styleName, ranges = ['0-255'], options = {}) {
8202
8216
  const db = await this.db;
8203
- const { maxConcurrency = 5, retries = 3, timeout = 10000, onProgress, enableValidation = true, priorityFonts = [], } = options;
8217
+ const { maxConcurrency = 5, retries = 3, timeout = 10000, onProgress, enableValidation = true, priorityFonts = [], signal, } = options;
8204
8218
  const startTime = Date.now();
8205
8219
  let totalSize = 0;
8206
8220
  let downloadedGlyphs = 0;
@@ -8232,7 +8246,8 @@ class GlyphService {
8232
8246
  // Process glyphs with concurrency control
8233
8247
  const semaphore = new Array(maxConcurrency).fill(0);
8234
8248
  const promises = semaphore.map(async () => {
8235
- while (glyphTasks.length > 0) {
8249
+ // Once cancelled, workers stop taking tasks; those in flight finish.
8250
+ while (glyphTasks.length > 0 && !signal?.aborted) {
8236
8251
  const task = glyphTasks.shift();
8237
8252
  if (!task)
8238
8253
  break;
@@ -10039,6 +10054,7 @@ const en = {
10039
10054
  'download.status.models': '3D models: {{completed}} of {{total}}',
10040
10055
  'download.status.tiles': 'Tiles: {{completed}} of {{total}}',
10041
10056
  'download.status.planning': 'Planning tiles',
10057
+ 'download.cancelRegion': 'Cancel downloading {{name}}',
10042
10058
  'download.status.saving': 'Saving region',
10043
10059
  // Panel Manager additional strings
10044
10060
  'panel.downloadedRegions': 'Downloaded Regions',
@@ -10261,6 +10277,7 @@ const ar = {
10261
10277
  'download.status.models': 'النماذج ثلاثية الأبعاد: {{completed}} من {{total}}',
10262
10278
  'download.status.tiles': 'البلاطات: {{completed}} من {{total}}',
10263
10279
  'download.status.planning': 'جارٍ تخطيط البلاطات',
10280
+ 'download.cancelRegion': 'إلغاء تحميل {{name}}',
10264
10281
  'download.status.saving': 'جارٍ حفظ المنطقة',
10265
10282
  // Panel Manager additional strings - سلاسل إضافية للوحة
10266
10283
  'panel.downloadedRegions': 'المناطق المحملة',
@@ -12002,6 +12019,15 @@ class PanelRenderer extends BaseComponent {
12002
12019
  const downloads = this.downloadManager.getCurrentDownloads();
12003
12020
  const progressHTML = this.createDownloadProgressHTML(downloads);
12004
12021
  this.downloadProgressContainer.innerHTML = progressHTML;
12022
+ // The rows are redrawn on every update, so listen on the container.
12023
+ this.downloadProgressContainer.addEventListener('click', event => {
12024
+ const button = event.target.closest('[data-action="cancel-download"]');
12025
+ const regionId = button?.dataset.regionId;
12026
+ if (!regionId)
12027
+ return;
12028
+ this.downloadManager.cancelDownload(regionId);
12029
+ void this.refresh();
12030
+ });
12005
12031
  this.element.appendChild(this.downloadProgressContainer);
12006
12032
  }
12007
12033
  }
@@ -12375,6 +12401,7 @@ class PanelRenderer extends BaseComponent {
12375
12401
  <div class="h-full bg-blue-600" style="width: ${download.percentage || 0}%"></div>
12376
12402
  </div>
12377
12403
  <span class="text-blue-700 dark:text-blue-300 min-w-12">${Math.round(download.percentage || 0)}%</span>
12404
+ <button type="button" class="px-2 py-1 rounded-md text-xs font-medium cursor-pointer text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600" data-action="cancel-download" data-region-id="${escapeHtml$1(download.regionId)}" aria-label="${t('download.cancelRegion', { name: String(download.regionName || download.regionId) })}">${t('app.cancel')}</button>
12378
12405
  </div>
12379
12406
  </div>
12380
12407
  `)