@smartnet360/svelte-components 0.0.142 → 0.0.143

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.
Files changed (25) hide show
  1. package/dist/apps/antenna-tools/components/AntennaControls.svelte +14 -6
  2. package/dist/apps/antenna-tools/components/AntennaTools.svelte +6 -5
  3. package/dist/core/Auth/auth.svelte.js +72 -39
  4. package/dist/core/Auth/config.d.ts +1 -0
  5. package/dist/core/Auth/config.js +3 -4
  6. package/dist/map-v3/demo/DemoMap.svelte +3 -1
  7. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte +105 -10
  8. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte.d.ts +3 -0
  9. package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte +398 -0
  10. package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte.d.ts +22 -0
  11. package/dist/map-v3/features/custom/components/index.d.ts +1 -0
  12. package/dist/map-v3/features/custom/components/index.js +1 -0
  13. package/dist/map-v3/features/custom/db/custom-sets-api.d.ts +65 -0
  14. package/dist/map-v3/features/custom/db/custom-sets-api.js +220 -0
  15. package/dist/map-v3/features/custom/db/custom-sets-repository.d.ts +77 -0
  16. package/dist/map-v3/features/custom/db/custom-sets-repository.js +195 -0
  17. package/dist/map-v3/features/custom/db/index.d.ts +10 -0
  18. package/dist/map-v3/features/custom/db/index.js +9 -0
  19. package/dist/map-v3/features/custom/db/schema.sql +102 -0
  20. package/dist/map-v3/features/custom/db/types.d.ts +95 -0
  21. package/dist/map-v3/features/custom/db/types.js +95 -0
  22. package/dist/map-v3/features/custom/index.d.ts +2 -0
  23. package/dist/map-v3/features/custom/index.js +2 -0
  24. package/dist/map-v3/features/custom/logic/csv-parser.js +8 -3
  25. package/package.json +1 -1
@@ -67,6 +67,10 @@
67
67
  // Avoid overwriting user selections
68
68
  if (selectedAntenna !== internalSelectedAntenna) {
69
69
  internalSelectedAntenna = selectedAntenna;
70
+ // Also sync frequency when antenna is set externally
71
+ if (selectedAntenna && selectedAntenna.frequency) {
72
+ selectedFrequency = selectedAntenna.frequency;
73
+ }
70
74
  }
71
75
  });
72
76
 
@@ -76,12 +80,13 @@
76
80
  let newTilts: string[] = ['0'];
77
81
 
