feat(ui): Settings view + per-space project cards (status/research/edit/delete) + theming pass
- Settings (#/settings): API tokens (mint/list/revoke), Agents list, Orthos Mode placeholder - Per-space Projects: Void-1-style expandable cards — inline status, ↻ Research (Eithan stub), Edit/New modal, Delete-with-confirm; migration 019 adds research_status/notes/timestamps; POST /api/projects/:id/research stub; GET /api/agent-tokens list - Global +1 font bump; themed scrollbars; larger/bolder themed topbar Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,10 @@
|
||||
// Space view — Projects + Open tasks side by side at the top, then a full
|
||||
// pages & references table below (all pages; refs up to the API max).
|
||||
// Space view — rich project cards (status, research, edit/delete) + open tasks,
|
||||
// then a full pages & references table.
|
||||
import { api } from '../api.js';
|
||||
import { el, mount } from '../dom.js';
|
||||
import { exportMenu } from '../components/export_menu.js';
|
||||
|
||||
function projItem(p) {
|
||||
return el('li', {},
|
||||
el('a', { href: '#/project/' + p.id }, p.name), ' ',
|
||||
el('span', { class: 'status' + (p.status === 'done' ? ' ok' : p.status === 'paused' ? ' warn' : '') }, p.status));
|
||||
}
|
||||
import { projectCard } from '../components/project_card.js';
|
||||
import { openProjectModal } from '../components/project_modal.js';
|
||||
|
||||
function taskItem(t) {
|
||||
return el('li', {},
|
||||
@@ -23,28 +19,32 @@ function tableRow(href, title, type) {
|
||||
|
||||
export async function render(main, ctx) {
|
||||
const id = ctx.params.id;
|
||||
mount(main,
|
||||
el('h1', { class: 'view-h1' }, 'Space'),
|
||||
el('p', { class: 'view-sub muted' }, 'Loading …')
|
||||
);
|
||||
mount(main, el('h1', { class: 'view-h1' }, 'Space'), el('p', { class: 'view-sub muted' }, 'Loading …'));
|
||||
|
||||
localStorage.setItem('last_space_id', id);
|
||||
let space;
|
||||
try { space = await api.get('/api/spaces/' + id); }
|
||||
catch (e) {
|
||||
mount(main,
|
||||
el('h1', { class: 'view-h1' }, 'Space not found'),
|
||||
el('p', { class: 'view-sub muted' }, e.message)
|
||||
);
|
||||
return;
|
||||
}
|
||||
catch (e) { mount(main, el('h1', { class: 'view-h1' }, 'Space not found'), el('p', { class: 'view-sub muted' }, e.message)); return; }
|
||||
|
||||
const [projects, tasks, pages, refs] = await Promise.all([
|
||||
api.get(`/api/spaces/${id}/projects`).catch(() => []),
|
||||
let projects = [];
|
||||
const [tasks, pages, refs] = await Promise.all([
|
||||
api.get(`/api/spaces/${id}/tasks?status=todo`).catch(() => []),
|
||||
api.get(`/api/spaces/${id}/pages`).catch(() => []), // returns ALL pages
|
||||
api.get(`/api/refs?space_id=${id}&limit=200`).catch(() => []) // 200 = API max
|
||||
api.get(`/api/spaces/${id}/pages`).catch(() => []),
|
||||
api.get(`/api/refs?space_id=${id}&limit=200`).catch(() => [])
|
||||
]);
|
||||
try { projects = await api.get(`/api/spaces/${id}/projects`); } catch { /* */ }
|
||||
|
||||
// ---- Projects section (rich cards) ----
|
||||
const projWrap = el('div', { class: 'proj-list' });
|
||||
function renderProjects() {
|
||||
projWrap.replaceChildren();
|
||||
projHead.textContent = `Projects${projects.length ? ` (${projects.length})` : ''}`;
|
||||
if (!projects.length) { projWrap.appendChild(el('div', { class: 'muted', style: { padding: '4px 2px' } }, 'No projects yet.')); return; }
|
||||
for (const p of projects) projWrap.appendChild(projectCard(p, { reload, rerender: renderProjects }));
|
||||
}
|
||||
async function reload() { try { projects = await api.get(`/api/spaces/${id}/projects`); } catch { /* */ } renderProjects(); }
|
||||
const projHead = el('h3', {}, 'Projects');
|
||||
renderProjects();
|
||||
|
||||
const rows = [
|
||||
...pages.map(p => tableRow('#/page/' + p.id, p.title, 'page')),
|
||||
@@ -65,33 +65,23 @@ export async function render(main, ctx) {
|
||||
),
|
||||
el('p', { class: 'view-sub' }, space.description || el('span', { class: 'muted' }, 'No description.')),
|
||||
|
||||
// Top: Projects + Open tasks, side by side.
|
||||
el('div', { class: 'row' },
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, `Projects${projects.length ? ` (${projects.length})` : ''}`),
|
||||
projects.length
|
||||
? el('ul', { class: 'plain' }, projects.map(projItem))
|
||||
: el('p', { class: 'muted' }, 'None yet.')
|
||||
),
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, `Open tasks${tasks.length ? ` (${tasks.length})` : ''}`),
|
||||
tasks.length
|
||||
? el('ul', { class: 'plain' }, tasks.map(taskItem))
|
||||
: el('p', { class: 'muted' }, 'Clear board.')
|
||||
)
|
||||
),
|
||||
el('div', { class: 'card' },
|
||||
el('div', { class: 'card-head' }, projHead,
|
||||
el('button', { class: 'primary', onclick: () => openProjectModal(id, null, reload) }, '+ New')),
|
||||
projWrap),
|
||||
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, `Open tasks${tasks.length ? ` (${tasks.length})` : ''}`),
|
||||
tasks.length ? el('ul', { class: 'plain' }, tasks.map(taskItem)) : el('p', { class: 'muted' }, 'Clear board.')),
|
||||
|
||||
// Below: the full pages & references table.
|
||||
el('div', { class: 'card' },
|
||||
el('h3', {}, `Pages & references${rows.length ? ` (${pages.length + refs.length})` : ''}`),
|
||||
rows.length
|
||||
? el('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: '13px' } },
|
||||
el('thead', {},
|
||||
el('tr', {},
|
||||
el('th', { class: 'muted', style: { textAlign: 'left', padding: '5px 8px', fontWeight: '500' } }, 'Title'),
|
||||
el('th', { class: 'muted', style: { textAlign: 'left', padding: '5px 8px', width: '90px', fontWeight: '500' } }, 'Type'))),
|
||||
el('tbody', {}, rows))
|
||||
: el('p', { class: 'muted' }, 'Nothing here yet.')
|
||||
)
|
||||
el('thead', {}, el('tr', {},
|
||||
el('th', { class: 'muted', style: { textAlign: 'left', padding: '5px 8px', fontWeight: '500' } }, 'Title'),
|
||||
el('th', { class: 'muted', style: { textAlign: 'left', padding: '5px 8px', width: '90px', fontWeight: '500' } }, 'Type'))),
|
||||
el('tbody', {}, rows))
|
||||
: el('p', { class: 'muted' }, 'Nothing here yet.'))
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user