@selimssevgi/cube-design-angular 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +64 -2
- package/fesm2022/selimssevgi-cube-design-angular.mjs +260 -0
- package/fesm2022/selimssevgi-cube-design-angular.mjs.map +1 -0
- package/package.json +35 -3
- package/tokens/dark.css +16 -0
- package/tokens/light.css +16 -0
- package/types/selimssevgi-cube-design-angular.d.ts +92 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- `drawCube`, `sizeFor` and their types: the isometric cube of small cubes, moved from cubes (`core/drawing/cube-drawing.ts`), the same file mosaic copied.
|
|
6
|
+
- `cube-picture` (`CubePicture`): the cube picture, its look from css variables; `tokens/light.css` (cubes, anlat) and `tokens/dark.css` (mosaic) set them.
|
package/README.md
CHANGED
|
@@ -1,3 +1,65 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @selimssevgi/cube-design-angular
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
cube design in angular. cube design is a way of looking where a piece sits on several groupings at
|
|
4
|
+
once and every piece can open as a cube of its own (https://cube-design.selimssevgi.com). this
|
|
5
|
+
package holds the pieces of it that anlat, mosaic and cubes found the same answer for, so a
|
|
6
|
+
solution found in one app is used by all.
|
|
7
|
+
|
|
8
|
+
it holds only what has settled. what is still being discovered stays in each app until two apps use
|
|
9
|
+
it and its cube design decision is kept; then it moves in here.
|
|
10
|
+
|
|
11
|
+
## install
|
|
12
|
+
|
|
13
|
+
```shell
|
|
14
|
+
npm install @selimssevgi/cube-design-angular@0.1.0 --save-exact
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
pin the exact version: a new version changes nothing in an app until that app takes it in its own
|
|
18
|
+
PR. it needs angular 22.
|
|
19
|
+
|
|
20
|
+
## the cube picture
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { CubePicture } from '@selimssevgi/cube-design-angular';
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
imports: [CubePicture],
|
|
27
|
+
template: `<cube-picture [count]="8" [colours]="colours()" [width]="96" [hint]="true" />`,
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| input | what it is |
|
|
32
|
+
| --------- | ----------------------------------------------------------------------------- |
|
|
33
|
+
| `count` | how many pieces; the cube is the smallest n × n × n that holds them (up to 7) |
|
|
34
|
+
| `colours` | the stickers' colours in order; `null` is a sticker not known yet, drawn pale |
|
|
35
|
+
| `width` | the width in pixels (64) |
|
|
36
|
+
| `hint` | while small, the bigger cube it can grow into, faint and dashed |
|
|
37
|
+
| `whole` | one colour on every sticker, as a value is drawn |
|
|
38
|
+
| `rot` | how many times it was turned; shifts the colours round |
|
|
39
|
+
|
|
40
|
+
its look comes from css variables, so each app keeps its own dialect. without any it draws in the
|
|
41
|
+
light dialect. add one of the two files to the app's global styles (`angular.json` › `styles`):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"styles": ["node_modules/@selimssevgi/cube-design-angular/tokens/dark.css", "src/styles.css"]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
or set the variables yourself: `--cube-top`, `--cube-left`, `--cube-right`, `--cube-edge`,
|
|
48
|
+
`--cube-edge-width`, `--cube-sticker-edge`, `--cube-unknown`, `--cube-unknown-edge`, `--cube-hint`,
|
|
49
|
+
`--cube-hint-opacity`. a dark mode is the same variables set again under
|
|
50
|
+
`@media (prefers-color-scheme: dark)`.
|
|
51
|
+
|
|
52
|
+
## the drawing alone
|
|
53
|
+
|
|
54
|
+
`drawCube(count, colours, width, { hint, whole, rot })` returns the polygons as svg points, and
|
|
55
|
+
`sizeFor(count)` the side of the smallest cube that holds `count`. they are plain functions, for a
|
|
56
|
+
canvas or a picture of your own.
|
|
57
|
+
|
|
58
|
+
## rules this package keeps
|
|
59
|
+
|
|
60
|
+
- no words: it never says anything to a person. every line comes from the app, in the app's language.
|
|
61
|
+
- no tailwind classes: tailwind only builds the classes it finds in an app's own code, so they would
|
|
62
|
+
do nothing here. plain css and variables.
|
|
63
|
+
- the code's words (cube, piece, kind, value, placement); each app keeps its own on screen.
|
|
64
|
+
|
|
65
|
+
what changed in each version is in `CHANGELOG.md`.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core";
|
|
3
|
+
function sizeFor(count) {
|
|
4
|
+
let s = 1;
|
|
5
|
+
while (s * s * s < count && s < 7) s++;
|
|
6
|
+
return s;
|
|
7
|
+
}
|
|
8
|
+
const C30 = Math.cos(Math.PI / 6);
|
|
9
|
+
function poly(pts, k) {
|
|
10
|
+
const cx = pts.reduce((a, q) => a + q[0], 0) / pts.length;
|
|
11
|
+
const cy = pts.reduce((a, q) => a + q[1], 0) / pts.length;
|
|
12
|
+
return pts.map(([x, y]) => `${(cx + (x - cx) * k).toFixed(1)},${(cy + (y - cy) * k).toFixed(1)}`).join(" ");
|
|
13
|
+
}
|
|
14
|
+
function squares(n, s, shift) {
|
|
15
|
+
const ox = n * s * C30;
|
|
16
|
+
const oy = n * s;
|
|
17
|
+
const p = (x, y, z) => [shift[0] + ox + (x - y) * C30 * s, shift[1] + oy + (x + y) * .5 * s - z * s];
|
|
18
|
+
const out = [];
|
|
19
|
+
for (let a = 0; a < n; a++) for (let b = n - 1; b >= 0; b--) out.push({
|
|
20
|
+
face: "left",
|
|
21
|
+
pts: [
|
|
22
|
+
p(a, n, b + 1),
|
|
23
|
+
p(a + 1, n, b + 1),
|
|
24
|
+
p(a + 1, n, b),
|
|
25
|
+
p(a, n, b)
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
for (let a = 0; a < n; a++) for (let b = n - 1; b >= 0; b--) out.push({
|
|
29
|
+
face: "right",
|
|
30
|
+
pts: [
|
|
31
|
+
p(n, a, b + 1),
|
|
32
|
+
p(n, a + 1, b + 1),
|
|
33
|
+
p(n, a + 1, b),
|
|
34
|
+
p(n, a, b)
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
for (let a = 0; a < n; a++) for (let b = 0; b < n; b++) out.push({
|
|
38
|
+
face: "top",
|
|
39
|
+
pts: [
|
|
40
|
+
p(a, b, n),
|
|
41
|
+
p(a + 1, b, n),
|
|
42
|
+
p(a + 1, b + 1, n),
|
|
43
|
+
p(a, b + 1, n)
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function drawCube(count, colours, width, options = {}) {
|
|
49
|
+
const n = sizeFor(Math.max(1, count));
|
|
50
|
+
const hinted = !!options.hint && count < 9;
|
|
51
|
+
const big = hinted ? n + 1 : n;
|
|
52
|
+
const s = width / (2 * big * C30);
|
|
53
|
+
const hint = hinted ? squares(big, s, [0, 0]).map((q) => poly(q.pts, .86)) : [];
|
|
54
|
+
const shift = hinted ? [(big - n) * s * C30, (big - n) * s * 2] : [0, 0];
|
|
55
|
+
const k = colours.length ? (options.rot ?? 0) % colours.length : 0;
|
|
56
|
+
const turned = colours.slice(k).concat(colours.slice(0, k));
|
|
57
|
+
const stickers = squares(n, s, shift).map((q, i) => ({
|
|
58
|
+
face: q.face,
|
|
59
|
+
outer: poly(q.pts, 1),
|
|
60
|
+
inner: poly(q.pts, .86),
|
|
61
|
+
colour: options.whole ? colours[0] ?? null : turned[i] ?? null
|
|
62
|
+
}));
|
|
63
|
+
const height = 2 * big * s;
|
|
64
|
+
return {
|
|
65
|
+
width,
|
|
66
|
+
height: Math.round(height),
|
|
67
|
+
viewBox: `-2 -2 ${(width + 4).toFixed(1)} ${(height + 4).toFixed(1)}`,
|
|
68
|
+
hint,
|
|
69
|
+
stickers
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
var CubePicture = class CubePicture {
|
|
73
|
+
count = input(1, ...ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []);
|
|
74
|
+
colours = input([], ...ngDevMode ? [{ debugName: "colours" }] : /* istanbul ignore next */ []);
|
|
75
|
+
width = input(64, ...ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []);
|
|
76
|
+
hint = input(false, ...ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []);
|
|
77
|
+
whole = input(false, ...ngDevMode ? [{ debugName: "whole" }] : /* istanbul ignore next */ []);
|
|
78
|
+
rot = input(0, ...ngDevMode ? [{ debugName: "rot" }] : /* istanbul ignore next */ []);
|
|
79
|
+
drawing = computed(() => drawCube(this.count(), this.colours(), this.width(), {
|
|
80
|
+
hint: this.hint(),
|
|
81
|
+
whole: this.whole(),
|
|
82
|
+
rot: this.rot()
|
|
83
|
+
}), ...ngDevMode ? [{ debugName: "drawing" }] : /* istanbul ignore next */ []);
|
|
84
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
85
|
+
minVersion: "12.0.0",
|
|
86
|
+
version: "22.2.4",
|
|
87
|
+
ngImport: i0,
|
|
88
|
+
type: CubePicture,
|
|
89
|
+
deps: [],
|
|
90
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
91
|
+
});
|
|
92
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
93
|
+
minVersion: "17.0.0",
|
|
94
|
+
version: "22.2.4",
|
|
95
|
+
type: CubePicture,
|
|
96
|
+
isStandalone: true,
|
|
97
|
+
selector: "cube-picture",
|
|
98
|
+
inputs: {
|
|
99
|
+
count: {
|
|
100
|
+
classPropertyName: "count",
|
|
101
|
+
publicName: "count",
|
|
102
|
+
isSignal: true,
|
|
103
|
+
isRequired: false,
|
|
104
|
+
transformFunction: null
|
|
105
|
+
},
|
|
106
|
+
colours: {
|
|
107
|
+
classPropertyName: "colours",
|
|
108
|
+
publicName: "colours",
|
|
109
|
+
isSignal: true,
|
|
110
|
+
isRequired: false,
|
|
111
|
+
transformFunction: null
|
|
112
|
+
},
|
|
113
|
+
width: {
|
|
114
|
+
classPropertyName: "width",
|
|
115
|
+
publicName: "width",
|
|
116
|
+
isSignal: true,
|
|
117
|
+
isRequired: false,
|
|
118
|
+
transformFunction: null
|
|
119
|
+
},
|
|
120
|
+
hint: {
|
|
121
|
+
classPropertyName: "hint",
|
|
122
|
+
publicName: "hint",
|
|
123
|
+
isSignal: true,
|
|
124
|
+
isRequired: false,
|
|
125
|
+
transformFunction: null
|
|
126
|
+
},
|
|
127
|
+
whole: {
|
|
128
|
+
classPropertyName: "whole",
|
|
129
|
+
publicName: "whole",
|
|
130
|
+
isSignal: true,
|
|
131
|
+
isRequired: false,
|
|
132
|
+
transformFunction: null
|
|
133
|
+
},
|
|
134
|
+
rot: {
|
|
135
|
+
classPropertyName: "rot",
|
|
136
|
+
publicName: "rot",
|
|
137
|
+
isSignal: true,
|
|
138
|
+
isRequired: false,
|
|
139
|
+
transformFunction: null
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
ngImport: i0,
|
|
143
|
+
template: `
|
|
144
|
+
@let d = drawing();
|
|
145
|
+
<svg
|
|
146
|
+
[attr.viewBox]="d.viewBox"
|
|
147
|
+
[attr.width]="d.width"
|
|
148
|
+
[attr.height]="d.height"
|
|
149
|
+
aria-hidden="true"
|
|
150
|
+
>
|
|
151
|
+
@for (q of d.hint; track $index) {
|
|
152
|
+
<polygon class="hint" [attr.points]="q" />
|
|
153
|
+
}
|
|
154
|
+
@for (q of d.stickers; track $index) {
|
|
155
|
+
<polygon [attr.points]="q.outer" [attr.class]="'side ' + q.face" />
|
|
156
|
+
}
|
|
157
|
+
@for (q of d.stickers; track $index) {
|
|
158
|
+
<polygon
|
|
159
|
+
[attr.points]="q.inner"
|
|
160
|
+
[attr.class]="q.colour ? 'sticker' : 'sticker unknown'"
|
|
161
|
+
[attr.fill]="q.colour"
|
|
162
|
+
/>
|
|
163
|
+
}
|
|
164
|
+
</svg>
|
|
165
|
+
`,
|
|
166
|
+
isInline: true,
|
|
167
|
+
styles: [":host{display:inline-block;line-height:0}svg{overflow:visible}.hint{fill:none;stroke:var(--cube-hint, #87938c);stroke-width:1;stroke-dasharray:3 3;opacity:var(--cube-hint-opacity, .5)}.side{stroke:var(--cube-edge, #cdd8d0);stroke-width:var(--cube-edge-width, .6)}.top{fill:var(--cube-top, #f9fbf8)}.left{fill:var(--cube-left, #e9eee9)}.right{fill:var(--cube-right, #dfe6e0)}.sticker{stroke:var(--cube-sticker-edge, rgba(31, 45, 39, .28));stroke-width:1}.unknown{fill:var(--cube-unknown, #e3e7e1);stroke:var(--cube-unknown-edge, none)}\n"],
|
|
168
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
172
|
+
minVersion: "12.0.0",
|
|
173
|
+
version: "22.2.4",
|
|
174
|
+
ngImport: i0,
|
|
175
|
+
type: CubePicture,
|
|
176
|
+
decorators: [{
|
|
177
|
+
type: Component,
|
|
178
|
+
args: [{
|
|
179
|
+
selector: "cube-picture",
|
|
180
|
+
template: `
|
|
181
|
+
@let d = drawing();
|
|
182
|
+
<svg
|
|
183
|
+
[attr.viewBox]="d.viewBox"
|
|
184
|
+
[attr.width]="d.width"
|
|
185
|
+
[attr.height]="d.height"
|
|
186
|
+
aria-hidden="true"
|
|
187
|
+
>
|
|
188
|
+
@for (q of d.hint; track $index) {
|
|
189
|
+
<polygon class="hint" [attr.points]="q" />
|
|
190
|
+
}
|
|
191
|
+
@for (q of d.stickers; track $index) {
|
|
192
|
+
<polygon [attr.points]="q.outer" [attr.class]="'side ' + q.face" />
|
|
193
|
+
}
|
|
194
|
+
@for (q of d.stickers; track $index) {
|
|
195
|
+
<polygon
|
|
196
|
+
[attr.points]="q.inner"
|
|
197
|
+
[attr.class]="q.colour ? 'sticker' : 'sticker unknown'"
|
|
198
|
+
[attr.fill]="q.colour"
|
|
199
|
+
/>
|
|
200
|
+
}
|
|
201
|
+
</svg>
|
|
202
|
+
`,
|
|
203
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
204
|
+
styles: [":host{display:inline-block;line-height:0}svg{overflow:visible}.hint{fill:none;stroke:var(--cube-hint, #87938c);stroke-width:1;stroke-dasharray:3 3;opacity:var(--cube-hint-opacity, .5)}.side{stroke:var(--cube-edge, #cdd8d0);stroke-width:var(--cube-edge-width, .6)}.top{fill:var(--cube-top, #f9fbf8)}.left{fill:var(--cube-left, #e9eee9)}.right{fill:var(--cube-right, #dfe6e0)}.sticker{stroke:var(--cube-sticker-edge, rgba(31, 45, 39, .28));stroke-width:1}.unknown{fill:var(--cube-unknown, #e3e7e1);stroke:var(--cube-unknown-edge, none)}\n"]
|
|
205
|
+
}]
|
|
206
|
+
}],
|
|
207
|
+
propDecorators: {
|
|
208
|
+
count: [{
|
|
209
|
+
type: i0.Input,
|
|
210
|
+
args: [{
|
|
211
|
+
isSignal: true,
|
|
212
|
+
alias: "count",
|
|
213
|
+
required: false
|
|
214
|
+
}]
|
|
215
|
+
}],
|
|
216
|
+
colours: [{
|
|
217
|
+
type: i0.Input,
|
|
218
|
+
args: [{
|
|
219
|
+
isSignal: true,
|
|
220
|
+
alias: "colours",
|
|
221
|
+
required: false
|
|
222
|
+
}]
|
|
223
|
+
}],
|
|
224
|
+
width: [{
|
|
225
|
+
type: i0.Input,
|
|
226
|
+
args: [{
|
|
227
|
+
isSignal: true,
|
|
228
|
+
alias: "width",
|
|
229
|
+
required: false
|
|
230
|
+
}]
|
|
231
|
+
}],
|
|
232
|
+
hint: [{
|
|
233
|
+
type: i0.Input,
|
|
234
|
+
args: [{
|
|
235
|
+
isSignal: true,
|
|
236
|
+
alias: "hint",
|
|
237
|
+
required: false
|
|
238
|
+
}]
|
|
239
|
+
}],
|
|
240
|
+
whole: [{
|
|
241
|
+
type: i0.Input,
|
|
242
|
+
args: [{
|
|
243
|
+
isSignal: true,
|
|
244
|
+
alias: "whole",
|
|
245
|
+
required: false
|
|
246
|
+
}]
|
|
247
|
+
}],
|
|
248
|
+
rot: [{
|
|
249
|
+
type: i0.Input,
|
|
250
|
+
args: [{
|
|
251
|
+
isSignal: true,
|
|
252
|
+
alias: "rot",
|
|
253
|
+
required: false
|
|
254
|
+
}]
|
|
255
|
+
}]
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
export { CubePicture, drawCube, sizeFor };
|
|
259
|
+
|
|
260
|
+
//# sourceMappingURL=selimssevgi-cube-design-angular.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selimssevgi-cube-design-angular.mjs","names":[],"sources":["../../../projects/cube-design-angular/src/lib/drawing/cube-drawing.ts","../../../projects/cube-design-angular/src/lib/cube-picture/cube-picture.ts"],"sourcesContent":["/**\n * drawing a cube of small cubes in isometric view: the left, right and top faces, each split into\n * n × n stickers. a sticker is coloured by a piece, or pale while nothing is known for it.\n *\n * the same projection anlat, mosaic and cubes drew by hand before this package (cube design,\n * `07-visual.md`). pure functions, no angular: they can be used anywhere.\n */\n\n/** The side of the smallest cube that holds this many pieces, up to 7 × 7 × 7. */\nexport function sizeFor(count: number): number {\n let s = 1;\n while (s * s * s < count && s < 7) s++;\n return s;\n}\n\nexport type Face = 'top' | 'left' | 'right';\n\nexport interface Sticker {\n face: Face;\n /** The sticker's square, as SVG points. */\n outer: string;\n /** The coloured inset inside it. */\n inner: string;\n colour: string | null;\n}\n\nexport interface Drawing {\n width: number;\n height: number;\n viewBox: string;\n /** The faint bigger cube it can grow into, as dashed outlines. */\n hint: string[];\n stickers: Sticker[];\n}\n\nexport interface DrawOptions {\n /** Show the next size as a faint cube behind (only while it is small). */\n hint?: boolean;\n /** A value: one colour on every sticker. */\n whole?: boolean;\n /** How many times it has been turned: shifts the colours round the stickers. */\n rot?: number;\n}\n\nconst C30 = Math.cos(Math.PI / 6);\ntype Point = [number, number];\n\nfunction poly(pts: Point[], k: number): string {\n const cx = pts.reduce((a, q) => a + q[0], 0) / pts.length;\n const cy = pts.reduce((a, q) => a + q[1], 0) / pts.length;\n return pts\n .map(([x, y]) => `${(cx + (x - cx) * k).toFixed(1)},${(cy + (y - cy) * k).toFixed(1)}`)\n .join(' ');\n}\n\nfunction squares(n: number, s: number, shift: Point): { face: Face; pts: Point[] }[] {\n const ox = n * s * C30;\n const oy = n * s;\n const p = (x: number, y: number, z: number): Point => [\n shift[0] + ox + (x - y) * C30 * s,\n shift[1] + oy + (x + y) * 0.5 * s - z * s,\n ];\n const out: { face: Face; pts: Point[] }[] = [];\n for (let a = 0; a < n; a++)\n for (let b = n - 1; b >= 0; b--)\n out.push({\n face: 'left',\n pts: [p(a, n, b + 1), p(a + 1, n, b + 1), p(a + 1, n, b), p(a, n, b)],\n });\n for (let a = 0; a < n; a++)\n for (let b = n - 1; b >= 0; b--)\n out.push({\n face: 'right',\n pts: [p(n, a, b + 1), p(n, a + 1, b + 1), p(n, a + 1, b), p(n, a, b)],\n });\n for (let a = 0; a < n; a++)\n for (let b = 0; b < n; b++)\n out.push({\n face: 'top',\n pts: [p(a, b, n), p(a + 1, b, n), p(a + 1, b + 1, n), p(a, b + 1, n)],\n });\n return out;\n}\n\n/** A cube for `count` pieces, `width` wide, coloured by `colours` in sticker order. */\nexport function drawCube(\n count: number,\n colours: (string | null)[],\n width: number,\n options: DrawOptions = {},\n): Drawing {\n const n = sizeFor(Math.max(1, count));\n const hinted = !!options.hint && count < 9;\n const big = hinted ? n + 1 : n;\n const s = width / (2 * big * C30);\n const hint = hinted ? squares(big, s, [0, 0]).map((q) => poly(q.pts, 0.86)) : [];\n const shift: Point = hinted ? [(big - n) * s * C30, (big - n) * s * 2] : [0, 0];\n const k = colours.length ? (options.rot ?? 0) % colours.length : 0;\n const turned = colours.slice(k).concat(colours.slice(0, k));\n const stickers = squares(n, s, shift).map((q, i) => ({\n face: q.face,\n outer: poly(q.pts, 1),\n inner: poly(q.pts, 0.86),\n colour: options.whole ? (colours[0] ?? null) : (turned[i] ?? null),\n }));\n const height = 2 * big * s;\n return {\n width,\n height: Math.round(height),\n viewBox: `-2 -2 ${(width + 4).toFixed(1)} ${(height + 4).toFixed(1)}`,\n hint,\n stickers,\n };\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { drawCube } from '../drawing/cube-drawing';\n\n/**\n * a cube of small cubes, one per piece: coloured where something is known, pale where not.\n *\n * its look comes from css variables, so each app keeps its own dialect (the light one of cubes and\n * anlat, the dark one of mosaic). every variable has a light fallback, so it draws without any;\n * `tokens/light.css` and `tokens/dark.css` set them all.\n */\n@Component({\n selector: 'cube-picture',\n template: `\n @let d = drawing();\n <svg\n [attr.viewBox]=\"d.viewBox\"\n [attr.width]=\"d.width\"\n [attr.height]=\"d.height\"\n aria-hidden=\"true\"\n >\n @for (q of d.hint; track $index) {\n <polygon class=\"hint\" [attr.points]=\"q\" />\n }\n @for (q of d.stickers; track $index) {\n <polygon [attr.points]=\"q.outer\" [attr.class]=\"'side ' + q.face\" />\n }\n @for (q of d.stickers; track $index) {\n <polygon\n [attr.points]=\"q.inner\"\n [attr.class]=\"q.colour ? 'sticker' : 'sticker unknown'\"\n [attr.fill]=\"q.colour\"\n />\n }\n </svg>\n `,\n styles: `\n :host {\n display: inline-block;\n line-height: 0;\n }\n svg {\n overflow: visible;\n }\n .hint {\n fill: none;\n stroke: var(--cube-hint, #87938c);\n stroke-width: 1;\n stroke-dasharray: 3 3;\n opacity: var(--cube-hint-opacity, 0.5);\n }\n .side {\n stroke: var(--cube-edge, #cdd8d0);\n stroke-width: var(--cube-edge-width, 0.6);\n }\n .top {\n fill: var(--cube-top, #f9fbf8);\n }\n .left {\n fill: var(--cube-left, #e9eee9);\n }\n .right {\n fill: var(--cube-right, #dfe6e0);\n }\n .sticker {\n stroke: var(--cube-sticker-edge, rgba(31, 45, 39, 0.28));\n stroke-width: 1;\n }\n .unknown {\n fill: var(--cube-unknown, #e3e7e1);\n stroke: var(--cube-unknown-edge, none);\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CubePicture {\n /** how many pieces: the cube is the smallest n × n × n that holds them. */\n readonly count = input(1);\n /** the stickers' colours in order; null is a sticker not known yet. */\n readonly colours = input<(string | null)[]>([]);\n /** the width in pixels. */\n readonly width = input(64);\n /** show the bigger cube it can grow into, faintly, while it is small. */\n readonly hint = input(false);\n /** one colour on every sticker (a value). */\n readonly whole = input(false);\n /** how many times it was turned: shifts the colours round the stickers. */\n readonly rot = input(0);\n\n protected readonly drawing = computed(() =>\n drawCube(this.count(), this.colours(), this.width(), {\n hint: this.hint(),\n whole: this.whole(),\n rot: this.rot(),\n }),\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selimssevgi/cube-design-angular",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "cube design in angular: the pieces anlat, mosaic and cubes share, starting with the isometric cube picture.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"cube design",
|
|
8
|
+
"cube"
|
|
9
|
+
],
|
|
10
|
+
"author": "selim sevgi (https://selimssevgi.com)",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://cube-design.selimssevgi.com",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@angular/common": "^22.2.0",
|
|
18
|
+
"@angular/core": "^22.2.0"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"tslib": "^2.3.0"
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": [
|
|
24
|
+
"*.css"
|
|
25
|
+
],
|
|
26
|
+
"module": "fesm2022/selimssevgi-cube-design-angular.mjs",
|
|
27
|
+
"typings": "types/selimssevgi-cube-design-angular.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
"./package.json": {
|
|
30
|
+
"default": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./types/selimssevgi-cube-design-angular.d.ts",
|
|
34
|
+
"default": "./fesm2022/selimssevgi-cube-design-angular.mjs"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"type": "module"
|
|
6
38
|
}
|
package/tokens/dark.css
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* cube design's dark dialect (mosaic; cube design `07-visual.md`): black plastic with one light
|
|
3
|
+
* source. import it once in an app's global styles, or set the same variables yourself.
|
|
4
|
+
*/
|
|
5
|
+
:root {
|
|
6
|
+
--cube-top: #374151;
|
|
7
|
+
--cube-left: #1f2937;
|
|
8
|
+
--cube-right: #111827;
|
|
9
|
+
--cube-edge: #030712;
|
|
10
|
+
--cube-edge-width: 0.8;
|
|
11
|
+
--cube-sticker-edge: rgba(3, 7, 18, 0.35);
|
|
12
|
+
--cube-unknown: rgba(156, 163, 175, 0.1);
|
|
13
|
+
--cube-unknown-edge: #4b5563;
|
|
14
|
+
--cube-hint: #4b5563;
|
|
15
|
+
--cube-hint-opacity: 0.6;
|
|
16
|
+
}
|
package/tokens/light.css
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* cube design's light dialect (cubes, anlat; cube design `07-visual.md`): import it once in an
|
|
3
|
+
* app's global styles, or set the same variables yourself.
|
|
4
|
+
*/
|
|
5
|
+
:root {
|
|
6
|
+
--cube-top: #f9fbf8;
|
|
7
|
+
--cube-left: #e9eee9;
|
|
8
|
+
--cube-right: #dfe6e0;
|
|
9
|
+
--cube-edge: #cdd8d0;
|
|
10
|
+
--cube-edge-width: 0.6;
|
|
11
|
+
--cube-sticker-edge: rgba(31, 45, 39, 0.28);
|
|
12
|
+
--cube-unknown: #e3e7e1;
|
|
13
|
+
--cube-unknown-edge: none;
|
|
14
|
+
--cube-hint: #87938c;
|
|
15
|
+
--cube-hint-opacity: 0.5;
|
|
16
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* drawing a cube of small cubes in isometric view: the left, right and top faces, each split into
|
|
4
|
+
* n × n stickers. a sticker is coloured by a piece, or pale while nothing is known for it.
|
|
5
|
+
*
|
|
6
|
+
* the same projection anlat, mosaic and cubes drew by hand before this package (cube design,
|
|
7
|
+
* `07-visual.md`). pure functions, no angular: they can be used anywhere.
|
|
8
|
+
*/
|
|
9
|
+
/** The side of the smallest cube that holds this many pieces, up to 7 × 7 × 7. */
|
|
10
|
+
export declare function sizeFor(count: number): number;
|
|
11
|
+
export type Face = 'top' | 'left' | 'right';
|
|
12
|
+
export interface Sticker {
|
|
13
|
+
face: Face;
|
|
14
|
+
/** The sticker's square, as SVG points. */
|
|
15
|
+
outer: string;
|
|
16
|
+
/** The coloured inset inside it. */
|
|
17
|
+
inner: string;
|
|
18
|
+
colour: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface Drawing {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
viewBox: string;
|
|
24
|
+
/** The faint bigger cube it can grow into, as dashed outlines. */
|
|
25
|
+
hint: string[];
|
|
26
|
+
stickers: Sticker[];
|
|
27
|
+
}
|
|
28
|
+
export interface DrawOptions {
|
|
29
|
+
/** Show the next size as a faint cube behind (only while it is small). */
|
|
30
|
+
hint?: boolean;
|
|
31
|
+
/** A value: one colour on every sticker. */
|
|
32
|
+
whole?: boolean;
|
|
33
|
+
/** How many times it has been turned: shifts the colours round the stickers. */
|
|
34
|
+
rot?: number;
|
|
35
|
+
}
|
|
36
|
+
/** A cube for `count` pieces, `width` wide, coloured by `colours` in sticker order. */
|
|
37
|
+
export declare function drawCube(count: number, colours: (string | null)[], width: number, options?: DrawOptions): Drawing;
|
|
38
|
+
/**
|
|
39
|
+
* a cube of small cubes, one per piece: coloured where something is known, pale where not.
|
|
40
|
+
*
|
|
41
|
+
* its look comes from css variables, so each app keeps its own dialect (the light one of cubes and
|
|
42
|
+
* anlat, the dark one of mosaic). every variable has a light fallback, so it draws without any;
|
|
43
|
+
* `tokens/light.css` and `tokens/dark.css` set them all.
|
|
44
|
+
*/
|
|
45
|
+
export declare class CubePicture {
|
|
46
|
+
/** how many pieces: the cube is the smallest n × n × n that holds them. */
|
|
47
|
+
readonly count: import("@angular/core").InputSignal<number>;
|
|
48
|
+
/** the stickers' colours in order; null is a sticker not known yet. */
|
|
49
|
+
readonly colours: import("@angular/core").InputSignal<(string | null)[]>;
|
|
50
|
+
/** the width in pixels. */
|
|
51
|
+
readonly width: import("@angular/core").InputSignal<number>;
|
|
52
|
+
/** show the bigger cube it can grow into, faintly, while it is small. */
|
|
53
|
+
readonly hint: import("@angular/core").InputSignal<boolean>;
|
|
54
|
+
/** one colour on every sticker (a value). */
|
|
55
|
+
readonly whole: import("@angular/core").InputSignal<boolean>;
|
|
56
|
+
/** how many times it was turned: shifts the colours round the stickers. */
|
|
57
|
+
readonly rot: import("@angular/core").InputSignal<number>;
|
|
58
|
+
protected readonly drawing: import("@angular/core").Signal<import("@selimssevgi/cube-design-angular").Drawing>;
|
|
59
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CubePicture, never>;
|
|
60
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CubePicture, "cube-picture", never, {
|
|
61
|
+
"count": {
|
|
62
|
+
"alias": "count";
|
|
63
|
+
"required": false;
|
|
64
|
+
"isSignal": true;
|
|
65
|
+
};
|
|
66
|
+
"colours": {
|
|
67
|
+
"alias": "colours";
|
|
68
|
+
"required": false;
|
|
69
|
+
"isSignal": true;
|
|
70
|
+
};
|
|
71
|
+
"width": {
|
|
72
|
+
"alias": "width";
|
|
73
|
+
"required": false;
|
|
74
|
+
"isSignal": true;
|
|
75
|
+
};
|
|
76
|
+
"hint": {
|
|
77
|
+
"alias": "hint";
|
|
78
|
+
"required": false;
|
|
79
|
+
"isSignal": true;
|
|
80
|
+
};
|
|
81
|
+
"whole": {
|
|
82
|
+
"alias": "whole";
|
|
83
|
+
"required": false;
|
|
84
|
+
"isSignal": true;
|
|
85
|
+
};
|
|
86
|
+
"rot": {
|
|
87
|
+
"alias": "rot";
|
|
88
|
+
"required": false;
|
|
89
|
+
"isSignal": true;
|
|
90
|
+
};
|
|
91
|
+
}, {}, never, never, true, never>;
|
|
92
|
+
}
|