78
82
  if (internalSelectedAntenna) {
79
- // Find ALL antennas with the same name and frequency to collect all electrical tilts
83
+ // Find antennas with the same name
80
84
  let sameName = antennas.filter(a => a.name === internalSelectedAntenna!.name);
81
85
 
82
- // If frequency is selected, filter by it
83
- if (selectedFrequency) {
84
- sameName = sameName.filter(a => a.frequency === selectedFrequency);
86
+ // Always filter by frequency - use selectedFrequency or fall back to antenna's frequency
87
+ const freqToUse = selectedFrequency ?? internalSelectedAntenna.frequency;
88
+ if (freqToUse) {
89
+ sameName = sameName.filter(a => a.frequency === freqToUse);
85
90
  }
86
91
 
87
92
  const allTilts = new Set<string>();
@@ -114,12 +119,15 @@
114
119
  // Handle case where availableElectricalTilts might not be populated yet
115
120
  const targetTilt = availableElectricalTilts[tiltIndex] || '0';
116
121
 
122
+ // Always use frequency filter - selectedFrequency or the current antenna's frequency
123
+ const freqToUse = selectedFrequency ?? internalSelectedAntenna?.frequency;
124
+
117
125
  // Find antenna with matching name, frequency, and electrical tilt
118
126
  return antennas.find(antenna => {
119
127
  if (antenna.name !== antennaName) return false;
120
128
 
121
- // If frequency is selected, must match
122
- if (selectedFrequency && antenna.frequency !== selectedFrequency) return false;
129
+ // Must match frequency
130
+ if (freqToUse && antenna.frequency !== freqToUse) return false;
123
131
 
124
132
  if (antenna.tilt) {
125
133
  const tiltString = antenna.tilt.toString();
@@ -197,10 +197,10 @@
197
197
 
198
198
  // === Helpers ===
199
199
  function updateAvailableTilts(antenna: Antenna) {
200
- // Collect ALL available tilts from all antennas with the same name
201
- const sameName = antennas.filter(a => a.name === antenna.name);
200
+ // Collect available tilts from antennas with the same name AND frequency
201
+ const sameNameAndFreq = antennas.filter(a => a.name === antenna.name && a.frequency === antenna.frequency);
202
202
  const allTilts = new Set<string>();
203
- sameName.forEach(a => {
203
+ sameNameAndFreq.forEach(a => {
204
204
  if (a.tilt) {
205
205
  const tiltStr = a.tilt.toString();
206
206
  if (tiltStr.includes(',')) {
@@ -214,9 +214,10 @@
214
214
  }
215
215
 
216
216
  function getAvailableTiltsForAntenna(antenna: Antenna): string[] {
217
- const sameName = antennas.filter(a => a.name === antenna.name);
217
+ // Collect available tilts from antennas with the same name AND frequency
218
+ const sameNameAndFreq = antennas.filter(a => a.name === antenna.name && a.frequency === antenna.frequency);
218
219
  const allTilts = new Set<string>();
219
- sameName.forEach(a => {
220
+ sameNameAndFreq.forEach(a => {
220
221
  if (a.tilt) {
221
222
  const tiltStr = a.tilt.toString();
222
223
  if (tiltStr.includes(',')) {
@@ -2,30 +2,61 @@
2
2
  import { browser } from '$app/environment';
3
3
  // Demo mode - enabled via PUBLIC_DEMO_MODE env var (GitHub Pages)
4
4
  const DEMO_MODE = import.meta.env.PUBLIC_DEMO_MODE === 'true';
5
- const DEMO_USERNAME = 'demo';
6
- const DEMO_PASSWORD = 'demo123';
7
- // Demo user session (client-side only, no server needed)
8
- const DEMO_SESSION = {
9
- user: {
10
- id: 'demo-user',
11
- username: 'demo',
12
- displayName: 'Demo User',
13
- email: 'demo@example.com',
14
- groups: ['demo'],
15
- loginTime: Date.now()
5
+ // Test users configuration (no backend required)
6
+ const TEST_USERS = {
7
+ // Demo user (view-only permissions)
8
+ demo: {
9
+ password: 'demo123',
10
+ session: {
11
+ user: {
12
+ id: 'demo-user',
13
+ username: 'demo',
14
+ displayName: 'Demo User',
15
+ email: 'demo@example.com',
16
+ groups: ['demo'],
17
+ loginTime: Date.now()
18
+ },
19
+ permissions: [
20
+ { featureId: 'map-view', canView: true, canEdit: false, canAdmin: false },
21
+ { featureId: 'coverage-view', canView: true, canEdit: false, canAdmin: false },
22
+ { featureId: 'charts-view', canView: true, canEdit: false, canAdmin: false },
23
+ { featureId: 'desktop-view', canView: true, canEdit: false, canAdmin: false },
24
+ { featureId: 'table-view', canView: true, canEdit: false, canAdmin: false },
25
+ { featureId: 'antenna-view', canView: true, canEdit: false, canAdmin: false },
26
+ { featureId: 'site-check', canView: true, canEdit: false, canAdmin: false },
27
+ { featureId: 'settings-view', canView: true, canEdit: false, canAdmin: false }
28
+ ],
29
+ token: 'demo-token',
30
+ expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
31
+ }
16
32
  },
17
- permissions: [
18
- { featureId: 'map-view', canView: true, canEdit: false, canAdmin: false },
19
- { featureId: 'coverage-view', canView: true, canEdit: false, canAdmin: false },
20
- { featureId: 'charts-view', canView: true, canEdit: false, canAdmin: false },
21
- { featureId: 'desktop-view', canView: true, canEdit: false, canAdmin: false },
22
- { featureId: 'table-view', canView: true, canEdit: false, canAdmin: false },
23
- { featureId: 'antenna-view', canView: true, canEdit: false, canAdmin: false },
24
- { featureId: 'site-check', canView: true, canEdit: false, canAdmin: false },
25
- { featureId: 'settings-view', canView: true, canEdit: false, canAdmin: false }
26
- ],
27
- token: 'demo-token',
28
- expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours for demo
33
+ // Test user (full permissions, for testing without backend)
34
+ test: {
35
+ password: 'test',
36
+ session: {
37
+ user: {
38
+ id: 'test-user',
39
+ username: 'test',
40
+ displayName: 'Test User',
41
+ email: 'test@localhost',
42
+ groups: ['testers', 'admins'],
43
+ loginTime: Date.now()
44
+ },
45
+ permissions: [
46
+ { featureId: 'map-view', canView: true, canEdit: true, canAdmin: true },
47
+ { featureId: 'coverage-view', canView: true, canEdit: true, canAdmin: true },
48
+ { featureId: 'charts-view', canView: true, canEdit: true, canAdmin: true },
49
+ { featureId: 'desktop-view', canView: true, canEdit: true, canAdmin: true },
50
+ { featureId: 'table-view', canView: true, canEdit: true, canAdmin: true },
51
+ { featureId: 'antenna-view', canView: true, canEdit: true, canAdmin: true },
52
+ { featureId: 'site-check', canView: true, canEdit: true, canAdmin: true },
53
+ { featureId: 'settings-view', canView: true, canEdit: true, canAdmin: true },
54
+ { featureId: 'admin', canView: true, canEdit: true, canAdmin: true }
55
+ ],
56
+ token: 'test-token',
57
+ expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
58
+ }
59
+ }
29
60
  };
30
61
  // Default configuration
31
62
  const defaultConfig = {
@@ -107,22 +138,22 @@ export function createAuthState(config = {}) {
107
138
  isLoading = true;
108
139
  error = null;
109
140
  try {
110
- // Demo mode: handle authentication client-side (no server needed)
141
+ // Check for test users (works in demo mode or as fallback)
142
+ const testUser = TEST_USERS[username.toLowerCase()];
143
+ if (testUser && password === testUser.password) {
144
+ // Create fresh session with updated timestamp
145
+ const testSession = {
146
+ ...testUser.session,
147
+ user: { ...testUser.session.user, loginTime: Date.now() },
148
+ expiresAt: Date.now() + (24 * 60 * 60 * 1000)
149
+ };
150
+ saveSession(testSession);
151
+ return true;
152
+ }
153
+ // In demo mode, only allow test users
111
154
  if (DEMO_MODE) {
112
- if (username === DEMO_USERNAME && password === DEMO_PASSWORD) {
113
- // Create fresh demo session with updated timestamp
114
- const demoSession = {
115
- ...DEMO_SESSION,
116
- user: { ...DEMO_SESSION.user, loginTime: Date.now() },
117
- expiresAt: Date.now() + (24 * 60 * 60 * 1000)
118
- };
119
- saveSession(demoSession);
120
- return true;
121
- }
122
- else {
123
- error = 'Demo mode: Use username "demo" and password "demo123"';
124
- return false;
125
- }
155
+ error = 'Demo mode: Use "demo/demo123" or "test/test" to login';
156
+ return false;
126
157
  }
127
158
  // Normal mode: call server API
128
159
  const response = await fetch(`${cfg.apiEndpoint}/login`, {
@@ -141,7 +172,9 @@ export function createAuthState(config = {}) {
141
172
  }
142
173
  }
143
174
  catch (e) {
144
- error = e instanceof Error ? e.message : 'Network error';
175
+ // If server is unavailable, show helpful message about test users
176
+ const networkError = e instanceof Error ? e.message : 'Network error';
177
+ error = `${networkError}. Try "test/test" for testing without a backend.`;
145
178
  return false;
146
179
  }
147
180
  finally {
@@ -21,5 +21,6 @@ export declare const DEFAULT_DEV_PERMISSIONS: FeatureAccess[];
21
21
  export declare function getDevConfig(): DevConfig;
22
22
  /**
23
23
  * Check if we're in development mode
24
+ * Currently disabled - authentication is always required
24
25
  */
25
26
  export declare function isDevMode(): boolean;
@@ -246,11 +246,10 @@ export function getDevConfig() {
246
246
  }
247
247
  /**
248
248
  * Check if we're in development mode
249
+ * Currently disabled - authentication is always required
249
250
  */
250
251
  export function isDevMode() {
251
- // Check environment - in dev server or explicit flag
252
- if (typeof import.meta !== 'undefined' && import.meta.env?.DEV) {
253
- return true;
254
- }
252
+ // Auto-login disabled - always require authentication
253
+ // Use test/test or demo/demo123 for testing without a backend
255
254
  return false;
256
255
  }
@@ -24,7 +24,8 @@
24
24
  import {
25
25
  CustomCellsLayer,
26
26
  CustomCellSetManager,
27
- createCustomCellSetsStore
27
+ createCustomCellSetsStore,
28
+ createCustomSetsApi
28
29
  } from '../features/custom';
29
30
  // Custom Sites Feature
30
31
  import {
@@ -103,6 +104,7 @@
103
104
  <CustomCellSetManager
104
105
  position="top-left"
105
106
  setsStore={customCellSets}
107
+ apiClient={createCustomSetsApi('/api/custom-sets')}
106
108
  />
107
109
 
108
110
 
@@ -7,6 +7,7 @@
7
7
  * - Quick Add (paste cell names)
8
8
  * - Global size/opacity sliders
9
9
  * - Collapsible set sections with TreeView + color pickers
10
+ * - Server sharing (optional, when apiClient provided)
10
11
  */
11
12
  import { untrack } from 'svelte';
12
13
  import { MapControl } from '../../../shared';
@@ -14,17 +15,22 @@
14
15
  import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
15
16
  import type { CustomCellImportResult, CustomCellSet } from '../types';
16
17
  import { buildCustomCellTree } from '../logic/tree-adapter';
18
+ import type { CustomSetsApiClient } from '../db/custom-sets-api';
19
+ import ServerSetBrowser from './ServerSetBrowser.svelte';
17
20
 
18
21
  interface Props {
19
22
  /** The custom cell sets store */
20
23
  setsStore: CustomCellSetsStore;
21
24
  /** Control position on map */
22
25
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
26
+ /** Optional API client for server sharing */
27
+ apiClient?: CustomSetsApiClient;
23
28
  }
24
29
 
25
30
  let {
26
31
  setsStore,
27
- position = 'top-left'
32
+ position = 'top-left',
33
+ apiClient
28
34
  }: Props = $props();
29
35
 
30
36
  // Global display settings - initialized from first set if available
@@ -57,6 +63,11 @@
57
63
  let quickAddName = $state('Quick Selection');
58
64
  let quickAddResult = $state<CustomCellImportResult | null>(null);
59
65
 
66
+ // Server sharing state
67
+ let showServerBrowser = $state(false);
68
+ let savingSetId = $state<string | null>(null);
69
+ let saveError = $state('');
70
+
60
71
  // Track expanded sets
61
72
  let expandedSets = $state<Set<string>>(new Set());
62
73
 
@@ -223,6 +234,26 @@
223
234
  }
224
235
  }
225
236
 
237
+ // Server sharing functions
238
+ async function saveSetToServer(set: CustomCellSet) {
239
+ if (!apiClient) return;
240
+
241
+ savingSetId = set.id;
242
+ saveError = '';
243
+
244
+ try {
245
+ const result = await apiClient.saveSet(set);
246
+
247
+ if (!result.success) {
248
+ saveError = result.error || 'Failed to save to server';
249
+ }
250
+ } catch (err) {
251
+ saveError = err instanceof Error ? err.message : 'Failed to save';
252
+ } finally {
253
+ savingSetId = null;
254
+ }
255
+ }
256
+
226
257
  function toggleExpanded(setId: string) {
227
258
  if (expandedSets.has(setId)) {
228
259
  expandedSets.delete(setId);
@@ -252,9 +283,20 @@
252
283
 
253
284
  <MapControl {position} title="Custom Layers" icon="layers" controlWidth="320px">
254
285
  {#snippet actions()}
286
+ {#if apiClient}
287
+ <button
288
+ class="btn btn-sm btn-outline-primary border-0 p-1 px-2"
289
+ title="Load from Server"
290
+ aria-label="Load from Server"
291
+ onclick={() => showServerBrowser = true}
292
+ >
293
+ <i class="bi bi-cloud-download"></i>
294
+ </button>
295
+ {/if}
255
296
  <button
256
297
  class="btn btn-sm btn-outline-primary border-0 p-1 px-2"
257
298
  title="Quick Add (paste cell names)"
299
+ aria-label="Quick Add"
258
300
  onclick={() => showQuickAdd = true}
259
301
  >
260
302
  <i class="bi bi-pencil-square"></i>
@@ -262,6 +304,7 @@
262
304
  <button
263
305
  class="btn btn-sm btn-outline-primary border-0 p-1 px-2"
264
306
  title="Import CSV"
307
+ aria-label="Import CSV"
265
308
  onclick={triggerFileInput}
266
309
  >
267
310
  <i class="bi bi-upload"></i>
@@ -290,6 +333,22 @@
290
333
  </div>
291
334
  {/if}
292
335
 
336
+ <!-- Server save error -->
337
+ {#if saveError}
338
+ <div class="alert alert-warning py-1 px-2 mb-2 small d-flex justify-content-between align-items-center">
339
+ <span>
340
+ <i class="bi bi-cloud-slash me-1"></i>
341
+ {saveError}
342
+ </span>
343
+ <button
344
+ type="button"
345
+ class="btn-close btn-sm"
346
+ aria-label="Dismiss"
347
+ onclick={() => saveError = ''}
348
+ ></button>
349
+ </div>
350
+ {/if}
351
+
293
352
  <!-- Empty state -->
294
353
  {#if setsStore.sets.length === 0}
295
354
  <div class="text-center p-3">
@@ -297,7 +356,15 @@
297
356
  <p class="text-muted small mt-2 mb-0">
298
357
  No custom layers loaded.
299
358
  </p>
300
- <div class="d-flex gap-2 justify-content-center mt-2">
359
+ <!-- <div class="d-flex gap-2 justify-content-center mt-2 flex-wrap">
360
+ {#if apiClient}
361
+ <button
362
+ class="btn btn-sm btn-outline-success"
363
+ onclick={() => showServerBrowser = true}
364
+ >
365
+ <i class="bi bi-cloud-download me-1"></i> Load from Server
366
+ </button>
367
+ {/if}
301
368
  <button
302
369
  class="btn btn-sm btn-outline-primary"
303
370
  onclick={() => showQuickAdd = true}
@@ -310,7 +377,7 @@
310
377
  >
311
378
  <i class="bi bi-upload me-1"></i> Import CSV
312
379
  </button>
313
- </div>
380
+ </div> -->
314
381
  </div>
315
382
  {:else}
316
383
  <!-- Global Controls -->
@@ -390,13 +457,31 @@
390
457
  <small class="text-muted">{getSetItemCounts(set)} · {set.groups.length} groups</small>
391
458
  </div>
392
459
  </div>
393
- <button
394
- class="btn btn-sm btn-outline-danger border-0 p-1 ms-2"
395
- title="Remove"
396
- onclick={() => removeSet(set.id)}
397
- >
398
- <i class="bi bi-trash"></i>
399
- </button>
460
+ <div class="d-flex gap-1">
461
+ {#if apiClient}
462
+ <button
463
+ class="btn btn-sm btn-outline-primary border-0 p-1"
464
+ title="Share to Server"
465
+ aria-label="Share to Server"
466
+ onclick={() => saveSetToServer(set)}
467
+ disabled={savingSetId === set.id}
468
+ >
469
+ {#if savingSetId === set.id}
470
+ <span class="spinner-border spinner-border-sm" role="status"></span>
471
+ {:else}
472
+ <i class="bi bi-cloud-upload"></i>
473
+ {/if}
474
+ </button>
475
+ {/if}
476
+ <button
477
+ class="btn btn-sm btn-outline-danger border-0 p-1"
478
+ title="Remove"
479
+ aria-label="Remove set"
480
+ onclick={() => removeSet(set.id)}
481
+ >
482
+ <i class="bi bi-trash"></i>
483
+ </button>
484
+ </div>
400
485
  </div>
401
486
 
402
487
  <!-- Set Content (TreeView) -->
@@ -635,6 +720,16 @@
635
720
  </div>
636
721
  {/if}
637
722
 
723
+ <!-- Server Set Browser Modal -->
724
+ {#if apiClient}
725
+ <ServerSetBrowser
726
+ {apiClient}
727
+ {setsStore}
728
+ show={showServerBrowser}
729
+ onclose={() => showServerBrowser = false}
730
+ />
731
+ {/if}
732
+
638
733
  <style>
639
734
  .custom-layers-manager {
640
735
  font-size: 0.875rem;
@@ -1,9 +1,12 @@
1
1
  import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
2
+ import type { CustomSetsApiClient } from '../db/custom-sets-api';
2
3
  interface Props {
3
4
  /** The custom cell sets store */
4
5
  setsStore: CustomCellSetsStore;
5
6
  /** Control position on map */
6
7
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
8
+ /** Optional API client for server sharing */
9
+ apiClient?: CustomSetsApiClient;
7
10
  }
8
11
  declare const CustomCellSetManager: import("svelte").Component<Props, {}, "">;
9
12
  type CustomCellSetManager = ReturnType<typeof CustomCellSetManager>;