@walkthru-earth/objex 1.4.0 → 1.5.0
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 +9 -9
- package/dist/components/layout/ConnectionDialog.svelte +7 -2
- package/dist/components/layout/SettingsSheet.svelte +2 -1
- package/dist/components/layout/StatusBar.svelte +16 -13
- package/dist/components/layout/TabBar.svelte +2 -2
- package/dist/components/viewers/ArchiveViewer.svelte +139 -112
- package/dist/components/viewers/CodeViewer.svelte +15 -27
- package/dist/components/viewers/CodeViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/CogViewer.svelte +8 -6
- package/dist/components/viewers/CopcViewer.svelte +8 -15
- package/dist/components/viewers/DatabaseViewer.svelte +22 -21
- package/dist/components/viewers/FileInfo.svelte +16 -16
- package/dist/components/viewers/FlatGeobufViewer.svelte +15 -45
- package/dist/components/viewers/GeoParquetMapViewer.svelte +5 -3
- package/dist/components/viewers/ImageViewer.svelte +10 -12
- package/dist/components/viewers/LoadProgress.svelte +6 -6
- package/dist/components/viewers/MarkdownViewer.svelte +17 -21
- package/dist/components/viewers/MediaViewer.svelte +11 -12
- package/dist/components/viewers/ModelViewer.svelte +17 -20
- package/dist/components/viewers/MultiCogViewer.svelte +11 -8
- package/dist/components/viewers/NotebookViewer.svelte +22 -26
- package/dist/components/viewers/PdfViewer.svelte +22 -31
- package/dist/components/viewers/PmtilesViewer.svelte +10 -9
- package/dist/components/viewers/QueryHistoryPanel.svelte +18 -18
- package/dist/components/viewers/RawViewer.svelte +21 -18
- package/dist/components/viewers/StacMapViewer.svelte +6 -13
- package/dist/components/viewers/StacMosaicViewer.svelte +9 -7
- package/dist/components/viewers/StacTabViewer.svelte +2 -2
- package/dist/components/viewers/TableGrid.svelte +34 -30
- package/dist/components/viewers/TableStatusBar.svelte +6 -6
- package/dist/components/viewers/TableToolbar.svelte +9 -8
- package/dist/components/viewers/TableViewer.svelte +22 -13
- package/dist/components/viewers/ViewerHeader.svelte +18 -0
- package/dist/components/viewers/ViewerHeader.svelte.d.ts +10 -0
- package/dist/components/viewers/ViewerStatus.svelte +19 -0
- package/dist/components/viewers/ViewerStatus.svelte.d.ts +7 -0
- package/dist/components/viewers/ZarrMapViewer.svelte +13 -12
- package/dist/components/viewers/ZarrViewer.svelte +94 -61
- package/dist/components/viewers/map/AttributeTable.svelte +6 -6
- package/dist/components/viewers/map/MapContainer.svelte +2 -2
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +109 -83
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +16 -16
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +8 -0
- package/dist/i18n/ar.js +3 -0
- package/dist/i18n/en.js +3 -0
- package/dist/query/stac-source-parquet.js +8 -5
- package/dist/query/wasm.js +6 -63
- package/dist/storage/presign.js +2 -1
- package/dist/storage/providers.js +2 -1
- package/dist/stores/settings.svelte.js +3 -3
- package/dist/utils/deck.d.ts +2 -0
- package/dist/utils/deck.js +5 -3
- package/dist/utils/media-query.svelte.d.ts +14 -0
- package/dist/utils/media-query.svelte.js +29 -0
- package/dist/utils/signed-url-effect.d.ts +7 -0
- package/dist/utils/signed-url-effect.js +19 -0
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
|
3
3
|
import XIcon from '@lucide/svelte/icons/x';
|
|
4
|
-
import { formatFileSize } from '@walkthru-earth/objex-utils';
|
|
4
|
+
import { formatFileSize, handleLoadError } from '@walkthru-earth/objex-utils';
|
|
5
5
|
import type { PMTiles } from 'pmtiles';
|
|
6
6
|
import { onDestroy } from 'svelte';
|
|
7
7
|
import { t } from '../../../i18n/index.svelte.js';
|
|
@@ -121,7 +121,7 @@ async function fetchTile() {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
} catch (e) {
|
|
124
|
-
error =
|
|
124
|
+
error = handleLoadError(e);
|
|
125
125
|
} finally {
|
|
126
126
|
loading = false;
|
|
127
127
|
}
|
|
@@ -180,7 +180,7 @@ function formatValue(v: unknown): string {
|
|
|
180
180
|
<div class="flex h-full flex-col overflow-hidden">
|
|
181
181
|
<!-- Navigation bar -->
|
|
182
182
|
<div
|
|
183
|
-
class="flex shrink-0 flex-wrap items-center gap-2 border-b border-
|
|
183
|
+
class="flex shrink-0 flex-wrap items-center gap-2 border-b border-border px-3 py-2"
|
|
184
184
|
>
|
|
185
185
|
<!-- Z/X/Y inputs -->
|
|
186
186
|
<div class="flex items-center gap-1 text-xs">
|
|
@@ -190,7 +190,7 @@ function formatValue(v: unknown): string {
|
|
|
190
190
|
bind:value={inputZ}
|
|
191
191
|
min={0}
|
|
192
192
|
max={30}
|
|
193
|
-
class="w-12 rounded border border-
|
|
193
|
+
class="w-12 rounded border border-border bg-transparent px-1.5 py-0.5 text-center font-mono text-xs"
|
|
194
194
|
onkeydown={handleKeydown}
|
|
195
195
|
/>
|
|
196
196
|
<span class="text-muted-foreground">x</span>
|
|
@@ -198,7 +198,7 @@ function formatValue(v: unknown): string {
|
|
|
198
198
|
type="number"
|
|
199
199
|
bind:value={inputX}
|
|
200
200
|
min={0}
|
|
201
|
-
class="w-16 rounded border border-
|
|
201
|
+
class="w-16 rounded border border-border bg-transparent px-1.5 py-0.5 text-center font-mono text-xs"
|
|
202
202
|
onkeydown={handleKeydown}
|
|
203
203
|
/>
|
|
204
204
|
<span class="text-muted-foreground">y</span>
|
|
@@ -206,7 +206,7 @@ function formatValue(v: unknown): string {
|
|
|
206
206
|
type="number"
|
|
207
207
|
bind:value={inputY}
|
|
208
208
|
min={0}
|
|
209
|
-
class="w-16 rounded border border-
|
|
209
|
+
class="w-16 rounded border border-border bg-transparent px-1.5 py-0.5 text-center font-mono text-xs"
|
|
210
210
|
onkeydown={handleKeydown}
|
|
211
211
|
/>
|
|
212
212
|
</div>
|
|
@@ -253,13 +253,13 @@ function formatValue(v: unknown): string {
|
|
|
253
253
|
</div>
|
|
254
254
|
|
|
255
255
|
<!-- Main content -->
|
|
256
|
-
<div class="flex min-h-0 flex-1 overflow-hidden">
|
|
256
|
+
<div class="flex min-h-0 flex-1 flex-col overflow-hidden sm:flex-row">
|
|
257
257
|
{#if loading}
|
|
258
258
|
<div class="flex flex-1 items-center justify-center text-xs text-muted-foreground">
|
|
259
259
|
Loading tile...
|
|
260
260
|
</div>
|
|
261
261
|
{:else if error}
|
|
262
|
-
<div class="flex flex-1 items-center justify-center text-xs text-
|
|
262
|
+
<div class="flex flex-1 items-center justify-center text-xs text-destructive">
|
|
263
263
|
{error}
|
|
264
264
|
</div>
|
|
265
265
|
{:else if tile}
|
|
@@ -312,16 +312,16 @@ function formatValue(v: unknown): string {
|
|
|
312
312
|
|
|
313
313
|
<!-- Feature properties panel -->
|
|
314
314
|
<div
|
|
315
|
-
class="flex w-56 shrink-0
|
|
315
|
+
class="flex w-full flex-col border-t border-border sm:w-56 sm:shrink-0 sm:border-s sm:border-t-0 lg:w-64"
|
|
316
316
|
>
|
|
317
317
|
<div
|
|
318
|
-
class="shrink-0 border-b border-
|
|
318
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
319
319
|
>
|
|
320
320
|
{t('pmtiles.featureProperties')}
|
|
321
321
|
</div>
|
|
322
322
|
{#if selectedFeature && selectedLayerName !== null}
|
|
323
323
|
<div class="flex-1 overflow-auto">
|
|
324
|
-
<div class="border-b border-
|
|
324
|
+
<div class="border-b border-border px-3 py-2">
|
|
325
325
|
<div class="flex items-center gap-1.5 text-xs">
|
|
326
326
|
<span
|
|
327
327
|
class="inline-block size-2 rounded-sm"
|
|
@@ -337,14 +337,14 @@ function formatValue(v: unknown): string {
|
|
|
337
337
|
· #{selectedFeatureIdx}
|
|
338
338
|
</div>
|
|
339
339
|
</div>
|
|
340
|
-
<div class="divide-y divide-
|
|
340
|
+
<div class="divide-y divide-border">
|
|
341
341
|
{#each Object.entries(selectedFeature.properties) as [key, value]}
|
|
342
342
|
<div class="px-3 py-1.5">
|
|
343
|
-
<div class="text-[10px] font-medium text-
|
|
343
|
+
<div class="text-[10px] font-medium text-muted-foreground">
|
|
344
344
|
{key}
|
|
345
345
|
</div>
|
|
346
346
|
<div
|
|
347
|
-
class="break-all text-xs text-
|
|
347
|
+
class="break-all text-xs text-foreground"
|
|
348
348
|
title={formatValue(value)}
|
|
349
349
|
>
|
|
350
350
|
{formatValue(value)}
|
|
@@ -402,10 +402,10 @@ function formatValue(v: unknown): string {
|
|
|
402
402
|
|
|
403
403
|
<!-- Raster tile info panel -->
|
|
404
404
|
<div
|
|
405
|
-
class="flex w-56 shrink-0
|
|
405
|
+
class="flex w-full flex-col border-t border-border sm:w-56 sm:shrink-0 sm:border-s sm:border-t-0 lg:w-64"
|
|
406
406
|
>
|
|
407
407
|
<div
|
|
408
|
-
class="shrink-0 border-b border-
|
|
408
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
409
409
|
>
|
|
410
410
|
{t('pmtiles.tileInfo')}
|
|
411
411
|
</div>
|
package/dist/constants.d.ts
CHANGED
|
@@ -30,3 +30,9 @@ export declare const VIEWER_DIR_EXTENSIONS: Set<string>;
|
|
|
30
30
|
export declare const LAYER_HUE_MULTIPLIER = 137;
|
|
31
31
|
/** Duration (ms) to show "Copied!" feedback before resetting. */
|
|
32
32
|
export declare const COPY_FEEDBACK_MS = 2000;
|
|
33
|
+
/** Region assumed when a connection or bucket name yields none. AWS's global default. */
|
|
34
|
+
export declare const DEFAULT_AWS_REGION = "us-east-1";
|
|
35
|
+
/** deck.gl tile-layer debounce (ms) before fetching after a viewport change. */
|
|
36
|
+
export declare const TILE_DEBOUNCE_MS = 200;
|
|
37
|
+
/** Zoom level used when flying to the first feature of a vector dataset. */
|
|
38
|
+
export declare const FIRST_FEATURE_FLY_ZOOM = 14;
|
package/dist/constants.js
CHANGED
|
@@ -36,3 +36,11 @@ export const LAYER_HUE_MULTIPLIER = 137;
|
|
|
36
36
|
// ── Clipboard ──
|
|
37
37
|
/** Duration (ms) to show "Copied!" feedback before resetting. */
|
|
38
38
|
export const COPY_FEEDBACK_MS = 2000;
|
|
39
|
+
// ── AWS defaults ──
|
|
40
|
+
/** Region assumed when a connection or bucket name yields none. AWS's global default. */
|
|
41
|
+
export const DEFAULT_AWS_REGION = 'us-east-1';
|
|
42
|
+
// ── Map / tiles ──
|
|
43
|
+
/** deck.gl tile-layer debounce (ms) before fetching after a viewport change. */
|
|
44
|
+
export const TILE_DEBOUNCE_MS = 200;
|
|
45
|
+
/** Zoom level used when flying to the first feature of a vector dataset. */
|
|
46
|
+
export const FIRST_FEATURE_FLY_ZOOM = 14;
|
package/dist/i18n/ar.js
CHANGED
package/dist/i18n/en.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* Yields a single batch with `done: true`. Slice 3 turns this into a real
|
|
28
28
|
* stream via `conn.send()` so large catalogs can render progressively.
|
|
29
29
|
*/
|
|
30
|
-
import { emptyPushdown, parseWKB, stacRowToItem } from '@walkthru-earth/objex-utils';
|
|
30
|
+
import { DEFAULT_APP_CONFIG, emptyPushdown, parseWKB, stacRowToItem } from '@walkthru-earth/objex-utils';
|
|
31
31
|
import { QueryCancelledError } from './engine.js';
|
|
32
32
|
import { getQueryEngine } from './index.js';
|
|
33
33
|
import { resolveTableSourceAsync } from './source.js';
|
|
@@ -36,14 +36,17 @@ import { resolveTableSourceAsync } from './source.js';
|
|
|
36
36
|
* iOS Safari caps the WASM heap at ~1.8 GiB and rarely engages OPFS spill
|
|
37
37
|
* (`credentialless` COEP only landed in 17.6), so STRUCT-heavy stac-geoparquet
|
|
38
38
|
* scans OOM during the parquet decode before any rows reach the consumer.
|
|
39
|
+
* Evaluated per source construction so a device that rotates or resizes re-checks.
|
|
40
|
+
* Desktop browsers are never classified low-memory regardless of window size.
|
|
39
41
|
*/
|
|
40
42
|
function detectLowMemoryDefault() {
|
|
41
43
|
if (typeof navigator === 'undefined')
|
|
42
44
|
return false;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
const isMobileUa = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
46
|
+
if (!isMobileUa)
|
|
47
|
+
return false; // desktop is never low-memory, regardless of window size
|
|
45
48
|
if (typeof window === 'undefined')
|
|
46
|
-
return
|
|
49
|
+
return true;
|
|
47
50
|
return Math.min(window.innerWidth, window.innerHeight) <= 820;
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
@@ -108,7 +111,7 @@ function joinWhere(parts) {
|
|
|
108
111
|
const live = parts.filter((p) => p !== null && p.length > 0);
|
|
109
112
|
return live.length === 0 ? '' : ` WHERE ${live.join(' AND ')}`;
|
|
110
113
|
}
|
|
111
|
-
const DEFAULT_LIMIT =
|
|
114
|
+
const DEFAULT_LIMIT = DEFAULT_APP_CONFIG.defaults.mosaicItemLimit;
|
|
112
115
|
/**
|
|
113
116
|
* Build the SELECT list. All columns are optional in the stac-geoparquet
|
|
114
117
|
* spec, so we only project what we know we'll use and the spec requires.
|
package/dist/query/wasm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildTransformExpr, wrapWkbWithCrs } from '@walkthru-earth/objex-utils';
|
|
2
|
-
import { DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, WGS84_CODES } from '../constants.js';
|
|
2
|
+
import { DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, STORAGE_KEYS, WGS84_CODES } from '../constants.js';
|
|
3
3
|
import { getAccessMode, resolveProviderEndpoint } from '../storage/providers.js';
|
|
4
4
|
import { credentialStore } from '../stores/credentials.svelte.js';
|
|
5
5
|
import { QueryCancelledError } from './engine';
|
|
@@ -579,67 +579,10 @@ function isBinaryType(typeStr) {
|
|
|
579
579
|
}
|
|
580
580
|
export class WasmQueryEngine {
|
|
581
581
|
async query(connId, sql) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const conn = await db.connect();
|
|
587
|
-
const tConn = performance.now();
|
|
588
|
-
log(`query → connected in ${elapsed(t0)}`);
|
|
589
|
-
try {
|
|
590
|
-
if (connId) {
|
|
591
|
-
await this.configureStorage(conn, connId, sql);
|
|
592
|
-
log(`query → storage configured in ${elapsed(tConn)}`);
|
|
593
|
-
}
|
|
594
|
-
const tQuery = performance.now();
|
|
595
|
-
const result = await conn.query(sql);
|
|
596
|
-
log(`query → executed in ${elapsed(tQuery)}, rows: ${result.numRows}`);
|
|
597
|
-
// DuckDB WASM returns an Arrow Table (bundled apache-arrow@17).
|
|
598
|
-
// Our project uses apache-arrow@21 — cross-version tableToIPC/tableFromIPC
|
|
599
|
-
// loses data rows. Extract rows directly from DuckDB's own Arrow Table.
|
|
600
|
-
const numRows = result.numRows;
|
|
601
|
-
const cols = result.schema.fields.map((f) => f.name);
|
|
602
|
-
const types = result.schema.fields.map((f) => String(f.type));
|
|
603
|
-
if (numRows === 0) {
|
|
604
|
-
log(`query → done (empty) in ${elapsed(t0)}`);
|
|
605
|
-
return {
|
|
606
|
-
columns: cols,
|
|
607
|
-
types,
|
|
608
|
-
rowCount: 0,
|
|
609
|
-
rows: []
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
// Arrow emits DECIMAL columns as multi-word BigInt / Uint32Array buffers.
|
|
613
|
-
// `String(rawDecimal)` yields the unscaled integer (or "0,0,0,0"),
|
|
614
|
-
// so rewrite each decimal cell through formatDecimal with the column scale.
|
|
615
|
-
const decimalCols = [];
|
|
616
|
-
for (let i = 0; i < cols.length; i++) {
|
|
617
|
-
const s = decimalScale(types[i]);
|
|
618
|
-
if (s >= 0)
|
|
619
|
-
decimalCols.push({ name: cols[i], scale: s });
|
|
620
|
-
}
|
|
621
|
-
// Extract rows directly — avoids Arrow version mismatch
|
|
622
|
-
const rows = result.toArray().map((row) => {
|
|
623
|
-
const obj = typeof row.toJSON === 'function' ? row.toJSON() : {};
|
|
624
|
-
if (typeof row.toJSON !== 'function') {
|
|
625
|
-
for (const col of cols)
|
|
626
|
-
obj[col] = row[col];
|
|
627
|
-
}
|
|
628
|
-
for (const { name, scale } of decimalCols) {
|
|
629
|
-
obj[name] = formatDecimal(obj[name], scale);
|
|
630
|
-
}
|
|
631
|
-
return obj;
|
|
632
|
-
});
|
|
633
|
-
log(`query → done in ${elapsed(t0)}, ${numRows} rows, ${cols.length} cols`);
|
|
634
|
-
return { columns: cols, types, rowCount: numRows, rows };
|
|
635
|
-
}
|
|
636
|
-
catch (err) {
|
|
637
|
-
logWarn(`query → failed after ${elapsed(t0)}:`, err?.message ?? err);
|
|
638
|
-
throw err;
|
|
639
|
-
}
|
|
640
|
-
finally {
|
|
641
|
-
await conn.close();
|
|
642
|
-
}
|
|
582
|
+
// Delegate to the send()-based path so a data query never blocks the
|
|
583
|
+
// single DuckDB worker (conn.query() is blocking). Same return shape.
|
|
584
|
+
const { result } = this.queryCancellable(connId, sql);
|
|
585
|
+
return result;
|
|
643
586
|
}
|
|
644
587
|
async queryForMap(connId, sql, geomCol, geomColType, sourceCrs) {
|
|
645
588
|
const t0 = performance.now();
|
|
@@ -855,7 +798,7 @@ export class WasmQueryEngine {
|
|
|
855
798
|
log('configureStorage → presigned HTTPS source, skipping S3 config');
|
|
856
799
|
return;
|
|
857
800
|
}
|
|
858
|
-
const stored = localStorage.getItem(
|
|
801
|
+
const stored = localStorage.getItem(STORAGE_KEYS.CONNECTIONS);
|
|
859
802
|
if (!stored) {
|
|
860
803
|
log('configureStorage → no connections in localStorage');
|
|
861
804
|
return;
|
package/dist/storage/presign.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { safeDecodeURIComponent } from '@walkthru-earth/objex-utils';
|
|
2
|
+
import { DEFAULT_AWS_REGION } from '../constants.js';
|
|
2
3
|
import { credentialStore } from '../stores/credentials.svelte.js';
|
|
3
4
|
import { buildProviderBaseUrl, getAccessMode } from './providers.js';
|
|
4
5
|
// 7 days is the SigV4 protocol maximum and is the hard cap on every
|
|
@@ -38,7 +39,7 @@ export async function presignHttpsUrl(conn, key, expiresIn = DEFAULT_EXPIRES_IN_
|
|
|
38
39
|
accessKeyId: creds.accessKey,
|
|
39
40
|
secretAccessKey: creds.secretKey,
|
|
40
41
|
service: 's3',
|
|
41
|
-
region: conn.region ||
|
|
42
|
+
region: conn.region || DEFAULT_AWS_REGION
|
|
42
43
|
});
|
|
43
44
|
const signed = await client.sign(url.toString(), {
|
|
44
45
|
method: 'GET',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Centralizes endpoint patterns, regions, auth methods, and UI metadata.
|
|
5
5
|
* Used by ConnectionDialog, browser-cloud adapter, host-detection, url-state, etc.
|
|
6
6
|
*/
|
|
7
|
+
import { DEFAULT_AWS_REGION } from '../constants.js';
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// Registry
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
@@ -463,7 +464,7 @@ export function buildProviderBaseUrl(provider, endpoint, bucket, region) {
|
|
|
463
464
|
return `${resolved}/${bucket}`;
|
|
464
465
|
}
|
|
465
466
|
// Fallback: AWS S3 path-style
|
|
466
|
-
return `https://s3.${region ||
|
|
467
|
+
return `https://s3.${region || DEFAULT_AWS_REGION}.amazonaws.com/${bucket}`;
|
|
467
468
|
}
|
|
468
469
|
/** Check if a provider uses the GCS JSON API (not S3 XML). */
|
|
469
470
|
export function isGcsProvider(provider, endpoint) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadFromStorage, parseVisibilityParam, persistToStorage, resolveSetting } from '@walkthru-earth/objex-utils';
|
|
1
|
+
import { DEFAULT_APP_CONFIG, loadFromStorage, parseVisibilityParam, persistToStorage, resolveSetting } from '@walkthru-earth/objex-utils';
|
|
2
2
|
import { STORAGE_KEYS } from '../constants.js';
|
|
3
3
|
import { appConfig } from './config.svelte.js';
|
|
4
4
|
/**
|
|
@@ -65,13 +65,13 @@ function createSettingsStore() {
|
|
|
65
65
|
return resolveSetting(user.locale, cfg().defaults.locale, 'en');
|
|
66
66
|
},
|
|
67
67
|
get featureLimit() {
|
|
68
|
-
return resolveSetting(user.featureLimit, cfg().defaults.featureLimit,
|
|
68
|
+
return resolveSetting(user.featureLimit, cfg().defaults.featureLimit, DEFAULT_APP_CONFIG.defaults.featureLimit);
|
|
69
69
|
},
|
|
70
70
|
get mosaicItemLimit() {
|
|
71
71
|
// Explicit user/query choice always wins, at any value.
|
|
72
72
|
if (user.mosaicItemLimit !== undefined)
|
|
73
73
|
return user.mosaicItemLimit;
|
|
74
|
-
const configured = resolveSetting(cfg().defaults.mosaicItemLimit,
|
|
74
|
+
const configured = resolveSetting(cfg().defaults.mosaicItemLimit, DEFAULT_APP_CONFIG.defaults.mosaicItemLimit);
|
|
75
75
|
// Mobile heap safety: clamp the default so API/static mosaic loads don't OOM.
|
|
76
76
|
return mobileLikeAtLoad ? Math.min(configured, MOBILE_MOSAIC_LIMIT) : configured;
|
|
77
77
|
},
|
package/dist/utils/deck.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare function hoverCursor(map: {
|
|
|
17
17
|
picked?: boolean;
|
|
18
18
|
}) => void;
|
|
19
19
|
type RGBA = [number, number, number, number];
|
|
20
|
+
/** RGBA used to highlight a hovered/selected feature across map viewers. */
|
|
21
|
+
export declare const HIGHLIGHT_COLOR: [number, number, number, number];
|
|
20
22
|
/** Distinct fill/line colors per geometry type. */
|
|
21
23
|
export declare const GEOMETRY_COLORS: Record<string, {
|
|
22
24
|
fill: RGBA;
|
package/dist/utils/deck.js
CHANGED
|
@@ -16,6 +16,8 @@ export function hoverCursor(map) {
|
|
|
16
16
|
map.getCanvas().style.cursor = info.picked ? 'pointer' : '';
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/** RGBA used to highlight a hovered/selected feature across map viewers. */
|
|
20
|
+
export const HIGHLIGHT_COLOR = [255, 255, 255, 100];
|
|
19
21
|
/** Distinct fill/line colors per geometry type. */
|
|
20
22
|
export const GEOMETRY_COLORS = {
|
|
21
23
|
point: { fill: [66, 133, 244, 180], line: [25, 103, 210, 220] },
|
|
@@ -85,7 +87,7 @@ function createLayerForResult(modules, result, layerId, onClick, onHover) {
|
|
|
85
87
|
radiusMaxPixels: 12,
|
|
86
88
|
pickable: true,
|
|
87
89
|
autoHighlight: true,
|
|
88
|
-
highlightColor:
|
|
90
|
+
highlightColor: HIGHLIGHT_COLOR,
|
|
89
91
|
_validate: false,
|
|
90
92
|
onHover,
|
|
91
93
|
onClick: handleClick
|
|
@@ -101,7 +103,7 @@ function createLayerForResult(modules, result, layerId, onClick, onHover) {
|
|
|
101
103
|
widthMinPixels: 1.5,
|
|
102
104
|
pickable: true,
|
|
103
105
|
autoHighlight: true,
|
|
104
|
-
highlightColor:
|
|
106
|
+
highlightColor: HIGHLIGHT_COLOR,
|
|
105
107
|
_validate: false,
|
|
106
108
|
onHover,
|
|
107
109
|
onClick: handleClick
|
|
@@ -117,7 +119,7 @@ function createLayerForResult(modules, result, layerId, onClick, onHover) {
|
|
|
117
119
|
lineWidthMinPixels: 1.5,
|
|
118
120
|
pickable: true,
|
|
119
121
|
autoHighlight: true,
|
|
120
|
-
highlightColor:
|
|
122
|
+
highlightColor: HIGHLIGHT_COLOR,
|
|
121
123
|
_validate: false,
|
|
122
124
|
onHover,
|
|
123
125
|
onClick: handleClick
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive media-query helpers for Svelte 5 runes.
|
|
3
|
+
*
|
|
4
|
+
* Usage (inside a component .svelte file):
|
|
5
|
+
* import { useIsWide } from '../utils/media-query.svelte.js';
|
|
6
|
+
* const isWide = useIsWide(); // true when viewport >= 640 px (Tailwind sm)
|
|
7
|
+
* // then: {#if isWide.value} ... {/if}
|
|
8
|
+
*
|
|
9
|
+
* SSR-safe: guards with `typeof window` (this is a CSR-only SPA, but be defensive).
|
|
10
|
+
*/
|
|
11
|
+
/** Reactive wrapper around a single MediaQueryList. */
|
|
12
|
+
export declare function useIsWide(): {
|
|
13
|
+
readonly value: boolean;
|
|
14
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive media-query helpers for Svelte 5 runes.
|
|
3
|
+
*
|
|
4
|
+
* Usage (inside a component .svelte file):
|
|
5
|
+
* import { useIsWide } from '../utils/media-query.svelte.js';
|
|
6
|
+
* const isWide = useIsWide(); // true when viewport >= 640 px (Tailwind sm)
|
|
7
|
+
* // then: {#if isWide.value} ... {/if}
|
|
8
|
+
*
|
|
9
|
+
* SSR-safe: guards with `typeof window` (this is a CSR-only SPA, but be defensive).
|
|
10
|
+
*/
|
|
11
|
+
/** Reactive wrapper around a single MediaQueryList. */
|
|
12
|
+
export function useIsWide() {
|
|
13
|
+
let value = $state(typeof window !== 'undefined' ? window.matchMedia('(min-width: 640px)').matches : true);
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
const mq = window.matchMedia('(min-width: 640px)');
|
|
16
|
+
const handler = (e) => {
|
|
17
|
+
value = e.matches;
|
|
18
|
+
};
|
|
19
|
+
$effect(() => {
|
|
20
|
+
mq.addEventListener('change', handler);
|
|
21
|
+
return () => mq.removeEventListener('change', handler);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
get value() {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Tab } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a tab's signed HTTPS URL reactively for iframe-style viewers.
|
|
4
|
+
* Call inside a component's $effect; returns a cleanup function.
|
|
5
|
+
* onResolved runs only if the tab is still current (guards the async race).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveSignedTabUrl(tab: Tab, onResolved: (url: string) => void): () => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { buildHttpsUrlAsync } from './signed-url.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a tab's signed HTTPS URL reactively for iframe-style viewers.
|
|
4
|
+
* Call inside a component's $effect; returns a cleanup function.
|
|
5
|
+
* onResolved runs only if the tab is still current (guards the async race).
|
|
6
|
+
*/
|
|
7
|
+
export function resolveSignedTabUrl(tab, onResolved) {
|
|
8
|
+
let cancelled = false;
|
|
9
|
+
const id = tab.id;
|
|
10
|
+
(async () => {
|
|
11
|
+
const url = await buildHttpsUrlAsync(tab);
|
|
12
|
+
if (cancelled || id !== tab.id)
|
|
13
|
+
return;
|
|
14
|
+
onResolved(url);
|
|
15
|
+
})();
|
|
16
|
+
return () => {
|
|
17
|
+
cancelled = true;
|
|
18
|
+
};
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkthru-earth/objex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Svelte 5 components and utilities for exploring geospatial object storage — S3, GCS, Azure, R2",
|
|
5
5
|
"author": "Youssef Harby <yharby@walkthru.earth>",
|
|
6
6
|
"license": "CC-BY-4.0",
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
"sql-formatter": "^15.8.0",
|
|
173
173
|
"yaml": "^2.9.0",
|
|
174
174
|
"zarrita": "^0.7.3",
|
|
175
|
-
"@walkthru-earth/objex-utils": "1.
|
|
175
|
+
"@walkthru-earth/objex-utils": "1.5.0"
|
|
176
176
|
},
|
|
177
177
|
"scripts": {
|
|
178
178
|
"dev": "vite dev",
|