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.js CHANGED
@@ -3762,7 +3762,7 @@ class ModelService {
3762
3762
  */
3763
3763
  async downloadModels(models, styleId, options = {}) {
3764
3764
  const db = await this.db;
3765
- const { onProgress, batchSize = 4, maxRetries = 3, skipExisting = true, timeoutMs = 30000, } = options;
3765
+ const { onProgress, batchSize = 4, maxRetries = 3, skipExisting = true, timeoutMs = 30000, signal, } = options;
3766
3766
  const entries = Object.entries(models);
3767
3767
  const progressTracker = createProgressTracker(entries.length);
3768
3768
  const result = {
@@ -3786,6 +3786,9 @@ class ModelService {
3786
3786
  }
3787
3787
  }
3788
3788
  await processBatch(entries, async ([modelName, url]) => {
3789
+ // Cancelled: let the remaining models go by without fetching them.
3790
+ if (signal?.aborted)
3791
+ return;
3789
3792
  const key = modelKey(styleId, modelName);
3790
3793
  const label = `${styleId}::${modelName}`;
3791
3794
  if (skipExisting && existingKeys.has(key)) {
@@ -4587,7 +4590,7 @@ class SpriteService {
4587
4590
  */
4588
4591
  async downloadSprites(spriteUrls, styleName, options = {}) {
4589
4592
  const db = await this.db;
4590
- const { onProgress, batchSize = 10, maxRetries = 3, skipExisting = true, timeoutMs = 15000, storageQuotaCheck = true, prioritySprites = [], bandwidthLimit, enableValidation = true, namePrefix, } = options;
4593
+ const { onProgress, batchSize = 10, maxRetries = 3, skipExisting = true, timeoutMs = 15000, storageQuotaCheck = true, prioritySprites = [], bandwidthLimit, enableValidation = true, namePrefix, signal, } = options;
4591
4594
  const startTime = Date.now();
4592
4595
  let totalSize = 0;
4593
4596
  let downloadedSprites = 0;
@@ -4659,6 +4662,9 @@ class SpriteService {
4659
4662
  spriteLogger.debug(` ${url} -> ${key}`);
4660
4663
  });
4661
4664
  await processBatch(urlsToDownload, async (spriteUrl) => {
4665
+ // Cancelled: let the remaining sprites go by without fetching them.
4666
+ if (signal?.aborted)
4667
+ return;
4662
4668
  try {
4663
4669
  const spriteName = namePrefix
4664
4670
  ? this.buildSpriteName(spriteUrl, namePrefix)
@@ -6462,7 +6468,8 @@ class RegionService {
6462
6468
  await (await dbPromise).put('styles', styleEntry);
6463
6469
  // 2. Sprites
6464
6470
  const spriteResults = [];
6465
- if (!skipSprites) {
6471
+ // After a cancel, later phases don't start; the tile step then cleans up.
6472
+ if (!skipSprites && !signal?.aborted) {
6466
6473
  const spriteSources = normalizeSpriteProperty(originalSpriteUrl);
6467
6474
  if (spriteSources.length > 0) {
6468
6475
  const { downloadSprites } = await Promise.resolve().then(function () { return spriteService$1; });
@@ -6504,6 +6511,7 @@ class RegionService {
6504
6511
  });
6505
6512
  try {
6506
6513
  const result = await downloadSprites(spriteUrls, styleId, {
6514
+ signal,
6507
6515
  enableValidation: true,
6508
6516
  skipExisting: false,
6509
6517
  namePrefix: source.id !== 'sprite' ? source.id : undefined,
@@ -6523,7 +6531,10 @@ class RegionService {
6523
6531
  }
6524
6532
  // 3. Glyphs
6525
6533
  let glyphResult;
6526
- if (!skipGlyphs && originalGlyphsUrl && !originalGlyphsUrl.startsWith('idb://')) {
6534
+ if (!skipGlyphs &&
6535
+ !signal?.aborted &&
6536
+ originalGlyphsUrl &&
6537
+ !originalGlyphsUrl.startsWith('idb://')) {
6527
6538
  const fontFamilies = new Set(extractAllFontNames(storedStyle));
6528
6539
  if (fontFamilies.size > 0) {
6529
6540
  const { downloadGlyphs } = await Promise.resolve().then(function () { return glyphService$1; });
@@ -6537,6 +6548,7 @@ class RegionService {
6537
6548
  // jump when the real total came in).
6538
6549
  try {
6539
6550
  glyphResult = await downloadGlyphs(glyphsUrl, Array.from(fontFamilies), styleId, ranges, {
6551
+ signal,
6540
6552
  onProgress: (progress) => {
6541
6553
  emit('glyphs', progress.completed, progress.total, 'Downloading glyphs');
6542
6554
  },
@@ -6551,7 +6563,7 @@ class RegionService {
6551
6563
  // turbine .glb assets. Two value shapes exist in the wild
6552
6564
  // (plain string or `{ uri }`) — we accept both.
6553
6565
  let modelResult;
6554
- if (!skipModels && storedStyle.models) {
6566
+ if (!skipModels && !signal?.aborted && storedStyle.models) {
6555
6567
  // Upstream URLs, from before patching, so models that failed last time are retried.
6556
6568
  const rawModels = (styleEntry.originalModels ?? storedStyle.models);
6557
6569
  const alreadyStored = await storedModelNames(styleId);
@@ -6574,6 +6586,7 @@ class RegionService {
6574
6586
  emit('models', 0, Object.keys(resolved).length, 'Downloading 3D models');
6575
6587
  try {
6576
6588
  modelResult = await downloadModels(resolved, styleId, {
6589
+ signal,
6577
6590
  onProgress: (progress) => {
6578
6591
  emit('models', progress.completed, progress.total, 'Downloading 3D models');
6579
6592
  },
@@ -6587,7 +6600,8 @@ class RegionService {
6587
6600
  // Renderer code fetched from api.mapbox.com at render time (3D decoders,
6588
6601
  // RTL text, procedural buildings). Never throws; see cacheMapboxRuntimeAssets.
6589
6602
  let runtimeAssetsCached;
6590
- if (/mapbox:\/\/|api\.mapbox\.com/.test(`${region.styleUrl ?? ''} ${originalGlyphsUrl ?? ''}`)) {
6603
+ if (!signal?.aborted &&
6604
+ /mapbox:\/\/|api\.mapbox\.com/.test(`${region.styleUrl ?? ''} ${originalGlyphsUrl ?? ''}`)) {
6591
6605
  const { cacheMapboxRuntimeAssets } = await Promise.resolve().then(function () { return runtimeAssetService; });
6592
6606
  runtimeAssetsCached = await cacheMapboxRuntimeAssets();
6593
6607
  }
@@ -8223,7 +8237,7 @@ class GlyphService {
8223
8237
  db = dbPromise;
8224
8238
  async downloadGlyphs(glyphUrl, fontstacks, styleName, ranges = ['0-255'], options = {}) {
8225
8239
  const db = await this.db;
8226
- const { maxConcurrency = 5, retries = 3, timeout = 10000, onProgress, enableValidation = true, priorityFonts = [], } = options;
8240
+ const { maxConcurrency = 5, retries = 3, timeout = 10000, onProgress, enableValidation = true, priorityFonts = [], signal, } = options;
8227
8241
  const startTime = Date.now();
8228
8242
  let totalSize = 0;
8229
8243
  let downloadedGlyphs = 0;
@@ -8255,7 +8269,8 @@ class GlyphService {
8255
8269
  // Process glyphs with concurrency control
8256
8270
  const semaphore = new Array(maxConcurrency).fill(0);
8257
8271
  const promises = semaphore.map(async () => {
8258
- while (glyphTasks.length > 0) {
8272
+ // Once cancelled, workers stop taking tasks; those in flight finish.
8273
+ while (glyphTasks.length > 0 && !signal?.aborted) {
8259
8274
  const task = glyphTasks.shift();
8260
8275
  if (!task)
8261
8276
  break;
@@ -10062,6 +10077,7 @@ const en = {
10062
10077
  'download.status.models': '3D models: {{completed}} of {{total}}',
10063
10078
  'download.status.tiles': 'Tiles: {{completed}} of {{total}}',
10064
10079
  'download.status.planning': 'Planning tiles',
10080
+ 'download.cancelRegion': 'Cancel downloading {{name}}',
10065
10081
  'download.status.saving': 'Saving region',
10066
10082
  // Panel Manager additional strings
10067
10083
  'panel.downloadedRegions': 'Downloaded Regions',
@@ -10284,6 +10300,7 @@ const ar = {
10284
10300
  'download.status.models': 'النماذج ثلاثية الأبعاد: {{completed}} من {{total}}',
10285
10301
  'download.status.tiles': 'البلاطات: {{completed}} من {{total}}',
10286
10302
  'download.status.planning': 'جارٍ تخطيط البلاطات',
10303
+ 'download.cancelRegion': 'إلغاء تحميل {{name}}',
10287
10304
  'download.status.saving': 'جارٍ حفظ المنطقة',
10288
10305
  // Panel Manager additional strings - سلاسل إضافية للوحة
10289
10306
  'panel.downloadedRegions': 'المناطق المحملة',
@@ -12025,6 +12042,15 @@ class PanelRenderer extends BaseComponent {
12025
12042
  const downloads = this.downloadManager.getCurrentDownloads();
12026
12043
  const progressHTML = this.createDownloadProgressHTML(downloads);
12027
12044
  this.downloadProgressContainer.innerHTML = progressHTML;
12045
+ // The rows are redrawn on every update, so listen on the container.
12046
+ this.downloadProgressContainer.addEventListener('click', event => {
12047
+ const button = event.target.closest('[data-action="cancel-download"]');
12048
+ const regionId = button?.dataset.regionId;
12049
+ if (!regionId)
12050
+ return;
12051
+ this.downloadManager.cancelDownload(regionId);
12052
+ void this.refresh();
12053
+ });
12028
12054
  this.element.appendChild(this.downloadProgressContainer);
12029
12055
  }
12030
12056
  }
@@ -12398,6 +12424,7 @@ class PanelRenderer extends BaseComponent {
12398
12424
  <div class="h-full bg-blue-600" style="width: ${download.percentage || 0}%"></div>
12399
12425
  </div>
12400
12426
  <span class="text-blue-700 dark:text-blue-300 min-w-12">${Math.round(download.percentage || 0)}%</span>
12427
+ <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>
12401
12428
  </div>
12402
12429
  </div>
12403
12430
  `)