@tio-canvas/canvas 0.1.0 → 0.1.2
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/client/canvas.css +14 -0
- package/client/canvas.js +11 -1
- package/client/changelog.js +58 -0
- package/client/index.html +5 -0
- package/package.json +34 -7
- package/src/project.js +4 -1
package/client/canvas.css
CHANGED
|
@@ -731,3 +731,17 @@ body[data-text-select],body[data-text-select] * { cursor: text !important; }
|
|
|
731
731
|
/* The group box steps aside while screens are being reordered. */
|
|
732
732
|
body[data-gesture="reorder"] #selection { border-color: transparent; }
|
|
733
733
|
body[data-gesture="reorder"] #selection #handles { visibility: hidden; }
|
|
734
|
+
|
|
735
|
+
/* Change log dialog: reuses the shortcuts dialog body; one block per release.
|
|
736
|
+
The dialog is as tall as its content, up to the picker height. */
|
|
737
|
+
#changelog { height: fit-content; max-height: min(var(--canvas-picker-height),calc(100dvh - 2 * var(--canvas-panel-inset))); }
|
|
738
|
+
.changelog-entry + .changelog-entry { margin-top: var(--canvas-layers-heading-inset); padding-top: var(--canvas-layers-heading-inset); border-top: var(--canvas-border) solid var(--canvas-line); }
|
|
739
|
+
.changelog-entry h3 { display: flex; align-items: baseline; gap: var(--canvas-padding); margin: 0 0 var(--canvas-padding); font-size: inherit; font-weight: 600; color: var(--canvas-text); }
|
|
740
|
+
.changelog-entry time { color: var(--canvas-muted); font-weight: 400; font-variant-numeric: tabular-nums; }
|
|
741
|
+
.changelog-items { display: grid; grid-template-columns: 4.5rem minmax(0,1fr); column-gap: var(--canvas-gap); row-gap: var(--canvas-padding); margin: 0; padding: 0; list-style: none; }
|
|
742
|
+
.changelog-items li { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; align-items: baseline; text-wrap: pretty; }
|
|
743
|
+
.changelog-tag { --tag-hue: 265; justify-self: start; padding: 1px var(--canvas-padding); border-radius: 999px; background: oklch(0.7 0.12 var(--tag-hue) / .16); color: color-mix(in oklch, oklch(0.5 0.15 var(--tag-hue)) 70%, var(--canvas-text)); font-size: 11px; font-weight: 600; line-height: 18px; }
|
|
744
|
+
.changelog-tag[data-type="new"] { --tag-hue: 150; }
|
|
745
|
+
.changelog-tag[data-type="improved"] { --tag-hue: 250; }
|
|
746
|
+
.changelog-tag[data-type="fixed"] { --tag-hue: 60; }
|
|
747
|
+
.changelog-tag[data-type="faster"] { --tag-hue: 330; }
|
package/client/canvas.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import './tooltip.js';
|
|
2
2
|
import './shortcuts.js';
|
|
3
|
+
import {renderChangelog} from './changelog.js';
|
|
3
4
|
import './device-gate.js';
|
|
4
5
|
import './onboarding.js';
|
|
5
6
|
import { setupSelectFields, closeSelectMenus, positionSelectMenus } from './select-field.js';
|
|
@@ -921,6 +922,15 @@ shortcuts.addEventListener('click',event=>{
|
|
|
921
922
|
if(event.clientX<rect.left || event.clientX>rect.right || event.clientY<rect.top || event.clientY>rect.bottom)shortcuts.close();
|
|
922
923
|
});
|
|
923
924
|
shortcuts.addEventListener('close',()=>$('#layers-more').focus());
|
|
925
|
+
const changelog=$('#changelog');
|
|
926
|
+
$('#changelog-open').onclick=()=>{closeContext();stop();clearContact();delete document.body.dataset.space;renderChangelog(changelog.querySelector('.changelog-body'));changelog.showModal();};
|
|
927
|
+
$('#changelog-close').onclick=()=>changelog.close();
|
|
928
|
+
changelog.addEventListener('click',event=>{
|
|
929
|
+
if(event.target!==changelog)return;
|
|
930
|
+
const rect=changelog.getBoundingClientRect();
|
|
931
|
+
if(event.clientX<rect.left || event.clientX>rect.right || event.clientY<rect.top || event.clientY>rect.bottom)changelog.close();
|
|
932
|
+
});
|
|
933
|
+
changelog.addEventListener('close',()=>$('#layers-more').focus());
|
|
924
934
|
$('#undo').onclick=()=>runHistory('undo');
|
|
925
935
|
$('#redo').onclick=()=>runHistory('redo');
|
|
926
936
|
$('#fit').onclick=()=>fitEntries(fitAll());
|
|
@@ -962,7 +972,7 @@ moreMenu.addEventListener('beforetoggle',event=>{
|
|
|
962
972
|
moreMenu.style.left=`${Math.max(inset,Math.min(anchor.left+(anchor.width-width)/2,innerWidth-width-inset))}px`;
|
|
963
973
|
});
|
|
964
974
|
moreMenu.addEventListener('toggle',event=>{if(event.newState==='open')moreMenu.focus({preventScroll:true});moreButton.setAttribute('aria-expanded',String(event.newState==='open'));});
|
|
965
|
-
moreMenu.addEventListener('click',event=>{if(event.target.closest('#shortcuts-open,#onboarding-open'))moreMenu.hidePopover();});
|
|
975
|
+
moreMenu.addEventListener('click',event=>{if(event.target.closest('#shortcuts-open,#onboarding-open,#changelog-open'))moreMenu.hidePopover();});
|
|
966
976
|
moreMenu.addEventListener('keydown',event=>{
|
|
967
977
|
event.stopPropagation();
|
|
968
978
|
if(!['ArrowDown','ArrowUp','Home','End'].includes(event.key))return;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// What changed for the person using the canvas, newest first. Only user-visible
|
|
2
|
+
// changes belong here: new things you can do, things that work differently,
|
|
3
|
+
// fixes you would have noticed, and speed. Build, release and refactor work
|
|
4
|
+
// never gets an entry. `type` is one of: new, improved, fixed, faster.
|
|
5
|
+
//
|
|
6
|
+
// New items go in the `unreleased` entry on top. On release,
|
|
7
|
+
// scripts/stamp-changelog.mjs gives it the real version and date and adds a fresh
|
|
8
|
+
// empty one. Keep that entry's shape.
|
|
9
|
+
export const entries = [
|
|
10
|
+
{
|
|
11
|
+
unreleased: true,
|
|
12
|
+
items: [],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
version: '0.1.2',
|
|
16
|
+
date: '2026-11-08',
|
|
17
|
+
items: [
|
|
18
|
+
{ type: 'new', text: 'A Change log in the More menu lists what changed in each release.' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
version: '0.1.0',
|
|
23
|
+
date: '2026-11-08',
|
|
24
|
+
items: [
|
|
25
|
+
{ type: 'new', text: 'The exploration canvas is now its own package, so any project can lay its pages out on an infinite board.' },
|
|
26
|
+
{ type: 'new', text: 'A demo project ships with the canvas, so you can try it without setting anything up.' },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const labels = { new: 'Feature', improved: 'Improved', fixed: 'Fixed', faster: 'Faster' };
|
|
32
|
+
|
|
33
|
+
const formatDate = iso => new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' });
|
|
34
|
+
const el = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text != null) node.textContent = text; return node; };
|
|
35
|
+
|
|
36
|
+
export function renderChangelog(body, list = entries) {
|
|
37
|
+
body.replaceChildren();
|
|
38
|
+
if (!list.some(entry => entry.items.length)) { body.append(el('p', '', 'Nothing to show yet.')); return; }
|
|
39
|
+
for (const entry of list.filter(entry => entry.items.length)) {
|
|
40
|
+
const section = el('section', 'changelog-entry');
|
|
41
|
+
const heading = el('h3', '', entry.unreleased ? 'Unreleased' : entry.version);
|
|
42
|
+
if (entry.date) {
|
|
43
|
+
const time = el('time', '', formatDate(entry.date));
|
|
44
|
+
time.dateTime = entry.date;
|
|
45
|
+
heading.append(time);
|
|
46
|
+
}
|
|
47
|
+
const items = el('ul', 'changelog-items');
|
|
48
|
+
for (const item of entry.items) {
|
|
49
|
+
const row = el('li');
|
|
50
|
+
const tag = el('span', 'changelog-tag', labels[item.type] ?? item.type);
|
|
51
|
+
tag.dataset.type = item.type;
|
|
52
|
+
row.append(tag, el('span', '', item.text));
|
|
53
|
+
items.append(row);
|
|
54
|
+
}
|
|
55
|
+
section.append(heading, items);
|
|
56
|
+
body.append(section);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/client/index.html
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
<button id="shortcuts-open" type="button" role="menuitem"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 5.5C8.6 4.4 6.6 4 3.5 4v11c3.1 0 5.1.4 6.5 1.5m0-11C11.4 4.4 13.4 4 16.5 4v11c-3.1 0-5.1.4-6.5 1.5m0-11v11"/></svg>Guide & hotkeys</button>
|
|
91
91
|
<hr role="separator">
|
|
92
92
|
<button id="onboarding-open" type="button" role="menuitem"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="7"/><path d="M8.2 8a1.9 1.9 0 1 1 2.9 1.6c-.7.4-1.1.8-1.1 1.6M10 14h.01"/></svg>Onboarding</button>
|
|
93
|
+
<button id="changelog-open" type="button" role="menuitem"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M5.5 3h9a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM7.5 7h5M7.5 10h5M7.5 13h3"/></svg>Change log</button>
|
|
93
94
|
</div>
|
|
94
95
|
<div id="screen-context" class="canvas-tools" popover="auto" role="menu" aria-label="Screen actions" tabindex="-1">
|
|
95
96
|
<button type="button" role="menuitem" data-action="duplicate"><svg viewBox="0 0 20 20" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2"/><path d="M12 7V3H3v9h4"/></svg>Duplicate</button>
|
|
@@ -99,6 +100,10 @@
|
|
|
99
100
|
<header class="layers-header"><h2 id="shortcuts-title">Guide & hotkeys</h2><div id="shortcuts-platform" class="segmented" role="radiogroup" aria-label="Keyboard layout"><button type="button" role="radio" data-platform="mac" aria-checked="false">macOS</button><button type="button" role="radio" data-platform="win" aria-checked="false">Windows</button></div><button id="shortcuts-close" type="button" aria-label="Close Guide & hotkeys" data-tooltip="Close" data-shortcut="esc">✕</button></header>
|
|
100
101
|
<div class="shortcuts-body"></div>
|
|
101
102
|
</dialog>
|
|
103
|
+
<dialog id="changelog" class="page-picker" aria-labelledby="changelog-title">
|
|
104
|
+
<header class="layers-header"><h2 id="changelog-title">Change log</h2><button id="changelog-close" type="button" aria-label="Close Change log" data-tooltip="Close" data-shortcut="esc">✕</button></header>
|
|
105
|
+
<div class="shortcuts-body changelog-body"></div>
|
|
106
|
+
</dialog>
|
|
102
107
|
<div id="device-gate" popover="manual" role="alert">
|
|
103
108
|
<div class="device-gate-icons" aria-hidden="true">
|
|
104
109
|
<svg viewBox="0 0 20 20"><rect x="3" y="3" width="14" height="10" rx="1"/><path d="M10 13v4m-4 0h8"/></svg>
|
package/package.json
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tio-canvas/canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A private exploration canvas: lay your project's pages out on an infinite board, frame real documents, annotate. Development tool, plain JavaScript, ships nothing to visitors.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Tio Putra",
|
|
8
|
-
"repository": {
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/tiobedil/tio-canvas.git"
|
|
11
|
+
},
|
|
9
12
|
"homepage": "https://github.com/tiobedil/tio-canvas#readme",
|
|
10
|
-
"bugs": {
|
|
11
|
-
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/tiobedil/tio-canvas/issues"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"tio-canvas": "./bin/tio-canvas.mjs"
|
|
18
|
+
},
|
|
12
19
|
"exports": {
|
|
13
20
|
".": "./src/index.js",
|
|
14
21
|
"./server": "./src/server.js",
|
|
@@ -18,10 +25,22 @@
|
|
|
18
25
|
"./client/*": "./client/*",
|
|
19
26
|
"./package.json": "./package.json"
|
|
20
27
|
},
|
|
21
|
-
"files": [
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"client",
|
|
31
|
+
"src",
|
|
32
|
+
"docs",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22"
|
|
41
|
+
},
|
|
24
42
|
"scripts": {
|
|
43
|
+
"dev": "node dev/serve.mjs",
|
|
25
44
|
"test": "node tests/run.mjs"
|
|
26
45
|
},
|
|
27
46
|
"dependencies": {
|
|
@@ -29,5 +48,13 @@
|
|
|
29
48
|
"interface-kit": "0.1.3",
|
|
30
49
|
"react": "^19.3.1",
|
|
31
50
|
"react-dom": "^19.3.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
54
|
+
"@semantic-release/exec": "^7.1.0",
|
|
55
|
+
"@semantic-release/git": "^11.0.1",
|
|
56
|
+
"@semantic-release/github": "^12.0.10",
|
|
57
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
58
|
+
"semantic-release": "^24.2.9"
|
|
32
59
|
}
|
|
33
60
|
}
|
package/src/project.js
CHANGED
|
@@ -20,9 +20,12 @@ export function resolveProject({ root = process.cwd(), config = DEFAULT_CONFIG,
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
let loadCount = 0;
|
|
24
|
+
|
|
23
25
|
/** Import the project's config afresh and refuse a screen the canvas cannot show. */
|
|
24
26
|
export async function loadProject(project) {
|
|
25
|
-
|
|
27
|
+
// A unique query per call defeats the module cache; Date.now() alone can repeat within one millisecond.
|
|
28
|
+
const module = await import(`${pathToFileURL(project.configFile).href}?load=${Date.now()}-${++loadCount}`);
|
|
26
29
|
if (!Array.isArray(module.screens)) throw new Error(`${project.configFile} must export "screens", an array.`);
|
|
27
30
|
validateScreens(module.screens);
|
|
28
31
|
return { screens: module.screens, storageKey: module.storageKey ?? DEFAULT_STORAGE_KEY, presets, screenKinds };
|