gitmaps 1.0.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 +167 -0
- package/app/api/auth/favorites/route.ts +56 -0
- package/app/api/auth/github/callback/route.ts +103 -0
- package/app/api/auth/github/route.ts +32 -0
- package/app/api/auth/me/route.ts +52 -0
- package/app/api/auth/positions/route.ts +50 -0
- package/app/api/chat/route.ts +101 -0
- package/app/api/connections/route.ts +72 -0
- package/app/api/github/repos/route.ts +111 -0
- package/app/api/positions/route.ts +80 -0
- package/app/api/repo/branch-diff/route.ts +201 -0
- package/app/api/repo/branches/route.ts +53 -0
- package/app/api/repo/browse/route.ts +55 -0
- package/app/api/repo/clone/route.ts +78 -0
- package/app/api/repo/clone-stream/route.ts +131 -0
- package/app/api/repo/file-content/route.ts +28 -0
- package/app/api/repo/file-delete/route.ts +62 -0
- package/app/api/repo/file-history/route.ts +45 -0
- package/app/api/repo/file-rename/route.ts +83 -0
- package/app/api/repo/file-save/route.ts +45 -0
- package/app/api/repo/files/route.ts +169 -0
- package/app/api/repo/git-blame/route.ts +86 -0
- package/app/api/repo/git-commit/route.ts +40 -0
- package/app/api/repo/git-heatmap/route.ts +55 -0
- package/app/api/repo/imports/route.ts +154 -0
- package/app/api/repo/load/route.ts +56 -0
- package/app/api/repo/mode/route.ts +14 -0
- package/app/api/repo/search/route.ts +127 -0
- package/app/api/repo/tree/route.ts +104 -0
- package/app/api/repo/upload/route.ts +53 -0
- package/app/api/repo/validate-path.ts +53 -0
- package/app/canvas_users.db +0 -0
- package/app/canvas_users.db-shm +0 -0
- package/app/canvas_users.db-wal +0 -0
- package/app/globals.css +7899 -0
- package/app/layout.tsx +493 -0
- package/app/lib/auth.ts +193 -0
- package/app/lib/auto-save.ts +137 -0
- package/app/lib/branch-compare.ts +443 -0
- package/app/lib/breadcrumbs.ts +170 -0
- package/app/lib/canvas-export.ts +358 -0
- package/app/lib/canvas-text.ts +912 -0
- package/app/lib/canvas.ts +564 -0
- package/app/lib/card-arrangement.ts +188 -0
- package/app/lib/card-context-menu.tsx +453 -0
- package/app/lib/card-diff-markers.ts +270 -0
- package/app/lib/card-expand.ts +189 -0
- package/app/lib/card-groups.ts +246 -0
- package/app/lib/cards.tsx +914 -0
- package/app/lib/chat.tsx +308 -0
- package/app/lib/code-editor.ts +508 -0
- package/app/lib/command-palette.ts +262 -0
- package/app/lib/connections.tsx +1037 -0
- package/app/lib/context.ts +94 -0
- package/app/lib/cursor-sharing.ts +281 -0
- package/app/lib/dependency-graph.ts +438 -0
- package/app/lib/events.tsx +1747 -0
- package/app/lib/file-card-plugin.ts +134 -0
- package/app/lib/file-modal.tsx +849 -0
- package/app/lib/file-preview.ts +400 -0
- package/app/lib/file-tabs.ts +318 -0
- package/app/lib/galaxydraw-bridge.ts +477 -0
- package/app/lib/galaxydraw.test.ts +229 -0
- package/app/lib/global-search.ts +264 -0
- package/app/lib/goto-definition.ts +224 -0
- package/app/lib/heatmap.ts +178 -0
- package/app/lib/hidden-files.tsx +222 -0
- package/app/lib/layers.ts +0 -0
- package/app/lib/layers.tsx +365 -0
- package/app/lib/loading.tsx +45 -0
- package/app/lib/multi-repo.ts +286 -0
- package/app/lib/new-file-dialog.tsx +230 -0
- package/app/lib/onboarding.tsx +213 -0
- package/app/lib/perf-overlay.ts +360 -0
- package/app/lib/positions.ts +176 -0
- package/app/lib/pr-review.ts +374 -0
- package/app/lib/production-mode.ts +47 -0
- package/app/lib/repo.tsx +977 -0
- package/app/lib/settings-modal.tsx +374 -0
- package/app/lib/settings.ts +97 -0
- package/app/lib/shortcuts-panel.ts +141 -0
- package/app/lib/status-bar.ts +128 -0
- package/app/lib/symbol-outline.ts +212 -0
- package/app/lib/syntax.ts +177 -0
- package/app/lib/tab-diff.ts +238 -0
- package/app/lib/user.tsx +133 -0
- package/app/lib/utils.ts +78 -0
- package/app/lib/viewport-culling.ts +728 -0
- package/app/page.client.tsx +215 -0
- package/app/page.tsx +291 -0
- package/app/state/machine.js +196 -0
- package/app/styles/main.css +2168 -0
- package/banner.png +0 -0
- package/cli.ts +44 -0
- package/package.json +75 -0
- package/packages/galaxydraw/README.md +296 -0
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +100 -0
- package/packages/galaxydraw/demo/client.ts +154 -0
- package/packages/galaxydraw/demo/dist/client.js +8 -0
- package/packages/galaxydraw/demo/index.html +256 -0
- package/packages/galaxydraw/demo/server.ts +96 -0
- package/packages/galaxydraw/dist/index.js +984 -0
- package/packages/galaxydraw/dist/index.js.map +16 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
- package/packages/galaxydraw/package.json +49 -0
- package/packages/galaxydraw/perf.test.ts +284 -0
- package/packages/galaxydraw/src/core/cards.ts +435 -0
- package/packages/galaxydraw/src/core/engine.ts +339 -0
- package/packages/galaxydraw/src/core/events.ts +81 -0
- package/packages/galaxydraw/src/core/layout.ts +136 -0
- package/packages/galaxydraw/src/core/minimap.ts +216 -0
- package/packages/galaxydraw/src/core/state.ts +177 -0
- package/packages/galaxydraw/src/core/viewport.ts +106 -0
- package/packages/galaxydraw/src/galaxydraw.css +166 -0
- package/packages/galaxydraw/src/index.ts +40 -0
- package/packages/galaxydraw/tsconfig.json +30 -0
- package/server.ts +62 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Settings Modal — gear icon opens a premium settings panel
|
|
4
|
+
* with organized toggle switches and sliders.
|
|
5
|
+
*/
|
|
6
|
+
import { render } from 'melina/client';
|
|
7
|
+
import { getSettings, updateSettings, resetSettings, type GitCanvasSettings } from './settings';
|
|
8
|
+
|
|
9
|
+
let _modal: HTMLElement | null = null;
|
|
10
|
+
|
|
11
|
+
// ─── JSX Components ─────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function ToggleGroup({ id, value, options }: {
|
|
14
|
+
id: string;
|
|
15
|
+
value: string;
|
|
16
|
+
options: { value: string; label: string }[];
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="settings-toggle-group" id={id}>
|
|
20
|
+
{options.map(opt => (
|
|
21
|
+
<button
|
|
22
|
+
key={opt.value}
|
|
23
|
+
className={`settings-toggle-btn ${value === opt.value ? 'active' : ''}`}
|
|
24
|
+
data-value={opt.value}
|
|
25
|
+
>
|
|
26
|
+
{opt.label}
|
|
27
|
+
</button>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Slider({ id, valueId, min, max, step, value, suffix }: {
|
|
34
|
+
id: string; valueId: string;
|
|
35
|
+
min: number; max: number; step: number;
|
|
36
|
+
value: number; suffix: string;
|
|
37
|
+
}) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="settings-slider-group">
|
|
40
|
+
<input type="range" id={id} className="settings-slider"
|
|
41
|
+
min={String(min)} max={String(max)} step={String(step)} value={String(value)} />
|
|
42
|
+
<span className="settings-slider-value" id={valueId}>{value}{suffix}</span>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Switch({ id, checked }: { id: string; checked: boolean }) {
|
|
48
|
+
return (
|
|
49
|
+
<label className="settings-switch">
|
|
50
|
+
<input type="checkbox" id={id} checked={checked} />
|
|
51
|
+
<span className="settings-switch-slider"></span>
|
|
52
|
+
</label>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function SettingsRow({ label, desc, children }: {
|
|
57
|
+
label: string; desc: string; children: any;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<div className="settings-row">
|
|
61
|
+
<div className="settings-label">
|
|
62
|
+
<span className="settings-label-text">{label}</span>
|
|
63
|
+
<span className="settings-label-desc">{desc}</span>
|
|
64
|
+
</div>
|
|
65
|
+
{children}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function SettingsSection({ title, children }: { title: string; children: any }) {
|
|
71
|
+
return (
|
|
72
|
+
<div className="settings-section">
|
|
73
|
+
<h3 className="settings-section-title">{title}</h3>
|
|
74
|
+
{children}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function SettingsPanel({ settings }: { settings: GitCanvasSettings }) {
|
|
80
|
+
const cardCols = Math.round(settings.cardWidth / 7.2);
|
|
81
|
+
return (
|
|
82
|
+
<div className="settings-modal">
|
|
83
|
+
<div className="settings-header">
|
|
84
|
+
<h2 className="settings-title">
|
|
85
|
+
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
|
|
86
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
87
|
+
<circle cx="12" cy="12" r="3" />
|
|
88
|
+
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z" />
|
|
89
|
+
</svg>
|
|
90
|
+
Settings
|
|
91
|
+
</h2>
|
|
92
|
+
<button className="settings-close" id="closeSettings">✕</button>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="settings-body">
|
|
95
|
+
{/* Rendering Section */}
|
|
96
|
+
<SettingsSection title="Rendering">
|
|
97
|
+
<SettingsRow label="Text Rendering" desc="Canvas (fast) or DOM (rich interactions)">
|
|
98
|
+
<ToggleGroup id="settingRenderMode" value={settings.renderMode}
|
|
99
|
+
options={[{ value: 'canvas', label: 'Canvas' }, { value: 'dom', label: 'DOM' }]} />
|
|
100
|
+
</SettingsRow>
|
|
101
|
+
<SettingsRow label="Font Size" desc="Code font size in pixels">
|
|
102
|
+
<Slider id="settingFontSize" valueId="fontSizeValue"
|
|
103
|
+
min={10} max={18} step={1} value={settings.fontSize} suffix="px" />
|
|
104
|
+
</SettingsRow>
|
|
105
|
+
<SettingsRow label="Popup Font Size" desc="Font size for hover popup previews">
|
|
106
|
+
<Slider id="settingPopupFontSize" valueId="popupFontSizeValue"
|
|
107
|
+
min={10} max={24} step={1} value={settings.popupFontSize} suffix="px" />
|
|
108
|
+
</SettingsRow>
|
|
109
|
+
<SettingsRow label="Card Width" desc="Character columns per card (like editors)">
|
|
110
|
+
<Slider id="settingCardWidth" valueId="cardWidthValue"
|
|
111
|
+
min={40} max={120} step={5} value={cardCols} suffix=" cols" />
|
|
112
|
+
</SettingsRow>
|
|
113
|
+
</SettingsSection>
|
|
114
|
+
|
|
115
|
+
{/* Interface Section */}
|
|
116
|
+
<SettingsSection title="Interface">
|
|
117
|
+
<SettingsRow label="Theme" desc="Dark or light appearance">
|
|
118
|
+
<ToggleGroup id="settingTheme" value={settings.theme}
|
|
119
|
+
options={[{ value: 'dark', label: '🌙 Dark' }, { value: 'light', label: '☀️ Light' }]} />
|
|
120
|
+
</SettingsRow>
|
|
121
|
+
<SettingsRow label="Control Mode" desc="Simple: drag=pan / Advanced: space+drag=pan">
|
|
122
|
+
<ToggleGroup id="settingControlMode" value={settings.controlMode}
|
|
123
|
+
options={[{ value: 'simple', label: 'Simple' }, { value: 'advanced', label: 'Advanced' }]} />
|
|
124
|
+
</SettingsRow>
|
|
125
|
+
<SettingsRow label="Show Minimap" desc="Overview map in the corner">
|
|
126
|
+
<Switch id="settingMinimap" checked={settings.showMinimap} />
|
|
127
|
+
</SettingsRow>
|
|
128
|
+
<SettingsRow label="Show Connections" desc="Lines between importing files">
|
|
129
|
+
<Switch id="settingConnections" checked={settings.showConnections} />
|
|
130
|
+
</SettingsRow>
|
|
131
|
+
<SettingsRow label="Auto-detect Imports" desc="Scan files for imports on load">
|
|
132
|
+
<Switch id="settingAutoImports" checked={settings.autoDetectImports} />
|
|
133
|
+
</SettingsRow>
|
|
134
|
+
</SettingsSection>
|
|
135
|
+
|
|
136
|
+
{/* Visualization Section */}
|
|
137
|
+
<SettingsSection title="Visualization">
|
|
138
|
+
<SettingsRow label="Git Heatmap" desc="Color-code cards by commit frequency (H key)">
|
|
139
|
+
<Switch id="settingHeatmap" checked={settings.heatmapEnabled} />
|
|
140
|
+
</SettingsRow>
|
|
141
|
+
<SettingsRow label="Heatmap Range" desc="Time range for commit activity">
|
|
142
|
+
<Slider id="settingHeatmapDays" valueId="heatmapDaysValue"
|
|
143
|
+
min={7} max={365} step={7} value={settings.heatmapDays} suffix=" days" />
|
|
144
|
+
</SettingsRow>
|
|
145
|
+
</SettingsSection>
|
|
146
|
+
|
|
147
|
+
{/* Advanced Section */}
|
|
148
|
+
<SettingsSection title="Advanced">
|
|
149
|
+
<SettingsRow label="Max Visible Lines" desc="Lines shown per card before virtual scroll">
|
|
150
|
+
<Slider id="settingMaxLines" valueId="maxLinesValue"
|
|
151
|
+
min={30} max={500} step={10} value={settings.maxVisibleLines} suffix="" />
|
|
152
|
+
</SettingsRow>
|
|
153
|
+
</SettingsSection>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="settings-footer">
|
|
156
|
+
<button className="settings-reset-btn" id="settingsReset">Reset to Defaults</button>
|
|
157
|
+
<span className="settings-footer-note">Changes are saved automatically</span>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Open the settings modal */
|
|
164
|
+
export function openSettingsModal(ctx?: any) {
|
|
165
|
+
// Remove existing modal if any
|
|
166
|
+
if (_modal) { _modal.remove(); _modal = null; }
|
|
167
|
+
|
|
168
|
+
const settings = getSettings();
|
|
169
|
+
|
|
170
|
+
_modal = document.createElement('div');
|
|
171
|
+
_modal.id = 'settingsModal';
|
|
172
|
+
_modal.className = 'settings-modal-backdrop';
|
|
173
|
+
Object.assign(_modal.style, {
|
|
174
|
+
position: 'fixed',
|
|
175
|
+
top: '0',
|
|
176
|
+
left: '0',
|
|
177
|
+
width: '100vw',
|
|
178
|
+
height: '100vh',
|
|
179
|
+
zIndex: '10000',
|
|
180
|
+
background: 'rgba(0, 0, 0, 0.6)',
|
|
181
|
+
backdropFilter: 'blur(8px)',
|
|
182
|
+
display: 'flex',
|
|
183
|
+
alignItems: 'center',
|
|
184
|
+
justifyContent: 'center',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
document.body.appendChild(_modal);
|
|
188
|
+
render(<SettingsPanel settings={settings} />, _modal);
|
|
189
|
+
|
|
190
|
+
// Wire close
|
|
191
|
+
const close = () => { if (_modal) { _modal.remove(); _modal = null; } };
|
|
192
|
+
_modal.querySelector('#closeSettings')!.addEventListener('click', close);
|
|
193
|
+
_modal.addEventListener('click', (e) => { if (e.target === _modal) close(); });
|
|
194
|
+
document.addEventListener('keydown', function onEsc(e) {
|
|
195
|
+
if (e.key === 'Escape' && _modal) { close(); document.removeEventListener('keydown', onEsc); }
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Wire render mode toggle
|
|
199
|
+
const renderModeBtns = _modal.querySelectorAll('#settingRenderMode .settings-toggle-btn');
|
|
200
|
+
renderModeBtns.forEach(btn => {
|
|
201
|
+
btn.addEventListener('click', () => {
|
|
202
|
+
renderModeBtns.forEach(b => b.classList.remove('active'));
|
|
203
|
+
btn.classList.add('active');
|
|
204
|
+
updateSettings({ renderMode: btn.dataset.value as 'canvas' | 'dom' });
|
|
205
|
+
applyRenderMode(ctx, btn.dataset.value as string);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Wire control mode toggle
|
|
210
|
+
const controlModeBtns = _modal.querySelectorAll('#settingControlMode .settings-toggle-btn');
|
|
211
|
+
controlModeBtns.forEach(btn => {
|
|
212
|
+
btn.addEventListener('click', () => {
|
|
213
|
+
controlModeBtns.forEach(b => b.classList.remove('active'));
|
|
214
|
+
btn.classList.add('active');
|
|
215
|
+
updateSettings({ controlMode: btn.dataset.value as 'simple' | 'advanced' });
|
|
216
|
+
applyControlMode(btn.dataset.value as string);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Wire theme toggle
|
|
221
|
+
const themeBtns = _modal.querySelectorAll('#settingTheme .settings-toggle-btn');
|
|
222
|
+
themeBtns.forEach(btn => {
|
|
223
|
+
btn.addEventListener('click', () => {
|
|
224
|
+
themeBtns.forEach(b => b.classList.remove('active'));
|
|
225
|
+
btn.classList.add('active');
|
|
226
|
+
updateSettings({ theme: btn.dataset.value as 'dark' | 'light' });
|
|
227
|
+
applyTheme(btn.dataset.value as string);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Wire sliders
|
|
232
|
+
const fontSlider = _modal.querySelector('#settingFontSize') as HTMLInputElement;
|
|
233
|
+
const fontValue = _modal.querySelector('#fontSizeValue')!;
|
|
234
|
+
fontSlider?.addEventListener('input', () => {
|
|
235
|
+
fontValue.textContent = `${fontSlider.value}px`;
|
|
236
|
+
updateSettings({ fontSize: parseInt(fontSlider.value) });
|
|
237
|
+
applyFontSize(parseInt(fontSlider.value));
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const popupFontSlider = _modal.querySelector('#settingPopupFontSize') as HTMLInputElement;
|
|
241
|
+
const popupFontValue = _modal.querySelector('#popupFontSizeValue')!;
|
|
242
|
+
popupFontSlider?.addEventListener('input', () => {
|
|
243
|
+
popupFontValue.textContent = `${popupFontSlider.value}px`;
|
|
244
|
+
updateSettings({ popupFontSize: parseInt(popupFontSlider.value) });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const cardWidthSlider = _modal.querySelector('#settingCardWidth') as HTMLInputElement;
|
|
248
|
+
const cardWidthValue = _modal.querySelector('#cardWidthValue')!;
|
|
249
|
+
cardWidthSlider?.addEventListener('input', () => {
|
|
250
|
+
const cols = parseInt(cardWidthSlider.value);
|
|
251
|
+
const px = Math.round(cols * 7.2);
|
|
252
|
+
cardWidthValue.textContent = `${cols} cols`;
|
|
253
|
+
updateSettings({ cardWidth: px });
|
|
254
|
+
applyCardWidth(px);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const maxLinesSlider = _modal.querySelector('#settingMaxLines') as HTMLInputElement;
|
|
258
|
+
const maxLinesValue = _modal.querySelector('#maxLinesValue')!;
|
|
259
|
+
maxLinesSlider?.addEventListener('input', () => {
|
|
260
|
+
maxLinesValue.textContent = maxLinesSlider.value;
|
|
261
|
+
updateSettings({ maxVisibleLines: parseInt(maxLinesSlider.value) });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Wire switches
|
|
265
|
+
const minimapSwitch = _modal.querySelector('#settingMinimap') as HTMLInputElement;
|
|
266
|
+
minimapSwitch?.addEventListener('change', () => {
|
|
267
|
+
updateSettings({ showMinimap: minimapSwitch.checked });
|
|
268
|
+
applyMinimap(minimapSwitch.checked);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const connectionsSwitch = _modal.querySelector('#settingConnections') as HTMLInputElement;
|
|
272
|
+
connectionsSwitch?.addEventListener('change', () => {
|
|
273
|
+
updateSettings({ showConnections: connectionsSwitch.checked });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const autoImportsSwitch = _modal.querySelector('#settingAutoImports') as HTMLInputElement;
|
|
277
|
+
autoImportsSwitch?.addEventListener('change', () => {
|
|
278
|
+
updateSettings({ autoDetectImports: autoImportsSwitch.checked });
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Wire heatmap switch
|
|
282
|
+
const heatmapSwitch = _modal.querySelector('#settingHeatmap') as HTMLInputElement;
|
|
283
|
+
heatmapSwitch?.addEventListener('change', () => {
|
|
284
|
+
updateSettings({ heatmapEnabled: heatmapSwitch.checked });
|
|
285
|
+
const repoPath = ctx?.snap?.()?.context?.repoPath;
|
|
286
|
+
if (repoPath) {
|
|
287
|
+
import('./heatmap').then(async ({ toggleHeatmap, injectHeatmapCSS, isHeatmapActive }) => {
|
|
288
|
+
injectHeatmapCSS();
|
|
289
|
+
// Only toggle if state differs from setting
|
|
290
|
+
if (heatmapSwitch.checked !== isHeatmapActive()) {
|
|
291
|
+
await toggleHeatmap(repoPath);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Wire heatmap days slider
|
|
298
|
+
const heatmapDaysSlider = _modal.querySelector('#settingHeatmapDays') as HTMLInputElement;
|
|
299
|
+
const heatmapDaysValue = _modal.querySelector('#heatmapDaysValue')!;
|
|
300
|
+
heatmapDaysSlider?.addEventListener('input', () => {
|
|
301
|
+
heatmapDaysValue.textContent = `${heatmapDaysSlider.value} days`;
|
|
302
|
+
updateSettings({ heatmapDays: parseInt(heatmapDaysSlider.value) });
|
|
303
|
+
const repoPath = ctx?.snap?.()?.context?.repoPath;
|
|
304
|
+
if (repoPath) {
|
|
305
|
+
import('./heatmap').then(async ({ refreshHeatmap, injectHeatmapCSS }) => {
|
|
306
|
+
injectHeatmapCSS();
|
|
307
|
+
await refreshHeatmap(repoPath);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Wire reset
|
|
313
|
+
_modal.querySelector('#settingsReset')!.addEventListener('click', () => {
|
|
314
|
+
const defaults = resetSettings();
|
|
315
|
+
close();
|
|
316
|
+
setTimeout(() => openSettingsModal(ctx), 50);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ─── Apply functions ─────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
function applyRenderMode(ctx: any, mode: string) {
|
|
323
|
+
if (!ctx) return;
|
|
324
|
+
ctx.useCanvasText = mode === 'canvas';
|
|
325
|
+
localStorage.setItem('gitcanvas:useCanvasText', String(ctx.useCanvasText));
|
|
326
|
+
const textToggle = document.getElementById('toggleCanvasText');
|
|
327
|
+
if (textToggle) textToggle.classList.toggle('active', ctx.useCanvasText);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function applyControlMode(mode: string) {
|
|
331
|
+
localStorage.setItem('gitcanvas:controlMode', mode);
|
|
332
|
+
const toggle = document.getElementById('toggleControlMode');
|
|
333
|
+
const icon = document.getElementById('controlModeIcon');
|
|
334
|
+
if (toggle) toggle.title = mode === 'simple'
|
|
335
|
+
? 'Toggle control mode: Simple (drag=pan) / Advanced (space+drag=pan)'
|
|
336
|
+
: 'Toggle control mode: Advanced (space+drag=pan) / Simple (drag=pan)';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function applyFontSize(size: number) {
|
|
340
|
+
document.documentElement.style.setProperty('--code-font-size', `${size}px`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function applyMinimap(show: boolean) {
|
|
344
|
+
const minimap = document.getElementById('minimapCanvas') || document.querySelector('.minimap-container');
|
|
345
|
+
if (minimap) (minimap as HTMLElement).style.display = show ? '' : 'none';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function applyCardWidth(width: number) {
|
|
349
|
+
document.documentElement.style.setProperty('--card-width', width + 'px');
|
|
350
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:card-width-changed', { detail: width }));
|
|
351
|
+
document.querySelectorAll('.file-card').forEach(card => {
|
|
352
|
+
const el = card as HTMLElement;
|
|
353
|
+
if (!el.style.height || el.style.height === '') {
|
|
354
|
+
el.style.width = `${width}px`;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Apply theme to document */
|
|
360
|
+
export function applyTheme(theme: string) {
|
|
361
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Apply all settings on startup */
|
|
365
|
+
export function applyAllSettings(ctx?: any) {
|
|
366
|
+
const s = getSettings();
|
|
367
|
+
applyFontSize(s.fontSize);
|
|
368
|
+
applyCardWidth(s.cardWidth);
|
|
369
|
+
applyTheme(s.theme);
|
|
370
|
+
if (ctx) {
|
|
371
|
+
ctx.useCanvasText = s.renderMode === 'canvas';
|
|
372
|
+
}
|
|
373
|
+
requestAnimationFrame(() => applyMinimap(s.showMinimap));
|
|
374
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Settings — persistent user preferences stored in localStorage.
|
|
4
|
+
* All settings have safe defaults and are loaded synchronously.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const STORAGE_KEY = 'gitcanvas:settings';
|
|
8
|
+
|
|
9
|
+
export interface GitCanvasSettings {
|
|
10
|
+
/** Text rendering mode: 'canvas' (default, fast) or 'dom' (rich, slower) */
|
|
11
|
+
renderMode: 'canvas' | 'dom';
|
|
12
|
+
/** Font size for code display (px) */
|
|
13
|
+
fontSize: number;
|
|
14
|
+
/** Show connection lines between imports */
|
|
15
|
+
showConnections: boolean;
|
|
16
|
+
/** Control mode: 'simple' (drag=pan) or 'advanced' (space+drag=pan) */
|
|
17
|
+
controlMode: 'simple' | 'advanced';
|
|
18
|
+
/** Show minimap */
|
|
19
|
+
showMinimap: boolean;
|
|
20
|
+
/** Card width (px) */
|
|
21
|
+
cardWidth: number;
|
|
22
|
+
/** Max visible lines before virtual scroll kicks in */
|
|
23
|
+
maxVisibleLines: number;
|
|
24
|
+
/** Auto-detect import connections on load */
|
|
25
|
+
autoDetectImports: boolean;
|
|
26
|
+
/** Theme: 'dark' (default) or 'light' */
|
|
27
|
+
theme: 'dark' | 'light';
|
|
28
|
+
/** Font size for hover popup (px) */
|
|
29
|
+
popupFontSize: number;
|
|
30
|
+
/** Git heatmap overlay enabled */
|
|
31
|
+
heatmapEnabled: boolean;
|
|
32
|
+
/** Heatmap time range in days */
|
|
33
|
+
heatmapDays: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULTS: GitCanvasSettings = {
|
|
37
|
+
renderMode: 'canvas',
|
|
38
|
+
fontSize: 12,
|
|
39
|
+
showConnections: true,
|
|
40
|
+
controlMode: 'simple',
|
|
41
|
+
showMinimap: true,
|
|
42
|
+
cardWidth: 540,
|
|
43
|
+
maxVisibleLines: 100,
|
|
44
|
+
autoDetectImports: false,
|
|
45
|
+
theme: 'dark',
|
|
46
|
+
popupFontSize: 14,
|
|
47
|
+
heatmapEnabled: false,
|
|
48
|
+
heatmapDays: 90,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let _settings: GitCanvasSettings | null = null;
|
|
52
|
+
|
|
53
|
+
/** Load settings from localStorage (synchronous, uses cache) */
|
|
54
|
+
export function getSettings(): GitCanvasSettings {
|
|
55
|
+
if (_settings) return _settings;
|
|
56
|
+
try {
|
|
57
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
58
|
+
if (raw) {
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
_settings = { ...DEFAULTS, ...parsed };
|
|
61
|
+
} else {
|
|
62
|
+
_settings = { ...DEFAULTS };
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
_settings = { ...DEFAULTS };
|
|
66
|
+
}
|
|
67
|
+
return _settings!;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Update one or more settings and persist */
|
|
71
|
+
export function updateSettings(partial: Partial<GitCanvasSettings>): GitCanvasSettings {
|
|
72
|
+
const current = getSettings();
|
|
73
|
+
Object.assign(current, partial);
|
|
74
|
+
_settings = current;
|
|
75
|
+
try {
|
|
76
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(current));
|
|
77
|
+
} catch { }
|
|
78
|
+
|
|
79
|
+
// Dispatch custom event so listeners can react
|
|
80
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:settings-changed', { detail: current }));
|
|
81
|
+
return current;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Reset all settings to defaults */
|
|
85
|
+
export function resetSettings(): GitCanvasSettings {
|
|
86
|
+
_settings = { ...DEFAULTS };
|
|
87
|
+
try {
|
|
88
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(_settings));
|
|
89
|
+
} catch { }
|
|
90
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:settings-changed', { detail: _settings }));
|
|
91
|
+
return _settings;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Get a single setting value */
|
|
95
|
+
export function getSetting<K extends keyof GitCanvasSettings>(key: K): GitCanvasSettings[K] {
|
|
96
|
+
return getSettings()[key];
|
|
97
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Shortcuts Panel — press ? to show all available shortcuts
|
|
3
|
+
*
|
|
4
|
+
* Collects all shortcuts scattered across the codebase into one
|
|
5
|
+
* discoverable, premium-looking reference panel.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isCommandPaletteOpen } from './command-palette';
|
|
9
|
+
|
|
10
|
+
const SHORTCUTS = [
|
|
11
|
+
{
|
|
12
|
+
category: 'Navigation', items: [
|
|
13
|
+
{ keys: ['Ctrl', 'K'], description: 'Quick file search (command palette)' },
|
|
14
|
+
{ keys: ['Ctrl', 'P'], description: 'Quick file search (VS Code style)' },
|
|
15
|
+
{ keys: ['Ctrl', 'O'], description: 'Quick file search' },
|
|
16
|
+
{ keys: ['Ctrl', 'F'], description: 'Global text search' },
|
|
17
|
+
{ keys: ['←', '→'], description: 'Navigate commits' },
|
|
18
|
+
{ keys: ['Space + Drag'], description: 'Pan canvas' },
|
|
19
|
+
{ keys: ['Scroll'], description: 'Zoom in/out' },
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
category: 'Selection & Arrangement', items: [
|
|
24
|
+
{ keys: ['Ctrl', 'A'], description: 'Select all cards' },
|
|
25
|
+
{ keys: ['Click'], description: 'Select card' },
|
|
26
|
+
{ keys: ['Shift + Click'], description: 'Multi-select cards' },
|
|
27
|
+
{ keys: ['H'], description: 'Arrange selected in row' },
|
|
28
|
+
{ keys: ['V'], description: 'Arrange selected in column' },
|
|
29
|
+
{ keys: ['G'], description: 'Arrange selected in grid' },
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
category: 'Cards', items: [
|
|
34
|
+
{ keys: ['F'], description: 'Toggle expand/collapse selected' },
|
|
35
|
+
{ keys: ['W'], description: 'Fit selected to screen' },
|
|
36
|
+
{ keys: ['Delete'], description: 'Hide selected cards' },
|
|
37
|
+
{ keys: ['Dbl-click'], description: 'Zoom into file' },
|
|
38
|
+
{ keys: ['Ctrl', '+'], description: 'Increase font size' },
|
|
39
|
+
{ keys: ['Ctrl', '−'], description: 'Decrease font size' },
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
category: 'Tools', items: [
|
|
44
|
+
{ keys: ['H'], description: 'Toggle git heatmap (no selection)' },
|
|
45
|
+
{ keys: ['Ctrl', 'N'], description: 'Create new file' },
|
|
46
|
+
{ keys: ['Shift + Click line'], description: 'Start connection' },
|
|
47
|
+
{ keys: ['Ctrl', 'Shift', 'E'], description: 'Export canvas as PNG' },
|
|
48
|
+
{ keys: ['Ctrl', 'Shift', 'V'], description: 'Export viewport as PNG' },
|
|
49
|
+
{ keys: ['Esc'], description: 'Deselect / cancel / close' },
|
|
50
|
+
{ keys: ['?'], description: 'Show this panel' },
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
let panel: HTMLElement | null = null;
|
|
56
|
+
|
|
57
|
+
function createPanel(): void {
|
|
58
|
+
panel = document.createElement('div');
|
|
59
|
+
panel.id = 'shortcuts-overlay';
|
|
60
|
+
|
|
61
|
+
const inner = document.createElement('div');
|
|
62
|
+
inner.id = 'shortcuts-panel';
|
|
63
|
+
|
|
64
|
+
inner.innerHTML = `
|
|
65
|
+
<div class="sp-header">
|
|
66
|
+
<h2>⌨️ Keyboard Shortcuts</h2>
|
|
67
|
+
<button class="sp-close" aria-label="Close">✕</button>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="sp-grid">
|
|
70
|
+
${SHORTCUTS.map(cat => `
|
|
71
|
+
<div class="sp-category">
|
|
72
|
+
<h3>${cat.category}</h3>
|
|
73
|
+
${cat.items.map(item => `
|
|
74
|
+
<div class="sp-row">
|
|
75
|
+
<div class="sp-keys">
|
|
76
|
+
${item.keys.map(k =>
|
|
77
|
+
k.includes('+') || k.includes('Drag') || k.includes('Click') || k.includes('click')
|
|
78
|
+
? `<span class="sp-key sp-key--text">${k}</span>`
|
|
79
|
+
: `<kbd>${k}</kbd>`
|
|
80
|
+
).join('<span class="sp-sep">+</span>')}
|
|
81
|
+
</div>
|
|
82
|
+
<span class="sp-desc">${item.description}</span>
|
|
83
|
+
</div>
|
|
84
|
+
`).join('')}
|
|
85
|
+
</div>
|
|
86
|
+
`).join('')}
|
|
87
|
+
</div>
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
panel.appendChild(inner);
|
|
91
|
+
document.body.appendChild(panel);
|
|
92
|
+
|
|
93
|
+
// Close handlers
|
|
94
|
+
panel.addEventListener('mousedown', (e) => {
|
|
95
|
+
if (e.target === panel) close();
|
|
96
|
+
});
|
|
97
|
+
inner.querySelector('.sp-close')?.addEventListener('click', close);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function open(): void {
|
|
101
|
+
if (!panel) createPanel();
|
|
102
|
+
panel!.style.display = 'flex';
|
|
103
|
+
// Re-run animation
|
|
104
|
+
const inner = panel!.querySelector('#shortcuts-panel') as HTMLElement;
|
|
105
|
+
if (inner) {
|
|
106
|
+
inner.style.animation = 'none';
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
inner.style.animation = '';
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function close(): void {
|
|
114
|
+
if (panel) panel.style.display = 'none';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isOpen(): boolean {
|
|
118
|
+
return panel?.style.display === 'flex';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function initShortcutsPanel(): void {
|
|
122
|
+
document.addEventListener('keydown', (e) => {
|
|
123
|
+
// Only trigger on `?` (Shift+/) when not in an input
|
|
124
|
+
const target = e.target as HTMLElement;
|
|
125
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;
|
|
126
|
+
if (isCommandPaletteOpen()) return;
|
|
127
|
+
|
|
128
|
+
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
if (isOpen()) {
|
|
131
|
+
close();
|
|
132
|
+
} else {
|
|
133
|
+
open();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (e.key === 'Escape' && isOpen()) {
|
|
138
|
+
close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|