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
package/app/lib/auth.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Galaxy Canvas — User Database & Session Management
|
|
3
|
+
*
|
|
4
|
+
* SQLite-backed user accounts with GitHub OAuth.
|
|
5
|
+
* Stores user profiles, favorites, and settings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Database, z } from 'sqlite-zod-orm';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
|
|
12
|
+
// ─── Database ────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const DB_PATH = path.join(import.meta.dir, '..', 'canvas_users.db');
|
|
15
|
+
|
|
16
|
+
export const db = new Database(DB_PATH, {
|
|
17
|
+
users: z.object({
|
|
18
|
+
githubId: z.string(), // GitHub numeric ID (unique)
|
|
19
|
+
username: z.string(), // GitHub login
|
|
20
|
+
displayName: z.string().default(''),
|
|
21
|
+
avatarUrl: z.string().default(''),
|
|
22
|
+
email: z.string().default(''),
|
|
23
|
+
createdAt: z.string().default(() => new Date().toISOString()),
|
|
24
|
+
lastLoginAt: z.string().default(() => new Date().toISOString()),
|
|
25
|
+
}),
|
|
26
|
+
sessions: z.object({
|
|
27
|
+
token: z.string(), // Random session token (cookie)
|
|
28
|
+
user_id: z.number(), // FK to users
|
|
29
|
+
expiresAt: z.string(), // ISO timestamp
|
|
30
|
+
createdAt: z.string().default(() => new Date().toISOString()),
|
|
31
|
+
}),
|
|
32
|
+
favorites: z.object({
|
|
33
|
+
user_id: z.number(), // FK to users
|
|
34
|
+
repoUrl: z.string(), // Git clone URL
|
|
35
|
+
repoName: z.string().default(''), // Display name
|
|
36
|
+
addedAt: z.string().default(() => new Date().toISOString()),
|
|
37
|
+
}),
|
|
38
|
+
settings: z.object({
|
|
39
|
+
user_id: z.number(), // FK to users
|
|
40
|
+
key: z.string(), // Setting key
|
|
41
|
+
value: z.string(), // Setting value (JSON or string)
|
|
42
|
+
}),
|
|
43
|
+
repo_positions: z.object({
|
|
44
|
+
user_id: z.number(), // FK to users
|
|
45
|
+
repoUrl: z.string(), // Repository URL/path
|
|
46
|
+
positionsJson: z.string().default('{}'), // JSON blob of all card positions
|
|
47
|
+
updatedAt: z.string().default(() => new Date().toISOString()),
|
|
48
|
+
}),
|
|
49
|
+
}, {
|
|
50
|
+
relations: {
|
|
51
|
+
sessions: { user_id: 'users' },
|
|
52
|
+
favorites: { user_id: 'users' },
|
|
53
|
+
settings: { user_id: 'users' },
|
|
54
|
+
repo_positions: { user_id: 'users' },
|
|
55
|
+
},
|
|
56
|
+
indexes: {
|
|
57
|
+
users: ['githubId', 'username'],
|
|
58
|
+
sessions: ['token', 'user_id'],
|
|
59
|
+
favorites: ['user_id', 'repoUrl'],
|
|
60
|
+
settings: ['user_id', 'key'],
|
|
61
|
+
repo_positions: ['user_id', 'repoUrl'],
|
|
62
|
+
},
|
|
63
|
+
reactive: false, // No need for reactivity
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ─── Session Helpers ─────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
69
|
+
|
|
70
|
+
export function createSession(userId: number): string {
|
|
71
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
72
|
+
const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString();
|
|
73
|
+
db.sessions.insert({ token, user_id: userId, expiresAt });
|
|
74
|
+
return token;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getSessionUser(token: string) {
|
|
78
|
+
if (!token) return null;
|
|
79
|
+
const session = db.sessions.select().where({ token }).get();
|
|
80
|
+
if (!session) return null;
|
|
81
|
+
|
|
82
|
+
// Check expiration
|
|
83
|
+
if (new Date(session.expiresAt) < new Date()) {
|
|
84
|
+
db.sessions.delete(session.id);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const user = db.users.select().where({ id: session.user_id }).get();
|
|
89
|
+
return user || null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function deleteSession(token: string): void {
|
|
93
|
+
const session = db.sessions.select().where({ token }).get();
|
|
94
|
+
if (session) db.sessions.delete(session.id);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── User Helpers ────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
export function findOrCreateUser(githubProfile: {
|
|
100
|
+
id: string;
|
|
101
|
+
login: string;
|
|
102
|
+
name?: string;
|
|
103
|
+
avatar_url?: string;
|
|
104
|
+
email?: string;
|
|
105
|
+
}) {
|
|
106
|
+
const existing = db.users.select().where({ githubId: githubProfile.id }).get();
|
|
107
|
+
if (existing) {
|
|
108
|
+
// Update last login and any changed profile data
|
|
109
|
+
existing.update({
|
|
110
|
+
lastLoginAt: new Date().toISOString(),
|
|
111
|
+
username: githubProfile.login,
|
|
112
|
+
displayName: githubProfile.name || existing.displayName,
|
|
113
|
+
avatarUrl: githubProfile.avatar_url || existing.avatarUrl,
|
|
114
|
+
email: githubProfile.email || existing.email,
|
|
115
|
+
});
|
|
116
|
+
return existing;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return db.users.insert({
|
|
120
|
+
githubId: githubProfile.id,
|
|
121
|
+
username: githubProfile.login,
|
|
122
|
+
displayName: githubProfile.name || githubProfile.login,
|
|
123
|
+
avatarUrl: githubProfile.avatar_url || '',
|
|
124
|
+
email: githubProfile.email || '',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Favorites Helpers ───────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
export function addFavorite(userId: number, repoUrl: string, repoName?: string) {
|
|
131
|
+
// Prevent duplicates
|
|
132
|
+
const existing = db.favorites.select().where({ user_id: userId, repoUrl }).get();
|
|
133
|
+
if (existing) return existing;
|
|
134
|
+
return db.favorites.insert({ user_id: userId, repoUrl, repoName: repoName || '' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function removeFavorite(userId: number, repoUrl: string): boolean {
|
|
138
|
+
const fav = db.favorites.select().where({ user_id: userId, repoUrl }).get();
|
|
139
|
+
if (!fav) return false;
|
|
140
|
+
db.favorites.delete(fav.id);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getUserFavorites(userId: number) {
|
|
145
|
+
return db.favorites.select().where({ user_id: userId }).orderBy('addedAt', 'desc').all();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Settings Helpers ────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
export function setSetting(userId: number, key: string, value: string) {
|
|
151
|
+
return db.settings.upsert(
|
|
152
|
+
{ user_id: userId, key },
|
|
153
|
+
{ user_id: userId, key, value },
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getSetting(userId: number, key: string): string | null {
|
|
158
|
+
const row = db.settings.select().where({ user_id: userId, key }).get();
|
|
159
|
+
return row?.value || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getAllSettings(userId: number): Record<string, string> {
|
|
163
|
+
const rows = db.settings.select().where({ user_id: userId }).all();
|
|
164
|
+
const result: Record<string, string> = {};
|
|
165
|
+
for (const row of rows) result[row.key] = row.value;
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Position Sync Helpers ───────────────────────────────────
|
|
170
|
+
|
|
171
|
+
export function saveRepoPositions(userId: number, repoUrl: string, positionsJson: string) {
|
|
172
|
+
return db.repo_positions.upsert(
|
|
173
|
+
{ user_id: userId, repoUrl },
|
|
174
|
+
{ user_id: userId, repoUrl, positionsJson, updatedAt: new Date().toISOString() },
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function loadRepoPositions(userId: number, repoUrl: string): string | null {
|
|
179
|
+
const row = db.repo_positions.select().where({ user_id: userId, repoUrl }).get();
|
|
180
|
+
return row?.positionsJson || null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── Auth from Request ───────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
export function getSessionFromRequest(req: Request) {
|
|
186
|
+
const cookie = req.headers.get('cookie') || '';
|
|
187
|
+
const match = cookie.match(/gc_session=([a-f0-9]+)/);
|
|
188
|
+
return match ? getSessionUser(match[1]) : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function sessionCookie(token: string, maxAge = 30 * 24 * 60 * 60): string {
|
|
192
|
+
return `gc_session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`;
|
|
193
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Auto-save drafts for the file editor modal.
|
|
4
|
+
* Stores unsaved edits in localStorage so they survive page refresh,
|
|
5
|
+
* accidental close, or browser crash.
|
|
6
|
+
*
|
|
7
|
+
* Draft key: `gitcanvas:draft:<repoPath>:<filePath>`
|
|
8
|
+
* Each draft stores: { content, timestamp, originalContent }
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DRAFT_PREFIX = 'gitcanvas:draft:';
|
|
12
|
+
const AUTO_SAVE_INTERVAL_MS = 3000; // save draft every 3 seconds if dirty
|
|
13
|
+
const DRAFT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // expire after 7 days
|
|
14
|
+
|
|
15
|
+
let _autoSaveTimer: any = null;
|
|
16
|
+
let _lastSavedContent: string | null = null;
|
|
17
|
+
|
|
18
|
+
/** Build the localStorage key for a draft */
|
|
19
|
+
function draftKey(repoPath: string, filePath: string): string {
|
|
20
|
+
return `${DRAFT_PREFIX}${repoPath}:${filePath}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DraftData {
|
|
24
|
+
content: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
originalContent: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Save a draft to localStorage */
|
|
30
|
+
export function saveDraft(repoPath: string, filePath: string, content: string, originalContent: string): void {
|
|
31
|
+
if (content === originalContent) {
|
|
32
|
+
// No changes — remove any existing draft
|
|
33
|
+
clearDraft(repoPath, filePath);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const key = draftKey(repoPath, filePath);
|
|
38
|
+
const draft: DraftData = {
|
|
39
|
+
content,
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
originalContent,
|
|
42
|
+
};
|
|
43
|
+
localStorage.setItem(key, JSON.stringify(draft));
|
|
44
|
+
_lastSavedContent = content;
|
|
45
|
+
} catch {
|
|
46
|
+
// localStorage full or unavailable — silently skip
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Load a draft from localStorage (returns null if none or expired) */
|
|
51
|
+
export function loadDraft(repoPath: string, filePath: string): DraftData | null {
|
|
52
|
+
try {
|
|
53
|
+
const key = draftKey(repoPath, filePath);
|
|
54
|
+
const raw = localStorage.getItem(key);
|
|
55
|
+
if (!raw) return null;
|
|
56
|
+
const draft: DraftData = JSON.parse(raw);
|
|
57
|
+
|
|
58
|
+
// Skip if expired
|
|
59
|
+
if (Date.now() - draft.timestamp > DRAFT_MAX_AGE_MS) {
|
|
60
|
+
localStorage.removeItem(key);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return draft;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Clear a draft (called on explicit save or discard) */
|
|
71
|
+
export function clearDraft(repoPath: string, filePath: string): void {
|
|
72
|
+
try {
|
|
73
|
+
const key = draftKey(repoPath, filePath);
|
|
74
|
+
localStorage.removeItem(key);
|
|
75
|
+
} catch { }
|
|
76
|
+
_lastSavedContent = null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Start auto-save timer for the current editor session */
|
|
80
|
+
export function startAutoSave(
|
|
81
|
+
repoPath: string,
|
|
82
|
+
filePath: string,
|
|
83
|
+
getContent: () => string,
|
|
84
|
+
originalContent: string
|
|
85
|
+
): void {
|
|
86
|
+
stopAutoSave();
|
|
87
|
+
_lastSavedContent = null;
|
|
88
|
+
|
|
89
|
+
_autoSaveTimer = setInterval(() => {
|
|
90
|
+
const current = getContent();
|
|
91
|
+
// Only write if content actually changed since last auto-save
|
|
92
|
+
if (current !== _lastSavedContent) {
|
|
93
|
+
saveDraft(repoPath, filePath, current, originalContent);
|
|
94
|
+
}
|
|
95
|
+
}, AUTO_SAVE_INTERVAL_MS);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Stop the auto-save timer */
|
|
99
|
+
export function stopAutoSave(): void {
|
|
100
|
+
if (_autoSaveTimer) {
|
|
101
|
+
clearInterval(_autoSaveTimer);
|
|
102
|
+
_autoSaveTimer = null;
|
|
103
|
+
}
|
|
104
|
+
_lastSavedContent = null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Check if a draft exists (quick check without parsing) */
|
|
108
|
+
export function hasDraft(repoPath: string, filePath: string): boolean {
|
|
109
|
+
try {
|
|
110
|
+
return localStorage.getItem(draftKey(repoPath, filePath)) !== null;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Clean up all expired drafts (call on app startup) */
|
|
117
|
+
export function cleanExpiredDrafts(): void {
|
|
118
|
+
try {
|
|
119
|
+
const keys: string[] = [];
|
|
120
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
121
|
+
const key = localStorage.key(i);
|
|
122
|
+
if (key?.startsWith(DRAFT_PREFIX)) keys.push(key);
|
|
123
|
+
}
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
try {
|
|
126
|
+
const raw = localStorage.getItem(key);
|
|
127
|
+
if (!raw) continue;
|
|
128
|
+
const draft = JSON.parse(raw);
|
|
129
|
+
if (Date.now() - draft.timestamp > DRAFT_MAX_AGE_MS) {
|
|
130
|
+
localStorage.removeItem(key);
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
localStorage.removeItem(key);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch { }
|
|
137
|
+
}
|