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 @@
node_modules
dist dist
.vite
node_modules

View File

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

View File

@ -10,6 +10,17 @@
- How can we compose together live and visual programming notations? - How can we compose together live and visual programming notations?
- Can we have lightweight visual and live scripting for web pages? - 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 ## Primitives
- `<fc-geometry>`: Manipulate HTML elements in space. - `<fc-geometry>`: Manipulate HTML elements in space.

View File

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

View File

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

View File

@ -36,13 +36,13 @@
} }
} }
spatial-connection { fc-connection {
display: block; display: block;
position: absolute; position: absolute;
inset: 0 0 0 0; inset: 0 0 0 0;
} }
spatial-geometry { fc-geometry {
border-radius: 7px; border-radius: 7px;
&::part(rotate), &::part(rotate),
@ -53,7 +53,7 @@
display: none; display: none;
} }
spatial-thought { fc-thought {
background-color: white; background-color: white;
border-radius: 6px; border-radius: 6px;
border: solid 1px light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); 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 { FolkGeometry } from "../../src/canvas/fc-geometry.ts";
import { SpatialConnection } from '../../src/arrows/spatial-connection.ts'; import { FolkConnection } from "../../src/arrows/fc-connection.ts";
import { FileSaver } from '../../src/persistence/file.ts'; import { FileSaver } from "../../src/persistence/file.ts";
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'spatial-thought': SpatialThought; "fc-thought": FolkThought;
} }
} }
class SpatialThought extends HTMLElement { class FolkThought extends HTMLElement {
static tagName = 'spatial-thought'; static tagName = "fc-thought";
static register() { static register() {
customElements.define(this.tagName, this); 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; #text = this.querySelector('[name="text"]') as HTMLElement;
#geometry = this.parentElement as SpatialGeometry; #geometry = this.parentElement as FolkGeometry;
constructor() { constructor() {
super(); super();
this.addEventListener('click', this); this.addEventListener("click", this);
} }
get text() { get text() {
@ -31,22 +33,22 @@ class SpatialThought extends HTMLElement {
} }
handleEvent(event: PointerEvent): void { handleEvent(event: PointerEvent): void {
if (event.type === 'click' && event.target === this.#deleteButton) { if (event.type === "click" && event.target === this.#deleteButton) {
this.#geometry.remove(); this.#geometry.remove();
document document
.querySelectorAll( .querySelectorAll(
`spatial-connection[source="spatial-geometry[id='${this.#geometry.id}']"], `fc-connection[source="fc-geometry[id='${this.#geometry.id}']"],
spatial-connection[target="spatial-geometry[id='${this.#geometry.id}']"]` fc-connection[target="fc-geometry[id='${this.#geometry.id}']"]`
) )
.forEach((el) => el.remove()); .forEach((el) => el.remove());
} }
} }
} }
SpatialGeometry.register(); FolkGeometry.register();
SpatialThought.register(); FolkThought.register();
SpatialConnection.register(); FolkConnection.register();
interface Thought { interface Thought {
id: string; id: string;
@ -68,57 +70,68 @@ interface ChainOfThought {
const html = String.raw; const html = String.raw;
function parseHTML(html: string): Element { function parseHTML(html: string): Element {
return document.createRange().createContextualFragment(html).firstElementChild!; return document.createRange().createContextualFragment(html)
.firstElementChild!;
} }
function renderThought({ id, x, y, text }: Thought) { function renderThought({ id, x, y, text }: Thought) {
return html`<spatial-geometry id="${id}" x="${x}" y="${y}"> return html`<fc-geometry id="${id}" x="${x}" y="${y}">
<spatial-thought> <fc-thought>
<div contenteditable="true" name="text">${text}</div> <div contenteditable="true" name="text">${text}</div>
<button name="delete"></button> <button name="delete"></button>
</spatial-thought> </fc-thought>
</spatial-geometry>`; </fc-geometry>`;
} }
function renderConnection({ sourceId, targetId }: Connection) { function renderConnection({ sourceId, targetId }: Connection) {
return html`<spatial-connection return html`<fc-connection
source="spatial-geometry[id='${sourceId}']" source="fc-geometry[id='${sourceId}']"
target="spatial-geometry[id='${targetId}']" target="fc-geometry[id='${targetId}']"
></spatial-connection>`; ></fc-connection>`;
} }
function renderChainOfThought({ thoughts, connections }: ChainOfThought) { 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 { function parseChainOfThought(): ChainOfThought {
return { return {
thoughts: Array.from(document.querySelectorAll('spatial-geometry')).map((el) => ({ thoughts: Array.from(document.querySelectorAll("fc-geometry")).map(
id: el.id, (el) => ({
text: (el.firstElementChild as SpatialThought).text, id: el.id,
x: el.x, text: (el.firstElementChild as FolkThought).text,
y: el.y, 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, 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 openButton = document.querySelector('button[name="open"]')!;
const saveButton = document.querySelector('button[name="save"]')!; const saveButton = document.querySelector('button[name="save"]')!;
const saveAsButton = document.querySelector('button[name="save-as"]')!; const saveAsButton = document.querySelector('button[name="save-as"]')!;
const main = document.querySelector('main')!; const main = document.querySelector("main")!;
const fileSaver = new FileSaver('chains-of-thought', 'json', 'application/json'); const fileSaver = new FileSaver(
"chains-of-thought",
"json",
"application/json"
);
main.addEventListener('dblclick', (e) => { main.addEventListener("dblclick", (e) => {
if (e.target === main) { if (e.target === main) {
main.appendChild( main.appendChild(
parseHTML( parseHTML(
renderThought({ renderThought({
id: String(document.querySelectorAll('spatial-thought').length + 1), id: String(document.querySelectorAll("fc-thought").length + 1),
text: '', text: "",
x: e.clientX, x: e.clientX,
y: e.clientY, 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); const file = JSON.stringify(parseChainOfThought(), null, 2);
fileSaver.save(file, promptNewFile); fileSaver.save(file, promptNewFile);
} }
openButton.addEventListener('click', () => { openButton.addEventListener("click", () => {
openFile(); openFile();
}); });
saveButton.addEventListener('click', () => { saveButton.addEventListener("click", () => {
saveFile(); saveFile();
}); });
saveAsButton.addEventListener('click', () => { saveAsButton.addEventListener("click", () => {
saveFile(true); saveFile(true);
}); });

View File

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

View File

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

View File

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

View File

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

View File

@ -37,27 +37,35 @@
</style> </style>
</head> </head>
<body> <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> <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"> <fc-geometry x="50" y="550" width="400" height="250">
<leaflet-map coordinates="51.50404120260676, -0.14007568359375003" zoom="13"></leaflet-map> <leaflet-map
</spatial-geometry> coordinates="51.50404120260676, -0.14007568359375003"
zoom="13"
></leaflet-map>
</fc-geometry>
<spatial-geometry x="500" y="400" width="500" height="300"> <fc-geometry x="500" y="400" width="500" height="300">
<geo-wiki coordinates="51.50404120260676, -0.14007568359375003"></geo-wiki> <geo-wiki
</spatial-geometry> coordinates="51.50404120260676, -0.14007568359375003"
></geo-wiki>
</fc-geometry>
<script type="module"> <script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts'; import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { LeafletMap } from '../src/maps'; import { LeafletMap } from '../src/maps';
SpatialGeometry.register(); FolkGeometry.register();
LeafletMap.register(); LeafletMap.register();
function collisionDetection(rect1, rect2) { function collisionDetection(rect1, rect2) {
return ( 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( 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) { function handleProximity(e) {
@ -83,7 +94,12 @@
const alreadyIntersection = set.has(e.target); const alreadyIntersection = set.has(e.target);
// TODO: refactor this hack once resizing and the vertices API are figured out // TODO: refactor this hack once resizing and the vertices API are figured out
const isNowIntersecting = proximityDetection( 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({ DOMRectReadOnly.fromRect({
x: e.target.x, x: e.target.x,
y: e.target.y, y: e.target.y,
@ -136,7 +152,9 @@
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
if (name === 'coordinates') { 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); this.searchWiki(this.#coordinates);
} }
} }
@ -152,7 +170,9 @@
origin: '*', origin: '*',
}); });
// https://www.mediawiki.org/wiki/API:Geosearch // 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((response) => response.json())
.then((data) => data?.query?.geosearch ?? []); .then((data) => data?.query?.geosearch ?? []);

View File

@ -15,7 +15,7 @@
margin: 0; margin: 0;
} }
spatial-geometry:has(record-player) { fc-geometry:has(record-player) {
&::part(resize-nw), &::part(resize-nw),
&::part(resize-ne), &::part(resize-ne),
&::part(resize-se), &::part(resize-se),
@ -24,7 +24,7 @@
} }
} }
spatial-geometry > video { fc-geometry > video {
height: 100%; height: 100%;
} }
</style> </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 -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 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> <record-player>
<audio src="/Feather.mp3"></audio> <audio src="/Feather.mp3"></audio>
</record-player> </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> <video loop>
<source src="/dancing-flower.mov" type="video/quicktime" /> <source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" /> <source src="/dancing-flower.webm" type="video/webm" />
</video> </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> <video loop>
<source src="/dancing-flower.mov" type="video/quicktime" /> <source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" /> <source src="/dancing-flower.webm" type="video/webm" />
</video> </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> <video loop>
<source src="/dancing-flower.mov" type="video/quicktime" /> <source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" /> <source src="/dancing-flower.webm" type="video/webm" />
</video> </video>
</spatial-geometry> </fc-geometry>
<script type="module"> <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'; import { RecordPlayer } from '../src/music/record-player.ts';
SpatialGeometry.register(); FolkGeometry.register();
RecordPlayer.register(); RecordPlayer.register();
let proximityDistance = 200; let proximityDistance = 200;
const proximitySet = new Set(); 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 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 // set playback rate when video is ready
function setPlayback(e) { function setPlayback(e) {
e.target.playbackRate = (91 / 60) * e.target.duration; e.target.playbackRate = (91 / 60) * e.target.duration;

View File

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

View File

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

View File

@ -22,21 +22,26 @@
</style> </style>
</head> </head>
<body> <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> <s-table></s-table>
</spatial-geometry> </fc-geometry>
<script type="module"> <script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts'; import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { SpreadsheetTable, SpreadsheetHeader, SpreadsheetCell } from '../src/spreadsheet/spreadsheet.ts'; import {
SpreadsheetTable,
SpreadsheetHeader,
SpreadsheetCell,
} from '../src/spreadsheet/spreadsheet.ts';
SpatialGeometry.register(); FolkGeometry.register();
SpreadsheetTable.register(); SpreadsheetTable.register();
SpreadsheetHeader.register(); SpreadsheetHeader.register();
SpreadsheetCell.register(); SpreadsheetCell.register();
document.querySelector(`s-cell[column="A"][row="1"]`).expression = '1'; 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> </script>
</body> </body>
</html> </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 { export class EventPropagator extends FolkConnection {
static tagName = 'event-propagator'; static override tagName = "event-propagator";
#triggers = (this.getAttribute('triggers') || '').split(','); #triggers = (this.getAttribute("triggers") || "").split(",");
get triggers() { get triggers() {
return this.#triggers; return this.#triggers;
} }
@ -11,23 +11,23 @@ export class EventPropagator extends SpatialConnection {
this.#triggers = triggers; this.#triggers = triggers;
} }
#expression = ''; #expression = "";
#function = new Function(); #function = new Function();
get expression() { get expression() {
return this.#expression; return this.#expression;
} }
set expression(expression) { set expression(expression) {
this.#expression = expression; this.#expression = expression;
this.#function = new Function('$source', '$target', '$event', expression); this.#function = new Function("$source", "$target", "$event", expression);
} }
constructor() { constructor() {
super(); super();
this.expression = this.getAttribute('expression') || ''; this.expression = this.getAttribute("expression") || "";
} }
observeSource() { override observeSource() {
super.observeSource(); super.observeSource();
for (const trigger of this.#triggers) { for (const trigger of this.#triggers) {
@ -38,7 +38,7 @@ export class EventPropagator extends SpatialConnection {
this.evaluateExpression(); this.evaluateExpression();
} }
unobserveSource() { override unobserveSource() {
super.unobserveSource(); super.unobserveSource();
for (const trigger of this.#triggers) { for (const trigger of this.#triggers) {
@ -47,12 +47,12 @@ export class EventPropagator extends SpatialConnection {
} }
} }
observeTarget() { override observeTarget() {
super.observeTarget(); super.observeTarget();
this.evaluateExpression(); this.evaluateExpression();
} }
unobserveTarget() { override unobserveTarget() {
super.unobserveTarget(); super.unobserveTarget();
} }

View File

@ -1,7 +1,7 @@
import { getBoxToBoxArrow } from 'perfect-arrows'; import { getBoxToBoxArrow } from "perfect-arrows";
import { AbstractArrow } from './abstract-arrow'; import { AbstractArrow } from "./abstract-arrow.ts";
import { pointsOnBezierCurves } from './points-on-path'; import { pointsOnBezierCurves } from "./points-on-path.ts";
import getStroke, { StrokeOptions } from 'perfect-freehand'; import { getStroke, StrokeOptions } from "perfect-freehand";
export type Arrow = [ export type Arrow = [
/** The x position of the (padded) starting point. */ /** The x position of the (padded) starting point. */
@ -26,12 +26,12 @@ export type Arrow = [
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'spatial-connection': SpatialConnection; "fc-connection": FolkConnection;
} }
} }
export class SpatialConnection extends AbstractArrow { export class FolkConnection extends AbstractArrow {
static tagName = 'spatial-connection'; static override tagName = "fc-connection";
#options: StrokeOptions = { #options: StrokeOptions = {
size: 7, size: 7,
@ -53,7 +53,7 @@ export class SpatialConnection extends AbstractArrow {
}, },
}; };
render() { override render() {
const { sourceRect, targetRect } = this; const { sourceRect, targetRect } = this;
const [sx, sy, cx, cy, ex, ey] = getBoxToBoxArrow( const [sx, sy, cx, cy, ex, ey] = getBoxToBoxArrow(
@ -77,12 +77,12 @@ export class SpatialConnection extends AbstractArrow {
const stroke = getStroke(points, this.#options); const stroke = getStroke(points, this.#options);
const path = getSvgPathFromStroke(stroke); const path = getSvgPathFromStroke(stroke);
this.style.clipPath = `path('${path}')`; this.style.clipPath = `path('${path}')`;
this.style.backgroundColor = 'black'; this.style.backgroundColor = "black";
} }
} }
function getSvgPathFromStroke(stroke: number[][]): string { function getSvgPathFromStroke(stroke: number[][]): string {
if (stroke.length === 0) return ''; if (stroke.length === 0) return "";
for (const point of stroke) { for (const point of stroke) {
point[0] = Math.round(point[0] * 100) / 100; 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); acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
return acc; return acc;
}, },
['M', ...stroke[0], 'Q'] ["M", ...stroke[0], "Q"]
); );
d.push('Z'); d.push("Z");
return d.join(' '); 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 { unobserve(target: Element, callback: ResizeObserverEntryCallback): void {
let callbacks = this.#elementMap.get(target); const callbacks = this.#elementMap.get(target);
if (callbacks === undefined) return; if (callbacks === undefined) return;
@ -45,13 +45,13 @@ class ResizeObserverManager {
const resizeObserver = new 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 type MoveEventDetail = { movementX: number; movementY: number };
export class MoveEvent extends CustomEvent<MoveEventDetail> { export class MoveEvent extends CustomEvent<MoveEventDetail> {
constructor(detail: 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> { export class ResizeEvent extends CustomEvent<MoveEventDetail> {
constructor(detail: 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> { export class RotateEvent extends CustomEvent<RotateEventDetail> {
constructor(detail: 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(); const styles = new CSSStyleSheet();
styles.replaceSync(` styles.replaceSync(`
@ -180,13 +180,13 @@ styles.replaceSync(`
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'spatial-geometry': SpatialGeometry; "fc-geometry": FolkGeometry;
} }
} }
// TODO: add z coordinate? // TODO: add z coordinate?
export class SpatialGeometry extends HTMLElement { export class FolkGeometry extends HTMLElement {
static tagName = 'spatial-geometry'; static tagName = "fc-geometry";
static register() { static register() {
customElements.define(this.tagName, this); customElements.define(this.tagName, this);
@ -194,17 +194,17 @@ export class SpatialGeometry extends HTMLElement {
#internals = this.attachInternals(); #internals = this.attachInternals();
#type = (this.getAttribute('type') || 'rectangle') as Shape; #type = (this.getAttribute("type") || "rectangle") as Shape;
get type(): Shape { get type(): Shape {
return this.#type; return this.#type;
} }
set type(type: Shape) { set type(type: Shape) {
this.setAttribute('type', type); this.setAttribute("type", type);
} }
#previousX = 0; #previousX = 0;
#x = Number(this.getAttribute('x')) || 0; #x = Number(this.getAttribute("x")) || 0;
get x() { get x() {
return this.#x; return this.#x;
} }
@ -212,11 +212,11 @@ export class SpatialGeometry extends HTMLElement {
set x(x) { set x(x) {
this.#previousX = this.#x; this.#previousX = this.#x;
this.#x = x; this.#x = x;
this.#requestUpdate('x'); this.#requestUpdate("x");
} }
#previousY = 0; #previousY = 0;
#y = Number(this.getAttribute('y')) || 0; #y = Number(this.getAttribute("y")) || 0;
get y() { get y() {
return this.#y; return this.#y;
} }
@ -224,7 +224,7 @@ export class SpatialGeometry extends HTMLElement {
set y(y) { set y(y) {
this.#previousY = this.#y; this.#previousY = this.#y;
this.#y = y; this.#y = y;
this.#requestUpdate('y'); this.#requestUpdate("y");
} }
#autoContentRect = this.getBoundingClientRect(); #autoContentRect = this.getBoundingClientRect();
@ -232,48 +232,48 @@ export class SpatialGeometry extends HTMLElement {
#previousWidth: Dimension = 0; #previousWidth: Dimension = 0;
#width: Dimension = 0; #width: Dimension = 0;
get width(): number { get width(): number {
if (this.#width === 'auto') { if (this.#width === "auto") {
return this.#autoContentRect.width; return this.#autoContentRect.width;
} }
return this.#width; return this.#width;
} }
set width(width: Dimension) { set width(width: Dimension) {
if (width === 'auto') { if (width === "auto") {
resizeObserver.observe(this, this.#onResize); 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); resizeObserver.unobserve(this, this.#onResize);
} }
this.#previousWidth = this.#width; this.#previousWidth = this.#width;
this.#width = width; this.#width = width;
this.#requestUpdate('width'); this.#requestUpdate("width");
} }
#previousHeight: Dimension = 0; #previousHeight: Dimension = 0;
#height: Dimension = 0; #height: Dimension = 0;
get height(): number { get height(): number {
if (this.#height === 'auto') { if (this.#height === "auto") {
return this.#autoContentRect.height; return this.#autoContentRect.height;
} }
return this.#height; return this.#height;
} }
set height(height: Dimension) { set height(height: Dimension) {
if (height === 'auto') { if (height === "auto") {
resizeObserver.observe(this, this.#onResize); 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); resizeObserver.unobserve(this, this.#onResize);
} }
this.#previousHeight = this.#height; this.#previousHeight = this.#height;
this.#height = height; this.#height = height;
this.#requestUpdate('height'); this.#requestUpdate("height");
} }
#initialRotation = 0; #initialRotation = 0;
#startAngle = 0; #startAngle = 0;
#previousRotate = 0; #previousRotate = 0;
#rotate = Number(this.getAttribute('rotate')) || 0; #rotate = Number(this.getAttribute("rotate")) || 0;
get rotate(): number { get rotate(): number {
return this.#rotate; return this.#rotate;
} }
@ -281,16 +281,16 @@ export class SpatialGeometry extends HTMLElement {
set rotate(rotate: number) { set rotate(rotate: number) {
this.#previousRotate = this.#rotate; this.#previousRotate = this.#rotate;
this.#rotate = rotate; this.#rotate = rotate;
this.#requestUpdate('rotate'); this.#requestUpdate("rotate");
} }
constructor() { constructor() {
super(); super();
this.addEventListener('pointerdown', this); this.addEventListener("pointerdown", this);
const shadowRoot = this.attachShadow({ const shadowRoot = this.attachShadow({
mode: 'open', mode: "open",
delegatesFocus: true, delegatesFocus: true,
}); });
shadowRoot.adoptedStyleSheets.push(styles); shadowRoot.adoptedStyleSheets.push(styles);
@ -305,12 +305,12 @@ export class SpatialGeometry extends HTMLElement {
<button part="resize-sw"></button> <button part="resize-sw"></button>
<slot></slot>`; <slot></slot>`;
this.height = Number(this.getAttribute('height')) || 'auto'; this.height = Number(this.getAttribute("height")) || "auto";
this.width = Number(this.getAttribute('width')) || 'auto'; this.width = Number(this.getAttribute("width")) || "auto";
} }
connectedCallback() { connectedCallback() {
this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotate'])); this.#update(new Set(["type", "x", "y", "height", "width", "rotate"]));
} }
disconnectedCallback() { disconnectedCallback() {
@ -319,7 +319,7 @@ export class SpatialGeometry extends HTMLElement {
// Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape. // Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape.
getBoundingPath(): string { getBoundingPath(): string {
return ''; return "";
} }
// We might also want some kind of utility function that maps a path into an approximate set of vertices. // 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) { handleEvent(event: PointerEvent) {
switch (event.type) { switch (event.type) {
case 'pointerdown': { case "pointerdown": {
if (event.button !== 0 || event.ctrlKey) return; if (event.button !== 0 || event.ctrlKey) return;
const target = event.composedPath()[0] as HTMLElement; const target = event.composedPath()[0] as HTMLElement;
// Store initial angle on rotation start // Store initial angle on rotation start
if (target.getAttribute('part') === 'rotate') { if (target.getAttribute("part") === "rotate") {
// We need to store initial rotation/angle somewhere. // 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. // 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. // 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. // ignore interactions from slotted elements.
if (target !== this && !target.hasAttribute('part')) return; if (target !== this && !target.hasAttribute("part")) return;
target.addEventListener('pointermove', this); target.addEventListener("pointermove", this);
this.addEventListener('lostpointercapture', this); this.addEventListener("lostpointercapture", this);
target.setPointerCapture(event.pointerId); target.setPointerCapture(event.pointerId);
const interaction = target.getAttribute('part') || 'move'; const interaction = target.getAttribute("part") || "move";
this.#internals.states.add(interaction); this.#internals.states.add(interaction);
this.focus(); this.focus();
return; return;
} }
case 'pointermove': { case "pointermove": {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
if (target === null) return; if (target === null) return;
@ -374,33 +374,33 @@ export class SpatialGeometry extends HTMLElement {
return; return;
} }
const part = target.getAttribute('part'); const part = target.getAttribute("part");
if (part === null) return; if (part === null) return;
if (part.includes('resize')) { if (part.includes("resize")) {
// This triggers a move and resize event :( // This triggers a move and resize event :(
if (part.includes('-n')) { if (part.includes("-n")) {
this.y += event.movementY; this.y += event.movementY;
this.height -= event.movementY; this.height -= event.movementY;
} }
if (part.endsWith('e')) { if (part.endsWith("e")) {
this.width += event.movementX; this.width += event.movementX;
} }
if (part.includes('-s')) { if (part.includes("-s")) {
this.height += event.movementY; this.height += event.movementY;
} }
if (part.endsWith('w')) { if (part.endsWith("w")) {
this.x += event.movementX; this.x += event.movementX;
this.width -= event.movementX; this.width -= event.movementX;
} }
return; return;
} }
if (part === 'rotate') { if (part === "rotate") {
const centerX = this.#x + this.width / 2; const centerX = this.#x + this.width / 2;
const centerY = this.#y + this.height / 2; const centerY = this.#y + this.height / 2;
const currentAngle = Math.atan2( const currentAngle = Math.atan2(
@ -415,12 +415,12 @@ export class SpatialGeometry extends HTMLElement {
return; return;
} }
case 'lostpointercapture': { case "lostpointercapture": {
const target = event.composedPath()[0] as HTMLElement; const target = event.composedPath()[0] as HTMLElement;
const interaction = target.getAttribute('part') || 'move'; const interaction = target.getAttribute("part") || "move";
this.#internals.states.delete(interaction); this.#internals.states.delete(interaction);
target.removeEventListener('pointermove', this); target.removeEventListener("pointermove", this);
this.removeEventListener('lostpointercapture', this); this.removeEventListener("lostpointercapture", this);
return; 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. // Any updates that should be batched should happen here like updating the DOM or emitting events should be executed here.
#update(updatedProperties: Set<string>) { #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. // 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 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. // 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/ // 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 // 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( const notCancelled = this.dispatchEvent(
new MoveEvent({ new MoveEvent({
@ -463,12 +463,12 @@ export class SpatialGeometry extends HTMLElement {
); );
if (notCancelled) { 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. // In the future, when CSS `attr()` is supported we could define this x/y projection in CSS.
this.style.left = `${this.#x}px`; this.style.left = `${this.#x}px`;
} }
if (updatedProperties.has('y')) { if (updatedProperties.has("y")) {
this.style.top = `${this.#y}px`; this.style.top = `${this.#y}px`;
} }
} else { } 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 // 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( const notCancelled = this.dispatchEvent(
new ResizeEvent({ new ResizeEvent({
movementX: movementX:
this.width - this.width -
(this.#previousWidth === 'auto' ? 0 : this.#previousWidth), (this.#previousWidth === "auto" ? 0 : this.#previousWidth),
movementY: movementY:
this.height - this.height -
(this.#previousHeight === 'auto' ? 0 : this.#previousHeight), (this.#previousHeight === "auto" ? 0 : this.#previousHeight),
}) })
); );
if (notCancelled) { if (notCancelled) {
if (updatedProperties.has('width')) { if (updatedProperties.has("width")) {
this.style.width = this.#width === 'auto' ? '' : `${this.#width}px`; this.style.width = this.#width === "auto" ? "" : `${this.#width}px`;
} }
if (updatedProperties.has('height')) { if (updatedProperties.has("height")) {
this.style.height = this.style.height =
this.#height === 'auto' ? '' : `${this.#height}px`; this.#height === "auto" ? "" : `${this.#height}px`;
} }
} else { } else {
// TODO: Revert changes to position too // 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 // 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( const notCancelled = this.dispatchEvent(
new RotateEvent({ rotate: this.#rotate - this.#previousRotate }) new RotateEvent({ rotate: this.#rotate - this.#previousRotate })
); );
if (notCancelled) { if (notCancelled) {
if (updatedProperties.has('rotate')) { if (updatedProperties.has("rotate")) {
this.style.rotate = `${this.#rotate}deg`; this.style.rotate = `${this.#rotate}deg`;
} }
} else { } else {
@ -529,23 +529,23 @@ export class SpatialGeometry extends HTMLElement {
new ResizeEvent({ new ResizeEvent({
movementX: movementX:
this.width - this.width -
(this.#previousWidth === 'auto' (this.#previousWidth === "auto"
? previousRect.width ? previousRect.width
: this.#previousWidth), : this.#previousWidth),
movementY: movementY:
this.height - this.height -
(this.#previousHeight === 'auto' (this.#previousHeight === "auto"
? previousRect.height ? previousRect.height
: this.#previousHeight), : this.#previousHeight),
}) })
); );
if (!notCancelled) { if (!notCancelled) {
if (this.#height === 'auto') { if (this.#height === "auto") {
this.height = previousRect?.height || 0; this.height = previousRect?.height || 0;
} }
if (this.#width === 'auto') { if (this.#width === "auto") {
this.width = previousRect?.width || 0; 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]; export type Point = [x: number, y: number, pressure: number];
@ -25,12 +25,12 @@ styles.replaceSync(`
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'spatial-ink': SpatialInk; "fc-ink": FolkInk;
} }
} }
export class SpatialInk extends HTMLElement { export class FolkInk extends HTMLElement {
static tagName = 'spatial-ink'; static tagName = "fc-ink";
static register() { static register() {
customElements.define(this.tagName, this); customElements.define(this.tagName, this);
@ -38,10 +38,10 @@ export class SpatialInk extends HTMLElement {
#internals = this.attachInternals(); #internals = this.attachInternals();
#svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); #svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
#path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); #path = document.createElementNS("http://www.w3.org/2000/svg", "path");
#size = Number(this.getAttribute('size') || 16); #size = Number(this.getAttribute("size") || 16);
get size() { get size() {
return this.#size; return this.#size;
@ -51,7 +51,7 @@ export class SpatialInk extends HTMLElement {
this.#update(); this.#update();
} }
#thinning = Number(this.getAttribute('thinning') || 0.5); #thinning = Number(this.getAttribute("thinning") || 0.5);
get thinning() { get thinning() {
return this.#thinning; return this.#thinning;
@ -61,7 +61,7 @@ export class SpatialInk extends HTMLElement {
this.#update(); this.#update();
} }
#smoothing = Number(this.getAttribute('smoothing') || 0.5); #smoothing = Number(this.getAttribute("smoothing") || 0.5);
get smoothing() { get smoothing() {
return this.#smoothing; return this.#smoothing;
@ -71,7 +71,7 @@ export class SpatialInk extends HTMLElement {
this.#update(); this.#update();
} }
#streamline = Number(this.getAttribute('streamline') || 0.5); #streamline = Number(this.getAttribute("streamline") || 0.5);
get streamline() { get streamline() {
return this.#streamline; return this.#streamline;
@ -81,7 +81,8 @@ export class SpatialInk extends HTMLElement {
this.#update(); this.#update();
} }
#simulatePressure = this.getAttribute('streamline') === 'false' ? false : true; #simulatePressure =
this.getAttribute("streamline") === "false" ? false : true;
get simulatePressure() { get simulatePressure() {
return this.#simulatePressure; return this.#simulatePressure;
@ -91,7 +92,7 @@ export class SpatialInk extends HTMLElement {
this.#update(); this.#update();
} }
#points: Point[] = JSON.parse(this.getAttribute('points') || '[]'); #points: Point[] = JSON.parse(this.getAttribute("points") || "[]");
get points() { get points() {
return this.#points; return this.#points;
@ -104,7 +105,10 @@ export class SpatialInk extends HTMLElement {
constructor() { constructor() {
super(); super();
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); const shadowRoot = this.attachShadow({
mode: "open",
delegatesFocus: true,
});
shadowRoot.adoptedStyleSheets.push(styles); shadowRoot.adoptedStyleSheets.push(styles);
this.#svg.appendChild(this.#path); this.#svg.appendChild(this.#path);
shadowRoot.appendChild(this.#svg); shadowRoot.appendChild(this.#svg);
@ -126,10 +130,10 @@ export class SpatialInk extends HTMLElement {
// TODO: cancel trace? // TODO: cancel trace?
draw(event?: PointerEvent) { draw(event?: PointerEvent) {
if (event?.type === 'pointerdown') { if (event?.type === "pointerdown") {
this.handleEvent(event); this.handleEvent(event);
} else { } else {
this.addEventListener('pointerdown', this); this.addEventListener("pointerdown", this);
} }
this.#tracingPromise = Promise.withResolvers(); this.#tracingPromise = Promise.withResolvers();
return this.#tracingPromise.promise; return this.#tracingPromise.promise;
@ -142,26 +146,26 @@ export class SpatialInk extends HTMLElement {
handleEvent(event: PointerEvent) { handleEvent(event: PointerEvent) {
switch (event.type) { switch (event.type) {
case 'pointerdown': { case "pointerdown": {
if (event.button !== 0 || event.ctrlKey) return; if (event.button !== 0 || event.ctrlKey) return;
this.points = []; this.points = [];
this.addPoint([event.offsetX, event.offsetY, event.pressure]); this.addPoint([event.offsetX, event.offsetY, event.pressure]);
this.addEventListener('lostpointercapture', this); this.addEventListener("lostpointercapture", this);
this.addEventListener('pointermove', this); this.addEventListener("pointermove", this);
this.setPointerCapture(event.pointerId); this.setPointerCapture(event.pointerId);
this.#internals.states.add('drawing'); this.#internals.states.add("drawing");
return; return;
} }
case 'pointermove': { case "pointermove": {
this.addPoint([event.offsetX, event.offsetY, event.pressure]); this.addPoint([event.offsetX, event.offsetY, event.pressure]);
return; return;
} }
case 'lostpointercapture': { case "lostpointercapture": {
this.removeEventListener('pointerdown', this); this.removeEventListener("pointerdown", this);
this.removeEventListener('pointermove', this); this.removeEventListener("pointermove", this);
this.removeEventListener('lostpointercapture', this); this.removeEventListener("lostpointercapture", this);
this.#internals.states.delete('drawing'); this.#internals.states.delete("drawing");
this.#tracingPromise?.resolve(); this.#tracingPromise?.resolve();
this.#tracingPromise = null; this.#tracingPromise = null;
return; return;
@ -189,11 +193,14 @@ export class SpatialInk extends HTMLElement {
cap: true, 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 { #getSvgPathFromStroke(stroke: Stroke): string {
if (stroke.length === 0) return ''; if (stroke.length === 0) return "";
const d = stroke.reduce( const d = stroke.reduce(
(acc, [x0, y0], i, arr) => { (acc, [x0, y0], i, arr) => {
@ -201,10 +208,10 @@ export class SpatialInk extends HTMLElement {
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
return acc; return acc;
}, },
['M', ...stroke[0], 'Q'] ["M", ...stroke[0], "Q"]
); );
d.push('Z'); d.push("Z");
return d.join(' '); 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 // @ts-ignore
// Vite specific import :( // Vite specific import :(
import css from 'leaflet/dist/leaflet.css?inline'; import css from "leaflet/dist/leaflet.css?inline";
const styles = new CSSStyleSheet(); const styles = new CSSStyleSheet();
styles.replaceSync(`${css} styles.replaceSync(`${css}
:host { :host {
@ -17,24 +24,24 @@ styles.replaceSync(`${css}
export class RecenterEvent extends CustomEvent<LatLng> { export class RecenterEvent extends CustomEvent<LatLng> {
constructor(detail: LatLng) { constructor(detail: LatLng) {
super('recenter', { detail, bubbles: true }); super("recenter", { detail, bubbles: true });
} }
} }
export class LeafletMap extends HTMLElement { export class LeafletMap extends HTMLElement {
static tagName = 'leaflet-map'; static tagName = "leaflet-map";
static register() { static register() {
customElements.define(this.tagName, this); customElements.define(this.tagName, this);
} }
#container = document.createElement('div'); #container = document.createElement("div");
#map!: Map; #map!: Map;
constructor() { constructor() {
super(); super();
this.handleEvent = this.handleEvent.bind(this); this.handleEvent = this.handleEvent.bind(this);
const shadow = this.attachShadow({ mode: 'open' }); const shadow = this.attachShadow({ mode: "open" });
shadow.adoptedStyleSheets.push(styles); shadow.adoptedStyleSheets.push(styles);
shadow.appendChild(this.#container); shadow.appendChild(this.#container);
} }
@ -42,25 +49,26 @@ export class LeafletMap extends HTMLElement {
connectedCallback() { connectedCallback() {
this.#map = map(this.#container); this.#map = map(this.#container);
this.#map.addLayer( this.#map.addLayer(
tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19, 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') const coordinates = (this.getAttribute("coordinates")
?.split(',') ?.split(",")
.map((str) => Number(str)) || [0, 0]) as LatLngExpression; .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.setView(coordinates, zoom);
this.#map.on('zoom', this.handleEvent); this.#map.on("zoom", this.handleEvent);
this.#map.on('moveend', this.handleEvent); this.#map.on("moveend", this.handleEvent);
} }
handleEvent(event: LeafletEvent) { handleEvent(event: LeafletEvent) {
switch (event.type) { switch (event.type) {
case 'zoom': case "zoom":
case 'moveend': { case "moveend": {
this.dispatchEvent(new RecenterEvent(this.#map.getCenter())); this.dispatchEvent(new RecenterEvent(this.#map.getCenter()));
break; 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"]
}