Skip to content

Commit 43dda5d

Browse files
committed
adding url sharing
1 parent 5d97bfd commit 43dda5d

File tree

6 files changed

+376
-9
lines changed

6 files changed

+376
-9
lines changed

index.html

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,22 @@
8181
</aside>
8282

8383
<main class="canvas-container">
84-
<button class="export-btn-floating" id="export-btn" aria-label="Export diagram" title="Export diagram">
85-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
86-
<circle cx="12" cy="12" r="9"></circle>
87-
<path d="M8 12h8M12 16l4-4-4-4"></path>
88-
</svg>
89-
</button>
84+
<div class="floating-actions">
85+
<button class="export-btn-floating refresh-btn" id="refresh-btn" aria-label="Refresh diagram" title="Refresh diagram">
86+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
87+
<path d="M23 4v6h-6"></path>
88+
<path d="M1 20v-6h6"></path>
89+
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"></path>
90+
<path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"></path>
91+
</svg>
92+
</button>
93+
<button class="export-btn-floating" id="export-btn" aria-label="Export diagram" title="Export diagram">
94+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
95+
<circle cx="12" cy="12" r="9"></circle>
96+
<path d="M8 12h8M12 16l4-4-4-4"></path>
97+
</svg>
98+
</button>
99+
</div>
90100
<div class="canvas">
91101
<div class="layer layer-marketing" data-layer="marketing">
92102
<div class="layer-label">Marketing Channels</div>

js/connections.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from './config.js';
2222
import { activeModel, customConnections, dismissedConnections } from './state.js';
2323
import { ensureLayerSlots } from './layout.js';
24+
import { persistDiagramState } from './persistence.js';
2425

2526
const connectionLabels = new Map(); // connectionKey -> Set<SVGTextElement>
2627

@@ -165,6 +166,7 @@ export function renderConnections() {
165166
removeConnectionLabel(key);
166167
}
167168
path.remove();
169+
void persistDiagramState();
168170
}, { once: true });
169171
});
170172
}

js/nodes.js

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
customEntries,
1616
dismissedConnections,
1717
getNextCustomEntryId,
18+
resetCustomEntryCounter,
19+
layerOrder,
1820
setActiveCategory,
1921
setActiveModel
2022
} from './state.js';
@@ -35,6 +37,11 @@ import {
3537
renderConnections
3638
} from './connections.js';
3739
import { trackAppLaunched, trackExportButtonClick, trackNodeAdded } from './analytics.js';
40+
import {
41+
clearPersistedDiagramState,
42+
loadDiagramState,
43+
persistDiagramState
44+
} from './persistence.js';
3845

3946
let pendingConnectionNode = null;
4047
let draggedNode = null;
@@ -61,6 +68,7 @@ export function initModelPicker() {
6168
setActiveModel(modelId);
6269
updateModelPickerState();
6370
renderConnections();
71+
void persistDiagramState();
6472
}
6573
applyModelAutoAdjustments(modelId);
6674
});
@@ -170,6 +178,13 @@ export function updateModelPickerState() {
170178
});
171179
}
172180

181+
function updateCategoryTabState() {
182+
const tabs = document.querySelectorAll('.category-tab');
183+
tabs.forEach(tab => {
184+
tab.classList.toggle('active', tab.dataset.category === activeCategory);
185+
});
186+
}
187+
173188
function createComponentListItem(item, category, isCustom) {
174189
const li = document.createElement('li');
175190
li.className = 'component-item';
@@ -223,6 +238,7 @@ export function addCustomEntry() {
223238
if (list) {
224239
list.scrollTop = list.scrollHeight;
225240
}
241+
void persistDiagramState();
226242
}
227243

228244
export function addItemToLayer(itemId, itemName, iconKey, category) {
@@ -254,6 +270,7 @@ export function addItemToLayer(itemId, itemName, iconKey, category) {
254270
updateSidebarItemState(itemId, category, true);
255271
renderConnections();
256272
trackNodeAdded(itemName);
273+
void persistDiagramState();
257274
}
258275

259276
export function ensureItemAdded(itemId) {
@@ -376,6 +393,7 @@ function toggleAmplitudeSdkBadge(node, badgeId) {
376393
amplitudeSdkSelectedBadges.add(badgeId);
377394
}
378395
syncAmplitudeSdkBadgeState(node);
396+
void persistDiagramState();
379397
}
380398

381399
function syncAmplitudeSdkBadgeState(node) {
@@ -404,6 +422,7 @@ function removeItemFromLayer(itemId, category, node) {
404422
removeRelatedCustomConnections(itemId);
405423
enforceNodeOrdering();
406424
renderConnections();
425+
void persistDiagramState();
407426
});
408427
}
409428

@@ -450,6 +469,7 @@ function addCustomConnection(sourceId, targetId) {
450469
customConnections.add(key);
451470
dismissedConnections.delete(key);
452471
renderConnections();
472+
void persistDiagramState();
453473
}
454474

455475
function removeRelatedCustomConnections(nodeId) {
@@ -591,6 +611,7 @@ function handleLayerDrop(e) {
591611
content.classList.remove('drag-over');
592612
enforceNodeOrdering();
593613
renderConnections();
614+
void persistDiagramState();
594615
handleDragEnd();
595616
}
596617

@@ -608,7 +629,7 @@ function loadHtml2Canvas() {
608629
});
609630
}
610631

