create-pmos 0.204.0 → 0.205.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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-capture-test.js +206 -0
- package/workspace/extension/README.md +22 -7
- package/workspace/extension/capture.js +114 -0
- package/workspace/extension/manifest.json +49 -8
- package/workspace/extension/popup.css +28 -0
- package/workspace/extension/popup.html +15 -0
- package/workspace/extension/popup.js +195 -0
- package/workspace/guide/report-design.md +15 -1
- package/workspace/lib/extension-cli.js +6 -4
- package/workspace/lib/report-design.js +62 -4
- package/workspace/lib/routes-report-design.js +33 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-boot.js +2 -1
- package/workspace/public/pm/c3-capture.css +23 -0
- package/workspace/public/pm/c3-capture.js +152 -0
- package/workspace/public/pm/c3-design-studio.js +17 -5
- package/workspace/public/pm/c3-shell.js +2 -0
- package/workspace/skills/report-design.skill +1 -0
- package/workspace/test-lists/report-design-studio-ui.json +3 -3
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* pm/c3-capture.js — the page the PMOS Chrome extension frames in its popup (initiative report-design-studio; mockup 42-reportdeck.js, hashes
|
|
2
|
+
* #design-chrome and #design-chrome-page). c3-boot.js loads it alone for /c3.html?capture=1&pop=1&pkind=deck|doc|page&ptitle=…&purl=…[&pscreens=N].
|
|
3
|
+
*
|
|
4
|
+
* It draws "Add this page to a report design": the page, the report to add it to (the report whose studio was opened last is chosen), how it is
|
|
5
|
+
* saved, and Add as a sample. The extension never posts to PMOS itself: every PMOS write needs a press on a PMOS page. So this page asks its
|
|
6
|
+
* parent, the extension popup, with postMessage { pmosCapture: 'request' }; the popup takes the page and answers { pmosCapture: 'data', kind, … }, and
|
|
7
|
+
* THIS page uploads it with its own press (POST /api/report-design/sample, from 'chrome').
|
|
8
|
+
*
|
|
9
|
+
* Messages in: only from window.parent, only from a chrome-extension:// origin or this page's own origin, only these words, and 'data' only while a
|
|
10
|
+
* request is waiting. Messages out: to the parent's origin, never '*'. Nothing here sends anything outside the workspace. */
|
|
11
|
+
import { el } from './dom.js';
|
|
12
|
+
import { icon, pbtn, plural } from './c3-ini-ui.js';
|
|
13
|
+
import { pmGet, pmPost } from './http.js';
|
|
14
|
+
import { ensureC3Css } from './pm-work-view.js';
|
|
15
|
+
|
|
16
|
+
const q = new URLSearchParams(location.search);
|
|
17
|
+
const KIND = ['deck', 'doc', 'page'].includes(q.get('pkind')) ? q.get('pkind') : 'page';
|
|
18
|
+
const TITLE = (q.get('ptitle') || 'This page').slice(0, 120);
|
|
19
|
+
const URL_OF = (q.get('purl') || '').slice(0, 2000);
|
|
20
|
+
const SCREENS = Math.max(0, Math.min(500, parseInt(q.get('pscreens') || '0', 10) || 0));
|
|
21
|
+
const MAX_MB = 20;
|
|
22
|
+
const host = (() => { try { return new URL(URL_OF).hostname; } catch { return ''; } })();
|
|
23
|
+
|
|
24
|
+
let parentOrigin = '';
|
|
25
|
+
try { parentOrigin = (location.ancestorOrigins && location.ancestorOrigins[0]) || ''; } catch { parentOrigin = ''; }
|
|
26
|
+
if (!parentOrigin) { try { parentOrigin = new URL(document.referrer).origin; } catch { parentOrigin = ''; } }
|
|
27
|
+
const trusted = (o) => !!o && (o.startsWith('chrome-extension://') || o === location.origin) && (!parentOrigin || o === parentOrigin);
|
|
28
|
+
const say = (m) => { if (window.parent !== window && parentOrigin) { try { window.parent.postMessage(m, parentOrigin); } catch { /* the popup is gone */ } } };
|
|
29
|
+
|
|
30
|
+
const S = { st: 'load', reports: [], rep: null, last: null, err: '', step: 0, of: 1, text: '', got: '', waiting: false, root: null };
|
|
31
|
+
|
|
32
|
+
const WHAT = {
|
|
33
|
+
deck: { what: 'Google Slides', how: 'PMOS saves the deck as a PDF so the design can follow its look. Only your own PMOS gets it.', saving: 'Saving the deck as a PDF' },
|
|
34
|
+
doc: { what: 'Google Docs', how: 'PMOS saves the document as a PDF so the design can follow its look. Only your own PMOS gets it.', saving: 'Saving the document as a PDF' },
|
|
35
|
+
page: { what: '', how: 'PMOS saves the page as pictures, scrolled top to bottom, so the design can follow its look. Only your own PMOS gets it.', saving: 'Saving the page as pictures' },
|
|
36
|
+
};
|
|
37
|
+
const whatLine = () => (KIND === 'page' ? [host || 'Web page', SCREENS ? 'about ' + plural(SCREENS, 'screen') + ' long' : ''].filter(Boolean).join(', ') : WHAT[KIND].what);
|
|
38
|
+
|
|
39
|
+
function whatCard() {
|
|
40
|
+
return el('div', { class: 'cap-what', 'data-cap': 'page' },
|
|
41
|
+
el('span', { class: 'cap-thumb ' + (KIND === 'page' ? 'page' : 'deck'), 'aria-hidden': 'true' }, el('i'), el('i'), el('i')),
|
|
42
|
+
el('div', null, el('b', null, icon(KIND === 'page' ? 'notes' : 'img', 'sm'), TITLE), el('span', { class: 'dim mono' }, whatLine())));
|
|
43
|
+
}
|
|
44
|
+
function head() {
|
|
45
|
+
return el('div', { class: 'cap-head' }, el('b', { class: 'mono' }, 'PMOS'), el('span', null, 'Add this page to a report design'));
|
|
46
|
+
}
|
|
47
|
+
const repOf = () => S.reports.find((r) => r.key === S.rep) || null;
|
|
48
|
+
|
|
49
|
+
function bar(pct) { const i = el('i'); i.style.setProperty('--rp-w', pct + '%'); return i; }
|
|
50
|
+
|
|
51
|
+
function draw() {
|
|
52
|
+
const r = repOf();
|
|
53
|
+
let body;
|
|
54
|
+
if (S.st === 'load') body = [el('p', { class: 'dim mono cap-msg', role: 'status' }, 'Reading your reports.')];
|
|
55
|
+
else if (S.st === 'none') body = [whatCard(), el('p', { class: 'cap-msg', 'data-cap': 'none' }, icon('alert', 'sm'), el('span', null, 'There is no report set up in PMOS yet. Set one up in Reports, then add this page to its design.'))];
|
|
56
|
+
else if (S.st === 'error') body = [el('p', { class: 'cap-msg', role: 'alert', 'data-cap': 'error' }, icon('alert', 'sm'), el('span', null, S.err)), el('div', { class: 'cap-acts' }, pbtn('outline', 'Try again', 'undo', { 'data-cap': 'retry', onclick: () => { S.st = 'ready'; draw(); } }))];
|
|
57
|
+
else if (S.st === 'sending') {
|
|
58
|
+
const pct = Math.max(6, Math.min(100, Math.round((S.step / Math.max(1, S.of)) * 100)));
|
|
59
|
+
body = [whatCard(), el('p', { class: 'cap-msg', 'data-cap': 'saving' }, el('b', null, WHAT[KIND].saving)),
|
|
60
|
+
el('div', { class: 'cap-prog', role: 'progressbar', 'aria-label': WHAT[KIND].saving, 'aria-valuenow': String(pct), 'aria-valuemin': '0', 'aria-valuemax': '100' }, bar(pct)),
|
|
61
|
+
el('div', { class: 'dim mono cap-step' }, (S.text || 'Starting') + '. Keep this popup open.')];
|
|
62
|
+
} else if (S.st === 'done') {
|
|
63
|
+
body = [el('p', { class: 'cap-msg ok', 'data-cap': 'done' }, icon('check', 'sm'), el('b', null, 'Added to ' + (r ? r.name : 'the report'))),
|
|
64
|
+
el('div', { class: 'dim cap-line' }, TITLE + ', ' + S.got + '. PMOS reads its look on your next turn in the studio.'),
|
|
65
|
+
el('div', { class: 'cap-acts' }, pbtn('primary', 'Open the studio', 'spark', { 'data-cap': 'studio', onclick: openStudio }))];
|
|
66
|
+
} else {
|
|
67
|
+
body = [whatCard(),
|
|
68
|
+
el('label', { class: 'cap-f' }, el('span', { class: 'dim cap-lbl' }, 'Report'),
|
|
69
|
+
el('select', { 'data-cap-rep': '', 'aria-label': 'Report', onchange: (e) => { S.rep = e.target.value; draw(); } },
|
|
70
|
+
...S.reports.map((x) => el('option', { value: x.key, selected: x.key === S.rep }, x.label)))),
|
|
71
|
+
S.rep === S.last ? el('div', { class: 'dim cap-lbl' }, 'The report whose studio you opened last.') : null,
|
|
72
|
+
el('div', { class: 'dim cap-line' }, WHAT[KIND].how),
|
|
73
|
+
el('div', { class: 'cap-acts' }, pbtn('primary', 'Add as a sample', 'plus', { 'data-cap': 'send', onclick: send }))];
|
|
74
|
+
}
|
|
75
|
+
S.root.replaceChildren(el('div', { class: 'card cap-pop', role: 'dialog', 'aria-label': 'PMOS extension' }, head(), ...body.filter(Boolean)));
|
|
76
|
+
requestAnimationFrame(() => say({ pmosCapture: 'resize', height: Math.max(120, Math.min(600, Math.ceil(S.root.getBoundingClientRect().height) + 8)) }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function send() {
|
|
80
|
+
if (!repOf()) return;
|
|
81
|
+
S.st = 'sending'; S.step = 0; S.of = 1; S.text = 'Asking the page'; S.waiting = true;
|
|
82
|
+
draw();
|
|
83
|
+
if (window.parent === window) { S.waiting = false; fail('Open this page from the PMOS button in Chrome.'); return; }
|
|
84
|
+
say({ pmosCapture: 'request' });
|
|
85
|
+
}
|
|
86
|
+
function fail(text) { S.waiting = false; S.st = 'error'; S.err = text; draw(); }
|
|
87
|
+
|
|
88
|
+
async function upload(m) {
|
|
89
|
+
const r = repOf();
|
|
90
|
+
if (!r) return fail('Choose a report, then try again.');
|
|
91
|
+
const body = m.kind === 'pdf'
|
|
92
|
+
? { ws: r.ws, id: r.id, name: m.name, kind: 'pdf', data: m.data, from: 'chrome', source_url: m.url, ...(m.pages ? { pages: m.pages } : {}) }
|
|
93
|
+
: { ws: r.ws, id: r.id, name: m.name, kind: 'pictures', pictures: m.pictures, from: 'chrome', source_url: m.url };
|
|
94
|
+
S.text = 'Sending it to PMOS'; S.step = S.of; draw();
|
|
95
|
+
const res = await Promise.resolve(pmPost('/api/report-design/sample', body)).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
96
|
+
if (!res || !res.ok) {
|
|
97
|
+
const e = String((res && res.error) || '');
|
|
98
|
+
return fail(res && res.status === 413 || /20 MB/.test(e) ? 'The sample is over ' + MAX_MB + ' MB. PMOS keeps a sample up to ' + MAX_MB + ' MB. Open a shorter page, or add a smaller copy.'
|
|
99
|
+
: e ? 'PMOS did not take the sample: ' + e + '.' : 'PMOS is not answering. Start PMOS, then try again.');
|
|
100
|
+
}
|
|
101
|
+
S.got = m.kind === 'pdf' ? (m.pages ? plural(m.pages, KIND === 'deck' ? 'slide' : 'page') + ' as a PDF' : 'a PDF') : plural(m.pictures.length, 'page picture');
|
|
102
|
+
S.st = 'done';
|
|
103
|
+
draw();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function openStudio() {
|
|
107
|
+
const r = repOf(); if (!r) return;
|
|
108
|
+
if (window.parent !== window) { say({ pmosCapture: 'open', report: r.id }); return; }
|
|
109
|
+
location.href = '/c3.html?studio=' + encodeURIComponent(r.id) + (r.ws ? '&ws=' + encodeURIComponent(r.ws) : '');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
window.addEventListener('message', (e) => {
|
|
113
|
+
if (e.source !== window.parent || window.parent === window || !trusted(e.origin)) return;
|
|
114
|
+
const m = e.data;
|
|
115
|
+
if (!m || typeof m !== 'object' || typeof m.pmosCapture !== 'string' || !S.waiting) return;
|
|
116
|
+
if (m.pmosCapture === 'progress' && typeof m.text === 'string' && Number.isInteger(m.step) && Number.isInteger(m.of) && m.of > 0) { S.text = m.text.slice(0, 80); S.step = m.step; S.of = m.of; draw(); return; }
|
|
117
|
+
if (m.pmosCapture === 'error' && typeof m.error === 'string') { fail(m.error.slice(0, 400)); return; }
|
|
118
|
+
if (m.pmosCapture !== 'data' || S.st !== 'sending') return;
|
|
119
|
+
if (m.kind === 'pdf' && typeof m.data === 'string' && m.data) { S.waiting = false; upload({ kind: 'pdf', name: String(m.name || TITLE).slice(0, 120), url: String(m.url || URL_OF), pages: Number.isInteger(m.pages) ? m.pages : 0, data: m.data }); return; }
|
|
120
|
+
if (m.kind === 'pictures' && Array.isArray(m.pictures) && m.pictures.length && m.pictures.length <= 12 && m.pictures.every((p) => typeof p === 'string' && p)) { S.waiting = false; upload({ kind: 'pictures', name: String(m.name || TITLE).slice(0, 120), url: String(m.url || URL_OF), pictures: m.pictures }); return; }
|
|
121
|
+
fail('The page came back in a form PMOS could not use.');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
async function load() {
|
|
125
|
+
const list = await pmGet('/api/workspaces').then((j) => (j && j.workspaces) || []).catch(() => []);
|
|
126
|
+
const want = q.get('ws');
|
|
127
|
+
const wss = want ? list.filter((w) => w.id === want) : list;
|
|
128
|
+
const per = await Promise.all(wss.map(async (w) => {
|
|
129
|
+
const [a, b] = await Promise.all([pmGet('/api/report-setups?ws=' + encodeURIComponent(w.id)).catch(() => null), pmGet('/api/report-design/last?ws=' + encodeURIComponent(w.id)).catch(() => null)]);
|
|
130
|
+
return { w, setups: ((a && a.setups) || []).filter((s) => s && s.id && s.status !== 'removed'), last: b && b.ok && b.id ? { id: b.id, at: b.at || '' } : null };
|
|
131
|
+
}));
|
|
132
|
+
const many = per.filter((x) => x.setups.length).length > 1;
|
|
133
|
+
S.reports = per.flatMap((x) => x.setups.map((s) => ({ key: x.w.id + '|' + s.id, ws: x.w.id, id: s.id, name: s.name || s.id, label: many ? (s.name || s.id) + ' (' + (x.w.name || x.w.id) + ')' : (s.name || s.id) })));
|
|
134
|
+
const lasts = per.filter((x) => x.last && x.setups.some((s) => s.id === x.last.id)).sort((a, b) => String(b.last.at).localeCompare(String(a.last.at)));
|
|
135
|
+
S.last = lasts.length ? lasts[0].w.id + '|' + lasts[0].last.id : null;
|
|
136
|
+
S.rep = S.last || (S.reports[0] && S.reports[0].key) || null;
|
|
137
|
+
S.st = S.reports.length ? 'ready' : 'none';
|
|
138
|
+
draw();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function boot() {
|
|
142
|
+
S.root = document.getElementById('c3-root');
|
|
143
|
+
const theme = q.get('theme') === 'dark' || (typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches && q.get('theme') !== 'light') ? 'dark' : 'light';
|
|
144
|
+
document.documentElement.setAttribute('data-pm-page-theme', theme);
|
|
145
|
+
S.root.setAttribute('data-pm-theme', theme);
|
|
146
|
+
S.root.classList.add('c3-cap');
|
|
147
|
+
ensureC3Css();
|
|
148
|
+
const l = document.createElement('link'); l.rel = 'stylesheet'; l.href = new URL('./c3-capture.css', import.meta.url).href; document.head.appendChild(l);
|
|
149
|
+
draw();
|
|
150
|
+
load().then(() => say({ pmosCapture: 'ready' }), () => { S.st = 'none'; draw(); say({ pmosCapture: 'ready' }); });
|
|
151
|
+
}
|
|
152
|
+
boot();
|
|
@@ -19,8 +19,8 @@ import { RD, failed, splitRender, slideCard, frame, wireFrame, diffOutlines, ens
|
|
|
19
19
|
|
|
20
20
|
const MAX = 20 * 1024 * 1024;
|
|
21
21
|
const KIND_OF = (name) => { const e = String(name || '').toLowerCase().split('.').pop(); return e === 'pdf' ? 'pdf' : e === 'pptx' ? 'pptx' : e === 'docx' ? 'docx' : /^(png|jpe?g)$/.test(e) ? 'image' : ''; };
|
|
22
|
-
const SICON = { pdf: 'pdf', pptx: 'img', docx: 'doc', image: 'img', link: 'link', 'pmos-doc': 'doc' };
|
|
23
|
-
const SWORD = { pdf: 'Upload, PDF', pptx: 'Upload, .pptx', docx: 'Upload, .docx', image: 'Upload, image', link: 'Link', 'pmos-doc': 'Past PMOS document' };
|
|
22
|
+
const SICON = { pdf: 'pdf', pptx: 'img', docx: 'doc', image: 'img', pictures: 'img', link: 'link', 'pmos-doc': 'doc' };
|
|
23
|
+
const SWORD = { pdf: 'Upload, PDF', pptx: 'Upload, .pptx', docx: 'Upload, .docx', image: 'Upload, image', pictures: 'Page pictures', link: 'Link', 'pmos-doc': 'Past PMOS document' };
|
|
24
24
|
/* the studio's width, as a session's terminal sheet has it: half, three-quarters or the whole window; kept on this browser */
|
|
25
25
|
const SIZE_KEY = 'pmos.workspace.c3.studio-size';
|
|
26
26
|
export const STUDIO_SIZES = [['half', 'Half', 'Half of the window'], ['three-quarters', '3/4', 'Three-quarters of the window'], ['whole', 'Whole', 'Whole window']];
|
|
@@ -36,8 +36,8 @@ const LOCAL = {};
|
|
|
36
36
|
const local = (id) => LOCAL[id] || (LOCAL[id] = { view: null, cmp: false, cmpA: null, sidx: 0, point: true, markers: [], draft: null, mOpen: null, panel: false, text: '', fmt: null });
|
|
37
37
|
|
|
38
38
|
function sampleChip(s) {
|
|
39
|
-
const th = s.thumb && /^data:image\//.test(s.thumb) ? el('img', { class: 'rd-dks-thumb img', src: s.thumb, alt: '' }) : el('span', { class: 'rd-dks-thumb ' + (s.kind === 'pptx' || s.kind === 'image' ? 'deck' : 'page'), 'aria-hidden': 'true' }, el('i'), el('i'), el('i'));
|
|
40
|
-
return el('div', { class: 'rd-dks-smp', 'data-rd-sample': s.id }, th, el('div', null, el('b', null, icon(SICON[s.kind] || 'doc', 'sm'), s.name), el('span', { class: 'dim mono' }, SWORD[s.kind] || s.kind),
|
|
39
|
+
const th = s.thumb && /^data:image\//.test(s.thumb) ? el('img', { class: 'rd-dks-thumb img', src: s.thumb, alt: '' }) : el('span', { class: 'rd-dks-thumb ' + (s.kind === 'pptx' || s.kind === 'image' || s.kind === 'pictures' || (s.via === 'chrome' && s.kind === 'pdf') ? 'deck' : 'page'), 'aria-hidden': 'true' }, el('i'), el('i'), el('i'));
|
|
40
|
+
return el('div', { class: 'rd-dks-smp', 'data-rd-sample': s.id }, th, el('div', null, el('b', null, icon(SICON[s.kind] || 'doc', 'sm'), s.name), el('span', { class: 'dim mono' }, s.line || SWORD[s.kind] || s.kind),
|
|
41
41
|
s.hint ? [el('p', { class: 'rd-warn', 'data-rd-hint': '' }, s.hint), s.took ? el('p', null, el('span', { class: 'dim' }, 'PMOS took: '), s.took) : null]
|
|
42
42
|
: s.readable === false ? el('p', { class: 'rd-warn' }, 'PMOS could not read it with your tools. It is kept, but it does not shape the design.')
|
|
43
43
|
: el('p', null, el('span', { class: 'dim' }, 'PMOS took: '), s.took || 'PMOS reads it on the next turn.')));
|
|
@@ -52,7 +52,7 @@ export function openStudio({ ws, id, name, view, onClose } = {}) {
|
|
|
52
52
|
let size = sizeOf(sizeStore.get());
|
|
53
53
|
const sizeBtns = STUDIO_SIZES.map(([k, label, title]) => btn({ class: 'ds-btn', 'aria-pressed': String(k === size), 'data-rd-size': k, title, 'aria-label': title, onclick: () => setSize(k) }, label));
|
|
54
54
|
const seg = el('div', { class: 'seg rd-dks-size', role: 'group', 'aria-label': 'Studio width' }, ...sizeBtns);
|
|
55
|
-
const ui = openSheet({ title: 'Design studio', icon: 'spark', wide: true, dw: true, cls: 'rd-wide rdsz-' + size, body, actions: [seg], onClose: () => { S.closed = true; clearTimeout(S.timer); if (onClose) onClose(); } });
|
|
55
|
+
const ui = openSheet({ title: 'Design studio', icon: 'spark', wide: true, dw: true, cls: 'rd-wide rdsz-' + size, body, actions: [seg], onClose: () => { S.closed = true; clearTimeout(S.timer); clearInterval(S.watch); window.removeEventListener('focus', checkNew); if (onClose) onClose(); } });
|
|
56
56
|
function setSize(k) {
|
|
57
57
|
ui.sheet.classList.remove('rdsz-' + size); size = sizeOf(k); ui.sheet.classList.add('rdsz-' + size);
|
|
58
58
|
sizeStore.set(size);
|
|
@@ -74,6 +74,16 @@ export function openStudio({ ws, id, name, view, onClose } = {}) {
|
|
|
74
74
|
if (n == null || S.cache[n]) return;
|
|
75
75
|
try { S.cache[n] = splitRender(await RD.version(ws, id, n)); } catch (e) { const style = ''; S.cache[n] = { err: (e && e.message) || 'not readable', slides: [], secs: [], style, body: '' }; }
|
|
76
76
|
}
|
|
77
|
+
/* A sample added from another tab (the PMOS Chrome extension) shows here: the open studio reads the design every few seconds and when the window gets focus. */
|
|
78
|
+
const sampleSig = (D) => ((D && D.samples) || []).map((x) => x.id + (x.fetching ? '~' : '')).join(',');
|
|
79
|
+
async function checkNew() {
|
|
80
|
+
if (S.closed || S.busy || !S.D || S.D.running) return;
|
|
81
|
+
const r = await RD.design(ws, id);
|
|
82
|
+
if (S.closed || !r || r.ok === false || !r.design || !S.D || S.D.running) return;
|
|
83
|
+
if (sampleSig(r.design) !== sampleSig(S.D)) load(true);
|
|
84
|
+
}
|
|
85
|
+
S.watch = setInterval(checkNew, 4000);
|
|
86
|
+
window.addEventListener('focus', checkNew);
|
|
77
87
|
function poll() { clearTimeout(S.timer); S.timer = setTimeout(async () => { if (S.closed) return; const was = S.D && S.D.current; await load(false); if (S.D && !S.D.running && S.D.current !== was) { K.markers = []; K.draft = null; scrollLog(); } }, 1500); }
|
|
78
88
|
const ver = (n) => (S.D.versions || []).find((v) => v.n === n);
|
|
79
89
|
const cmpA = () => { const b = S.D.current; const a = K.cmpA != null && K.cmpA !== b && ver(K.cmpA) ? K.cmpA : (S.D.versions.filter((v) => v.n < b).pop() || S.D.versions.find((v) => v.n !== b) || {}).n; return a; };
|
|
@@ -104,6 +114,8 @@ export function openStudio({ ws, id, name, view, onClose } = {}) {
|
|
|
104
114
|
return el('div', { class: 'rd-dks-panel', 'data-rd-panel': '' }, el('div', { class: 'ds-h' }, 'Add a sample'),
|
|
105
115
|
btn({ class: 'rd-dks-opt', 'data-rd-smp': 'file', onclick: () => file.click() }, icon('drop', 'sm'), el('span', null, 'Upload a past deck or document', el('br'), el('span', { class: 'dim rd-small' }, 'PDF, .pptx, .docx or a screenshot, up to 20 MB'))), file,
|
|
106
116
|
el('div', { class: 'ds-whyadd' }, link, btn({ class: 'ds-btn', 'data-rd-smp': 'link', onclick: () => addLink() }, 'Add')),
|
|
117
|
+
btn({ class: 'rd-dks-opt', 'data-rd-smp': 'chrome', 'aria-expanded': String(!!K.chrome), onclick: () => { K.chrome = !K.chrome; paint(); } }, icon('out', 'sm'), el('span', null, 'From Chrome', el('br'), el('span', { class: 'dim rd-small' }, 'Open any page in Chrome and press the PMOS button. Decks come as a PDF, other pages as pictures.'))),
|
|
118
|
+
K.chrome ? el('div', { class: 'fold', 'data-rd-chrome': '' }, icon('info', 'sm'), el('span', null, 'In Chrome, open the page you want, for example a Google Slides deck, and press the PMOS button in the toolbar. Choose this report and press Add as a sample. The sample shows here in a moment. If the PMOS button is not in Chrome yet, run ', el('code', null, 'pmos extension'), ' in a terminal for the steps to load it.')) : null,
|
|
107
119
|
docs.map((d) => btn({ class: 'rd-dks-opt', 'data-rd-smp': 'doc', 'data-v': d.path, onclick: () => addDoc(d) }, icon('doc', 'sm'), el('span', null, 'Pick a past PMOS document', el('br'), el('span', { class: 'dim rd-small' }, d.name + ', ' + slotWord(d.slot))))),
|
|
108
120
|
el('div', { class: 'dim rd-small' }, 'PMOS reads a sample to learn how it looks. It does not copy its numbers. A Google or Confluence link is read through your connectors for its words and structure, not its look.'));
|
|
109
121
|
}
|
|
@@ -624,6 +624,8 @@ async function boot() {
|
|
|
624
624
|
const saved = DEMO ? null : await pmGet('/api/settings' + (S.ws ? '?ws=' + encodeURIComponent(S.ws) : '')).catch(() => null); // demo mode's workspace is not registered
|
|
625
625
|
if (saved && saved.ok) { applyLook(root, saved.settings); if (!start) { const s = (saved.settings || []).find((x) => x.key === 'look.start'); if (s && BUILD[s.value]) start = s.value; } }
|
|
626
626
|
if (q.get('focus') || q.get('fdate')) import('./c3-focus.js').then((m) => m.openFocus({ event: { id: q.get('focus') || '', ...(q.get('fat') ? { at: q.get('fat'), title: q.get('ftitle') || '' } : {}) }, date: q.get('fdate') || '', pop: q.get('pop') === '1', meet: q.get('meet') || '', mtitle: q.get('mtitle') || '' })).catch(() => { /* the focus page is not available */ }); // a reload keeps the Meeting focus page open; ?pop=1 is its compact form for the Chrome extension (focus=now asks which meeting the Meet call is)
|
|
627
|
+
/* ?studio=<report id> opens that report's design studio over the Reports tab (the PMOS Chrome extension's Open the studio). */
|
|
628
|
+
if (q.get('studio') && /^[a-z][a-z0-9-]{0,39}$/.test(q.get('studio'))) { if (!start) start = 'rep'; import('./c3-design-studio.js').then((m) => m.openStudio({ ws: S.ws, id: q.get('studio'), name: '' })).catch(() => { /* the studio is not available */ }); }
|
|
627
629
|
if (!S.userNav) showTab(start || 'brief', false); // a press during the read above (the gear, a tab) wins over the start page
|
|
628
630
|
new ResizeObserver(moveInd).observe(S.tabNav);
|
|
629
631
|
workCount(); briefCount(); askCount(); S.dock.start();
|
|
@@ -38,6 +38,7 @@ The modes:
|
|
|
38
38
|
|
|
39
39
|
How to read a sample:
|
|
40
40
|
- A PDF or an image: read the file with Read and look at every page. Follow its look closely: the grid and the place of each part, the colours, the header and footer bands, the type sizes, how dense each slide or page is, and the slide kinds and their order. Say in "took" what you followed.
|
|
41
|
+
- Pictures of a web page (a sample the PM added from Chrome): the lines name every picture file, in order, top to bottom. Read every one with Read, as you would the pages of a PDF, and follow the look closely in the same way. They are one long page cut into screens, so do not read them as separate slides. Say in "took" what you followed.
|
|
41
42
|
- A .pptx or a .docx: read its words in the .txt file named for it. You cannot see its look, only its words and order.
|
|
42
43
|
- A report document: read the .md file named for it.
|
|
43
44
|
- A link: when the lines name a .txt copy for it, read that file. It holds the words and the structure of the linked file (slide titles, order, sections, tables), copied through the PM's connectors. It does not show the look, so take the look from a PDF or an image sample, and say so in "took". When the lines name no copy, you have no web tool: say so with "readable": false.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "report-design-studio UI slice: the design studio, the deck of a cycle in the Read tab, Present, speaker notes and notes on the slide, Print / PDF, the document design and the Slide deck output, in demo mode; and the same flows against the real backend with a stub claude (c3-report-design-real-test.js).",
|
|
3
|
-
"panels": ["c3-report-design-test.js", "c3-report-design-real-test.js"],
|
|
4
|
-
"seconds": { "c3-report-design-test.js": 75, "c3-report-design-real-test.js": 40 }
|
|
2
|
+
"_comment": "report-design-studio UI slice: the design studio, the deck of a cycle in the Read tab, Present, speaker notes and notes on the slide, Print / PDF, the document design and the Slide deck output, in demo mode; and the same flows against the real backend with a stub claude (c3-report-design-real-test.js); and the PMOS Chrome extension's popup page against the real backend, with a parent page that plays the extension (c3-capture-test.js).",
|
|
3
|
+
"panels": ["c3-report-design-test.js", "c3-report-design-real-test.js", "c3-capture-test.js"],
|
|
4
|
+
"seconds": { "c3-report-design-test.js": 75, "c3-report-design-real-test.js": 40, "c3-capture-test.js": 40 }
|
|
5
5
|
}
|