Skip to content

Commit 1c90e37

Browse files
committed
misc - uncommitted
1 parent 41e951d commit 1c90e37

File tree

6 files changed

+195
-114
lines changed

6 files changed

+195
-114
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# svelte-circles
1+
# svelte-happy-demo
22

33
A playground for developing _interactive_ SVG graphics, as a Svelte component.
44

@@ -9,6 +9,8 @@ Aims:
99
- [ ] allow working with SVG assets in separate files (editable)
1010
- [ ] ... (other aims still foggy)
1111

12+
[HTML 5 SVG page](https://www.w3schools.com/html/html5_svg.asp) (w3schools) mentions that SVG were "not suited for game applications". One of the purpose of this demo is to see, how far that holds true, in 2019-20.
13+
1214

1315
## Requirements
1416

@@ -74,6 +76,10 @@ This should provide you the component in the other project.
7476

7577
*<font color=red>Warning: not tried!</font>*
7678

79+
<!-- see here if there are problems: #remove when works
80+
https://docs.npmjs.com/cli/link.html
81+
-->
82+
7783
See [app/App.svelte](app/App.svelte) for a sample.
7884

7985
<!-- disabled (enable if there are properties)
@@ -111,3 +117,4 @@ Then try again `npm install`.
111117
112118
- [lukeed/svelte-demo](https://github.com/lukeed/svelte-demo) used as a template for the `app` part (demo/testbed)
113119
120+
- [Understanding npm-link](https://medium.com/dailyjs/how-to-use-npm-link-7375b6219557) (Medium; Oct 2018)

TODO.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
# Todo
22

3+
- [ ] Grouped graphics (incl. transformations when group is e.g. tilted)
4+
- [ ] Graphics from external `.svg` files
35
- [ ] Tests with mocha
46
- [ ] <strike>publishing</strike>
5-
- [ ] peer feedback from some seasoned Svelte 3 users
7+
- [ ] more seasoned undo/redo system
8+
- [ ] make sure SVG groups work fine; also with rotation (mouse/touch coords)
9+
- [ ] animations by class, e.g. ":wobble" (akin to Svelte)
10+
[ ] host a sample at a URL, link at GitHub title
11+
12+
<!-- consider
13+
>For example, the fill property controls the paint used to fill the inside of a shape, and the width and height properties are used to control the size of a ‘rect’ element.
614
15+
We could make `fill` style be applied, i.e. make SVG seem more uniform than
16+
it is.
17+
18+
Such a feature should be explicitly "opt-in", so that we don't confuse people about what SVG abstraction level really is.
19+
-->
720

821
## Help requests
922

23+
- [ ] peer feedback from some seasoned Svelte 3 users
1024
- [ ] Should we have a `.files` field in `package.json`? What's it used for?
1125

1226

app/App.svelte

+75-7
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,85 @@
11
<script>
22
// Use this for npm-published module
3-
//import SVGCircles from 'svelte-circles';
3+
//import HappyFace from 'svelte-happy-demo';
44
55
// Use this for local developed code
6-
import SVGCircles from '../src/index.svelte';
6+
import HappyFace from '../src/index.svelte';
7+
8+
import { evToSvgPointGen } from '../src/conv.js';
9+
10+
let i = 0; // position to undo to in 'undoStack' +1 (0: nothing to undo)
11+
let undoStack = [[]]; // clones of 'faces'
12+
13+
let faces = []; // { cx: num, cy: num, diam: num }
14+
15+
let activeFace = null; // Last clicked face (changes appearance and maybe even detail level)
16+
17+
let thisEl; // particular SVG element
18+
19+
const evToSvgPoint = evToSvgPointGen(thisEl); // tbd. maybe needs to be in 'onMount'
20+
21+
/*
22+
* Click on the SVG
23+
*
24+
* Create a new face at the clicked coords
25+
*/
26+
function handleClick(ev) {
27+
const p = evToSvgPoint(ev);
28+
const tmp = { cx: p.x, cy: p.y, diam: 50 };
29+
30+
faces = faces.concat(tmp); // Svelte note: so changes are acted upon
31+
activeFace = tmp;
32+
33+
pushState();
34+
}
35+
36+
// Note: This undo/redo is very basic, in that it stores the whole application state (instead of a diff between states)
37+
//
38+
function pushState() {
39+
const tmp = undoStack.slice(0, ++i); // lose old future if there was any
40+
tmp.push(cloneState(faces));
41+
undoStack = tmp;
42+
}
43+
44+
function stepState(step) { // step: -1 or +1
45+
faces = cloneState(undoStack[i += step]);
46+
}
47+
48+
// tbd. cloning may not be required?
49+
function cloneState(faces_) {
50+
return faces_.map(({ cx, cy, diam }) => ({ cx, cy, diam }));
51+
}
752
</script>
853

9-
<h1>Svelte Circles</h1>
54+
<style>
55+
svg {
56+
background-color: #eee;
57+
width: 100%;
58+
height: 300px;
59+
}
60+
61+
div#controls {
62+
position: absolute;
63+
width: 100%;
64+
text-align: center;
65+
}
66+
</style>
1067

11-
<hr />
12-
asdasds
68+
<!-- HTML -->
1369

14-
<SVGCircles />
70+
<h1>Svelte Happy Demo</h1>
1571

16-
<hr />
72+
<div id="controls">
73+
Undo stack length: {undoStack.length}
74+
<button on:click="{() => stepState(-1)}" disabled="{i === 0}">undo</button>
75+
<button on:click="{() => stepState(+1)}" disabled="{i === undoStack.length -1}">redo</button>
76+
</div>
1777

78+
<svg on:click={(ev) => console.log(ev) }
79+
bind:this={thisEl}
80+
>
81+
{#each faces as o}
82+
<HappyFace cx={o.cx} cy={o.cy} diam={o.diam} active={o === activeFace}
83+
/>
84+
{/each}
85+
</svg>

app/public/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<meta http-equiv="X-UA-Compatible" content="ie=edge">
77

8-
<title>Svelte Spinner Demo</title>
8+
<title>Svelte Happy Demo</title>
99

1010
<style>
1111
label, input {

src/conv.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Convert a browser event (touch or click) coordinates to a particular SVG element's inner coordinates.
3+
*
4+
* Based on:
5+
* - svg.rx.js -> https://github.com/akauppi/svg.rx.js
6+
*/
7+
8+
/*
9+
* Factory of event -> SVG coordinate conversion functions.
10+
*
11+
* svgEl: <svg> or <g> element: the target element for the coordinates
12+
*
13+
* An 'SVGPoint' node is created under this element, unless such is already there. This is needed for transforms.
14+
*
15+
* The returned function can be used for transforming events to the local SVG coordinates.
16+
*/
17+
function evToSvgPointGen(svgEl) { // (ev: { ... }) -> { x: num, y: num }
18+
19+
console.log("evToSvgPointGen", svgEl);
20+
21+
// tbd. assert it's an 'SVGDoc' or 'SVGGroup'
22+
23+
24+
25+
const m = svgEl.screenCTM().inverse().native();
26+
27+
// Transformation will need an 'SVGPoint' buffer. We create one per factory (may not be ideal but at least we
28+
// don't create one per transform or need to runtime-check whether there's one already).
29+
//
30+
const buf = svgEl.node.createSVGPoint(); // tbd. would 'new SVGPoint' work?
31+
32+
const convF = function (ev /*, offset*/) { // (MouseEvent or Touch) -> SVGPoint (which has '.x' and '.y')
33+
buf.x = ev.clientX; // - (offset || 0)
34+
buf.y = ev.clientY;
35+
36+
const tmp = buf.matrixTransform(m); // SVGPoint has { x: num, y: num }
37+
return tmp;
38+
};
39+
return convF;
40+
}
41+
42+
export { evToSvgPointGen };

src/index.svelte

+54-104
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,77 @@
11
<!--
2-
- SVGCircles node
2+
- HappyFace node
33
-
4-
- Based on https://eugenkiss.github.io/7guis/tasks#circle
5-
- discovered via Svelte Examples
4+
- To be used within '<svg>' block.
5+
-
6+
- References:
7+
- - based on https://eugenkiss.github.io/7guis/tasks#circle - discovered via Svelte Examples
8+
-->
9+
10+
<!-- Once-for-all initializer - we need an 'SVGPoint' for coordinate transforms.
11+
-
12+
- Note: SVG 2 may offer other means for this. Check 'DOMPoint' at some point.
613
-->
14+
<script context="module">
15+
let svgPoint = null; // SVGPoint for all instances; injected in a parent 'svg' or 'g' at first use
16+
</script>
717

818
<script>
9-
let i = 0;
10-
let undoStack = [[]];
11-
let circles = [];
12-
let selected;
13-
let adjusting = false;
14-
let adjusted = false;
15-
16-
function handleClick(event) {
17-
if (adjusting) {
18-
adjusting = false;
19-
20-
// if circle was adjusted,
21-
// push to the stack
22-
if (adjusted) push();
23-
return;
24-
}
25-
26-
const circle = {
27-
cx: event.clientX,
28-
cy: event.clientY,
29-
r: 50
30-
};
31-
32-
circles = circles.concat(circle);
33-
selected = circle;
34-
35-
push();
36-
}
19+
// #later
20+
//import { evToSvgPointGen } from './conv.js';
3721
38-
function adjust(event) {
39-
selected.r = +event.target.value;
40-
circles = circles;
41-
adjusted = true;
42-
}
22+
let selected; // selected circle object, or 'null'
4323
44-
function select(circle, event) {
45-
if (!adjusting) {
46-
event.stopPropagation();
47-
selected = circle;
48-
}
49-
}
24+
// tbd. clicking on the face would create freckles (unless a freckle already is there)
25+
//let freckles = [];
5026
51-
function push() {
52-
const newUndoStack = undoStack.slice(0, ++i);
53-
newUndoStack.push(clone(circles));
54-
undoStack = newUndoStack;
55-
}
27+
//... tbd. make eventToSvgCoord function here
5628
57-
function travel(d) {
58-
circles = clone(undoStack[i += d]);
59-
adjusting = false;
60-
}
29+
let thisG; // particular SVG element
6130
62-
function clone(circles) {
63-
return circles.map(({ cx, cy, r }) => ({ cx, cy, r }));
64-
}
65-
</script>
31+
$: active = false; // thisG.hasClass("active"); // tbd.
6632
67-
<style>
68-
.controls {
69-
position: absolute;
70-
width: 100%;
71-
text-align: center;
33+
// Properties
34+
export let cx;
35+
export let cy;
36+
export let diam;
37+
export let active; // bool; am I the active face? (smiley and more details)
38+
39+
//#later
40+
//export let mood = "happy"; // "happy"|"nah"|"sad"
41+
42+
if (!svgPoint) {
43+
svgPoint = undefined; // tbd. create one next to 'thisG'
7244
}
7345
74-
svg {
75-
background-color: #eee;
76-
width: 100%;
77-
height: 100%;
46+
function handleClick(ev) {
47+
console.log(`Face ev: ${ev}`);
48+
// tbd.
7849
}
7950
51+
</script>
52+
53+
<style>
8054
circle {
8155
stroke: black;
8256
}
8357
84-
.adjuster {
85-
position: absolute;
86-
width: 80%;
87-
top: 50%;
88-
left: 50%;
89-
transform: translate(-50%,-50%);
90-
padding: 1em;
91-
text-align: center;
92-
background-color: rgba(255,255,255,0.7);
93-
border-radius: 4px;
94-
}
95-
96-
input[type='range'] {
97-
width: 100%;
58+
g.active {
59+
fill: #fed;
60+
strokeWidth: 2px
9861
}
9962
</style>
10063

101-
<!-- HTML -->
102-
103-
<div class="controls">
104-
<button on:click="{() => travel(-1)}" disabled="{i === 0}">undo</button>
105-
<button on:click="{() => travel(+1)}" disabled="{i === undoStack.length -1}">redo</button>
106-
</div>
107-
108-
<svg on:click={handleClick} >
109-
{#each circles as circle}
110-
<circle cx={circle.cx} cy={circle.cy} r={circle.r}
111-
on:click="{event => select(circle, event)}"
112-
on:contextmenu|stopPropagation|preventDefault="{() => {
113-
adjusting = !adjusting;
114-
if (adjusting) selected = circle;
115-
}}"
116-
fill="{circle === selected ? '#ccc': 'white'}"
117-
/>
118-
{/each}
119-
</svg>
120-
121-
{#if adjusting}
122-
<div class="adjuster">
123-
<p>adjust diameter of circle at {selected.cx}, {selected.cy}</p>
124-
<input type="range" value={selected.r} on:input={adjust}>
125-
</div>
126-
{/if}
64+
<!-- SVG -->
12765

66+
<g
67+
bind:this={thisG}
68+
class:active={active}
69+
>
70+
<circle cx={cx} cy={cy} r={diam/2}
71+
on:click="{ev => handleClick(ev)}"
72+
fill="{active ? 'red': 'white'}"
73+
/>
74+
</g>
75+
76+
<!-- tbd. Can we get 'fill' visuals from the styling part? Maybe not.
77+
-->

0 commit comments

Comments
 (0)