611-
export function initializeApp() {
632+
export async function initializeApp() {
612633
if (document?.documentElement) {
613634
document.documentElement.style.setProperty('--slot-columns', String(SLOT_COLUMNS));
614635
}
@@ -618,7 +639,129 @@ export function initializeApp() {
618639
initModelPicker();
619640
initLayerDragTargets();
620641
initExportButton();
642+
initRefreshButton();
643+
const restored = await restoreDiagramStateFromStorage();
644+
if (!restored) {
645+
updateCategoryTabState();
646+
renderComponentList(activeCategory);
647+
renderConnections();
648+
}
649+
window.addEventListener('resize', () => renderConnections());
650+
}
651+
652+
async function restoreDiagramStateFromStorage() {
653+
const stored = await loadDiagramState();
654+
if (!stored) return false;
655+
try {
656+
Object.values(addedItems).forEach(set => set.clear());
657+
Object.keys(layerOrder).forEach(category => {
658+
layerOrder[category] = [];
659+
});
660+
Object.keys(customEntries).forEach(category => {
661+
customEntries[category] = [];
662+
});
663+
customConnections.clear();
664+
dismissedConnections.clear();
665+
amplitudeSdkSelectedBadges.clear();
666+
clearCustomItemIndex();
667+
668+
if (Array.isArray(stored.amplitudeSdkSelectedBadges)) {
669+
stored.amplitudeSdkSelectedBadges.forEach(id => amplitudeSdkSelectedBadges.add(id));
670+
}
671+
672+
let maxCustomId = 0;
673+
if (stored.customEntries) {
674+
Object.entries(stored.customEntries).forEach(([category, entries]) => {
675+
if (!customEntries[category]) {
676+
customEntries[category] = [];
677+
}
678+
entries.forEach(entry => {
679+
customEntries[category].push({ ...entry });
680+
itemCategoryIndex[entry.id] = category;
681+
const match = /custom-[a-z-]+-(\d+)/.exec(entry.id);
682+
if (match) {
683+
const parsed = Number(match[1]);
684+
if (Number.isFinite(parsed)) {
685+
maxCustomId = Math.max(maxCustomId, parsed);
686+
}
687+
}
688+
});
689+
});
690+
}
691+
resetCustomEntryCounter(maxCustomId);
692+
693+
if (stored.layerOrder) {
694+
Object.entries(stored.layerOrder).forEach(([category, slots]) => {
695+
layerOrder[category] = Array.isArray(slots) ? [...slots] : [];
696+
});
697+
}
698+
699+
if (stored.activeModel) {
700+
setActiveModel(stored.activeModel);
701+
}
702+
if (stored.activeCategory) {
703+
setActiveCategory(stored.activeCategory);
704+
}
705+
706+
Object.keys(addedItems).forEach(category => {
707+
const slots = layerOrder[category] || [];
708+
slots.forEach(id => {
709+
if (id) ensureItemAdded(id);
710+
});
711+
const extraIds = new Set(stored.addedItems?.[category] || []);
712+
slots.forEach(id => extraIds.delete(id));
713+
extraIds.forEach(id => ensureItemAdded(id));
714+
});
715+
716+
(stored.customConnections || []).forEach(key => customConnections.add(key));
717+
(stored.dismissedConnections || []).forEach(key => dismissedConnections.add(key));
718+
719+
updateCategoryTabState();
720+
updateModelPickerState();
721+
renderComponentList(activeCategory);
722+
renderConnections();
723+
return true;
724+
} catch (error) {
725+
console.error('Failed to restore diagram state', error);
726+
return false;
727+
}
728+
}
729+
730+
function initRefreshButton() {
731+
const refreshBtn = document.getElementById('refresh-btn');
732+
if (!refreshBtn) return;
733+
refreshBtn.addEventListener('click', () => {
734+
clearDiagram();
735+
});
736+
}
737+
738+
function clearCustomItemIndex() {
739+
Object.keys(itemCategoryIndex).forEach(id => {
740+
if (id.startsWith('custom-')) {
741+
delete itemCategoryIndex[id];
742+
}
743+
});
744+
}
745+
746+
function clearDiagram() {
747+
document.querySelectorAll('.diagram-node').forEach(node => node.remove());
748+
Object.keys(addedItems).forEach(category => addedItems[category].clear());
749+
Object.keys(layerOrder).forEach(category => {
750+
layerOrder[category] = [];
751+
});
752+
customConnections.clear();
753+
dismissedConnections.clear();
754+
amplitudeSdkSelectedBadges.clear();
755+
Object.keys(customEntries).forEach(category => {
756+
customEntries[category] = [];
757+
});
758+
resetCustomEntryCounter(0);
759+
clearCustomItemIndex();
760+
setActiveModel(null);
761+
setActiveCategory('marketing');
762+
updateCategoryTabState();
763+
updateModelPickerState();
621764
renderComponentList(activeCategory);
622765
renderConnections();
623-
window.addEventListener('resize', () => renderConnections());
766+
clearPersistedDiagramState();
624767
}

0 commit comments

Comments
 (0)