This commit is contained in:
“chrisshank” 2024-11-01 21:02:29 -07:00
commit 321ecd7364
30 changed files with 709 additions and 1287 deletions

41
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Setup repo
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install dependencies
run: deno i
- name: Build
run: deno task build
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./demo/dist"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
dist
.vite
node_modules
dist

View File

@ -1,3 +0,0 @@
{
"singleQuote": true
}

View File

@ -10,6 +10,17 @@
- How can we compose together live and visual programming notations?
- Can we have lightweight visual and live scripting for web pages?
## Development
1. Install [Deno](https://docs.deno.com/runtime/getting_started/installation/)
```bash
deno i
# then
deno task dev
```
All config is in `deno.json`.
## Primitives
- `<fc-geometry>`: Manipulate HTML elements in space.

View File

@ -15,12 +15,12 @@
margin: 0;
}
spatial-geometry {
fc-geometry {
border: 1px solid black;
border-radius: 3px;
}
spatial-connection {
fc-connection {
display: block;
position: absolute;
inset: 0 0 0 0;
@ -32,18 +32,18 @@
</style>
</head>
<body>
<spatial-geometry id="box1" x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry id="box2" x="200" y="300">Hello World</spatial-geometry>
<spatial-connection source="#box1" target="#box2"></spatial-connection>
<fc-geometry id="box1" x="100" y="100" width="50" height="50"></fc-geometry>
<fc-geometry id="box2" x="200" y="300">Hello World</fc-geometry>
<fc-connection source="#box1" target="#box2"></fc-connection>
<spatial-connection source="#box1" target="400,100"></spatial-connection>
<fc-connection source="#box1" target="400,100"></fc-connection>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { SpatialConnection } from '../src/arrows/spatial-connection.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { FolkConnection } from '../src/arrows/fc-connection.ts';
SpatialGeometry.register();
SpatialConnection.register();
FolkGeometry.register();
FolkConnection.register();
</script>
</body>
</html>

View File

@ -35,7 +35,7 @@
font-style: italic;
}
spatial-geometry {
fc-geometry {
z-index: 2;
}
</style>
@ -45,64 +45,77 @@
<h1>My article</h1>
<p>
<span
>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.</span
>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.</span
>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa
qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur.
<span
>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.</span
>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.</span
>
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet,>consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.
Lorem ipsum dolor sit amet,>consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
</main>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
SpatialGeometry.register();
FolkGeometry.register();
function copyStyles(from, to) {
const styles = getComputedStyle(from);
@ -115,7 +128,8 @@
}
document.addEventListener('click', ({ target }) => {
if (target.closest('spatial-geometry') || target.matches('body, html')) return;
if (target.closest('fc-geometry') || target.matches('body, html'))
return;
const rect = target.getBoundingClientRect();
@ -133,7 +147,7 @@
copyStyles(el, copiedElements[i]);
});
const geometry = document.createElement('spatial-geometry');
const geometry = document.createElement('fc-geometry');
geometry.x = rect.x;
geometry.y = rect.y;
geometry.height = rect.height;

View File

@ -36,13 +36,13 @@
}
}
spatial-connection {
fc-connection {
display: block;
position: absolute;
inset: 0 0 0 0;
}
spatial-geometry {
fc-geometry {
border-radius: 7px;
&::part(rotate),
@ -53,7 +53,7 @@
display: none;
}
spatial-thought {
fc-thought {
background-color: white;
border-radius: 6px;
border: solid 1px light-dark(rgb(118, 118, 118), rgb(133, 133, 133));

View File

@ -1,29 +1,31 @@
import { SpatialGeometry } from '../../src/canvas/spatial-geometry.ts';
import { SpatialConnection } from '../../src/arrows/spatial-connection.ts';
import { FileSaver } from '../../src/persistence/file.ts';
import { FolkGeometry } from "../../src/canvas/fc-geometry.ts";
import { FolkConnection } from "../../src/arrows/fc-connection.ts";
import { FileSaver } from "../../src/persistence/file.ts";
declare global {
interface HTMLElementTagNameMap {
'spatial-thought': SpatialThought;
"fc-thought": FolkThought;
}
}
class SpatialThought extends HTMLElement {
static tagName = 'spatial-thought';
class FolkThought extends HTMLElement {
static tagName = "fc-thought";
static register() {
customElements.define(this.tagName, this);
}
#deleteButton = this.querySelector('button[name="delete"]') as HTMLButtonElement;
#deleteButton = this.querySelector(
'button[name="delete"]'
) as HTMLButtonElement;
#text = this.querySelector('[name="text"]') as HTMLElement;
#geometry = this.parentElement as SpatialGeometry;
#geometry = this.parentElement as FolkGeometry;
constructor() {
super();
this.addEventListener('click', this);
this.addEventListener("click", this);
}
get text() {
@ -31,22 +33,22 @@ class SpatialThought extends HTMLElement {
}
handleEvent(event: PointerEvent): void {
if (event.type === 'click' && event.target === this.#deleteButton) {
if (event.type === "click" && event.target === this.#deleteButton) {
this.#geometry.remove();
document
.querySelectorAll(
`spatial-connection[source="spatial-geometry[id='${this.#geometry.id}']"],
spatial-connection[target="spatial-geometry[id='${this.#geometry.id}']"]`
`fc-connection[source="fc-geometry[id='${this.#geometry.id}']"],
fc-connection[target="fc-geometry[id='${this.#geometry.id}']"]`
)
.forEach((el) => el.remove());
}
}
}
SpatialGeometry.register();
SpatialThought.register();
SpatialConnection.register();
FolkGeometry.register();
FolkThought.register();
FolkConnection.register();
interface Thought {
id: string;
@ -68,57 +70,68 @@ interface ChainOfThought {
const html = String.raw;
function parseHTML(html: string): Element {
return document.createRange().createContextualFragment(html).firstElementChild!;
return document.createRange().createContextualFragment(html)
.firstElementChild!;
}
function renderThought({ id, x, y, text }: Thought) {
return html`<spatial-geometry id="${id}" x="${x}" y="${y}">
<spatial-thought>
return html`<fc-geometry id="${id}" x="${x}" y="${y}">
<fc-thought>
<div contenteditable="true" name="text">${text}</div>
<button name="delete"></button>
</spatial-thought>
</spatial-geometry>`;
</fc-thought>
</fc-geometry>`;
}
function renderConnection({ sourceId, targetId }: Connection) {
return html`<spatial-connection
source="spatial-geometry[id='${sourceId}']"
target="spatial-geometry[id='${targetId}']"
></spatial-connection>`;
return html`<fc-connection
source="fc-geometry[id='${sourceId}']"
target="fc-geometry[id='${targetId}']"
></fc-connection>`;
}
function renderChainOfThought({ thoughts, connections }: ChainOfThought) {
return html`${thoughts.map(renderThought).join('')}${connections.map(renderConnection).join('')}`;
return html`${thoughts.map(renderThought).join("")}${connections
.map(renderConnection)
.join("")}`;
}
function parseChainOfThought(): ChainOfThought {
return {
thoughts: Array.from(document.querySelectorAll('spatial-geometry')).map((el) => ({
id: el.id,
text: (el.firstElementChild as SpatialThought).text,
x: el.x,
y: el.y,
})),
connections: Array.from(document.querySelectorAll('spatial-connection')).map((el) => ({
sourceId: (el.sourceElement as SpatialGeometry).id,
targetId: (el.targetElement as SpatialGeometry).id,
})),
thoughts: Array.from(document.querySelectorAll("fc-geometry")).map(
(el) => ({
id: el.id,
text: (el.firstElementChild as FolkThought).text,
x: el.x,
y: el.y,
})
),
connections: Array.from(document.querySelectorAll("fc-connection")).map(
(el) => ({
sourceId: (el.sourceElement as FolkGeometry).id,
targetId: (el.targetElement as FolkGeometry).id,
})
),
};
}
const openButton = document.querySelector('button[name="open"]')!;
const saveButton = document.querySelector('button[name="save"]')!;
const saveAsButton = document.querySelector('button[name="save-as"]')!;
const main = document.querySelector('main')!;
const fileSaver = new FileSaver('chains-of-thought', 'json', 'application/json');
const main = document.querySelector("main")!;
const fileSaver = new FileSaver(
"chains-of-thought",
"json",
"application/json"
);
main.addEventListener('dblclick', (e) => {
main.addEventListener("dblclick", (e) => {
if (e.target === main) {
main.appendChild(
parseHTML(
renderThought({
id: String(document.querySelectorAll('spatial-thought').length + 1),
text: '',
id: String(document.querySelectorAll("fc-thought").length + 1),
text: "",
x: e.clientX,
y: e.clientY,
})
@ -138,20 +151,20 @@ async function openFile(showPicker = true) {
}
}
async function saveFile(promptNewFile = false) {
function saveFile(promptNewFile = false) {
const file = JSON.stringify(parseChainOfThought(), null, 2);
fileSaver.save(file, promptNewFile);
}
openButton.addEventListener('click', () => {
openButton.addEventListener("click", () => {
openFile();
});
saveButton.addEventListener('click', () => {
saveButton.addEventListener("click", () => {
saveFile();
});
saveAsButton.addEventListener('click', () => {
saveAsButton.addEventListener("click", () => {
saveFile(true);
});

View File

@ -19,24 +19,27 @@
margin: 0;
}
spatial-geometry {
fc-geometry {
border: 2px solid black;
}
</style>
</head>
<body>
<spatial-geometry x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry x="200" y="200" width="50" height="50"></spatial-geometry>
<fc-geometry x="100" y="100" width="50" height="50"></fc-geometry>
<fc-geometry x="200" y="200" width="50" height="50"></fc-geometry>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
SpatialGeometry.register();
const geometryElements = document.querySelectorAll('spatial-geometry');
FolkGeometry.register();
const geometryElements = document.querySelectorAll('fc-geometry');
function collisionDetection(rect1, rect2) {
return (
rect1.left < rect2.right && rect1.right > rect2.left && rect1.top < rect2.bottom && rect1.bottom > rect2.top
rect1.left < rect2.right &&
rect1.right > rect2.left &&
rect1.top < rect2.bottom &&
rect1.bottom > rect2.top
);
}
@ -46,7 +49,12 @@
el !== e.target &&
collisionDetection(
// TODO: refactor this hack once resizing and the vertices API are figured out
DOMRectReadOnly.fromRect({ x: el.x, y: el.y, height: el.height, width: el.width }),
DOMRectReadOnly.fromRect({
x: el.x,
y: el.y,
height: el.height,
width: el.width,
}),
DOMRectReadOnly.fromRect({
x: e.target.x,
y: e.target.y,

View File

@ -15,7 +15,7 @@
margin: 0;
}
spatial-geometry {
fc-geometry {
border: 1px solid black;
border-radius: 3px;
}
@ -28,8 +28,10 @@
</style>
</head>
<body>
<spatial-geometry id="box1" x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry id="box2" x="200" y="300" width="50" height="50">Hello World</spatial-geometry>
<fc-geometry id="box1" x="100" y="100" width="50" height="50"></fc-geometry>
<fc-geometry id="box2" x="200" y="300" width="50" height="50"
>Hello World</fc-geometry
>
<event-propagator
source="#box1"
target="#box2"
@ -37,8 +39,8 @@
expression="$target.textContent += '!'"
></event-propagator>
<spatial-geometry id="box3" x="150" y="200" width="50" height="50"></spatial-geometry>
<spatial-geometry id="box4" x="300" y="350" width="50" height="50"></spatial-geometry>
<fc-geometry id="box3" x="150" y="200" width="50" height="50"></fc-geometry>
<fc-geometry id="box4" x="300" y="350" width="50" height="50"></fc-geometry>
<event-propagator
source="#box3"
target="#box4"
@ -47,10 +49,10 @@
></event-propagator>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { EventPropagator } from '../src/arrows/event-propagator.ts';
SpatialGeometry.register();
FolkGeometry.register();
EventPropagator.register();
</script>
</body>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spatial Canvas Primitives</title>
<title>Folk Canvas Primitives</title>
<style>
html {
height: 100%;
@ -16,18 +16,18 @@
</style>
</head>
<body>
<h1>Spatial Canvas Primitives</h1>
<h1>Folk Canvas Primitives</h1>
<ul>
<li><a href="/shapes">Shapes</a></li>
<li><a href="/collision">Collision</a></li>
<li><a href="/maps">Maps</a></li>
<li><a href="/music">Music</a></li>
<li><a href="/ink">Ink</a></li>
<li><a href="/arrows">Arrows</a></li>
<li><a href="/canvasify">Canvasify</a></li>
<li><a href="/spreadsheet">Spreadsheet</a></li>
<li><a href="/chains-of-thought/index.html">Chains of thought</a></li>
<li><a href="shapes.html">Shapes</a></li>
<li><a href="collision.html">Collision</a></li>
<li><a href="maps.html">Maps</a></li>
<li><a href="music.html">Music</a></li>
<li><a href="ink.html">Ink</a></li>
<li><a href="arrows.html">Arrows</a></li>
<li><a href="canvasify.html">Canvasify</a></li>
<li><a href="spreadsheet.html">Spreadsheet</a></li>
<li><a href="chains-of-thought/index.html">Chains of thought</a></li>
</ul>
</body>
</html>

View File

@ -21,11 +21,11 @@
<button on:click="DRAW">Draw</button>
</nav>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { SpatialInk } from '../src/canvas/spatial-ink.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { FolkInk } from '../src/canvas/fc-ink.ts';
SpatialGeometry.register();
SpatialInk.register();
FolkGeometry.register();
FolkInk.register();
const drawButton = document.querySelector('button');
@ -36,7 +36,7 @@
e.preventDefault();
e.stopPropagation();
const ink = document.createElement('spatial-ink');
const ink = document.createElement('fc-ink');
document.body.appendChild(ink);
@ -45,7 +45,7 @@
ink.points = ink.points.map(([x, y, p]) => [x - rect.x, y - rect.y, p]);
const geometry = document.createElement('spatial-geometry');
const geometry = document.createElement('fc-geometry');
geometry.x = rect.x;
geometry.y = rect.y;
geometry.height = rect.height;

View File

@ -37,27 +37,35 @@
</style>
</head>
<body>
<spatial-geometry x="25" y="100" width="400" height="200">
<fc-geometry x="25" y="100" width="400" height="200">
<leaflet-map coordinates="52.09, 5.12" zoom="13"></leaflet-map>
</spatial-geometry>
</fc-geometry>
<spatial-geometry x="50" y="550" width="400" height="250">
<leaflet-map coordinates="51.50404120260676, -0.14007568359375003" zoom="13"></leaflet-map>
</spatial-geometry>
<fc-geometry x="50" y="550" width="400" height="250">
<leaflet-map
coordinates="51.50404120260676, -0.14007568359375003"
zoom="13"
></leaflet-map>
</fc-geometry>
<spatial-geometry x="500" y="400" width="500" height="300">
<geo-wiki coordinates="51.50404120260676, -0.14007568359375003"></geo-wiki>
</spatial-geometry>
<fc-geometry x="500" y="400" width="500" height="300">
<geo-wiki
coordinates="51.50404120260676, -0.14007568359375003"
></geo-wiki>
</fc-geometry>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { LeafletMap } from '../src/maps';
SpatialGeometry.register();
FolkGeometry.register();
LeafletMap.register();
function collisionDetection(rect1, rect2) {
return (
rect1.left < rect2.right && rect1.right > rect2.left && rect1.top < rect2.bottom && rect1.bottom > rect2.top
rect1.left < rect2.right &&
rect1.right > rect2.left &&
rect1.top < rect2.bottom &&
rect1.bottom > rect2.top
);
}
@ -74,7 +82,10 @@
}
const proximityMap = new Map(
Array.from(document.querySelectorAll('spatial-geometry')).map((el) => [el, new Set()])
Array.from(document.querySelectorAll('fc-geometry')).map((el) => [
el,
new Set(),
])
);
function handleProximity(e) {
@ -83,7 +94,12 @@
const alreadyIntersection = set.has(e.target);
// TODO: refactor this hack once resizing and the vertices API are figured out
const isNowIntersecting = proximityDetection(
DOMRectReadOnly.fromRect({ x: el.x, y: el.y, height: el.height, width: el.width }),
DOMRectReadOnly.fromRect({
x: el.x,
y: el.y,
height: el.height,
width: el.width,
}),
DOMRectReadOnly.fromRect({
x: e.target.x,
y: e.target.y,
@ -136,7 +152,9 @@
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'coordinates') {
this.#coordinates = newValue.split(',').map((str) => Number(str)) || [0, 0];
this.#coordinates = newValue
.split(',')
.map((str) => Number(str)) || [0, 0];
this.searchWiki(this.#coordinates);
}
}
@ -152,7 +170,9 @@
origin: '*',
});
// https://www.mediawiki.org/wiki/API:Geosearch
this.#results = await fetch(`https://en.wikipedia.org/w/api.php?${params}`)
this.#results = await fetch(
`https://en.wikipedia.org/w/api.php?${params}`
)
.then((response) => response.json())
.then((data) => data?.query?.geosearch ?? []);

View File

@ -15,7 +15,7 @@
margin: 0;
}
spatial-geometry:has(record-player) {
fc-geometry:has(record-player) {
&::part(resize-nw),
&::part(resize-ne),
&::part(resize-se),
@ -24,7 +24,7 @@
}
}
spatial-geometry > video {
fc-geometry > video {
height: 100%;
}
</style>
@ -35,45 +35,47 @@
ffmpeg -framerate 25 -pattern_type glob -i '*.png' -c:v prores -pix_fmt yuva444p10le dancing-flower.mov
ffmpeg -framerate 25 -f image2 -pattern_type glob -i '*.png' -c:v libvpx-vp9 -pix_fmt yuva420p dancing-flowers.webm
-->
<spatial-geometry x="100" y="75" width="330" height="198">
<fc-geometry x="100" y="75" width="330" height="198">
<record-player>
<audio src="/Feather.mp3"></audio>
</record-player>
</spatial-geometry>
</fc-geometry>
<spatial-geometry x="25" y="300" width="166" height="200">
<fc-geometry x="25" y="300" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
</fc-geometry>
<spatial-geometry x="155" y="315" width="166" height="200">
<fc-geometry x="155" y="315" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
</fc-geometry>
<spatial-geometry x="280" y="305" width="166" height="200">
<fc-geometry x="280" y="305" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
</fc-geometry>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { RecordPlayer } from '../src/music/record-player.ts';
SpatialGeometry.register();
FolkGeometry.register();
RecordPlayer.register();
let proximityDistance = 200;
const proximitySet = new Set();
const recordPlayerGeometry = document.querySelector('spatial-geometry:has(record-player)');
const recordPlayerGeometry = document.querySelector(
'fc-geometry:has(record-player)'
);
const recordPlayer = recordPlayerGeometry.firstElementChild;
const flowers = document.querySelectorAll('spatial-geometry:has(video)');
const flowers = document.querySelectorAll('fc-geometry:has(video)');
// set playback rate when video is ready
function setPlayback(e) {
e.target.playbackRate = (91 / 60) * e.target.duration;

View File

@ -15,23 +15,23 @@
margin: 0;
}
spatial-geometry {
fc-geometry {
border: 2px solid black;
}
</style>
</head>
<body>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
SpatialGeometry.register();
FolkGeometry.register();
const geometries = [];
const now = performance.now();
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 100; j++) {
const geo = document.createElement('spatial-geometry');
const geo = document.createElement('fc-geometry');
geo.x = 50 * i;
geo.y = 50 * j;
geo.width = geo.height = 45;

View File

@ -15,27 +15,27 @@
margin: 0;
}
spatial-geometry {
fc-geometry {
background: rgb(187, 178, 178);
box-shadow: rgba(0, 0, 0, 0.2) 1.95px 1.95px 2.6px;
transition: scale 100ms ease-out, box-shadow 100ms ease-out;
}
spatial-geometry:state(move) {
fc-geometry:state(move) {
scale: 1.05;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}
</style>
</head>
<body>
<spatial-geometry x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry x="100" y="200" width="50" height="50"></spatial-geometry>
<spatial-geometry x="100" y="300" width="50" height="50"></spatial-geometry>
<fc-geometry x="100" y="100" width="50" height="50"></fc-geometry>
<fc-geometry x="100" y="200" width="50" height="50"></fc-geometry>
<fc-geometry x="100" y="300" width="50" height="50"></fc-geometry>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
SpatialGeometry.register();
FolkGeometry.register();
</script>
</body>
</html>

View File

@ -22,21 +22,26 @@
</style>
</head>
<body>
<spatial-geometry x="50" y="50" height="300" width="500">
<fc-geometry x="50" y="50" height="300" width="500">
<s-table></s-table>
</spatial-geometry>
</fc-geometry>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { SpreadsheetTable, SpreadsheetHeader, SpreadsheetCell } from '../src/spreadsheet/spreadsheet.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import {
SpreadsheetTable,
SpreadsheetHeader,
SpreadsheetCell,
} from '../src/spreadsheet/spreadsheet.ts';
SpatialGeometry.register();
FolkGeometry.register();
SpreadsheetTable.register();
SpreadsheetHeader.register();
SpreadsheetCell.register();
document.querySelector(`s-cell[column="A"][row="1"]`).expression = '1';
document.querySelector(`s-cell[column="A"][row="2"]`).expression = '$A1 * 2';
document.querySelector(`s-cell[column="A"][row="2"]`).expression =
'$A1 * 2';
</script>
</body>
</html>

View File

@ -1,23 +0,0 @@
import { resolve } from 'node:path';
import { readdirSync } from 'node:fs';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
target: 'esnext',
rollupOptions: {
input: readdirSync(__dirname)
.filter((file) => file.endsWith('.html'))
.reduce((acc, file) => {
acc[file.replace('.html', '')] = resolve(__dirname, file);
return acc;
}, {} as Record<string, string>),
// input: {
// thoughts: resolve(__dirname, 'chains-of-thought/index.html'),
// },
},
modulePreload: {
polyfill: false,
},
},
});

21
deno.json Normal file
View File

@ -0,0 +1,21 @@
{
"tasks": {
"dev": "vite demo",
"build": "vite build demo --outDir dist --base=/"
},
"imports": {
"perfect-arrows": "npm:perfect-arrows@^0.3.7",
"perfect-freehand": "npm:perfect-freehand@^1.2.2",
"leaflet": "npm:leaflet@^1.9.4",
"vite": "npm:vite@^5.4.10"
},
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"dom.asynciterable",
"deno.ns"
]
},
"nodeModulesDir": "auto"
}

237
deno.lock Normal file
View File

@ -0,0 +1,237 @@
{
"version": "4",
"specifiers": {
"npm:leaflet@^1.9.4": "1.9.4",
"npm:perfect-arrows@~0.3.7": "0.3.7",
"npm:perfect-freehand@^1.2.2": "1.2.2",
"npm:vite@5.4.10": "5.4.10",
"npm:vite@^5.4.10": "5.4.10"
},
"npm": {
"@esbuild/aix-ppc64@0.21.5": {
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="
},
"@esbuild/android-arm64@0.21.5": {
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="
},
"@esbuild/android-arm@0.21.5": {
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="
},
"@esbuild/android-x64@0.21.5": {
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="
},
"@esbuild/darwin-arm64@0.21.5": {
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="
},
"@esbuild/darwin-x64@0.21.5": {
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="
},
"@esbuild/freebsd-arm64@0.21.5": {
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="
},
"@esbuild/freebsd-x64@0.21.5": {
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="
},
"@esbuild/linux-arm64@0.21.5": {
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="
},
"@esbuild/linux-arm@0.21.5": {
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="
},
"@esbuild/linux-ia32@0.21.5": {
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="
},
"@esbuild/linux-loong64@0.21.5": {
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="
},
"@esbuild/linux-mips64el@0.21.5": {
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="
},
"@esbuild/linux-ppc64@0.21.5": {
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="
},
"@esbuild/linux-riscv64@0.21.5": {
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="
},
"@esbuild/linux-s390x@0.21.5": {
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="
},
"@esbuild/linux-x64@0.21.5": {
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="
},
"@esbuild/netbsd-x64@0.21.5": {
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="
},
"@esbuild/openbsd-x64@0.21.5": {
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="
},
"@esbuild/sunos-x64@0.21.5": {
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="
},
"@esbuild/win32-arm64@0.21.5": {
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="
},
"@esbuild/win32-ia32@0.21.5": {
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="
},
"@esbuild/win32-x64@0.21.5": {
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="
},
"@rollup/rollup-android-arm-eabi@4.24.3": {
"integrity": "sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ=="
},
"@rollup/rollup-android-arm64@4.24.3": {
"integrity": "sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q=="
},
"@rollup/rollup-darwin-arm64@4.24.3": {
"integrity": "sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w=="
},
"@rollup/rollup-darwin-x64@4.24.3": {
"integrity": "sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow=="
},
"@rollup/rollup-freebsd-arm64@4.24.3": {
"integrity": "sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A=="
},
"@rollup/rollup-freebsd-x64@4.24.3": {
"integrity": "sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg=="
},
"@rollup/rollup-linux-arm-gnueabihf@4.24.3": {
"integrity": "sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A=="
},
"@rollup/rollup-linux-arm-musleabihf@4.24.3": {
"integrity": "sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw=="
},
"@rollup/rollup-linux-arm64-gnu@4.24.3": {
"integrity": "sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ=="
},
"@rollup/rollup-linux-arm64-musl@4.24.3": {
"integrity": "sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw=="
},
"@rollup/rollup-linux-powerpc64le-gnu@4.24.3": {
"integrity": "sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g=="
},
"@rollup/rollup-linux-riscv64-gnu@4.24.3": {
"integrity": "sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA=="
},
"@rollup/rollup-linux-s390x-gnu@4.24.3": {
"integrity": "sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw=="
},
"@rollup/rollup-linux-x64-gnu@4.24.3": {
"integrity": "sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ=="
},
"@rollup/rollup-linux-x64-musl@4.24.3": {
"integrity": "sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw=="
},
"@rollup/rollup-win32-arm64-msvc@4.24.3": {
"integrity": "sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA=="
},
"@rollup/rollup-win32-ia32-msvc@4.24.3": {
"integrity": "sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ=="
},
"@rollup/rollup-win32-x64-msvc@4.24.3": {
"integrity": "sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ=="
},
"@types/estree@1.0.6": {
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
"esbuild@0.21.5": {
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dependencies": [
"@esbuild/aix-ppc64",
"@esbuild/android-arm",
"@esbuild/android-arm64",
"@esbuild/android-x64",
"@esbuild/darwin-arm64",
"@esbuild/darwin-x64",
"@esbuild/freebsd-arm64",
"@esbuild/freebsd-x64",
"@esbuild/linux-arm",
"@esbuild/linux-arm64",
"@esbuild/linux-ia32",
"@esbuild/linux-loong64",
"@esbuild/linux-mips64el",
"@esbuild/linux-ppc64",
"@esbuild/linux-riscv64",
"@esbuild/linux-s390x",
"@esbuild/linux-x64",
"@esbuild/netbsd-x64",
"@esbuild/openbsd-x64",
"@esbuild/sunos-x64",
"@esbuild/win32-arm64",
"@esbuild/win32-ia32",
"@esbuild/win32-x64"
]
},
"fsevents@2.3.3": {
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
},
"leaflet@1.9.4": {
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"nanoid@3.3.7": {
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
},
"perfect-arrows@0.3.7": {
"integrity": "sha512-wEN2gerTPVWl3yqoFEF8OeGbg3aRe2sxNUi9rnyYrCsL4JcI6K2tBDezRtqVrYG0BNtsWLdYiiTrYm+X//8yLQ=="
},
"perfect-freehand@1.2.2": {
"integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ=="
},
"picocolors@1.1.0": {
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
},
"postcss@8.4.47": {
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dependencies": [
"nanoid",
"picocolors",
"source-map-js"
]
},
"rollup@4.24.3": {
"integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==",
"dependencies": [
"@rollup/rollup-android-arm-eabi",
"@rollup/rollup-android-arm64",
"@rollup/rollup-darwin-arm64",
"@rollup/rollup-darwin-x64",
"@rollup/rollup-freebsd-arm64",
"@rollup/rollup-freebsd-x64",
"@rollup/rollup-linux-arm-gnueabihf",
"@rollup/rollup-linux-arm-musleabihf",
"@rollup/rollup-linux-arm64-gnu",
"@rollup/rollup-linux-arm64-musl",
"@rollup/rollup-linux-powerpc64le-gnu",
"@rollup/rollup-linux-riscv64-gnu",
"@rollup/rollup-linux-s390x-gnu",
"@rollup/rollup-linux-x64-gnu",
"@rollup/rollup-linux-x64-musl",
"@rollup/rollup-win32-arm64-msvc",
"@rollup/rollup-win32-ia32-msvc",
"@rollup/rollup-win32-x64-msvc",
"@types/estree",
"fsevents"
]
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"vite@5.4.10": {
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"dependencies": [
"esbuild",
"fsevents",
"postcss",
"rollup"
]
}
},
"workspace": {
"dependencies": [
"npm:leaflet@^1.9.4",
"npm:perfect-arrows@~0.3.7",
"npm:perfect-freehand@^1.2.2",
"npm:vite@^5.4.10"
]
}
}

874
package-lock.json generated
View File

@ -1,874 +0,0 @@
{
"name": "folk-canvas",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "folk-canvas",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"leaflet": "^1.9.4",
"perfect-arrows": "^0.3.7",
"perfect-freehand": "^1.1.0"
},
"devDependencies": {
"@types/leaflet": "^1.9.12",
"@types/node": "^20.12.7",
"typescript": "^5.0.0",
"vite": "^5.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/geojson": {
"version": "7946.0.14",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
"integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==",
"dev": true
},
"node_modules/@types/leaflet": {
"version": "1.9.12",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz",
"integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==",
"dev": true,
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/node": {
"version": "20.15.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.15.0.tgz",
"integrity": "sha512-eQf4OkH6gA9v1W0iEpht/neozCsZKMTK+C4cU6/fv7wtJCCL8LEQ4hie2Ln8ZP/0YYM2xGj7//f8xyqItkJ6QA==",
"dev": true,
"dependencies": {
"undici-types": "~6.13.0"
}
},
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/perfect-arrows": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/perfect-arrows/-/perfect-arrows-0.3.7.tgz",
"integrity": "sha512-wEN2gerTPVWl3yqoFEF8OeGbg3aRe2sxNUi9rnyYrCsL4JcI6K2tBDezRtqVrYG0BNtsWLdYiiTrYm+X//8yLQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/perfect-freehand": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz",
"integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ=="
},
"node_modules/picocolors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"dev": true
},
"node_modules/postcss": {
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.22.4",
"@rollup/rollup-android-arm64": "4.22.4",
"@rollup/rollup-darwin-arm64": "4.22.4",
"@rollup/rollup-darwin-x64": "4.22.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
"@rollup/rollup-linux-arm64-gnu": "4.22.4",
"@rollup/rollup-linux-arm64-musl": "4.22.4",
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
"@rollup/rollup-linux-s390x-gnu": "4.22.4",
"@rollup/rollup-linux-x64-gnu": "4.22.4",
"@rollup/rollup-linux-x64-musl": "4.22.4",
"@rollup/rollup-win32-arm64-msvc": "4.22.4",
"@rollup/rollup-win32-ia32-msvc": "4.22.4",
"@rollup/rollup-win32-x64-msvc": "4.22.4",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz",
"integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==",
"dev": true
},
"node_modules/vite": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
"integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}

View File

@ -1,42 +0,0 @@
{
"name": "folk-canvas",
"description": "",
"version": "0.1.0",
"type": "module",
"sideEffects": false,
"author": "Christopher Shank <chris.shank.23@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/ChrisShank/folk-canvas.git"
},
"bugs": {
"url": "https://github.com/ChrisShank/folk-canvas/issues"
},
"homepage": "https://github.com/ChrisShank/folk-canvas#readme",
"files": [
"dist",
"src"
],
"keywords": [
"spatial canvas",
"custom elements"
],
"scripts": {
"build": "tsc --build",
"dev": "vite demo",
"demo:build": "vite build demo",
"demo:preview": "pnpm demo:build && vite preview demo"
},
"devDependencies": {
"@types/leaflet": "^1.9.12",
"@types/node": "^20.12.7",
"typescript": "^5.0.0",
"vite": "^5.0.0"
},
"dependencies": {
"leaflet": "^1.9.4",
"perfect-arrows": "^0.3.7",
"perfect-freehand": "^1.1.0"
}
}

View File

@ -1,9 +1,9 @@
import { SpatialConnection } from './spatial-connection';
import { FolkConnection } from "./fc-connection.ts";
export class EventPropagator extends SpatialConnection {
static tagName = 'event-propagator';
export class EventPropagator extends FolkConnection {
static override tagName = "event-propagator";
#triggers = (this.getAttribute('triggers') || '').split(',');
#triggers = (this.getAttribute("triggers") || "").split(",");
get triggers() {
return this.#triggers;
}
@ -11,23 +11,23 @@ export class EventPropagator extends SpatialConnection {
this.#triggers = triggers;
}
#expression = '';
#expression = "";
#function = new Function();
get expression() {
return this.#expression;
}
set expression(expression) {
this.#expression = expression;
this.#function = new Function('$source', '$target', '$event', expression);
this.#function = new Function("$source", "$target", "$event", expression);
}
constructor() {
super();
this.expression = this.getAttribute('expression') || '';
this.expression = this.getAttribute("expression") || "";
}
observeSource() {
override observeSource() {
super.observeSource();
for (const trigger of this.#triggers) {
@ -38,7 +38,7 @@ export class EventPropagator extends SpatialConnection {
this.evaluateExpression();
}
unobserveSource() {
override unobserveSource() {
super.unobserveSource();
for (const trigger of this.#triggers) {
@ -47,12 +47,12 @@ export class EventPropagator extends SpatialConnection {
}
}
observeTarget() {
override observeTarget() {
super.observeTarget();
this.evaluateExpression();
}
unobserveTarget() {
override unobserveTarget() {
super.unobserveTarget();
}

View File

@ -1,7 +1,7 @@
import { getBoxToBoxArrow } from 'perfect-arrows';
import { AbstractArrow } from './abstract-arrow';
import { pointsOnBezierCurves } from './points-on-path';
import getStroke, { StrokeOptions } from 'perfect-freehand';
import { getBoxToBoxArrow } from "perfect-arrows";
import { AbstractArrow } from "./abstract-arrow.ts";
import { pointsOnBezierCurves } from "./points-on-path.ts";
import { getStroke, StrokeOptions } from "perfect-freehand";
export type Arrow = [
/** The x position of the (padded) starting point. */
@ -26,12 +26,12 @@ export type Arrow = [
declare global {
interface HTMLElementTagNameMap {
'spatial-connection': SpatialConnection;
"fc-connection": FolkConnection;
}
}
export class SpatialConnection extends AbstractArrow {
static tagName = 'spatial-connection';
export class FolkConnection extends AbstractArrow {
static override tagName = "fc-connection";
#options: StrokeOptions = {
size: 7,
@ -53,7 +53,7 @@ export class SpatialConnection extends AbstractArrow {
},
};
render() {
override render() {
const { sourceRect, targetRect } = this;
const [sx, sy, cx, cy, ex, ey] = getBoxToBoxArrow(
@ -77,12 +77,12 @@ export class SpatialConnection extends AbstractArrow {
const stroke = getStroke(points, this.#options);
const path = getSvgPathFromStroke(stroke);
this.style.clipPath = `path('${path}')`;
this.style.backgroundColor = 'black';
this.style.backgroundColor = "black";
}
}
function getSvgPathFromStroke(stroke: number[][]): string {
if (stroke.length === 0) return '';
if (stroke.length === 0) return "";
for (const point of stroke) {
point[0] = Math.round(point[0] * 100) / 100;
@ -95,9 +95,9 @@ function getSvgPathFromStroke(stroke: number[][]): string {
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
return acc;
},
['M', ...stroke[0], 'Q']
["M", ...stroke[0], "Q"]
);
d.push('Z');
return d.join(' ');
d.push("Z");
return d.join(" ");
}

7
src/canvas/fc-canvas.ts Normal file
View File

@ -0,0 +1,7 @@
export class FCSpace extends HTMLElement {
static tagName = 'fc-space';
static register() {
customElements.define(this.tagName, this);
}
}

View File

@ -30,7 +30,7 @@ class ResizeObserverManager {
}
unobserve(target: Element, callback: ResizeObserverEntryCallback): void {
let callbacks = this.#elementMap.get(target);
const callbacks = this.#elementMap.get(target);
if (callbacks === undefined) return;
@ -45,13 +45,13 @@ class ResizeObserverManager {
const resizeObserver = new ResizeObserverManager();
export type Shape = 'rectangle' | 'circle' | 'triangle';
export type Shape = "rectangle" | "circle" | "triangle";
export type MoveEventDetail = { movementX: number; movementY: number };
export class MoveEvent extends CustomEvent<MoveEventDetail> {
constructor(detail: MoveEventDetail) {
super('move', { detail, cancelable: true, bubbles: true });
super("move", { detail, cancelable: true, bubbles: true });
}
}
@ -59,7 +59,7 @@ export type ResizeEventDetail = { movementX: number; movementY: number };
export class ResizeEvent extends CustomEvent<MoveEventDetail> {
constructor(detail: MoveEventDetail) {
super('resize', { detail, cancelable: true, bubbles: true });
super("resize", { detail, cancelable: true, bubbles: true });
}
}
@ -67,11 +67,11 @@ export type RotateEventDetail = { rotate: number };
export class RotateEvent extends CustomEvent<RotateEventDetail> {
constructor(detail: RotateEventDetail) {
super('rotate', { detail, cancelable: true, bubbles: true });
super("rotate", { detail, cancelable: true, bubbles: true });
}
}
export type Dimension = number | 'auto';
export type Dimension = number | "auto";
const styles = new CSSStyleSheet();
styles.replaceSync(`
@ -180,13 +180,13 @@ styles.replaceSync(`
declare global {
interface HTMLElementTagNameMap {
'spatial-geometry': SpatialGeometry;
"fc-geometry": FolkGeometry;
}
}
// TODO: add z coordinate?
export class SpatialGeometry extends HTMLElement {
static tagName = 'spatial-geometry';
export class FolkGeometry extends HTMLElement {
static tagName = "fc-geometry";
static register() {
customElements.define(this.tagName, this);
@ -194,17 +194,17 @@ export class SpatialGeometry extends HTMLElement {
#internals = this.attachInternals();
#type = (this.getAttribute('type') || 'rectangle') as Shape;
#type = (this.getAttribute("type") || "rectangle") as Shape;
get type(): Shape {
return this.#type;
}
set type(type: Shape) {
this.setAttribute('type', type);
this.setAttribute("type", type);
}
#previousX = 0;
#x = Number(this.getAttribute('x')) || 0;
#x = Number(this.getAttribute("x")) || 0;
get x() {
return this.#x;
}
@ -212,11 +212,11 @@ export class SpatialGeometry extends HTMLElement {
set x(x) {
this.#previousX = this.#x;
this.#x = x;
this.#requestUpdate('x');
this.#requestUpdate("x");
}
#previousY = 0;
#y = Number(this.getAttribute('y')) || 0;
#y = Number(this.getAttribute("y")) || 0;
get y() {
return this.#y;
}
@ -224,7 +224,7 @@ export class SpatialGeometry extends HTMLElement {
set y(y) {
this.#previousY = this.#y;
this.#y = y;
this.#requestUpdate('y');
this.#requestUpdate("y");
}
#autoContentRect = this.getBoundingClientRect();
@ -232,48 +232,48 @@ export class SpatialGeometry extends HTMLElement {
#previousWidth: Dimension = 0;
#width: Dimension = 0;
get width(): number {
if (this.#width === 'auto') {
if (this.#width === "auto") {
return this.#autoContentRect.width;
}
return this.#width;
}
set width(width: Dimension) {
if (width === 'auto') {
if (width === "auto") {
resizeObserver.observe(this, this.#onResize);
} else if (this.#width === 'auto' && this.#height !== 'auto') {
} else if (this.#width === "auto" && this.#height !== "auto") {
resizeObserver.unobserve(this, this.#onResize);
}
this.#previousWidth = this.#width;
this.#width = width;
this.#requestUpdate('width');
this.#requestUpdate("width");
}
#previousHeight: Dimension = 0;
#height: Dimension = 0;
get height(): number {
if (this.#height === 'auto') {
if (this.#height === "auto") {
return this.#autoContentRect.height;
}
return this.#height;
}
set height(height: Dimension) {
if (height === 'auto') {
if (height === "auto") {
resizeObserver.observe(this, this.#onResize);
} else if (this.#height === 'auto' && this.#width !== 'auto') {
} else if (this.#height === "auto" && this.#width !== "auto") {
resizeObserver.unobserve(this, this.#onResize);
}
this.#previousHeight = this.#height;
this.#height = height;
this.#requestUpdate('height');
this.#requestUpdate("height");
}
#initialRotation = 0;
#startAngle = 0;
#previousRotate = 0;
#rotate = Number(this.getAttribute('rotate')) || 0;
#rotate = Number(this.getAttribute("rotate")) || 0;
get rotate(): number {
return this.#rotate;
}
@ -281,16 +281,16 @@ export class SpatialGeometry extends HTMLElement {
set rotate(rotate: number) {
this.#previousRotate = this.#rotate;
this.#rotate = rotate;
this.#requestUpdate('rotate');
this.#requestUpdate("rotate");
}
constructor() {
super();
this.addEventListener('pointerdown', this);
this.addEventListener("pointerdown", this);
const shadowRoot = this.attachShadow({
mode: 'open',
mode: "open",
delegatesFocus: true,
});
shadowRoot.adoptedStyleSheets.push(styles);
@ -305,12 +305,12 @@ export class SpatialGeometry extends HTMLElement {
<button part="resize-sw"></button>
<slot></slot>`;
this.height = Number(this.getAttribute('height')) || 'auto';
this.width = Number(this.getAttribute('width')) || 'auto';
this.height = Number(this.getAttribute("height")) || "auto";
this.width = Number(this.getAttribute("width")) || "auto";
}
connectedCallback() {
this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotate']));
this.#update(new Set(["type", "x", "y", "height", "width", "rotate"]));
}
disconnectedCallback() {
@ -319,7 +319,7 @@ export class SpatialGeometry extends HTMLElement {
// Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape.
getBoundingPath(): string {
return '';
return "";
}
// We might also want some kind of utility function that maps a path into an approximate set of vertices.
@ -329,13 +329,13 @@ export class SpatialGeometry extends HTMLElement {
handleEvent(event: PointerEvent) {
switch (event.type) {
case 'pointerdown': {
case "pointerdown": {
if (event.button !== 0 || event.ctrlKey) return;
const target = event.composedPath()[0] as HTMLElement;
// Store initial angle on rotation start
if (target.getAttribute('part') === 'rotate') {
if (target.getAttribute("part") === "rotate") {
// We need to store initial rotation/angle somewhere.
// This is a little awkward as we'll want to do *quite a lot* of this kind of thing.
// Might be an argument for making elements dumber (i.e. not have them manage their own state) and do this from the outside.
@ -351,19 +351,19 @@ export class SpatialGeometry extends HTMLElement {
}
// ignore interactions from slotted elements.
if (target !== this && !target.hasAttribute('part')) return;
if (target !== this && !target.hasAttribute("part")) return;
target.addEventListener('pointermove', this);
this.addEventListener('lostpointercapture', this);
target.addEventListener("pointermove", this);
this.addEventListener("lostpointercapture", this);
target.setPointerCapture(event.pointerId);
const interaction = target.getAttribute('part') || 'move';
const interaction = target.getAttribute("part") || "move";
this.#internals.states.add(interaction);
this.focus();
return;
}
case 'pointermove': {
case "pointermove": {
const target = event.target as HTMLElement;
if (target === null) return;
@ -374,33 +374,33 @@ export class SpatialGeometry extends HTMLElement {
return;
}
const part = target.getAttribute('part');
const part = target.getAttribute("part");
if (part === null) return;
if (part.includes('resize')) {
if (part.includes("resize")) {
// This triggers a move and resize event :(
if (part.includes('-n')) {
if (part.includes("-n")) {
this.y += event.movementY;
this.height -= event.movementY;
}
if (part.endsWith('e')) {
if (part.endsWith("e")) {
this.width += event.movementX;
}
if (part.includes('-s')) {
if (part.includes("-s")) {
this.height += event.movementY;
}
if (part.endsWith('w')) {
if (part.endsWith("w")) {
this.x += event.movementX;
this.width -= event.movementX;
}
return;
}
if (part === 'rotate') {
if (part === "rotate") {
const centerX = this.#x + this.width / 2;
const centerY = this.#y + this.height / 2;
const currentAngle = Math.atan2(
@ -415,12 +415,12 @@ export class SpatialGeometry extends HTMLElement {
return;
}
case 'lostpointercapture': {
case "lostpointercapture": {
const target = event.composedPath()[0] as HTMLElement;
const interaction = target.getAttribute('part') || 'move';
const interaction = target.getAttribute("part") || "move";
this.#internals.states.delete(interaction);
target.removeEventListener('pointermove', this);
this.removeEventListener('lostpointercapture', this);
target.removeEventListener("pointermove", this);
this.removeEventListener("lostpointercapture", this);
return;
}
}
@ -446,14 +446,14 @@ export class SpatialGeometry extends HTMLElement {
// Any updates that should be batched should happen here like updating the DOM or emitting events should be executed here.
#update(updatedProperties: Set<string>) {
if (updatedProperties.has('type')) {
if (updatedProperties.has("type")) {
// TODO: Update shape styles. For many shapes, we could just use clip-path to style the shape.
// If we use relative values in `clip-path: polygon()`, then no JS is needed to style the shape
// If `clip-path: path()` is used then we need to update the path in JS.
// See https://www.smashingmagazine.com/2024/05/modern-guide-making-css-shapes/
}
if (updatedProperties.has('x') || updatedProperties.has('y')) {
if (updatedProperties.has("x") || updatedProperties.has("y")) {
// Although the change in movement isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
const notCancelled = this.dispatchEvent(
new MoveEvent({
@ -463,12 +463,12 @@ export class SpatialGeometry extends HTMLElement {
);
if (notCancelled) {
if (updatedProperties.has('x')) {
if (updatedProperties.has("x")) {
// In the future, when CSS `attr()` is supported we could define this x/y projection in CSS.
this.style.left = `${this.#x}px`;
}
if (updatedProperties.has('y')) {
if (updatedProperties.has("y")) {
this.style.top = `${this.#y}px`;
}
} else {
@ -477,26 +477,26 @@ export class SpatialGeometry extends HTMLElement {
}
}
if (updatedProperties.has('width') || updatedProperties.has('height')) {
if (updatedProperties.has("width") || updatedProperties.has("height")) {
// Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
const notCancelled = this.dispatchEvent(
new ResizeEvent({
movementX:
this.width -
(this.#previousWidth === 'auto' ? 0 : this.#previousWidth),
(this.#previousWidth === "auto" ? 0 : this.#previousWidth),
movementY:
this.height -
(this.#previousHeight === 'auto' ? 0 : this.#previousHeight),
(this.#previousHeight === "auto" ? 0 : this.#previousHeight),
})
);
if (notCancelled) {
if (updatedProperties.has('width')) {
this.style.width = this.#width === 'auto' ? '' : `${this.#width}px`;
if (updatedProperties.has("width")) {
this.style.width = this.#width === "auto" ? "" : `${this.#width}px`;
}
if (updatedProperties.has('height')) {
if (updatedProperties.has("height")) {
this.style.height =
this.#height === 'auto' ? '' : `${this.#height}px`;
this.#height === "auto" ? "" : `${this.#height}px`;
}
} else {
// TODO: Revert changes to position too
@ -505,14 +505,14 @@ export class SpatialGeometry extends HTMLElement {
}
}
if (updatedProperties.has('rotate')) {
if (updatedProperties.has("rotate")) {
// Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
const notCancelled = this.dispatchEvent(
new RotateEvent({ rotate: this.#rotate - this.#previousRotate })
);
if (notCancelled) {
if (updatedProperties.has('rotate')) {
if (updatedProperties.has("rotate")) {
this.style.rotate = `${this.#rotate}deg`;
}
} else {
@ -529,23 +529,23 @@ export class SpatialGeometry extends HTMLElement {
new ResizeEvent({
movementX:
this.width -
(this.#previousWidth === 'auto'
(this.#previousWidth === "auto"
? previousRect.width
: this.#previousWidth),
movementY:
this.height -
(this.#previousHeight === 'auto'
(this.#previousHeight === "auto"
? previousRect.height
: this.#previousHeight),
})
);
if (!notCancelled) {
if (this.#height === 'auto') {
if (this.#height === "auto") {
this.height = previousRect?.height || 0;
}
if (this.#width === 'auto') {
if (this.#width === "auto") {
this.width = previousRect?.width || 0;
}
}

View File

@ -1,4 +1,4 @@
import getStroke, { StrokeOptions } from 'perfect-freehand';
import { getStroke, StrokeOptions } from "perfect-freehand";
export type Point = [x: number, y: number, pressure: number];
@ -25,12 +25,12 @@ styles.replaceSync(`
declare global {
interface HTMLElementTagNameMap {
'spatial-ink': SpatialInk;
"fc-ink": FolkInk;
}
}
export class SpatialInk extends HTMLElement {
static tagName = 'spatial-ink';
export class FolkInk extends HTMLElement {
static tagName = "fc-ink";
static register() {
customElements.define(this.tagName, this);
@ -38,10 +38,10 @@ export class SpatialInk extends HTMLElement {
#internals = this.attachInternals();
#svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
#path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
#svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
#path = document.createElementNS("http://www.w3.org/2000/svg", "path");
#size = Number(this.getAttribute('size') || 16);
#size = Number(this.getAttribute("size") || 16);
get size() {
return this.#size;
@ -51,7 +51,7 @@ export class SpatialInk extends HTMLElement {
this.#update();
}
#thinning = Number(this.getAttribute('thinning') || 0.5);
#thinning = Number(this.getAttribute("thinning") || 0.5);
get thinning() {
return this.#thinning;
@ -61,7 +61,7 @@ export class SpatialInk extends HTMLElement {
this.#update();
}
#smoothing = Number(this.getAttribute('smoothing') || 0.5);
#smoothing = Number(this.getAttribute("smoothing") || 0.5);
get smoothing() {
return this.#smoothing;
@ -71,7 +71,7 @@ export class SpatialInk extends HTMLElement {
this.#update();
}
#streamline = Number(this.getAttribute('streamline') || 0.5);
#streamline = Number(this.getAttribute("streamline") || 0.5);
get streamline() {
return this.#streamline;
@ -81,7 +81,8 @@ export class SpatialInk extends HTMLElement {
this.#update();
}
#simulatePressure = this.getAttribute('streamline') === 'false' ? false : true;
#simulatePressure =
this.getAttribute("streamline") === "false" ? false : true;
get simulatePressure() {
return this.#simulatePressure;
@ -91,7 +92,7 @@ export class SpatialInk extends HTMLElement {
this.#update();
}
#points: Point[] = JSON.parse(this.getAttribute('points') || '[]');
#points: Point[] = JSON.parse(this.getAttribute("points") || "[]");
get points() {
return this.#points;
@ -104,7 +105,10 @@ export class SpatialInk extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
const shadowRoot = this.attachShadow({
mode: "open",
delegatesFocus: true,
});
shadowRoot.adoptedStyleSheets.push(styles);
this.#svg.appendChild(this.#path);
shadowRoot.appendChild(this.#svg);
@ -126,10 +130,10 @@ export class SpatialInk extends HTMLElement {
// TODO: cancel trace?
draw(event?: PointerEvent) {
if (event?.type === 'pointerdown') {
if (event?.type === "pointerdown") {
this.handleEvent(event);
} else {
this.addEventListener('pointerdown', this);
this.addEventListener("pointerdown", this);
}
this.#tracingPromise = Promise.withResolvers();
return this.#tracingPromise.promise;
@ -142,26 +146,26 @@ export class SpatialInk extends HTMLElement {
handleEvent(event: PointerEvent) {
switch (event.type) {
case 'pointerdown': {
case "pointerdown": {
if (event.button !== 0 || event.ctrlKey) return;
this.points = [];
this.addPoint([event.offsetX, event.offsetY, event.pressure]);
this.addEventListener('lostpointercapture', this);
this.addEventListener('pointermove', this);
this.addEventListener("lostpointercapture", this);
this.addEventListener("pointermove", this);
this.setPointerCapture(event.pointerId);
this.#internals.states.add('drawing');
this.#internals.states.add("drawing");
return;
}
case 'pointermove': {
case "pointermove": {
this.addPoint([event.offsetX, event.offsetY, event.pressure]);
return;
}
case 'lostpointercapture': {
this.removeEventListener('pointerdown', this);
this.removeEventListener('pointermove', this);
this.removeEventListener('lostpointercapture', this);
this.#internals.states.delete('drawing');
case "lostpointercapture": {
this.removeEventListener("pointerdown", this);
this.removeEventListener("pointermove", this);
this.removeEventListener("lostpointercapture", this);
this.#internals.states.delete("drawing");
this.#tracingPromise?.resolve();
this.#tracingPromise = null;
return;
@ -189,11 +193,14 @@ export class SpatialInk extends HTMLElement {
cap: true,
},
};
this.#path.setAttribute('d', this.#getSvgPathFromStroke(getStroke(this.#points, options)));
this.#path.setAttribute(
"d",
this.#getSvgPathFromStroke(getStroke(this.#points, options))
);
}
#getSvgPathFromStroke(stroke: Stroke): string {
if (stroke.length === 0) return '';
if (stroke.length === 0) return "";
const d = stroke.reduce(
(acc, [x0, y0], i, arr) => {
@ -201,10 +208,10 @@ export class SpatialInk extends HTMLElement {
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
return acc;
},
['M', ...stroke[0], 'Q']
["M", ...stroke[0], "Q"]
);
d.push('Z');
return d.join(' ');
d.push("Z");
return d.join(" ");
}
}

View File

@ -1,7 +0,0 @@
export class SpatialCanvas extends HTMLElement {
static tagName = 'spatial-canvas';
static register() {
customElements.define(this.tagName, this);
}
}

View File

@ -1,8 +1,15 @@
import { LatLng, LatLngExpression, LeafletEvent, map, Map, tileLayer } from 'leaflet';
import {
LatLng,
LatLngExpression,
LeafletEvent,
map,
Map,
tileLayer,
} from "leaflet";
// @ts-ignore
// Vite specific import :(
import css from 'leaflet/dist/leaflet.css?inline';
import css from "leaflet/dist/leaflet.css?inline";
const styles = new CSSStyleSheet();
styles.replaceSync(`${css}
:host {
@ -17,24 +24,24 @@ styles.replaceSync(`${css}
export class RecenterEvent extends CustomEvent<LatLng> {
constructor(detail: LatLng) {
super('recenter', { detail, bubbles: true });
super("recenter", { detail, bubbles: true });
}
}
export class LeafletMap extends HTMLElement {
static tagName = 'leaflet-map';
static tagName = "leaflet-map";
static register() {
customElements.define(this.tagName, this);
}
#container = document.createElement('div');
#container = document.createElement("div");
#map!: Map;
constructor() {
super();
this.handleEvent = this.handleEvent.bind(this);
const shadow = this.attachShadow({ mode: 'open' });
const shadow = this.attachShadow({ mode: "open" });
shadow.adoptedStyleSheets.push(styles);
shadow.appendChild(this.#container);
}
@ -42,25 +49,26 @@ export class LeafletMap extends HTMLElement {
connectedCallback() {
this.#map = map(this.#container);
this.#map.addLayer(
tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
attribution:
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
})
);
const coordinates = (this.getAttribute('coordinates')
?.split(',')
const coordinates = (this.getAttribute("coordinates")
?.split(",")
.map((str) => Number(str)) || [0, 0]) as LatLngExpression;
const zoom = Number(this.getAttribute('zoom') || 13);
const zoom = Number(this.getAttribute("zoom") || 13);
this.#map.setView(coordinates, zoom);
this.#map.on('zoom', this.handleEvent);
this.#map.on('moveend', this.handleEvent);
this.#map.on("zoom", this.handleEvent);
this.#map.on("moveend", this.handleEvent);
}
handleEvent(event: LeafletEvent) {
switch (event.type) {
case 'zoom':
case 'moveend': {
case "zoom":
case "moveend": {
this.dispatchEvent(new RecenterEvent(this.#map.getCenter()));
break;
}

View File

@ -1,26 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": false,
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
/* Transpile */
"outDir": "dist"
},
"include": ["src"]
}