@unbrained/pm-web 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/CHANGELOG.md +7 -0
- package/README.md +107 -0
- package/dist/auth.js +20 -0
- package/dist/auth.js.map +1 -0
- package/dist/crypto.js +42 -0
- package/dist/crypto.js.map +1 -0
- package/dist/db.js +111 -0
- package/dist/db.js.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.js +16 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/routes/admin.js +207 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.js +163 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/github.js +354 -0
- package/dist/routes/github.js.map +1 -0
- package/dist/routes/groups.js +180 -0
- package/dist/routes/groups.js.map +1 -0
- package/dist/routes/pm.js +2446 -0
- package/dist/routes/pm.js.map +1 -0
- package/dist/routes/projects.js +151 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/sharing.js +155 -0
- package/dist/routes/sharing.js.map +1 -0
- package/dist/server.js +64 -0
- package/dist/server.js.map +1 -0
- package/dist/services/pm-runner.js +190 -0
- package/dist/services/pm-runner.js.map +1 -0
- package/dist/services/sse.js +111 -0
- package/dist/services/sse.js.map +1 -0
- package/manifest.json +15 -0
- package/package.json +111 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/index.html +265 -0
- package/public/manifest.json +66 -0
- package/public/src/api.js +28 -0
- package/public/src/api.js.map +1 -0
- package/public/src/api.ts +29 -0
- package/public/src/app.js +926 -0
- package/public/src/app.js.map +1 -0
- package/public/src/app.ts +929 -0
- package/public/src/components/modals.js +62 -0
- package/public/src/components/modals.js.map +1 -0
- package/public/src/components/modals.ts +73 -0
- package/public/src/components/toast.js +10 -0
- package/public/src/components/toast.js.map +1 -0
- package/public/src/components/toast.ts +13 -0
- package/public/src/constants.js +30 -0
- package/public/src/constants.js.map +1 -0
- package/public/src/constants.ts +41 -0
- package/public/src/state.js +15 -0
- package/public/src/state.js.map +1 -0
- package/public/src/state.ts +19 -0
- package/public/src/types.js +5 -0
- package/public/src/types.js.map +1 -0
- package/public/src/types.ts +253 -0
- package/public/src/utils.js +57 -0
- package/public/src/utils.js.map +1 -0
- package/public/src/utils.ts +56 -0
- package/public/src/views/activity.js +47 -0
- package/public/src/views/activity.js.map +1 -0
- package/public/src/views/activity.ts +41 -0
- package/public/src/views/admin.js +435 -0
- package/public/src/views/admin.js.map +1 -0
- package/public/src/views/admin.ts +504 -0
- package/public/src/views/auth.js +81 -0
- package/public/src/views/auth.js.map +1 -0
- package/public/src/views/auth.ts +74 -0
- package/public/src/views/calendar.js +133 -0
- package/public/src/views/calendar.js.map +1 -0
- package/public/src/views/calendar.ts +129 -0
- package/public/src/views/comments-audit.js +109 -0
- package/public/src/views/comments-audit.js.map +1 -0
- package/public/src/views/comments-audit.ts +108 -0
- package/public/src/views/config.js +322 -0
- package/public/src/views/config.js.map +1 -0
- package/public/src/views/config.ts +344 -0
- package/public/src/views/context.js +98 -0
- package/public/src/views/context.js.map +1 -0
- package/public/src/views/context.ts +100 -0
- package/public/src/views/create.js +293 -0
- package/public/src/views/create.js.map +1 -0
- package/public/src/views/create.ts +246 -0
- package/public/src/views/dedupe.js +51 -0
- package/public/src/views/dedupe.js.map +1 -0
- package/public/src/views/dedupe.ts +43 -0
- package/public/src/views/export.js +300 -0
- package/public/src/views/export.js.map +1 -0
- package/public/src/views/export.ts +274 -0
- package/public/src/views/github.js +360 -0
- package/public/src/views/github.js.map +1 -0
- package/public/src/views/github.ts +308 -0
- package/public/src/views/graph-canvas.js +1986 -0
- package/public/src/views/graph-canvas.js.map +1 -0
- package/public/src/views/graph-canvas.ts +2218 -0
- package/public/src/views/graph.js +1824 -0
- package/public/src/views/graph.js.map +1 -0
- package/public/src/views/graph.ts +1891 -0
- package/public/src/views/groups.js +186 -0
- package/public/src/views/groups.js.map +1 -0
- package/public/src/views/groups.ts +172 -0
- package/public/src/views/guide.js +151 -0
- package/public/src/views/guide.js.map +1 -0
- package/public/src/views/guide.ts +162 -0
- package/public/src/views/health.js +105 -0
- package/public/src/views/health.js.map +1 -0
- package/public/src/views/health.ts +102 -0
- package/public/src/views/items.js +1306 -0
- package/public/src/views/items.js.map +1 -0
- package/public/src/views/items.ts +1196 -0
- package/public/src/views/normalize.js +67 -0
- package/public/src/views/normalize.js.map +1 -0
- package/public/src/views/normalize.ts +58 -0
- package/public/src/views/plan.js +454 -0
- package/public/src/views/plan.js.map +1 -0
- package/public/src/views/plan.ts +496 -0
- package/public/src/views/projects.js +204 -0
- package/public/src/views/projects.js.map +1 -0
- package/public/src/views/projects.ts +196 -0
- package/public/src/views/router.js +227 -0
- package/public/src/views/router.js.map +1 -0
- package/public/src/views/router.ts +188 -0
- package/public/src/views/search.js +103 -0
- package/public/src/views/search.js.map +1 -0
- package/public/src/views/search.ts +94 -0
- package/public/src/views/settings.js +272 -0
- package/public/src/views/settings.js.map +1 -0
- package/public/src/views/settings.ts +190 -0
- package/public/src/views/shared.js +49 -0
- package/public/src/views/shared.js.map +1 -0
- package/public/src/views/shared.ts +49 -0
- package/public/src/views/sharing.js +152 -0
- package/public/src/views/sharing.js.map +1 -0
- package/public/src/views/sharing.ts +139 -0
- package/public/src/views/stats.js +92 -0
- package/public/src/views/stats.js.map +1 -0
- package/public/src/views/stats.ts +88 -0
- package/public/src/views/templates.js +117 -0
- package/public/src/views/templates.js.map +1 -0
- package/public/src/views/templates.ts +113 -0
- package/public/src/views/validate.js +54 -0
- package/public/src/views/validate.js.map +1 -0
- package/public/src/views/validate.ts +48 -0
- package/public/styles.css +2231 -0
- package/public/sw.js +318 -0
- package/public/tsconfig.json +20 -0
- package/sql/schema.sql +105 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// PROJECTS VIEW
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
import { state } from '../state.js';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { escHtml, fmtDate } from '../utils.js';
|
|
7
|
+
import { hideModal, createModal, confirmDialog } from '../components/modals.js';
|
|
8
|
+
import { toast } from '../components/toast.js';
|
|
9
|
+
import { showView } from '../views/router.js';
|
|
10
|
+
export async function loadProjects() {
|
|
11
|
+
const data = await api('GET', '/projects');
|
|
12
|
+
state.projects = data.projects || [];
|
|
13
|
+
renderProjectSelector();
|
|
14
|
+
}
|
|
15
|
+
export function renderProjectSelector() {
|
|
16
|
+
const sel = document.getElementById('project-selector');
|
|
17
|
+
if (!sel)
|
|
18
|
+
return;
|
|
19
|
+
const cur = sel.value;
|
|
20
|
+
sel.innerHTML = '<option value="">— Select project —</option>' +
|
|
21
|
+
state.projects.map(p => `<option value="${p.id}"${p.id === cur ? ' selected' : ''}>${escHtml(p.name)}</option>`).join('');
|
|
22
|
+
}
|
|
23
|
+
async function fetchProjectSchema(projectId) {
|
|
24
|
+
try {
|
|
25
|
+
const schema = await api('GET', `/projects/${projectId}/pm/schema`);
|
|
26
|
+
if (schema && Array.isArray(schema.types) && schema.types.length) {
|
|
27
|
+
state.schema = schema;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (_) { /* fallback to constants */ }
|
|
31
|
+
}
|
|
32
|
+
export async function onProjectSelect(id) {
|
|
33
|
+
if (!id) {
|
|
34
|
+
state.currentProject = null;
|
|
35
|
+
state.schema = null;
|
|
36
|
+
window.__app?.disconnectSSE?.();
|
|
37
|
+
const pmSection = document.getElementById('sidebar-pm-section');
|
|
38
|
+
if (pmSection)
|
|
39
|
+
pmSection.style.display = 'none';
|
|
40
|
+
showView('projects');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const proj = state.projects.find(p => p.id === id);
|
|
44
|
+
if (!proj)
|
|
45
|
+
return;
|
|
46
|
+
state.currentProject = proj;
|
|
47
|
+
const pmSection = document.getElementById('sidebar-pm-section');
|
|
48
|
+
if (pmSection)
|
|
49
|
+
pmSection.style.display = '';
|
|
50
|
+
const projName = document.getElementById('sidebar-project-name');
|
|
51
|
+
if (projName)
|
|
52
|
+
projName.textContent = proj.name;
|
|
53
|
+
window.__app?.connectSSE?.(proj.id);
|
|
54
|
+
// Fetch schema in background — views use fallback until it resolves
|
|
55
|
+
fetchProjectSchema(proj.id);
|
|
56
|
+
showView('items');
|
|
57
|
+
loadItemsBadge();
|
|
58
|
+
}
|
|
59
|
+
export async function loadItemsBadge() {
|
|
60
|
+
if (!state.currentProject)
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
const data = await api('GET', `/projects/${state.currentProject.id}/pm/list?status=open&limit=200`);
|
|
64
|
+
const count = (data.items || []).length;
|
|
65
|
+
const badge = document.getElementById('badge-items');
|
|
66
|
+
if (badge)
|
|
67
|
+
badge.textContent = count ? String(count) : '';
|
|
68
|
+
}
|
|
69
|
+
catch (_) { /* ignore */ }
|
|
70
|
+
}
|
|
71
|
+
export function renderProjectsView() {
|
|
72
|
+
const el = document.getElementById('content-projects');
|
|
73
|
+
if (!el)
|
|
74
|
+
return;
|
|
75
|
+
if (state.projects.length === 0) {
|
|
76
|
+
el.innerHTML = `
|
|
77
|
+
<div class="page-header">
|
|
78
|
+
<div><div class="page-title">Projects</div><div class="page-subtitle">Your project workspaces</div></div>
|
|
79
|
+
<div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="welcome-state">
|
|
82
|
+
<div class="welcome-icon">⊞</div>
|
|
83
|
+
<div class="welcome-title">No projects yet</div>
|
|
84
|
+
<div class="welcome-text">Create your first project to start managing tasks, features, bugs, and more with git-native storage.</div>
|
|
85
|
+
<button class="btn btn-primary btn-lg" onclick="window.__app.showModal('create-project-modal')">+ Create Project</button>
|
|
86
|
+
</div>`;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
el.innerHTML = `
|
|
90
|
+
<div class="page-header">
|
|
91
|
+
<div><div class="page-title">Projects</div><div class="page-subtitle">${state.projects.length} project${state.projects.length !== 1 ? 's' : ''}</div></div>
|
|
92
|
+
<div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="projects-grid">
|
|
95
|
+
${state.projects.map(p => `
|
|
96
|
+
<div class="project-card" onclick="window.__app.selectProject('${p.id}')">
|
|
97
|
+
<button class="btn btn-ghost btn-sm project-card-del" onclick="event.stopPropagation();window.__app.deleteProject('${p.id}','${escHtml(p.name)}')" title="Delete project">✕</button>
|
|
98
|
+
<div class="project-card-name">${escHtml(p.name)}</div>
|
|
99
|
+
<div class="project-card-slug mono">${escHtml(p.slug)}</div>
|
|
100
|
+
<div class="project-card-desc">${escHtml(p.description || 'No description')}</div>
|
|
101
|
+
<div class="project-card-meta">
|
|
102
|
+
<span class="project-card-prefix">${escHtml(p.prefix)}</span>
|
|
103
|
+
<span class="project-card-date">${fmtDate(p.created_at)}</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>`).join('')}
|
|
106
|
+
</div>`;
|
|
107
|
+
}
|
|
108
|
+
export function selectProject(id) {
|
|
109
|
+
const sel = document.getElementById('project-selector');
|
|
110
|
+
if (sel)
|
|
111
|
+
sel.value = id;
|
|
112
|
+
onProjectSelect(id);
|
|
113
|
+
}
|
|
114
|
+
export function deleteProject(id, name) {
|
|
115
|
+
confirmDialog('Delete Project?', `Delete project "${name}"? This cannot be undone.`, async () => {
|
|
116
|
+
try {
|
|
117
|
+
await api('DELETE', `/projects/${id}`);
|
|
118
|
+
toast('Project deleted', 'success');
|
|
119
|
+
if (state.currentProject?.id === id) {
|
|
120
|
+
state.currentProject = null;
|
|
121
|
+
const pmSection = document.getElementById('sidebar-pm-section');
|
|
122
|
+
if (pmSection)
|
|
123
|
+
pmSection.style.display = 'none';
|
|
124
|
+
}
|
|
125
|
+
await loadProjects();
|
|
126
|
+
if (state.currentView === 'projects')
|
|
127
|
+
renderProjectsView();
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
toast(err instanceof Error ? err.message : String(err), 'error');
|
|
131
|
+
}
|
|
132
|
+
}, true);
|
|
133
|
+
}
|
|
134
|
+
export function buildCreateProjectModal() {
|
|
135
|
+
createModal('create-project-modal', 'New Project', `
|
|
136
|
+
<form id="create-project-form" onsubmit="window.__app.submitCreateProject(event)">
|
|
137
|
+
<div class="form-group">
|
|
138
|
+
<label class="form-label">Project Name *</label>
|
|
139
|
+
<input class="form-input" id="cp-name" type="text" placeholder="My Awesome Project" required>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="form-group">
|
|
142
|
+
<label class="form-label">ID Prefix *</label>
|
|
143
|
+
<input class="form-input" id="cp-prefix" type="text" placeholder="myproj" pattern="[a-z0-9\\-]+" required>
|
|
144
|
+
<div style="font-size:11px;color:var(--text-muted);margin-top:4px">Lowercase letters, numbers, hyphens. Used as item ID prefix (e.g. myproj-1)</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="form-group">
|
|
147
|
+
<label class="form-label">Description</label>
|
|
148
|
+
<input class="form-input" id="cp-desc" type="text" placeholder="What is this project about?">
|
|
149
|
+
</div>
|
|
150
|
+
<div class="form-error" id="cp-error" style="display:none"></div>
|
|
151
|
+
</form>`, `<button class="btn btn-ghost" onclick="window.__app.hideModal('create-project-modal')">Cancel</button>
|
|
152
|
+
<button class="btn btn-primary" onclick="window.__app.submitCreateProject2()"><span>Create Project</span></button>`);
|
|
153
|
+
const nameEl = document.getElementById('cp-name');
|
|
154
|
+
const prefixEl = document.getElementById('cp-prefix');
|
|
155
|
+
if (nameEl && prefixEl) {
|
|
156
|
+
nameEl.addEventListener('input', function () {
|
|
157
|
+
const prefix = this.value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 20);
|
|
158
|
+
if (!prefixEl._touched)
|
|
159
|
+
prefixEl.value = prefix;
|
|
160
|
+
});
|
|
161
|
+
prefixEl.addEventListener('input', function () { this._touched = !!this.value; });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function submitCreateProject(e) {
|
|
165
|
+
e?.preventDefault();
|
|
166
|
+
await submitCreateProject2();
|
|
167
|
+
}
|
|
168
|
+
async function submitCreateProject2() {
|
|
169
|
+
const nameEl = document.getElementById('cp-name');
|
|
170
|
+
const prefixEl = document.getElementById('cp-prefix');
|
|
171
|
+
const descEl = document.getElementById('cp-desc');
|
|
172
|
+
const errEl = document.getElementById('cp-error');
|
|
173
|
+
if (!nameEl || !prefixEl || !errEl)
|
|
174
|
+
return;
|
|
175
|
+
const name = nameEl.value.trim();
|
|
176
|
+
const prefix = prefixEl.value.trim();
|
|
177
|
+
const desc = descEl?.value.trim() || '';
|
|
178
|
+
errEl.style.display = 'none';
|
|
179
|
+
if (!name || !prefix) {
|
|
180
|
+
errEl.textContent = 'Name and prefix are required.';
|
|
181
|
+
errEl.style.display = 'block';
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const data = await api('POST', '/projects', { name, prefix, description: desc });
|
|
186
|
+
state.projects.unshift(data.project);
|
|
187
|
+
renderProjectSelector();
|
|
188
|
+
hideModal('create-project-modal');
|
|
189
|
+
toast('Project created!', 'success');
|
|
190
|
+
nameEl.value = '';
|
|
191
|
+
prefixEl.value = '';
|
|
192
|
+
prefixEl._touched = false;
|
|
193
|
+
if (descEl)
|
|
194
|
+
descEl.value = '';
|
|
195
|
+
if (state.currentView === 'projects')
|
|
196
|
+
renderProjectsView();
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
errEl.textContent = err instanceof Error ? err.message : String(err);
|
|
200
|
+
errEl.style.display = 'block';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export { submitCreateProject2 };
|
|
204
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["projects.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// PROJECTS VIEW
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
import { state } from '../state.js';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { escHtml, fmtDate } from '../utils.js';
|
|
7
|
+
import { showModal, hideModal, createModal, confirmDialog } from '../components/modals.js';
|
|
8
|
+
import { toast } from '../components/toast.js';
|
|
9
|
+
import { showView } from '../views/router.js';
|
|
10
|
+
|
|
11
|
+
export async function loadProjects(): Promise<void> {
|
|
12
|
+
const data = await api('GET','/projects');
|
|
13
|
+
state.projects = data.projects || [];
|
|
14
|
+
renderProjectSelector();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function renderProjectSelector(): void {
|
|
18
|
+
const sel = document.getElementById('project-selector') as HTMLSelectElement | null;
|
|
19
|
+
if (!sel) return;
|
|
20
|
+
const cur = sel.value;
|
|
21
|
+
sel.innerHTML = '<option value="">— Select project —</option>' +
|
|
22
|
+
state.projects.map(p=>`<option value="${p.id}"${p.id===cur?' selected':''}>${escHtml(p.name)}</option>`).join('');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchProjectSchema(projectId: string): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const schema = await api('GET', `/projects/${projectId}/pm/schema`);
|
|
28
|
+
if (schema && Array.isArray(schema.types) && schema.types.length) {
|
|
29
|
+
state.schema = schema as import('../types.js').ProjectSchema;
|
|
30
|
+
}
|
|
31
|
+
} catch (_) { /* fallback to constants */ }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function onProjectSelect(id: string): Promise<void> {
|
|
35
|
+
if (!id) {
|
|
36
|
+
state.currentProject = null;
|
|
37
|
+
state.schema = null;
|
|
38
|
+
(window as any).__app?.disconnectSSE?.();
|
|
39
|
+
const pmSection = document.getElementById('sidebar-pm-section');
|
|
40
|
+
if (pmSection) pmSection.style.display = 'none';
|
|
41
|
+
showView('projects');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const proj = state.projects.find(p=>p.id===id);
|
|
45
|
+
if (!proj) return;
|
|
46
|
+
state.currentProject = proj;
|
|
47
|
+
const pmSection = document.getElementById('sidebar-pm-section');
|
|
48
|
+
if (pmSection) pmSection.style.display = '';
|
|
49
|
+
const projName = document.getElementById('sidebar-project-name');
|
|
50
|
+
if (projName) projName.textContent = proj.name;
|
|
51
|
+
(window as any).__app?.connectSSE?.(proj.id);
|
|
52
|
+
// Fetch schema in background — views use fallback until it resolves
|
|
53
|
+
fetchProjectSchema(proj.id);
|
|
54
|
+
showView('items');
|
|
55
|
+
loadItemsBadge();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadItemsBadge(): Promise<void> {
|
|
59
|
+
if (!state.currentProject) return;
|
|
60
|
+
try {
|
|
61
|
+
const data = await api('GET',`/projects/${state.currentProject.id}/pm/list?status=open&limit=200`);
|
|
62
|
+
const count = (data.items||[]).length;
|
|
63
|
+
const badge = document.getElementById('badge-items');
|
|
64
|
+
if (badge) badge.textContent = count ? String(count) : '';
|
|
65
|
+
} catch(_) { /* ignore */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function renderProjectsView(): void {
|
|
69
|
+
const el = document.getElementById('content-projects');
|
|
70
|
+
if (!el) return;
|
|
71
|
+
if (state.projects.length === 0) {
|
|
72
|
+
el.innerHTML = `
|
|
73
|
+
<div class="page-header">
|
|
74
|
+
<div><div class="page-title">Projects</div><div class="page-subtitle">Your project workspaces</div></div>
|
|
75
|
+
<div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="welcome-state">
|
|
78
|
+
<div class="welcome-icon">⊞</div>
|
|
79
|
+
<div class="welcome-title">No projects yet</div>
|
|
80
|
+
<div class="welcome-text">Create your first project to start managing tasks, features, bugs, and more with git-native storage.</div>
|
|
81
|
+
<button class="btn btn-primary btn-lg" onclick="window.__app.showModal('create-project-modal')">+ Create Project</button>
|
|
82
|
+
</div>`;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
el.innerHTML = `
|
|
86
|
+
<div class="page-header">
|
|
87
|
+
<div><div class="page-title">Projects</div><div class="page-subtitle">${state.projects.length} project${state.projects.length!==1?'s':''}</div></div>
|
|
88
|
+
<div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="projects-grid">
|
|
91
|
+
${state.projects.map(p=>`
|
|
92
|
+
<div class="project-card" onclick="window.__app.selectProject('${p.id}')">
|
|
93
|
+
<button class="btn btn-ghost btn-sm project-card-del" onclick="event.stopPropagation();window.__app.deleteProject('${p.id}','${escHtml(p.name)}')" title="Delete project">✕</button>
|
|
94
|
+
<div class="project-card-name">${escHtml(p.name)}</div>
|
|
95
|
+
<div class="project-card-slug mono">${escHtml(p.slug)}</div>
|
|
96
|
+
<div class="project-card-desc">${escHtml(p.description||'No description')}</div>
|
|
97
|
+
<div class="project-card-meta">
|
|
98
|
+
<span class="project-card-prefix">${escHtml(p.prefix)}</span>
|
|
99
|
+
<span class="project-card-date">${fmtDate(p.created_at)}</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>`).join('')}
|
|
102
|
+
</div>`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function selectProject(id: string): void {
|
|
106
|
+
const sel = document.getElementById('project-selector') as HTMLSelectElement | null;
|
|
107
|
+
if (sel) sel.value = id;
|
|
108
|
+
onProjectSelect(id);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function deleteProject(id: string, name: string): void {
|
|
112
|
+
confirmDialog('Delete Project?', `Delete project "${name}"? This cannot be undone.`, async () => {
|
|
113
|
+
try {
|
|
114
|
+
await api('DELETE',`/projects/${id}`);
|
|
115
|
+
toast('Project deleted','success');
|
|
116
|
+
if (state.currentProject?.id === id) {
|
|
117
|
+
state.currentProject = null;
|
|
118
|
+
const pmSection = document.getElementById('sidebar-pm-section');
|
|
119
|
+
if (pmSection) pmSection.style.display = 'none';
|
|
120
|
+
}
|
|
121
|
+
await loadProjects();
|
|
122
|
+
if (state.currentView === 'projects') renderProjectsView();
|
|
123
|
+
} catch(err: unknown) {
|
|
124
|
+
toast(err instanceof Error ? err.message : String(err),'error');
|
|
125
|
+
}
|
|
126
|
+
}, true);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function buildCreateProjectModal(): void {
|
|
130
|
+
createModal('create-project-modal','New Project',`
|
|
131
|
+
<form id="create-project-form" onsubmit="window.__app.submitCreateProject(event)">
|
|
132
|
+
<div class="form-group">
|
|
133
|
+
<label class="form-label">Project Name *</label>
|
|
134
|
+
<input class="form-input" id="cp-name" type="text" placeholder="My Awesome Project" required>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="form-group">
|
|
137
|
+
<label class="form-label">ID Prefix *</label>
|
|
138
|
+
<input class="form-input" id="cp-prefix" type="text" placeholder="myproj" pattern="[a-z0-9\\-]+" required>
|
|
139
|
+
<div style="font-size:11px;color:var(--text-muted);margin-top:4px">Lowercase letters, numbers, hyphens. Used as item ID prefix (e.g. myproj-1)</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="form-group">
|
|
142
|
+
<label class="form-label">Description</label>
|
|
143
|
+
<input class="form-input" id="cp-desc" type="text" placeholder="What is this project about?">
|
|
144
|
+
</div>
|
|
145
|
+
<div class="form-error" id="cp-error" style="display:none"></div>
|
|
146
|
+
</form>`,
|
|
147
|
+
`<button class="btn btn-ghost" onclick="window.__app.hideModal('create-project-modal')">Cancel</button>
|
|
148
|
+
<button class="btn btn-primary" onclick="window.__app.submitCreateProject2()"><span>Create Project</span></button>`
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const nameEl = document.getElementById('cp-name');
|
|
152
|
+
const prefixEl = document.getElementById('cp-prefix') as (HTMLInputElement & { _touched?: boolean }) | null;
|
|
153
|
+
if (nameEl && prefixEl) {
|
|
154
|
+
nameEl.addEventListener('input', function() {
|
|
155
|
+
const prefix = (this as HTMLInputElement).value.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'').slice(0,20);
|
|
156
|
+
if (!prefixEl._touched) prefixEl.value = prefix;
|
|
157
|
+
});
|
|
158
|
+
prefixEl.addEventListener('input', function() { (this as HTMLInputElement & { _touched?: boolean })._touched = !!(this as HTMLInputElement).value; });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function submitCreateProject(e?: Event): Promise<void> {
|
|
163
|
+
e?.preventDefault();
|
|
164
|
+
await submitCreateProject2();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function submitCreateProject2(): Promise<void> {
|
|
168
|
+
const nameEl = document.getElementById('cp-name') as HTMLInputElement | null;
|
|
169
|
+
const prefixEl = document.getElementById('cp-prefix') as HTMLInputElement | null;
|
|
170
|
+
const descEl = document.getElementById('cp-desc') as HTMLInputElement | null;
|
|
171
|
+
const errEl = document.getElementById('cp-error') as HTMLElement | null;
|
|
172
|
+
if (!nameEl || !prefixEl || !errEl) return;
|
|
173
|
+
|
|
174
|
+
const name = nameEl.value.trim();
|
|
175
|
+
const prefix = prefixEl.value.trim();
|
|
176
|
+
const desc = descEl?.value.trim() || '';
|
|
177
|
+
errEl.style.display = 'none';
|
|
178
|
+
if (!name || !prefix) { errEl.textContent = 'Name and prefix are required.'; errEl.style.display = 'block'; return; }
|
|
179
|
+
try {
|
|
180
|
+
const data = await api('POST','/projects',{name,prefix,description:desc});
|
|
181
|
+
state.projects.unshift(data.project);
|
|
182
|
+
renderProjectSelector();
|
|
183
|
+
hideModal('create-project-modal');
|
|
184
|
+
toast('Project created!','success');
|
|
185
|
+
nameEl.value = '';
|
|
186
|
+
prefixEl.value = '';
|
|
187
|
+
(prefixEl as HTMLInputElement & { _touched?: boolean })._touched = false;
|
|
188
|
+
if (descEl) descEl.value = '';
|
|
189
|
+
if (state.currentView === 'projects') renderProjectsView();
|
|
190
|
+
} catch(err: unknown) {
|
|
191
|
+
errEl.textContent = err instanceof Error ? err.message : String(err);
|
|
192
|
+
errEl.style.display = 'block';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { submitCreateProject2 };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// ROUTER — View switching with URL routing
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
import { state } from '../state.js';
|
|
5
|
+
import { VIEW_NAMES } from '../constants.js';
|
|
6
|
+
import { renderProjectsView } from './projects.js';
|
|
7
|
+
import { renderItemsView } from './items.js';
|
|
8
|
+
import { renderCreateView } from './create.js';
|
|
9
|
+
import { renderActivityView } from './activity.js';
|
|
10
|
+
import { renderSearchView } from './search.js';
|
|
11
|
+
import { renderStatsView } from './stats.js';
|
|
12
|
+
import { renderCalendarView } from './calendar.js';
|
|
13
|
+
import { renderContextView } from './context.js';
|
|
14
|
+
import { renderGraphView } from './graph.js';
|
|
15
|
+
import { renderSharingView } from './sharing.js';
|
|
16
|
+
import { renderGroupsView } from './groups.js';
|
|
17
|
+
import { renderHealthView } from './health.js';
|
|
18
|
+
import { renderDedupeAuditView } from './dedupe.js';
|
|
19
|
+
import { renderValidateView } from './validate.js';
|
|
20
|
+
import { renderSettingsView } from './settings.js';
|
|
21
|
+
import { renderGitHubView } from './github.js';
|
|
22
|
+
import { renderExportView } from './export.js';
|
|
23
|
+
import { renderNormalizeView } from './normalize.js';
|
|
24
|
+
import { renderSharedView } from './shared.js';
|
|
25
|
+
import { renderTemplatesView } from './templates.js';
|
|
26
|
+
import { renderCommentsAuditView } from './comments-audit.js';
|
|
27
|
+
import { renderConfigView } from './config.js';
|
|
28
|
+
import { renderGuideView } from './guide.js';
|
|
29
|
+
import { renderAdminView } from './admin.js';
|
|
30
|
+
import { initPlanView } from './plan.js';
|
|
31
|
+
// View name → URL path mapping
|
|
32
|
+
const VIEW_TO_PATH = {
|
|
33
|
+
'projects': '/',
|
|
34
|
+
'items': '/items',
|
|
35
|
+
'create': '/create',
|
|
36
|
+
'activity': '/activity',
|
|
37
|
+
'search': '/search',
|
|
38
|
+
'stats': '/stats',
|
|
39
|
+
'calendar': '/calendar',
|
|
40
|
+
'context': '/context',
|
|
41
|
+
'graph': '/graph',
|
|
42
|
+
'sharing': '/sharing',
|
|
43
|
+
'groups': '/groups',
|
|
44
|
+
'health': '/health',
|
|
45
|
+
'dedupe': '/dedupe',
|
|
46
|
+
'validate': '/validate',
|
|
47
|
+
'settings': '/settings',
|
|
48
|
+
'github': '/github',
|
|
49
|
+
'export': '/export',
|
|
50
|
+
'normalize': '/normalize',
|
|
51
|
+
'shared': '/shared',
|
|
52
|
+
'templates': '/templates',
|
|
53
|
+
'comments-audit': '/comments-audit',
|
|
54
|
+
'config': '/config',
|
|
55
|
+
'guide': '/guide',
|
|
56
|
+
'admin': '/admin',
|
|
57
|
+
'plan': '/plan',
|
|
58
|
+
};
|
|
59
|
+
// Reverse: URL path → view name
|
|
60
|
+
const PATH_TO_VIEW = {};
|
|
61
|
+
for (const [view, path] of Object.entries(VIEW_TO_PATH)) {
|
|
62
|
+
PATH_TO_VIEW[path] = view;
|
|
63
|
+
}
|
|
64
|
+
// Whether pushState was just called (to ignore the resulting popstate)
|
|
65
|
+
let navigatingInternally = false;
|
|
66
|
+
// Callback invoked when the view changes — used to notify presence service
|
|
67
|
+
let onViewChange = null;
|
|
68
|
+
export function setOnViewChange(cb) {
|
|
69
|
+
onViewChange = cb;
|
|
70
|
+
}
|
|
71
|
+
export function getPathForView(view) {
|
|
72
|
+
return VIEW_TO_PATH[view] || '/';
|
|
73
|
+
}
|
|
74
|
+
export function getViewForPath(path) {
|
|
75
|
+
// Normalize: remove trailing slash except for root
|
|
76
|
+
const normalized = path.replace(/\/$/, '') || '/';
|
|
77
|
+
// Direct match
|
|
78
|
+
if (PATH_TO_VIEW[normalized])
|
|
79
|
+
return PATH_TO_VIEW[normalized];
|
|
80
|
+
// Try matching first segment for deeper paths (e.g. /items/DETAIL-1 → items)
|
|
81
|
+
const firstSegment = '/' + normalized.slice(1).split('/')[0];
|
|
82
|
+
return PATH_TO_VIEW[firstSegment] || 'projects';
|
|
83
|
+
}
|
|
84
|
+
export function showView(view, pushState = true) {
|
|
85
|
+
if (view === 'admin' && !state.user?.is_admin) {
|
|
86
|
+
history.replaceState({ view: 'projects' }, '', '/');
|
|
87
|
+
view = 'projects';
|
|
88
|
+
}
|
|
89
|
+
state.currentView = view;
|
|
90
|
+
// Update URL via pushState
|
|
91
|
+
if (pushState) {
|
|
92
|
+
const path = getPathForView(view);
|
|
93
|
+
if (window.location.pathname !== path) {
|
|
94
|
+
navigatingInternally = true;
|
|
95
|
+
history.pushState({ view }, '', path);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Full-screen graph mode: hide sidebar
|
|
99
|
+
document.body.classList.toggle('graph-mode', view === 'graph');
|
|
100
|
+
VIEW_NAMES.forEach(v => {
|
|
101
|
+
const el = document.getElementById(`content-${v}`);
|
|
102
|
+
if (el)
|
|
103
|
+
el.style.display = v === view ? '' : 'none';
|
|
104
|
+
});
|
|
105
|
+
document.querySelectorAll('.sidebar-item[data-view]').forEach(el => {
|
|
106
|
+
el.classList.toggle('active', el.dataset.view === view);
|
|
107
|
+
});
|
|
108
|
+
if (view === 'projects') {
|
|
109
|
+
document.querySelectorAll('#sidebar-projects-section .sidebar-item').forEach((el, i) => {
|
|
110
|
+
el.classList.toggle('active', i === 0);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
switch (view) {
|
|
114
|
+
case 'projects':
|
|
115
|
+
renderProjectsView();
|
|
116
|
+
break;
|
|
117
|
+
case 'items':
|
|
118
|
+
renderItemsView();
|
|
119
|
+
break;
|
|
120
|
+
case 'create':
|
|
121
|
+
renderCreateView();
|
|
122
|
+
break;
|
|
123
|
+
case 'activity':
|
|
124
|
+
renderActivityView();
|
|
125
|
+
break;
|
|
126
|
+
case 'search':
|
|
127
|
+
renderSearchView();
|
|
128
|
+
break;
|
|
129
|
+
case 'stats':
|
|
130
|
+
renderStatsView();
|
|
131
|
+
break;
|
|
132
|
+
case 'calendar':
|
|
133
|
+
renderCalendarView();
|
|
134
|
+
break;
|
|
135
|
+
case 'context':
|
|
136
|
+
renderContextView();
|
|
137
|
+
break;
|
|
138
|
+
case 'graph':
|
|
139
|
+
renderGraphView();
|
|
140
|
+
break;
|
|
141
|
+
case 'sharing':
|
|
142
|
+
renderSharingView();
|
|
143
|
+
break;
|
|
144
|
+
case 'groups':
|
|
145
|
+
renderGroupsView();
|
|
146
|
+
break;
|
|
147
|
+
case 'health':
|
|
148
|
+
renderHealthView();
|
|
149
|
+
break;
|
|
150
|
+
case 'dedupe':
|
|
151
|
+
renderDedupeAuditView();
|
|
152
|
+
break;
|
|
153
|
+
case 'validate':
|
|
154
|
+
renderValidateView();
|
|
155
|
+
break;
|
|
156
|
+
case 'settings':
|
|
157
|
+
renderSettingsView();
|
|
158
|
+
break;
|
|
159
|
+
case 'github':
|
|
160
|
+
renderGitHubView();
|
|
161
|
+
break;
|
|
162
|
+
case 'export':
|
|
163
|
+
renderExportView();
|
|
164
|
+
break;
|
|
165
|
+
case 'normalize':
|
|
166
|
+
renderNormalizeView();
|
|
167
|
+
break;
|
|
168
|
+
case 'shared':
|
|
169
|
+
renderSharedView();
|
|
170
|
+
break;
|
|
171
|
+
case 'templates':
|
|
172
|
+
renderTemplatesView();
|
|
173
|
+
break;
|
|
174
|
+
case 'comments-audit':
|
|
175
|
+
renderCommentsAuditView();
|
|
176
|
+
break;
|
|
177
|
+
case 'config':
|
|
178
|
+
renderConfigView();
|
|
179
|
+
break;
|
|
180
|
+
case 'guide':
|
|
181
|
+
renderGuideView();
|
|
182
|
+
break;
|
|
183
|
+
case 'admin':
|
|
184
|
+
renderAdminView();
|
|
185
|
+
break;
|
|
186
|
+
case 'plan':
|
|
187
|
+
void initPlanView();
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
updateMobileNav(view);
|
|
191
|
+
// Notify presence service of view change
|
|
192
|
+
onViewChange?.(view);
|
|
193
|
+
// Scroll main content to top on view change
|
|
194
|
+
const mainContent = document.getElementById('main-content');
|
|
195
|
+
if (mainContent)
|
|
196
|
+
mainContent.scrollTop = 0;
|
|
197
|
+
// Move focus to main content for accessibility
|
|
198
|
+
const activeArea = document.getElementById(`content-${view}`);
|
|
199
|
+
if (activeArea) {
|
|
200
|
+
activeArea.setAttribute('tabindex', '-1');
|
|
201
|
+
activeArea.focus({ preventScroll: true });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function updateMobileNav(view) {
|
|
205
|
+
document.querySelectorAll('.mobile-bottom-nav-item').forEach(el => {
|
|
206
|
+
el.classList.toggle('active', el.dataset.mobview === view);
|
|
207
|
+
});
|
|
208
|
+
const nav = document.getElementById('mobile-bottom-nav');
|
|
209
|
+
if (nav) {
|
|
210
|
+
nav.classList.toggle('visible', !!state.currentProject && view !== 'projects');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Handle browser back/forward
|
|
214
|
+
function onPopState(e) {
|
|
215
|
+
// Ignore if we just pushed state (some browsers fire popstate after pushState)
|
|
216
|
+
if (navigatingInternally) {
|
|
217
|
+
navigatingInternally = false;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const view = e.state?.view || getViewForPath(window.location.pathname);
|
|
221
|
+
showView(view, false);
|
|
222
|
+
}
|
|
223
|
+
// Initialize popstate listener
|
|
224
|
+
if (typeof window !== 'undefined') {
|
|
225
|
+
window.addEventListener('popstate', onPopState);
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["router.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,2CAA2C;AAC3C,kEAAkE;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,+BAA+B;AAC/B,MAAM,YAAY,GAA2B;IAC3C,UAAU,EAAE,GAAG;IACf,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,WAAW;IACvB,QAAQ,EAAE,SAAS;IACnB,OAAO,EAAE,QAAQ;IACjB,UAAU,EAAE,WAAW;IACvB,SAAS,EAAE,UAAU;IACrB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,UAAU;IACrB,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,WAAW;IACvB,UAAU,EAAE,WAAW;IACvB,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,YAAY;IACzB,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,YAAY;IACzB,gBAAgB,EAAE,iBAAiB;IACnC,QAAQ,EAAE,SAAS;IACnB,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,OAAO;CAChB,CAAC;AAEF,gCAAgC;AAChC,MAAM,YAAY,GAA2B,EAAE,CAAC;AAChD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;IACxD,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAC5B,CAAC;AAED,uEAAuE;AACvE,IAAI,oBAAoB,GAAG,KAAK,CAAC;AAEjC,2EAA2E;AAC3E,IAAI,YAAY,GAAoC,IAAI,CAAC;AACzD,MAAM,UAAU,eAAe,CAAC,EAA0B;IACxD,YAAY,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,mDAAmD;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IAClD,eAAe;IACf,IAAI,YAAY,CAAC,UAAU,CAAC;QAAE,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC;IAC9D,6EAA6E;IAC7E,MAAM,YAAY,GAAG,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,YAAY,CAAC,YAAY,CAAC,IAAI,UAAU,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,SAAS,GAAG,IAAI;IACrD,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC9C,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,IAAI,GAAG,UAAU,CAAC;IACpB,CAAC;IACD,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;IAEzB,2BAA2B;IAC3B,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtC,oBAAoB,GAAG,IAAI,CAAC;YAC5B,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,uCAAuC;IACvC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAC/D,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACrB,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACnD,IAAI,EAAE;YAAE,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACtD,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;QAChE,EAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAG,EAAkB,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IACH,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,QAAQ,CAAC,gBAAgB,CAAC,yCAAyC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAC,CAAC,EAAE,EAAE;YACnF,EAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAG,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC;IACD,QAAO,IAAI,EAAE,CAAC;QACZ,KAAK,UAAU;YAAE,kBAAkB,EAAE,CAAC;YAAC,MAAM;QAC7C,KAAK,OAAO;YAAE,eAAe,EAAE,CAAC;YAAC,MAAM;QACvC,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,UAAU;YAAE,kBAAkB,EAAE,CAAC;YAAC,MAAM;QAC7C,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,OAAO;YAAE,eAAe,EAAE,CAAC;YAAC,MAAM;QACvC,KAAK,UAAU;YAAE,kBAAkB,EAAE,CAAC;YAAC,MAAM;QAC7C,KAAK,SAAS;YAAE,iBAAiB,EAAE,CAAC;YAAC,MAAM;QAC3C,KAAK,OAAO;YAAE,eAAe,EAAE,CAAC;YAAC,MAAM;QACvC,KAAK,SAAS;YAAE,iBAAiB,EAAE,CAAC;YAAC,MAAM;QAC3C,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,QAAQ;YAAE,qBAAqB,EAAE,CAAC;YAAC,MAAM;QAC9C,KAAK,UAAU;YAAE,kBAAkB,EAAE,CAAC;YAAC,MAAM;QAC7C,KAAK,UAAU;YAAE,kBAAkB,EAAE,CAAC;YAAC,MAAM;QAC7C,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,WAAW;YAAE,mBAAmB,EAAE,CAAC;YAAC,MAAM;QAC/C,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,WAAW;YAAE,mBAAmB,EAAE,CAAC;YAAC,MAAM;QAC/C,KAAK,gBAAgB;YAAE,uBAAuB,EAAE,CAAC;YAAC,MAAM;QACxD,KAAK,QAAQ;YAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACzC,KAAK,OAAO;YAAE,eAAe,EAAE,CAAC;YAAC,MAAM;QACvC,KAAK,OAAO;YAAE,eAAe,EAAE,CAAC;YAAC,MAAM;QACvC,KAAK,MAAM;YAAE,KAAK,YAAY,EAAE,CAAC;YAAC,MAAM;IAC1C,CAAC;IACD,eAAe,CAAC,IAAI,CAAC,CAAC;IAEtB,yCAAyC;IACzC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC;IAErB,4CAA4C;IAC5C,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;IAC5D,IAAI,WAAW;QAAE,WAAW,CAAC,SAAS,GAAG,CAAC,CAAC;IAE3C,+CAA+C;IAC/C,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC9D,IAAI,UAAU,EAAE,CAAC;QACf,UAAU,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC1C,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,QAAQ,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;QAC/D,EAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAG,EAAkB,CAAC,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;IACzD,IAAI,GAAG,EAAE,CAAC;QACR,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,IAAI,IAAI,KAAK,UAAU,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED,8BAA8B;AAC9B,SAAS,UAAU,CAAC,CAAgB;IAClC,+EAA+E;IAC/E,IAAI,oBAAoB,EAAE,CAAC;QACzB,oBAAoB,GAAG,KAAK,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,+BAA+B;AAC/B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;IAClC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAClD,CAAC"}
|