@digital-ai/dot-illustrations 2.0.35 → 2.0.37
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/demo/github-upload.js +40 -8
- package/demo/index.html +223 -169
- package/demo/local-proxy.js +90 -0
- package/demo/script.js +107 -60
- package/package.json +1 -1
package/demo/github-upload.js
CHANGED
|
@@ -15,23 +15,54 @@ const GITHUB_UPLOAD_CONFIG = {
|
|
|
15
15
|
REPO_NAME: 'dot-illustrations',
|
|
16
16
|
BASE_BRANCH: 'main',
|
|
17
17
|
SCOPES: 'public_repo',
|
|
18
|
+
LOCAL_PROXY: 'http://localhost:3001', // run: node demo/local-proxy.js
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
const STORAGE_USER_KEY = 'gh_illus_user';
|
|
21
|
+
// ─── In-memory only — never stored, cleared on page close ────────────────────
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
let _ghToken = null;
|
|
24
|
+
let _ghUser = null;
|
|
25
|
+
let _usingProxy = false;
|
|
24
26
|
|
|
25
|
-
function ghGetToken() { return
|
|
26
|
-
function ghSetToken(t) {
|
|
27
|
-
function ghClearToken() {
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
function ghGetToken() { return _ghToken; }
|
|
28
|
+
function ghSetToken(t) { _ghToken = t; }
|
|
29
|
+
function ghClearToken() { _ghToken = null; _ghUser = null; _usingProxy = false; }
|
|
30
|
+
|
|
31
|
+
// ─── Auto-connect via local proxy ────────────────────────────────────────────
|
|
32
|
+
// Called once on page load. If `node demo/local-proxy.js` is running, the user
|
|
33
|
+
// is authenticated automatically using their existing `gh auth` credentials —
|
|
34
|
+
// no token input required.
|
|
35
|
+
|
|
36
|
+
async function ghTryLocalProxy() {
|
|
37
|
+
try {
|
|
38
|
+
const resp = await fetch(`${GITHUB_UPLOAD_CONFIG.LOCAL_PROXY}/whoami`, {
|
|
39
|
+
signal: AbortSignal.timeout(900),
|
|
40
|
+
});
|
|
41
|
+
if (!resp.ok) return null;
|
|
42
|
+
const data = await resp.json();
|
|
43
|
+
if (data.login) {
|
|
44
|
+
_usingProxy = true;
|
|
45
|
+
_ghToken = '__proxy__'; // sentinel; actual auth lives in the proxy process
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
} catch { /* proxy not running */ }
|
|
49
|
+
return null;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
// ─── GitHub API helpers ──────────────────────────────────────────────────────
|
|
33
53
|
|
|
34
54
|
async function ghApi(path, opts = {}) {
|
|
55
|
+
// Route through local proxy when available (no token exposed to browser)
|
|
56
|
+
if (_usingProxy) {
|
|
57
|
+
const resp = await fetch(`${GITHUB_UPLOAD_CONFIG.LOCAL_PROXY}${path}`, {
|
|
58
|
+
...opts,
|
|
59
|
+
headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) },
|
|
60
|
+
});
|
|
61
|
+
const data = await resp.json();
|
|
62
|
+
if (!resp.ok) throw new Error(data.message || `GitHub API error ${resp.status}`);
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
const token = ghGetToken();
|
|
36
67
|
const resp = await fetch(`https://api.github.com${path}`, {
|
|
37
68
|
...opts,
|
|
@@ -282,6 +313,7 @@ window.GitHubUpload = {
|
|
|
282
313
|
setToken: ghSetToken,
|
|
283
314
|
clearToken: ghClearToken,
|
|
284
315
|
getUser: ghGetUser,
|
|
316
|
+
tryLocalProxy: ghTryLocalProxy,
|
|
285
317
|
startDeviceFlow: ghStartDeviceFlow,
|
|
286
318
|
pollForToken: ghPollForToken,
|
|
287
319
|
createIllustrationPR: ghCreateIllustrationPR,
|
package/demo/index.html
CHANGED
|
@@ -3,226 +3,280 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<title>Dot Illustrations Library</title>
|
|
6
|
-
<!-- Tailwind CSS CDN -->
|
|
7
6
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
-
<!-- Heroicons CDN for Tailwind icons -->
|
|
9
|
-
<script src="https://unpkg.com/@heroicons/vue@2.0.18/dist/heroicons.min.js"></script>
|
|
10
7
|
<link rel="stylesheet" href="../index.css" />
|
|
11
8
|
<link rel="stylesheet" href="./demo.css" />
|
|
12
9
|
<script src="./github-upload.js" defer></script>
|
|
13
10
|
<script src="script.js" defer></script>
|
|
14
|
-
<script>
|
|
15
|
-
// Tailwind dark mode config
|
|
16
|
-
tailwind.config = { darkMode: 'class' };
|
|
17
|
-
</script>
|
|
11
|
+
<script>tailwind.config = { darkMode: 'class' };</script>
|
|
18
12
|
</head>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<button class="floating-
|
|
29
|
-
<button class="floating-
|
|
13
|
+
|
|
14
|
+
<body class="bg-gray-50 dark:bg-gray-950 min-h-screen transition-colors duration-300 antialiased">
|
|
15
|
+
|
|
16
|
+
<!-- ── Sticky top bar (appears on scroll) ────────────────────────────── -->
|
|
17
|
+
<div id="floating-bar" class="fixed top-0 left-0 w-full z-50 bg-white/95 dark:bg-gray-900/95 border-b border-gray-200 dark:border-gray-800 px-4 py-2 shadow-sm" style="backdrop-filter:blur(8px);">
|
|
18
|
+
<div class="flex items-center gap-3 max-w-7xl mx-auto">
|
|
19
|
+
<input id="floatingInput" type="text" autocomplete="off" placeholder="Search…"
|
|
20
|
+
class="flex-1 min-w-0 px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-800 border border-transparent focus:border-blue-500 focus:bg-white dark:focus:bg-gray-900 rounded-lg outline-none text-gray-900 dark:text-white transition">
|
|
21
|
+
<div class="flex gap-1 shrink-0" id="floating-section-btns">
|
|
22
|
+
<button data-section="illustrations" class="floating-section-btn px-3 py-1.5 text-xs font-medium rounded-lg bg-blue-600 text-white focus:outline-none transition">Illustrations</button>
|
|
23
|
+
<button data-section="integrations" class="floating-section-btn px-3 py-1.5 text-xs font-medium rounded-lg bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none transition">Integrations</button>
|
|
24
|
+
</div>
|
|
25
|
+
<nav id="floating-tabs" class="hidden items-center gap-0.5 bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5" role="tablist">
|
|
26
|
+
<!-- injected by JS -->
|
|
30
27
|
</nav>
|
|
31
28
|
</div>
|
|
32
29
|
</div>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
|
|
31
|
+
<!-- ── Main header ────────────────────────────────────────────────────── -->
|
|
32
|
+
<header class="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 px-4 py-4">
|
|
33
|
+
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
|
34
|
+
|
|
35
|
+
<!-- Title + subtitle -->
|
|
36
|
+
<div>
|
|
37
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white tracking-tight">Dot Illustrations</h1>
|
|
38
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
39
|
+
<a href="https://github.com/digital-ai/dot-illustrations" target="_blank" class="hover:text-blue-600 dark:hover:text-blue-400 transition">github.com/digital-ai/dot-illustrations</a>
|
|
40
|
+
·
|
|
41
|
+
<code class="font-mono">link rel="stylesheet" href="./index.css"</code>
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- Toolbar: Upload · GitHub · Theme -->
|
|
46
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
47
|
+
|
|
48
|
+
<!-- Upload New -->
|
|
49
|
+
<button id="upload-new-btn"
|
|
50
|
+
class="flex items-center gap-1.5 px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg shadow-sm transition focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1">
|
|
51
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/></svg>
|
|
52
|
+
Upload New
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<!-- GitHub auth -->
|
|
56
|
+
<div id="gh-auth-area" class="flex">
|
|
57
|
+
<button id="gh-connect-btn"
|
|
58
|
+
class="flex items-center gap-1.5 px-3 py-2 bg-gray-900 dark:bg-gray-700 hover:bg-gray-800 dark:hover:bg-gray-600 text-white text-sm font-medium rounded-lg shadow-sm transition focus:outline-none focus:ring-2 focus:ring-gray-500">
|
|
59
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.015 2.898-.015 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
|
41
60
|
Connect GitHub
|
|
42
61
|
</button>
|
|
43
|
-
<span id="gh-user-badge" class="hidden items-center gap-2 px-3 py-2 bg-green-
|
|
44
|
-
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
|
|
62
|
+
<span id="gh-user-badge" class="hidden items-center gap-2 px-3 py-2 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-800 text-green-800 dark:text-green-300 text-sm font-medium rounded-lg">
|
|
63
|
+
<svg class="w-4 h-4 text-green-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
|
|
45
64
|
<span id="gh-user-name"></span>
|
|
46
|
-
<button id="gh-logout-btn" class="ml-1 text-xs
|
|
65
|
+
<button id="gh-logout-btn" class="ml-1 text-xs opacity-50 hover:opacity-100 transition">Log out</button>
|
|
47
66
|
</span>
|
|
48
67
|
</div>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
68
|
+
|
|
69
|
+
<!-- Dark mode -->
|
|
70
|
+
<button id="theme-toggle" aria-label="Toggle dark mode"
|
|
71
|
+
class="p-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-lg transition focus:outline-none focus:ring-2 focus:ring-gray-400">
|
|
72
|
+
<span id="theme-icon"></span>
|
|
53
73
|
</button>
|
|
54
74
|
</div>
|
|
55
75
|
</div>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
76
|
+
</header>
|
|
77
|
+
|
|
78
|
+
<!-- ── Page body ──────────────────────────────────────────────────────── -->
|
|
79
|
+
<main class="max-w-7xl mx-auto px-4 py-6">
|
|
80
|
+
|
|
81
|
+
<!-- Search -->
|
|
82
|
+
<div class="relative mb-5 max-w-xl">
|
|
83
|
+
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"/></svg>
|
|
84
|
+
<input id="myInput" type="text" autocomplete="off" placeholder="Search illustrations and integrations…"
|
|
85
|
+
class="w-full pl-10 pr-4 py-2.5 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl text-sm text-gray-900 dark:text-white outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition">
|
|
86
|
+
<div id="search-suggestions" class="absolute top-full left-0 right-0 z-50 mt-1 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg hidden max-h-64 overflow-y-auto"></div>
|
|
64
87
|
</div>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
|
|
89
|
+
<!-- Section switcher: Illustrations | Integrations -->
|
|
90
|
+
<div class="flex items-center gap-1 mb-5 bg-gray-100 dark:bg-gray-800 rounded-xl p-1 w-fit" id="section-switcher">
|
|
91
|
+
<button id="section-illustrations" data-section="illustrations"
|
|
92
|
+
class="section-btn px-5 py-2 text-sm font-semibold rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm focus:outline-none transition">
|
|
93
|
+
🖼 Illustrations
|
|
94
|
+
</button>
|
|
95
|
+
<button id="section-integrations" data-section="integrations"
|
|
96
|
+
class="section-btn px-5 py-2 text-sm font-semibold rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition">
|
|
97
|
+
🔌 Integrations
|
|
98
|
+
</button>
|
|
75
99
|
</div>
|
|
76
100
|
|
|
77
|
-
<!--
|
|
78
|
-
<div id="
|
|
79
|
-
<button class="
|
|
101
|
+
<!-- Illustration sub-tabs -->
|
|
102
|
+
<div id="illus-sub-tabs" class="flex items-center gap-1 mb-4 bg-gray-100 dark:bg-gray-800 rounded-xl p-1 w-fit" role="tablist">
|
|
103
|
+
<button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm focus:outline-none transition" data-tab="all" role="tab">All</button>
|
|
104
|
+
<button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition" data-tab="global" role="tab">Global</button>
|
|
105
|
+
<button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition" data-tab="dashboards" role="tab">Dashboards</button>
|
|
106
|
+
<button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition" data-tab="favourites" role="tab">Favourites ⭐</button>
|
|
80
107
|
</div>
|
|
81
108
|
|
|
82
|
-
<!--
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
<!-- Alphabet filter -->
|
|
110
|
+
<div id="alphabet-strip" class="hidden flex flex-wrap gap-1 mb-4"></div>
|
|
111
|
+
|
|
112
|
+
<!-- Result count -->
|
|
113
|
+
<p id="result-count" class="text-xs text-gray-400 dark:text-gray-500 mb-3"></p>
|
|
87
114
|
|
|
88
115
|
<!-- Grid -->
|
|
89
|
-
<div id="illustration-list"
|
|
90
|
-
|
|
116
|
+
<div id="illustration-list"
|
|
117
|
+
class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3 max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
|
|
91
118
|
</div>
|
|
92
119
|
|
|
93
|
-
<!-- Upload panel -->
|
|
94
|
-
<div id="upload-panel" class="hidden mt-
|
|
95
|
-
<div class="
|
|
120
|
+
<!-- Upload panel (slide-in below header) -->
|
|
121
|
+
<div id="upload-panel" class="hidden mt-6">
|
|
122
|
+
<div class="bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-lg p-6 max-w-2xl">
|
|
123
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-5">Upload new asset → Create PR</h2>
|
|
124
|
+
<div class="grid md:grid-cols-2 gap-6">
|
|
96
125
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<input id="upload-illus-id" type="text" placeholder="e.g. my-illustration" class="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
126
|
+
<!-- Illustration upload -->
|
|
127
|
+
<div class="flex flex-col gap-3">
|
|
128
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 flex items-center gap-2">
|
|
129
|
+
<span class="w-5 h-5 bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300 rounded text-xs flex items-center justify-center">🖼</span>
|
|
130
|
+
Illustration (light + dark)
|
|
131
|
+
</h3>
|
|
132
|
+
<div id="upload-illus-auth-gate" class="flex items-center gap-2 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg text-xs text-amber-700 dark:text-amber-300">
|
|
133
|
+
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
|
|
134
|
+
Connect GitHub to enable uploads
|
|
107
135
|
</div>
|
|
108
|
-
<div>
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
<div id="upload-illus-form" class="hidden flex flex-col gap-3">
|
|
137
|
+
<input id="upload-illus-id" type="text" placeholder="Illustration ID (e.g. my-illustration)"
|
|
138
|
+
class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
|
|
139
|
+
<select id="upload-illus-category"
|
|
140
|
+
class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
|
|
141
|
+
<option value="global">Global</option>
|
|
112
142
|
<option value="dashboards">Dashboards</option>
|
|
113
143
|
</select>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
</
|
|
122
|
-
<
|
|
144
|
+
<div class="grid grid-cols-2 gap-2">
|
|
145
|
+
<label class="flex flex-col gap-1">
|
|
146
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">☀️ Light SVG</span>
|
|
147
|
+
<div id="illus-light-drop" class="border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg p-3 text-center text-xs text-gray-400 cursor-pointer hover:border-blue-400 transition">
|
|
148
|
+
Drop or click<input id="illus-light-input" type="file" accept=".svg" class="hidden">
|
|
149
|
+
</div>
|
|
150
|
+
<p id="illus-light-name" class="hidden text-xs text-green-600 dark:text-green-400 truncate"></p>
|
|
151
|
+
</label>
|
|
152
|
+
<label class="flex flex-col gap-1">
|
|
153
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">🌙 Dark SVG</span>
|
|
154
|
+
<div id="illus-dark-drop" class="border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg p-3 text-center text-xs text-gray-400 cursor-pointer hover:border-blue-400 transition">
|
|
155
|
+
Drop or click<input id="illus-dark-input" type="file" accept=".svg" class="hidden">
|
|
156
|
+
</div>
|
|
157
|
+
<p id="illus-dark-name" class="hidden text-xs text-green-600 dark:text-green-400 truncate"></p>
|
|
158
|
+
</label>
|
|
123
159
|
</div>
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<
|
|
160
|
+
<button id="upload-illus-btn"
|
|
161
|
+
class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition disabled:opacity-50">
|
|
162
|
+
Create Pull Request
|
|
163
|
+
</button>
|
|
164
|
+
<div id="upload-illus-status" class="hidden text-xs text-center"></div>
|
|
165
|
+
<div id="upload-illus-pr-link" class="hidden">
|
|
166
|
+
<a id="upload-illus-pr-anchor" href="#" target="_blank"
|
|
167
|
+
class="flex items-center justify-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition">
|
|
168
|
+
View Pull Request ↗
|
|
169
|
+
</a>
|
|
131
170
|
</div>
|
|
132
171
|
</div>
|
|
133
|
-
<button id="upload-illus-btn" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium text-sm transition focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50">Create PR</button>
|
|
134
|
-
<div id="upload-illus-status" class="hidden text-sm text-center"></div>
|
|
135
|
-
<div id="upload-illus-pr-link" class="hidden text-center">
|
|
136
|
-
<a id="upload-illus-pr-anchor" href="#" target="_blank" class="inline-flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium text-sm transition">View Pull Request ↗</a>
|
|
137
|
-
</div>
|
|
138
172
|
</div>
|
|
139
|
-
</div>
|
|
140
173
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<input id="upload-integ-id" type="text" placeholder="e.g. my-tool" class="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
174
|
+
<!-- Integration upload -->
|
|
175
|
+
<div class="flex flex-col gap-3">
|
|
176
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 flex items-center gap-2">
|
|
177
|
+
<span class="w-5 h-5 bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300 rounded text-xs flex items-center justify-center">🔌</span>
|
|
178
|
+
Integration logo (single SVG)
|
|
179
|
+
</h3>
|
|
180
|
+
<div id="upload-integ-auth-gate" class="flex items-center gap-2 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg text-xs text-amber-700 dark:text-amber-300">
|
|
181
|
+
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
|
|
182
|
+
Connect GitHub to enable uploads
|
|
151
183
|
</div>
|
|
152
|
-
<div>
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<input id="integ-svg-input" type="file" accept=".svg" class="hidden"
|
|
184
|
+
<div id="upload-integ-form" class="hidden flex flex-col gap-3">
|
|
185
|
+
<input id="upload-integ-id" type="text" placeholder="Integration ID (e.g. my-tool)"
|
|
186
|
+
class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
|
|
187
|
+
<div id="integ-svg-drop" class="border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg p-4 text-center text-sm text-gray-400 cursor-pointer hover:border-blue-400 transition">
|
|
188
|
+
Drop SVG here or click to browse<input id="integ-svg-input" type="file" accept=".svg" class="hidden">
|
|
189
|
+
</div>
|
|
190
|
+
<p id="integ-svg-name" class="hidden text-xs text-green-600 dark:text-green-400 truncate"></p>
|
|
191
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">Single-theme only — no dark variant for integrations.</p>
|
|
192
|
+
<button id="upload-integ-btn"
|
|
193
|
+
class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition disabled:opacity-50">
|
|
194
|
+
Create Pull Request
|
|
195
|
+
</button>
|
|
196
|
+
<div id="upload-integ-status" class="hidden text-xs text-center"></div>
|
|
197
|
+
<div id="upload-integ-pr-link" class="hidden">
|
|
198
|
+
<a id="upload-integ-pr-anchor" href="#" target="_blank"
|
|
199
|
+
class="flex items-center justify-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition">
|
|
200
|
+
View Pull Request ↗
|
|
201
|
+
</a>
|
|
157
202
|
</div>
|
|
158
|
-
<p id="integ-svg-name" class="text-xs text-green-600 dark:text-green-400 mt-1 truncate hidden"></p>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="p-3 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700 rounded-lg text-xs text-blue-800 dark:text-blue-200">
|
|
161
|
-
Integration logos have no dark variant. Use a square, brand-accurate SVG.
|
|
162
|
-
</div>
|
|
163
|
-
<button id="upload-integ-btn" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium text-sm transition focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50">Create PR</button>
|
|
164
|
-
<div id="upload-integ-status" class="hidden text-sm text-center"></div>
|
|
165
|
-
<div id="upload-integ-pr-link" class="hidden text-center">
|
|
166
|
-
<a id="upload-integ-pr-anchor" href="#" target="_blank" class="inline-flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium text-sm transition">View Pull Request ↗</a>
|
|
167
203
|
</div>
|
|
168
204
|
</div>
|
|
205
|
+
|
|
169
206
|
</div>
|
|
170
207
|
</div>
|
|
171
208
|
</div>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
<div
|
|
185
|
-
<
|
|
186
|
-
<
|
|
209
|
+
|
|
210
|
+
</main>
|
|
211
|
+
|
|
212
|
+
<!-- ── Illustration detail modal ──────────────────────────────────────── -->
|
|
213
|
+
<div id="illustration-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 dark:bg-black/60 backdrop-blur-sm hidden">
|
|
214
|
+
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl max-w-md w-full mx-4 p-6 relative flex flex-col items-center border border-gray-200 dark:border-gray-700">
|
|
215
|
+
<button id="modal-close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition">
|
|
216
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
217
|
+
</button>
|
|
218
|
+
<div id="modal-image" class="mb-4 p-4 bg-gray-50 dark:bg-gray-800 rounded-xl"></div>
|
|
219
|
+
<p class="text-base font-semibold text-gray-900 dark:text-white mb-4" id="modal-illustration-name"></p>
|
|
220
|
+
<div class="w-full space-y-3 mb-4">
|
|
221
|
+
<div>
|
|
222
|
+
<label class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1 block">Dot Code</label>
|
|
223
|
+
<div class="flex gap-2">
|
|
224
|
+
<textarea id="modal-dot-code" readonly rows="2"
|
|
225
|
+
class="flex-1 px-3 py-2 text-xs font-mono bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white resize-none outline-none"></textarea>
|
|
226
|
+
<button id="modal-copy-dot-code"
|
|
227
|
+
class="px-3 py-1 text-xs bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-900/30 transition">Copy</button>
|
|
228
|
+
</div>
|
|
187
229
|
</div>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
230
|
+
<div>
|
|
231
|
+
<label class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1 block">HTML Code</label>
|
|
232
|
+
<div class="flex gap-2">
|
|
233
|
+
<textarea id="modal-code" readonly rows="2"
|
|
234
|
+
class="flex-1 px-3 py-2 text-xs font-mono bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white resize-none outline-none"></textarea>
|
|
235
|
+
<button id="modal-copy-code"
|
|
236
|
+
class="px-3 py-1 text-xs bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-900/30 transition">Copy</button>
|
|
237
|
+
</div>
|
|
194
238
|
</div>
|
|
195
239
|
</div>
|
|
196
|
-
<button id="modal-close-bottom"
|
|
240
|
+
<button id="modal-close-bottom"
|
|
241
|
+
class="px-5 py-2 text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition">Close</button>
|
|
197
242
|
</div>
|
|
198
243
|
</div>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<
|
|
208
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
244
|
+
|
|
245
|
+
<!-- ── GitHub Device Flow modal ───────────────────────────────────────── -->
|
|
246
|
+
<div id="gh-device-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 dark:bg-black/60 backdrop-blur-sm hidden">
|
|
247
|
+
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl max-w-sm w-full mx-4 p-6 relative border border-gray-200 dark:border-gray-700">
|
|
248
|
+
<button id="gh-device-close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition">
|
|
249
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
250
|
+
</button>
|
|
251
|
+
<div class="flex flex-col items-center gap-4">
|
|
252
|
+
<svg class="w-10 h-10 text-gray-800 dark:text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.015 2.898-.015 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
|
253
|
+
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Connect GitHub</h2>
|
|
254
|
+
|
|
255
|
+
<div id="gh-device-step-code" class="w-full flex flex-col items-center gap-3">
|
|
256
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 text-center">Open the URL below and enter the code.</p>
|
|
257
|
+
<a id="gh-device-uri" href="#" target="_blank" class="text-sm text-blue-600 dark:text-blue-400 underline font-medium"></a>
|
|
258
|
+
<div class="flex items-center gap-2">
|
|
259
|
+
<code id="gh-device-code" class="text-2xl font-mono tracking-widest bg-gray-100 dark:bg-gray-800 px-4 py-2 rounded-xl text-gray-900 dark:text-white"></code>
|
|
260
|
+
<button id="gh-copy-code-btn" class="text-xs px-2 py-1 bg-gray-200 dark:bg-gray-700 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">Copy</button>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="flex items-center gap-2 text-gray-400 text-sm">
|
|
263
|
+
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path></svg>
|
|
264
|
+
Waiting for authorisation…
|
|
265
|
+
</div>
|
|
211
266
|
</div>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
267
|
+
|
|
268
|
+
<div id="gh-device-step-pat" class="w-full flex flex-col gap-3 hidden">
|
|
269
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 text-center">Enter a GitHub Personal Access Token with <code class="font-mono text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">public_repo</code> scope.</p>
|
|
270
|
+
<input id="gh-pat-input" type="password" placeholder="ghp_…"
|
|
271
|
+
class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
|
|
272
|
+
<a href="https://github.com/settings/tokens/new?scopes=public_repo&description=dot-illustrations+demo" target="_blank" class="text-xs text-blue-600 dark:text-blue-400 underline text-center">Create token on GitHub ↗</a>
|
|
273
|
+
<button id="gh-pat-submit" class="w-full px-4 py-2 bg-gray-900 dark:bg-gray-700 hover:bg-gray-800 dark:hover:bg-gray-600 text-white text-sm font-medium rounded-lg transition">Connect</button>
|
|
215
274
|
</div>
|
|
275
|
+
|
|
276
|
+
<div id="gh-device-error" class="hidden text-xs text-red-600 dark:text-red-400 text-center"></div>
|
|
216
277
|
</div>
|
|
217
|
-
<div id="gh-device-step-pat" class="w-full flex flex-col gap-3 hidden">
|
|
218
|
-
<p class="text-sm text-gray-600 dark:text-gray-400 text-center">Enter a GitHub Personal Access Token with <code class="font-mono text-xs">public_repo</code> scope.</p>
|
|
219
|
-
<input id="gh-pat-input" type="password" placeholder="ghp_…" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
220
|
-
<a href="https://github.com/settings/tokens/new?scopes=public_repo&description=dot-illustrations+demo+upload" target="_blank" class="text-xs text-blue-600 dark:text-blue-400 underline text-center">Create a token on GitHub ↗</a>
|
|
221
|
-
<button id="gh-pat-submit" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition">Connect with token</button>
|
|
222
|
-
</div>
|
|
223
|
-
<div id="gh-device-error" class="hidden mt-2 text-sm text-red-600 dark:text-red-400 text-center"></div>
|
|
224
278
|
</div>
|
|
225
279
|
</div>
|
|
226
280
|
|
|
227
281
|
</body>
|
|
228
|
-
</html>
|
|
282
|
+
</html>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* local-proxy.js — GitHub API proxy for local demo development
|
|
4
|
+
*
|
|
5
|
+
* Uses your existing `gh auth` credentials — no token input needed.
|
|
6
|
+
* Run from the repo root: node demo/local-proxy.js
|
|
7
|
+
*
|
|
8
|
+
* The demo page detects this proxy at http://localhost:3001 and
|
|
9
|
+
* authenticates automatically using your gh CLI identity.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const http = require('http');
|
|
15
|
+
const https = require('https');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const PROXY_PORT = 3001;
|
|
19
|
+
|
|
20
|
+
// Resolve the gh CLI token once at startup
|
|
21
|
+
let GH_TOKEN, GH_USER;
|
|
22
|
+
try {
|
|
23
|
+
GH_TOKEN = execSync('gh auth token', { stdio: ['pipe','pipe','pipe'] }).toString().trim();
|
|
24
|
+
GH_USER = execSync('gh api user --jq .login', { stdio: ['pipe','pipe','pipe'] }).toString().trim();
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error('❌ gh CLI is not authenticated. Run: gh auth login --web');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const server = http.createServer((req, res) => {
|
|
31
|
+
// Allow the demo page (any localhost origin) to call this proxy
|
|
32
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
33
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
34
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
35
|
+
|
|
36
|
+
if (req.method === 'OPTIONS') {
|
|
37
|
+
res.writeHead(204);
|
|
38
|
+
res.end();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Special: /whoami — tells the demo page who is logged in
|
|
43
|
+
if (req.url === '/whoami') {
|
|
44
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
45
|
+
res.end(JSON.stringify({ login: GH_USER, proxy: true }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Proxy everything else to api.github.com
|
|
50
|
+
let body = '';
|
|
51
|
+
req.on('data', chunk => { body += chunk; });
|
|
52
|
+
req.on('end', () => {
|
|
53
|
+
const options = {
|
|
54
|
+
hostname: 'api.github.com',
|
|
55
|
+
path: req.url,
|
|
56
|
+
method: req.method,
|
|
57
|
+
headers: {
|
|
58
|
+
'Authorization': `Bearer ${GH_TOKEN}`,
|
|
59
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'User-Agent': 'dot-illustrations-local-proxy',
|
|
62
|
+
...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}),
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const proxyReq = https.request(options, proxyRes => {
|
|
67
|
+
res.writeHead(proxyRes.statusCode, {
|
|
68
|
+
'Content-Type': proxyRes.headers['content-type'] || 'application/json',
|
|
69
|
+
'Access-Control-Allow-Origin': '*',
|
|
70
|
+
});
|
|
71
|
+
proxyRes.pipe(res);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
proxyReq.on('error', err => {
|
|
75
|
+
res.writeHead(502);
|
|
76
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (body) proxyReq.write(body);
|
|
80
|
+
proxyReq.end();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
server.listen(PROXY_PORT, '127.0.0.1', () => {
|
|
85
|
+
console.log(`✅ GitHub proxy running at http://localhost:${PROXY_PORT}`);
|
|
86
|
+
console.log(`👤 Authenticated as: @${GH_USER}`);
|
|
87
|
+
console.log(`\n Open the demo: http://localhost:5175/demo/`);
|
|
88
|
+
console.log(' The demo will connect automatically — no token input needed.\n');
|
|
89
|
+
console.log(' Press Ctrl+C to stop.\n');
|
|
90
|
+
});
|
package/demo/script.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Make currentTab and root globally accessible
|
|
2
2
|
const root = document.documentElement;
|
|
3
|
-
let
|
|
4
|
-
let
|
|
3
|
+
let currentSection = 'illustrations'; // 'illustrations' | 'integrations' | 'upload'
|
|
4
|
+
let currentTab = 'all';
|
|
5
|
+
let alphabetFilter = '';
|
|
5
6
|
|
|
6
7
|
function myFunction() {
|
|
7
8
|
var input, filter, divs, i, div, txtValue;
|
|
@@ -116,48 +117,91 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
116
117
|
const mainTabs = document.querySelectorAll('.tab-btn');
|
|
117
118
|
const floatingTabs = document.querySelectorAll('.floating-tab');
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
// ── Upload New button (in header) ────────────────────────────────────────
|
|
121
|
+
let uploadPanelOpen = false;
|
|
122
|
+
document.getElementById('upload-new-btn')?.addEventListener('click', () => {
|
|
123
|
+
uploadPanelOpen = !uploadPanelOpen;
|
|
124
|
+
const panel = document.getElementById('upload-panel');
|
|
125
|
+
panel.classList.toggle('hidden', !uploadPanelOpen);
|
|
126
|
+
if (uploadPanelOpen) {
|
|
127
|
+
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
128
|
+
updateUploadAuthUI();
|
|
129
|
+
}
|
|
130
|
+
const btn = document.getElementById('upload-new-btn');
|
|
131
|
+
btn.classList.toggle('bg-blue-700', uploadPanelOpen);
|
|
132
|
+
btn.classList.toggle('ring-2', uploadPanelOpen);
|
|
133
|
+
btn.classList.toggle('ring-blue-400', uploadPanelOpen);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ── Section switcher (Illustrations | Integrations) — pill style ──────────
|
|
137
|
+
function updateSectionUI(section) {
|
|
138
|
+
document.querySelectorAll('.section-btn, .floating-section-btn').forEach(btn => {
|
|
139
|
+
const active = btn.dataset.section === section;
|
|
140
|
+
// Active pill: white card on gray background
|
|
141
|
+
btn.classList.toggle('bg-white', active);
|
|
142
|
+
btn.classList.toggle('dark:bg-gray-700', active);
|
|
143
|
+
btn.classList.toggle('text-gray-900', active);
|
|
144
|
+
btn.classList.toggle('dark:text-white', active);
|
|
145
|
+
btn.classList.toggle('shadow-sm', active);
|
|
146
|
+
// Inactive: transparent
|
|
147
|
+
btn.classList.toggle('text-gray-500', !active);
|
|
148
|
+
btn.classList.toggle('dark:text-gray-400', !active);
|
|
129
149
|
});
|
|
130
150
|
}
|
|
131
151
|
|
|
132
|
-
function
|
|
133
|
-
|
|
152
|
+
function setSection(section) {
|
|
153
|
+
currentSection = section;
|
|
134
154
|
alphabetFilter = '';
|
|
135
|
-
|
|
155
|
+
updateSectionUI(section);
|
|
136
156
|
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
157
|
+
const grid = document.getElementById('illustration-list');
|
|
158
|
+
const alphaStrip = document.getElementById('alphabet-strip');
|
|
159
|
+
const illusSubTabs = document.getElementById('illus-sub-tabs');
|
|
160
|
+
const floatingTabs = document.getElementById('floating-tabs');
|
|
161
|
+
const resultCount = document.getElementById('result-count');
|
|
141
162
|
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
grid.classList.add('hidden');
|
|
163
|
+
if (section === 'integrations') {
|
|
164
|
+
grid.classList.remove('hidden');
|
|
145
165
|
alphaStrip.classList.add('hidden');
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
illusSubTabs.classList.add('hidden');
|
|
167
|
+
if (floatingTabs) floatingTabs.classList.add('hidden');
|
|
168
|
+
renderIllustrations();
|
|
149
169
|
} else {
|
|
150
|
-
uploadPanel.classList.add('hidden');
|
|
151
170
|
grid.classList.remove('hidden');
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
alphaStrip.classList.toggle('hidden', !showStrip);
|
|
155
|
-
integNote.classList.toggle('hidden', tab !== 'integrations');
|
|
171
|
+
illusSubTabs.classList.remove('hidden');
|
|
172
|
+
if (floatingTabs) floatingTabs.classList.remove('hidden');
|
|
156
173
|
renderAlphabetStrip();
|
|
157
174
|
renderIllustrations();
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
177
|
|
|
178
|
+
document.querySelectorAll('.section-btn, .floating-section-btn').forEach(btn => {
|
|
179
|
+
btn.addEventListener('click', () => setSection(btn.dataset.section));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ── Illustration sub-tabs — pill style ───────────────────────────────────
|
|
183
|
+
function updateTabUI(tab) {
|
|
184
|
+
document.querySelectorAll('.tab-btn').forEach(t => {
|
|
185
|
+
const active = t.dataset.tab === tab;
|
|
186
|
+
t.classList.toggle('bg-white', active);
|
|
187
|
+
t.classList.toggle('dark:bg-gray-700', active);
|
|
188
|
+
t.classList.toggle('text-gray-900', active);
|
|
189
|
+
t.classList.toggle('dark:text-white', active);
|
|
190
|
+
t.classList.toggle('shadow-sm', active);
|
|
191
|
+
t.classList.toggle('text-gray-500', !active);
|
|
192
|
+
t.classList.toggle('dark:text-gray-400', !active);
|
|
193
|
+
t.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function setTab(tab) {
|
|
198
|
+
currentTab = tab;
|
|
199
|
+
alphabetFilter = '';
|
|
200
|
+
updateTabUI(tab);
|
|
201
|
+
renderAlphabetStrip();
|
|
202
|
+
renderIllustrations();
|
|
203
|
+
}
|
|
204
|
+
|
|
161
205
|
function wireTabs() {
|
|
162
206
|
document.querySelectorAll('.tab-btn').forEach(tab => {
|
|
163
207
|
tab.addEventListener('click', function() { setTab(this.dataset.tab); });
|
|
@@ -368,14 +412,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
368
412
|
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
|
369
413
|
|
|
370
414
|
strip.innerHTML = `
|
|
371
|
-
<button class="alpha-btn px-2 py-
|
|
415
|
+
<button class="alpha-btn px-2 py-1 text-xs rounded-lg font-mono font-medium ${alphabetFilter === '' ? 'bg-blue-600 text-white shadow-sm' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-blue-400 hover:text-blue-600 dark:hover:text-blue-400'} transition" data-letter="">All</button>
|
|
372
416
|
${letters.map(l => `
|
|
373
|
-
<button class="alpha-btn px-2 py-
|
|
417
|
+
<button class="alpha-btn px-2 py-1 text-xs rounded-lg font-mono font-medium ${
|
|
374
418
|
available.has(l)
|
|
375
419
|
? alphabetFilter === l
|
|
376
|
-
? 'bg-blue-600 text-white'
|
|
377
|
-
: 'bg-
|
|
378
|
-
: 'opacity-
|
|
420
|
+
? 'bg-blue-600 text-white shadow-sm'
|
|
421
|
+
: 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-blue-400 hover:text-blue-600 dark:hover:text-blue-400 transition'
|
|
422
|
+
: 'opacity-25 cursor-default text-gray-400 dark:text-gray-600'
|
|
379
423
|
}" data-letter="${l}" ${available.has(l) ? '' : 'disabled'}>${l}</button>
|
|
380
424
|
`).join('')}
|
|
381
425
|
`;
|
|
@@ -391,11 +435,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
391
435
|
function getCurrentPool(filterOverride) {
|
|
392
436
|
const f = filterOverride !== undefined ? filterOverride : alphabetFilter;
|
|
393
437
|
let base = [];
|
|
394
|
-
if (
|
|
395
|
-
|
|
396
|
-
else
|
|
397
|
-
|
|
398
|
-
|
|
438
|
+
if (currentSection === 'integrations') {
|
|
439
|
+
base = integrationsList;
|
|
440
|
+
} else {
|
|
441
|
+
// illustrations section
|
|
442
|
+
if (currentTab === 'all') base = illustrations;
|
|
443
|
+
else if (currentTab === 'global') base = globalList;
|
|
444
|
+
else if (currentTab === 'dashboards') base = dashboardsList;
|
|
445
|
+
else if (currentTab === 'favourites') base = illustrations.filter(id => getFavourites().includes(id));
|
|
446
|
+
}
|
|
399
447
|
if (f) base = base.filter(id => id[0].toUpperCase() === f);
|
|
400
448
|
return base;
|
|
401
449
|
}
|
|
@@ -417,8 +465,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
417
465
|
return;
|
|
418
466
|
}
|
|
419
467
|
|
|
420
|
-
const isIntegrations =
|
|
421
|
-
(currentTab === 'favourites' && toShow.every(id => integrationsList.includes(id)));
|
|
468
|
+
const isIntegrations = currentSection === 'integrations';
|
|
422
469
|
const favs = getFavourites();
|
|
423
470
|
const themeClass = root.classList.contains('dark') ? 'dark' : 'light';
|
|
424
471
|
|
|
@@ -427,8 +474,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
427
474
|
const isFav = favs.includes(item);
|
|
428
475
|
|
|
429
476
|
if (isInteg) {
|
|
430
|
-
// Integration card
|
|
431
|
-
const
|
|
477
|
+
// Integration card — use direct <img src> so the image always loads
|
|
478
|
+
const htmlCode = `<span class="dot-integration"><img class="${item}"/></span>`;
|
|
432
479
|
const div = document.createElement('div');
|
|
433
480
|
div.setAttribute('class', 'copy-container relative group flex flex-col items-center justify-between bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg p-2 md:p-3 transition-transform duration-200 hover:scale-105 hover:shadow-2xl h-[302px] w-full');
|
|
434
481
|
div.dataset.itemId = item;
|
|
@@ -438,7 +485,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
438
485
|
<svg class="h-5 w-5" fill="${isFav ? 'gold' : 'none'}" viewBox="0 0 24 24" stroke="gold"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l2.036 6.29a1 1 0 00.95.69h6.631c.969 0 1.371 1.24.588 1.81l-5.37 3.905a1 1 0 00-.364 1.118l2.036 6.29c.3.921-.755 1.688-1.54 1.118l-5.37-3.905a1 1 0 00-1.176 0l-5.37 3.905c-.784.57-1.838-.197-1.54-1.118l2.036-6.29a1 1 0 00-.364-1.118L2.342 11.717c-.783-.57-.38-1.81.588-1.81h6.631a1 1 0 00.95-.69l2.036-6.29z"/></svg>
|
|
439
486
|
</button>
|
|
440
487
|
<div class="flex-1 flex flex-col justify-center items-center">
|
|
441
|
-
<
|
|
488
|
+
<img src="../integrations/${item}.svg" alt="${item}" class="w-28 h-28 object-contain" loading="lazy" />
|
|
442
489
|
<span class="iconID block text-center text-base font-medium text-gray-800 dark:text-gray-200 my-2">${item}</span>
|
|
443
490
|
</div>
|
|
444
491
|
<div class="button-group flex flex-row items-center justify-center gap-2 mt-2">
|
|
@@ -449,7 +496,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
449
496
|
Copy Code<span class="tooltiptext left-1/2 -translate-x-1/2 bottom-10 shadow-md">Copy Code</span>
|
|
450
497
|
</button>
|
|
451
498
|
</div>
|
|
452
|
-
<pre class="codeBlock"><code class="codeBlock">${
|
|
499
|
+
<pre class="codeBlock"><code class="codeBlock">${htmlCode}</code></pre>
|
|
453
500
|
`;
|
|
454
501
|
list.appendChild(div);
|
|
455
502
|
} else {
|
|
@@ -516,7 +563,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
516
563
|
badge.classList.remove('hidden');
|
|
517
564
|
badge.style.display = 'flex';
|
|
518
565
|
name.textContent = `@${user.login}`;
|
|
519
|
-
localStorage.setItem('gh_illus_user', JSON.stringify(user));
|
|
520
566
|
} else {
|
|
521
567
|
btn.classList.remove('hidden');
|
|
522
568
|
badge.style.display = 'none';
|
|
@@ -591,11 +637,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
591
637
|
}
|
|
592
638
|
});
|
|
593
639
|
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
640
|
+
// Auto-connect via local proxy (node demo/local-proxy.js) — no token input needed
|
|
641
|
+
window.GitHubUpload?.tryLocalProxy?.().then(user => {
|
|
642
|
+
if (user) { updateAuthBadge(user); updateUploadAuthUI(); }
|
|
643
|
+
});
|
|
599
644
|
|
|
600
645
|
// ── Upload panel ─────────────────────────────────────────────────────────
|
|
601
646
|
const uploadBufs = { illusLight: null, illusDark: null, integSvg: null };
|
|
@@ -683,8 +728,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
683
728
|
document.getElementById('upload-illus-btn')?.addEventListener('click', handleIllusUpload);
|
|
684
729
|
document.getElementById('upload-integ-btn')?.addEventListener('click', handleIntegUpload);
|
|
685
730
|
|
|
686
|
-
//
|
|
687
|
-
|
|
731
|
+
// Initial render
|
|
732
|
+
updateSectionUI('illustrations');
|
|
733
|
+
updateTabUI('all');
|
|
734
|
+
renderAlphabetStrip();
|
|
735
|
+
renderIllustrations();
|
|
688
736
|
|
|
689
737
|
// --- FLOATING BAR LOGIC ---
|
|
690
738
|
const floatingBar = document.getElementById('floating-bar');
|
|
@@ -714,13 +762,12 @@ function fuzzyMatch(str, query) {
|
|
|
714
762
|
|
|
715
763
|
function getSuggestions(query) {
|
|
716
764
|
if (!query) return [];
|
|
717
|
-
// Use current tab's list
|
|
718
765
|
let pool = [];
|
|
719
|
-
if (
|
|
720
|
-
else if (currentTab === '
|
|
721
|
-
else if (currentTab === '
|
|
722
|
-
else if (currentTab === '
|
|
723
|
-
else if (currentTab === 'favourites')
|
|
766
|
+
if (currentSection === 'integrations') pool = integrationsList;
|
|
767
|
+
else if (currentTab === 'all') pool = illustrations;
|
|
768
|
+
else if (currentTab === 'global') pool = globalList;
|
|
769
|
+
else if (currentTab === 'dashboards') pool = dashboardsList;
|
|
770
|
+
else if (currentTab === 'favourites') pool = illustrations.filter(id => getFavourites().includes(id));
|
|
724
771
|
// Fuzzy match, sort by closeness (shorter index of first match, then length)
|
|
725
772
|
let scored = pool
|
|
726
773
|
.map(id => {
|
package/package.json
CHANGED