@qr-city/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/LICENSE +21 -0
- package/README.md +56 -2
- package/fesm2022/qr-city-angular.mjs +171 -0
- package/fesm2022/qr-city-angular.mjs.map +1 -0
- package/package.json +38 -3
- package/types/qr-city-angular.d.ts +97 -0
- package/types/qr-city-angular.d.ts.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dani Oliva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @qr-city/angular
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Angular component for **qr-city**: a 3D city whose rooftops form a scannable QR code. Standalone, zoneless- and SSR-friendly. Angular ≥ 21, `three` as a *peerDependency*.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i @qr-city/angular three
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component } from '@angular/core';
|
|
11
|
+
import { QrCityComponent } from '@qr-city/angular';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'app-city',
|
|
15
|
+
imports: [QrCityComponent],
|
|
16
|
+
template: `
|
|
17
|
+
<qr-city-view #city data="https://your-site.dev" cityStyle="ciberpunk" style="width: 480px" (revealed)="ready()" />
|
|
18
|
+
<button (click)="city.reveal()">Reveal QR</button>
|
|
19
|
+
`,
|
|
20
|
+
})
|
|
21
|
+
export class CityComponent {
|
|
22
|
+
ready() { console.log('city ready'); }
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Inputs and outputs
|
|
27
|
+
|
|
28
|
+
The same options as [`createQrCity`](../core), as `input()`s: `data` (required), `cityStyle`, `intro`, `pauseAtThreshold`, `view`, `errorCorrection`, `quality`, `interactive`, `animate`, `maxPixelRatio`, `zoomable`, `maxZoom`, `wheelZoom`, `fallback`, `background`, `label` and `doorLabel`. **`cityStyle`** is the city's style (`'moderna' | 'costera' | 'ciberpunk' | 'nevada'` or a `QrCityStyle` object).
|
|
29
|
+
|
|
30
|
+
| Output | Payload |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `revealed` | — |
|
|
33
|
+
| `introStageChange` | `'door' \| 'entering' \| 'threshold' \| 'flying' \| 'done'` |
|
|
34
|
+
| `viewChange` | `'city' \| 'qr'` |
|
|
35
|
+
| `zoomChange` | `number` |
|
|
36
|
+
| `fallbackUsed` | `Error`: no WebGL, a flat scannable QR is shown |
|
|
37
|
+
| `qrError` | `Error` |
|
|
38
|
+
|
|
39
|
+
The selector is `qr-city-view` (so it does not clash with the `<qr-city>` Web Component). By default it is a square as wide as its container.
|
|
40
|
+
|
|
41
|
+
## Behaviour
|
|
42
|
+
|
|
43
|
+
- **Server (SSR):** WebGL starts in `afterNextRender`, which does not run on the server.
|
|
44
|
+
- **Zone:** the animation loop runs outside `NgZone`, so it does not trigger change detection every frame. Outputs are emitted inside the zone.
|
|
45
|
+
- **Input changes:** applied in place; the city only rebuilds when `data`, `cityStyle`, `errorCorrection` or `quality` change.
|
|
46
|
+
- **`intro`** is fixed at creation: if it changes, the component restarts.
|
|
47
|
+
- **Without WebGL** a flat, scannable QR is shown and `fallbackUsed` is emitted (`fallback="none"` to get a `qrError` instead). **Other errors** (invalid data…) reach `qrError`; in dev mode they are also written to the console.
|
|
48
|
+
- **Printable QR:** `toQrSvg()` / `toQrPng()` on the component, or `qrToSvg(data)` / `qrToPng(data)` (exported).
|
|
49
|
+
- **Component methods:** `reveal()`, `setView()`, `toggleView()`, `setZoom()`, `zoomBy()`, `resetZoom()`, `openDoor()`, `continueIntro()`, `skipIntro()`, `toPNG()`, `play()`, `pause()`, plus `controller`, `stage`, `currentView`, `zoom`.
|
|
50
|
+
|
|
51
|
+
## Publishing
|
|
52
|
+
|
|
53
|
+
ng-packagr builds the package into `packages/angular/dist`; publish from that folder.
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT. "QR Code" is a registered trademark of DENSO WAVE INCORPORATED.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, inject, ElementRef, NgZone, signal, computed, afterNextRender, effect, untracked, DestroyRef, isDevMode, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { createQrCity } from '@qr-city/core';
|
|
4
|
+
export { qrToPng, qrToSvg, styles } from '@qr-city/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `<qr-city-view data="https://…" />`: a 3D city whose rooftops form a scannable QR code.
|
|
8
|
+
*
|
|
9
|
+
* Standalone and zoneless-friendly: it starts WebGL after the first render (so it is SSR-safe), runs
|
|
10
|
+
* the animation loop outside the Angular zone, and applies input changes in place.
|
|
11
|
+
* Size it with CSS (default: a square as wide as its container).
|
|
12
|
+
*/
|
|
13
|
+
class QrCityComponent {
|
|
14
|
+
/** Text or URL encoded in the QR (1–300 characters). */
|
|
15
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
16
|
+
/** Built-in style id or a custom style object. */
|
|
17
|
+
cityStyle = input(...(ngDevMode ? [undefined, { debugName: "cityStyle" }] : /* istanbul ignore next */ []));
|
|
18
|
+
/** Fixed at creation: changing it starts over. */
|
|
19
|
+
intro = input(...(ngDevMode ? [undefined, { debugName: "intro" }] : /* istanbul ignore next */ []));
|
|
20
|
+
pauseAtThreshold = input(...(ngDevMode ? [undefined, { debugName: "pauseAtThreshold" }] : /* istanbul ignore next */ []));
|
|
21
|
+
view = input(...(ngDevMode ? [undefined, { debugName: "view" }] : /* istanbul ignore next */ []));
|
|
22
|
+
errorCorrection = input(...(ngDevMode ? [undefined, { debugName: "errorCorrection" }] : /* istanbul ignore next */ []));
|
|
23
|
+
quality = input(...(ngDevMode ? [undefined, { debugName: "quality" }] : /* istanbul ignore next */ []));
|
|
24
|
+
interactive = input(...(ngDevMode ? [undefined, { debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
25
|
+
animate = input(...(ngDevMode ? [undefined, { debugName: "animate" }] : /* istanbul ignore next */ []));
|
|
26
|
+
maxPixelRatio = input(...(ngDevMode ? [undefined, { debugName: "maxPixelRatio" }] : /* istanbul ignore next */ []));
|
|
27
|
+
zoomable = input(...(ngDevMode ? [undefined, { debugName: "zoomable" }] : /* istanbul ignore next */ []));
|
|
28
|
+
maxZoom = input(...(ngDevMode ? [undefined, { debugName: "maxZoom" }] : /* istanbul ignore next */ []));
|
|
29
|
+
wheelZoom = input(...(ngDevMode ? [undefined, { debugName: "wheelZoom" }] : /* istanbul ignore next */ []));
|
|
30
|
+
/** `'qr'` (default) shows a plain QR when WebGL is missing; `'none'` reports an error instead. */
|
|
31
|
+
fallback = input(...(ngDevMode ? [undefined, { debugName: "fallback" }] : /* istanbul ignore next */ []));
|
|
32
|
+
background = input(...(ngDevMode ? [undefined, { debugName: "background" }] : /* istanbul ignore next */ []));
|
|
33
|
+
label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
34
|
+
doorLabel = input(...(ngDevMode ? [undefined, { debugName: "doorLabel" }] : /* istanbul ignore next */ []));
|
|
35
|
+
/** The city is on screen: it finished building (or the door intro finished). */
|
|
36
|
+
revealed = output();
|
|
37
|
+
introStageChange = output();
|
|
38
|
+
viewChange = output();
|
|
39
|
+
zoomChange = output();
|
|
40
|
+
/** WebGL is not available: a plain QR is shown instead of the city. */
|
|
41
|
+
fallbackUsed = output();
|
|
42
|
+
/** Failures: no WebGL, invalid data, a lost context… */
|
|
43
|
+
qrError = output();
|
|
44
|
+
host = inject(ElementRef);
|
|
45
|
+
zone = inject(NgZone);
|
|
46
|
+
mounted = signal(false, ...(ngDevMode ? [{ debugName: "mounted" }] : /* istanbul ignore next */ []));
|
|
47
|
+
instance = null;
|
|
48
|
+
/** Everything but `intro` and the callbacks, without `undefined` so the core applies its defaults. */
|
|
49
|
+
config = computed(() => {
|
|
50
|
+
const all = {
|
|
51
|
+
data: this.data(),
|
|
52
|
+
style: this.cityStyle(),
|
|
53
|
+
pauseAtThreshold: this.pauseAtThreshold(),
|
|
54
|
+
view: this.view(),
|
|
55
|
+
errorCorrection: this.errorCorrection(),
|
|
56
|
+
quality: this.quality(),
|
|
57
|
+
interactive: this.interactive(),
|
|
58
|
+
animate: this.animate(),
|
|
59
|
+
maxPixelRatio: this.maxPixelRatio(),
|
|
60
|
+
zoomable: this.zoomable(),
|
|
61
|
+
maxZoom: this.maxZoom(),
|
|
62
|
+
wheelZoom: this.wheelZoom(),
|
|
63
|
+
fallback: this.fallback(),
|
|
64
|
+
background: this.background(),
|
|
65
|
+
label: this.label(),
|
|
66
|
+
doorLabel: this.doorLabel(),
|
|
67
|
+
};
|
|
68
|
+
for (const k of Object.keys(all))
|
|
69
|
+
if (all[k] === undefined)
|
|
70
|
+
delete all[k];
|
|
71
|
+
return all;
|
|
72
|
+
}, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
73
|
+
/** The controller (`null` until the first render, and on the server). */
|
|
74
|
+
get controller() { return this.instance; }
|
|
75
|
+
get stage() { return this.instance?.stage ?? 'done'; }
|
|
76
|
+
get currentView() { return this.instance?.view ?? 'city'; }
|
|
77
|
+
get zoom() { return this.instance?.zoom ?? 1; }
|
|
78
|
+
/** `true` while a plain QR is shown because WebGL is not available. */
|
|
79
|
+
get usingFallback() { return this.instance?.fallback ?? false; }
|
|
80
|
+
constructor() {
|
|
81
|
+
// Browser only: never runs during server-side rendering.
|
|
82
|
+
afterNextRender(() => this.mounted.set(true));
|
|
83
|
+
// `intro` is fixed when the city is created, so (re)create it when it changes.
|
|
84
|
+
effect((onCleanup) => {
|
|
85
|
+
if (!this.mounted())
|
|
86
|
+
return;
|
|
87
|
+
const intro = this.intro();
|
|
88
|
+
untracked(() => this.create(intro));
|
|
89
|
+
onCleanup(() => this.destroy());
|
|
90
|
+
});
|
|
91
|
+
// Everything else is applied in place; the city is rebuilt only when it has to be.
|
|
92
|
+
effect(() => {
|
|
93
|
+
const next = this.config();
|
|
94
|
+
untracked(() => {
|
|
95
|
+
try {
|
|
96
|
+
this.instance?.update(next);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.report(error);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
inject(DestroyRef).onDestroy(() => this.destroy());
|
|
104
|
+
}
|
|
105
|
+
openDoor() { this.instance?.openDoor(); }
|
|
106
|
+
continueIntro() { this.instance?.continueIntro(); }
|
|
107
|
+
skipIntro() { this.instance?.skipIntro(); }
|
|
108
|
+
reveal() { this.instance?.reveal(); }
|
|
109
|
+
setView(view, opts) { this.instance?.setView(view, opts); }
|
|
110
|
+
toggleView() { this.instance?.toggleView(); }
|
|
111
|
+
setZoom(zoom, opts) { this.instance?.setZoom(zoom, opts); }
|
|
112
|
+
zoomBy(factor, opts) { this.instance?.zoomBy(factor, opts); }
|
|
113
|
+
resetZoom(opts) { this.instance?.resetZoom(opts); }
|
|
114
|
+
/** The current code as a plain, print-ready SVG. */
|
|
115
|
+
toQrSvg(opts) {
|
|
116
|
+
if (!this.instance)
|
|
117
|
+
throw new Error('[qr-city] The city is not mounted.');
|
|
118
|
+
return this.instance.toQrSvg(opts);
|
|
119
|
+
}
|
|
120
|
+
toQrPng(opts) {
|
|
121
|
+
return this.instance ? this.instance.toQrPng(opts) : Promise.reject(new Error('[qr-city] The city is not mounted.'));
|
|
122
|
+
}
|
|
123
|
+
play() { this.instance?.play(); }
|
|
124
|
+
pause() { this.instance?.pause(); }
|
|
125
|
+
toPNG(opts) {
|
|
126
|
+
return this.instance ? this.instance.toPNG(opts) : Promise.reject(new Error('[qr-city] The city is not mounted.'));
|
|
127
|
+
}
|
|
128
|
+
create(intro) {
|
|
129
|
+
this.destroy();
|
|
130
|
+
const inZone = (fn) => () => this.zone.run(fn);
|
|
131
|
+
try {
|
|
132
|
+
// The animation loop must not trigger change detection every frame.
|
|
133
|
+
this.instance = this.zone.runOutsideAngular(() => createQrCity(this.host.nativeElement, {
|
|
134
|
+
...this.config(),
|
|
135
|
+
...(intro ? { intro } : {}),
|
|
136
|
+
onReveal: inZone(() => this.revealed.emit()),
|
|
137
|
+
onIntroStage: (s) => inZone(() => this.introStageChange.emit(s))(),
|
|
138
|
+
onViewChange: (v) => inZone(() => this.viewChange.emit(v))(),
|
|
139
|
+
onZoomChange: (z) => inZone(() => this.zoomChange.emit(z))(),
|
|
140
|
+
onFallback: (r) => inZone(() => this.fallbackUsed.emit(r))(),
|
|
141
|
+
onError: (e) => this.report(e),
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.report(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
destroy() {
|
|
149
|
+
this.instance?.dispose();
|
|
150
|
+
this.instance = null;
|
|
151
|
+
}
|
|
152
|
+
report(error) {
|
|
153
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
154
|
+
if (isDevMode())
|
|
155
|
+
console.error(err);
|
|
156
|
+
this.zone.run(() => this.qrError.emit(err));
|
|
157
|
+
}
|
|
158
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: QrCityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
159
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.25", type: QrCityComponent, isStandalone: true, selector: "qr-city-view", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, cityStyle: { classPropertyName: "cityStyle", publicName: "cityStyle", isSignal: true, isRequired: false, transformFunction: null }, intro: { classPropertyName: "intro", publicName: "intro", isSignal: true, isRequired: false, transformFunction: null }, pauseAtThreshold: { classPropertyName: "pauseAtThreshold", publicName: "pauseAtThreshold", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, errorCorrection: { classPropertyName: "errorCorrection", publicName: "errorCorrection", isSignal: true, isRequired: false, transformFunction: null }, quality: { classPropertyName: "quality", publicName: "quality", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, animate: { classPropertyName: "animate", publicName: "animate", isSignal: true, isRequired: false, transformFunction: null }, maxPixelRatio: { classPropertyName: "maxPixelRatio", publicName: "maxPixelRatio", isSignal: true, isRequired: false, transformFunction: null }, zoomable: { classPropertyName: "zoomable", publicName: "zoomable", isSignal: true, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: true, isRequired: false, transformFunction: null }, wheelZoom: { classPropertyName: "wheelZoom", publicName: "wheelZoom", isSignal: true, isRequired: false, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, doorLabel: { classPropertyName: "doorLabel", publicName: "doorLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { revealed: "revealed", introStageChange: "introStageChange", viewChange: "viewChange", zoomChange: "zoomChange", fallbackUsed: "fallbackUsed", qrError: "qrError" }, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;position:relative;aspect-ratio:1 / 1}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
160
|
+
}
|
|
161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: QrCityComponent, decorators: [{
|
|
162
|
+
type: Component,
|
|
163
|
+
args: [{ selector: 'qr-city-view', template: '', changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;aspect-ratio:1 / 1}\n"] }]
|
|
164
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], cityStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cityStyle", required: false }] }], intro: [{ type: i0.Input, args: [{ isSignal: true, alias: "intro", required: false }] }], pauseAtThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "pauseAtThreshold", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }], errorCorrection: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorCorrection", required: false }] }], quality: [{ type: i0.Input, args: [{ isSignal: true, alias: "quality", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], animate: [{ type: i0.Input, args: [{ isSignal: true, alias: "animate", required: false }] }], maxPixelRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPixelRatio", required: false }] }], zoomable: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomable", required: false }] }], maxZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxZoom", required: false }] }], wheelZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "wheelZoom", required: false }] }], fallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallback", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], doorLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doorLabel", required: false }] }], revealed: [{ type: i0.Output, args: ["revealed"] }], introStageChange: [{ type: i0.Output, args: ["introStageChange"] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }], zoomChange: [{ type: i0.Output, args: ["zoomChange"] }], fallbackUsed: [{ type: i0.Output, args: ["fallbackUsed"] }], qrError: [{ type: i0.Output, args: ["qrError"] }] } });
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generated bundle index. Do not edit.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
export { QrCityComponent };
|
|
171
|
+
//# sourceMappingURL=qr-city-angular.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-city-angular.mjs","sources":["../../src/qr-city.component.ts","../../src/qr-city-angular.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n isDevMode,\n output,\n signal,\n untracked,\n} from '@angular/core';\nimport { createQrCity } from '@qr-city/core';\nimport type {\n QrCityController,\n QrCityEcc,\n QrCityIntro,\n QrCityIntroStage,\n QrCityOptions,\n QrCityQuality,\n QrCityStyle,\n QrCityStyleId,\n QrCityView,\n} from '@qr-city/core';\n\ntype ConfigOptions = Omit<QrCityOptions, 'onReveal' | 'onIntroStage' | 'onViewChange' | 'onZoomChange' | 'onError' | 'intro'>;\n\n/**\n * `<qr-city-view data=\"https://…\" />`: a 3D city whose rooftops form a scannable QR code.\n *\n * Standalone and zoneless-friendly: it starts WebGL after the first render (so it is SSR-safe), runs\n * the animation loop outside the Angular zone, and applies input changes in place.\n * Size it with CSS (default: a square as wide as its container).\n */\n@Component({\n selector: 'qr-city-view',\n template: '',\n styles: [':host { display: block; position: relative; aspect-ratio: 1 / 1; }'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class QrCityComponent {\n /** Text or URL encoded in the QR (1–300 characters). */\n readonly data = input.required<string>();\n /** Built-in style id or a custom style object. */\n readonly cityStyle = input<QrCityStyleId | QrCityStyle>();\n /** Fixed at creation: changing it starts over. */\n readonly intro = input<QrCityIntro>();\n readonly pauseAtThreshold = input<boolean>();\n readonly view = input<QrCityView>();\n readonly errorCorrection = input<QrCityEcc>();\n readonly quality = input<QrCityQuality>();\n readonly interactive = input<boolean>();\n readonly animate = input<boolean | 'auto'>();\n readonly maxPixelRatio = input<number>();\n readonly zoomable = input<boolean>();\n readonly maxZoom = input<number>();\n readonly wheelZoom = input<'ctrl' | 'always' | 'off'>();\n /** `'qr'` (default) shows a plain QR when WebGL is missing; `'none'` reports an error instead. */\n readonly fallback = input<'qr' | 'none'>();\n readonly background = input<string>();\n readonly label = input<string>();\n readonly doorLabel = input<string>();\n\n /** The city is on screen: it finished building (or the door intro finished). */\n readonly revealed = output<void>();\n readonly introStageChange = output<QrCityIntroStage>();\n readonly viewChange = output<QrCityView>();\n readonly zoomChange = output<number>();\n /** WebGL is not available: a plain QR is shown instead of the city. */\n readonly fallbackUsed = output<Error>();\n /** Failures: no WebGL, invalid data, a lost context… */\n readonly qrError = output<Error>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly mounted = signal(false);\n private instance: QrCityController | null = null;\n\n /** Everything but `intro` and the callbacks, without `undefined` so the core applies its defaults. */\n private readonly config = computed<ConfigOptions>(() => {\n const all: Record<string, unknown> = {\n data: this.data(),\n style: this.cityStyle(),\n pauseAtThreshold: this.pauseAtThreshold(),\n view: this.view(),\n errorCorrection: this.errorCorrection(),\n quality: this.quality(),\n interactive: this.interactive(),\n animate: this.animate(),\n maxPixelRatio: this.maxPixelRatio(),\n zoomable: this.zoomable(),\n maxZoom: this.maxZoom(),\n wheelZoom: this.wheelZoom(),\n fallback: this.fallback(),\n background: this.background(),\n label: this.label(),\n doorLabel: this.doorLabel(),\n };\n for (const k of Object.keys(all)) if (all[k] === undefined) delete all[k];\n return all as unknown as ConfigOptions;\n });\n\n /** The controller (`null` until the first render, and on the server). */\n get controller(): QrCityController | null { return this.instance; }\n get stage(): QrCityIntroStage { return this.instance?.stage ?? 'done'; }\n get currentView(): QrCityView { return this.instance?.view ?? 'city'; }\n get zoom(): number { return this.instance?.zoom ?? 1; }\n /** `true` while a plain QR is shown because WebGL is not available. */\n get usingFallback(): boolean { return this.instance?.fallback ?? false; }\n\n constructor() {\n // Browser only: never runs during server-side rendering.\n afterNextRender(() => this.mounted.set(true));\n\n // `intro` is fixed when the city is created, so (re)create it when it changes.\n effect((onCleanup) => {\n if (!this.mounted()) return;\n const intro = this.intro();\n untracked(() => this.create(intro));\n onCleanup(() => this.destroy());\n });\n\n // Everything else is applied in place; the city is rebuilt only when it has to be.\n effect(() => {\n const next = this.config();\n untracked(() => {\n try { this.instance?.update(next as QrCityOptions); } catch (error) { this.report(error); }\n });\n });\n\n inject(DestroyRef).onDestroy(() => this.destroy());\n }\n\n openDoor(): void { this.instance?.openDoor(); }\n continueIntro(): void { this.instance?.continueIntro(); }\n skipIntro(): void { this.instance?.skipIntro(); }\n reveal(): void { this.instance?.reveal(); }\n setView(view: QrCityView, opts?: { immediate?: boolean }): void { this.instance?.setView(view, opts); }\n toggleView(): void { this.instance?.toggleView(); }\n setZoom(zoom: number, opts?: { immediate?: boolean; at?: { x: number; y: number } }): void { this.instance?.setZoom(zoom, opts); }\n zoomBy(factor: number, opts?: { at?: { x: number; y: number } }): void { this.instance?.zoomBy(factor, opts); }\n resetZoom(opts?: { immediate?: boolean }): void { this.instance?.resetZoom(opts); }\n /** The current code as a plain, print-ready SVG. */\n toQrSvg(opts?: Parameters<QrCityController['toQrSvg']>[0]): string {\n if (!this.instance) throw new Error('[qr-city] The city is not mounted.');\n return this.instance.toQrSvg(opts);\n }\n toQrPng(opts?: Parameters<QrCityController['toQrPng']>[0]): Promise<Blob> {\n return this.instance ? this.instance.toQrPng(opts) : Promise.reject(new Error('[qr-city] The city is not mounted.'));\n }\n play(): void { this.instance?.play(); }\n pause(): void { this.instance?.pause(); }\n toPNG(opts?: { scale?: number }): Promise<Blob> {\n return this.instance ? this.instance.toPNG(opts) : Promise.reject(new Error('[qr-city] The city is not mounted.'));\n }\n\n private create(intro: QrCityIntro | undefined): void {\n this.destroy();\n const inZone = <T>(fn: () => T) => () => this.zone.run(fn);\n try {\n // The animation loop must not trigger change detection every frame.\n this.instance = this.zone.runOutsideAngular(() =>\n createQrCity(this.host.nativeElement, {\n ...this.config(),\n ...(intro ? { intro } : {}),\n onReveal: inZone(() => this.revealed.emit()),\n onIntroStage: (s) => inZone(() => this.introStageChange.emit(s))(),\n onViewChange: (v) => inZone(() => this.viewChange.emit(v))(),\n onZoomChange: (z) => inZone(() => this.zoomChange.emit(z))(),\n onFallback: (r) => inZone(() => this.fallbackUsed.emit(r))(),\n onError: (e) => this.report(e),\n } as QrCityOptions),\n );\n } catch (error) {\n this.report(error);\n }\n }\n\n private destroy(): void {\n this.instance?.dispose();\n this.instance = null;\n }\n\n private report(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n if (isDevMode()) console.error(err);\n this.zone.run(() => this.qrError.emit(err));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qr-city/angular",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Angular component for qr-city: a 3D city whose rooftops form a scannable QR code.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"qr",
|
|
7
|
+
"qrcode",
|
|
8
|
+
"angular",
|
|
9
|
+
"three",
|
|
10
|
+
"3d",
|
|
11
|
+
"isometric"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Dani Oliva",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/dolivag/qr-city.git",
|
|
18
|
+
"directory": "packages/angular"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@qr-city/core": "0.1.0",
|
|
23
|
+
"tslib": "^2.3.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@angular/core": ">=21.0.0",
|
|
27
|
+
"three": ">=0.160.0 <1"
|
|
28
|
+
},
|
|
29
|
+
"module": "fesm2022/qr-city-angular.mjs",
|
|
30
|
+
"typings": "types/qr-city-angular.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
"./package.json": {
|
|
33
|
+
"default": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./types/qr-city-angular.d.ts",
|
|
37
|
+
"default": "./fesm2022/qr-city-angular.mjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"type": "module"
|
|
6
41
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { QrCityStyleId, QrCityStyle, QrCityIntro, QrCityView, QrCityEcc, QrCityQuality, QrCityIntroStage, QrCityController } from '@qr-city/core';
|
|
3
|
+
export { QrCityController, QrCityDoorTheme, QrCityEcc, QrCityIntro, QrCityIntroStage, QrCityOptions, QrCityQuality, QrCityStyle, QrCityStyleId, QrCityView, QrImageOptions, QrPngOptions, qrToPng, qrToSvg, styles } from '@qr-city/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `<qr-city-view data="https://…" />`: a 3D city whose rooftops form a scannable QR code.
|
|
7
|
+
*
|
|
8
|
+
* Standalone and zoneless-friendly: it starts WebGL after the first render (so it is SSR-safe), runs
|
|
9
|
+
* the animation loop outside the Angular zone, and applies input changes in place.
|
|
10
|
+
* Size it with CSS (default: a square as wide as its container).
|
|
11
|
+
*/
|
|
12
|
+
declare class QrCityComponent {
|
|
13
|
+
/** Text or URL encoded in the QR (1–300 characters). */
|
|
14
|
+
readonly data: _angular_core.InputSignal<string>;
|
|
15
|
+
/** Built-in style id or a custom style object. */
|
|
16
|
+
readonly cityStyle: _angular_core.InputSignal<QrCityStyleId | QrCityStyle | undefined>;
|
|
17
|
+
/** Fixed at creation: changing it starts over. */
|
|
18
|
+
readonly intro: _angular_core.InputSignal<QrCityIntro | undefined>;
|
|
19
|
+
readonly pauseAtThreshold: _angular_core.InputSignal<boolean | undefined>;
|
|
20
|
+
readonly view: _angular_core.InputSignal<QrCityView | undefined>;
|
|
21
|
+
readonly errorCorrection: _angular_core.InputSignal<QrCityEcc | undefined>;
|
|
22
|
+
readonly quality: _angular_core.InputSignal<QrCityQuality | undefined>;
|
|
23
|
+
readonly interactive: _angular_core.InputSignal<boolean | undefined>;
|
|
24
|
+
readonly animate: _angular_core.InputSignal<boolean | "auto" | undefined>;
|
|
25
|
+
readonly maxPixelRatio: _angular_core.InputSignal<number | undefined>;
|
|
26
|
+
readonly zoomable: _angular_core.InputSignal<boolean | undefined>;
|
|
27
|
+
readonly maxZoom: _angular_core.InputSignal<number | undefined>;
|
|
28
|
+
readonly wheelZoom: _angular_core.InputSignal<"ctrl" | "always" | "off" | undefined>;
|
|
29
|
+
/** `'qr'` (default) shows a plain QR when WebGL is missing; `'none'` reports an error instead. */
|
|
30
|
+
readonly fallback: _angular_core.InputSignal<"none" | "qr" | undefined>;
|
|
31
|
+
readonly background: _angular_core.InputSignal<string | undefined>;
|
|
32
|
+
readonly label: _angular_core.InputSignal<string | undefined>;
|
|
33
|
+
readonly doorLabel: _angular_core.InputSignal<string | undefined>;
|
|
34
|
+
/** The city is on screen: it finished building (or the door intro finished). */
|
|
35
|
+
readonly revealed: _angular_core.OutputEmitterRef<void>;
|
|
36
|
+
readonly introStageChange: _angular_core.OutputEmitterRef<QrCityIntroStage>;
|
|
37
|
+
readonly viewChange: _angular_core.OutputEmitterRef<QrCityView>;
|
|
38
|
+
readonly zoomChange: _angular_core.OutputEmitterRef<number>;
|
|
39
|
+
/** WebGL is not available: a plain QR is shown instead of the city. */
|
|
40
|
+
readonly fallbackUsed: _angular_core.OutputEmitterRef<Error>;
|
|
41
|
+
/** Failures: no WebGL, invalid data, a lost context… */
|
|
42
|
+
readonly qrError: _angular_core.OutputEmitterRef<Error>;
|
|
43
|
+
private readonly host;
|
|
44
|
+
private readonly zone;
|
|
45
|
+
private readonly mounted;
|
|
46
|
+
private instance;
|
|
47
|
+
/** Everything but `intro` and the callbacks, without `undefined` so the core applies its defaults. */
|
|
48
|
+
private readonly config;
|
|
49
|
+
/** The controller (`null` until the first render, and on the server). */
|
|
50
|
+
get controller(): QrCityController | null;
|
|
51
|
+
get stage(): QrCityIntroStage;
|
|
52
|
+
get currentView(): QrCityView;
|
|
53
|
+
get zoom(): number;
|
|
54
|
+
/** `true` while a plain QR is shown because WebGL is not available. */
|
|
55
|
+
get usingFallback(): boolean;
|
|
56
|
+
constructor();
|
|
57
|
+
openDoor(): void;
|
|
58
|
+
continueIntro(): void;
|
|
59
|
+
skipIntro(): void;
|
|
60
|
+
reveal(): void;
|
|
61
|
+
setView(view: QrCityView, opts?: {
|
|
62
|
+
immediate?: boolean;
|
|
63
|
+
}): void;
|
|
64
|
+
toggleView(): void;
|
|
65
|
+
setZoom(zoom: number, opts?: {
|
|
66
|
+
immediate?: boolean;
|
|
67
|
+
at?: {
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
};
|
|
71
|
+
}): void;
|
|
72
|
+
zoomBy(factor: number, opts?: {
|
|
73
|
+
at?: {
|
|
74
|
+
x: number;
|
|
75
|
+
y: number;
|
|
76
|
+
};
|
|
77
|
+
}): void;
|
|
78
|
+
resetZoom(opts?: {
|
|
79
|
+
immediate?: boolean;
|
|
80
|
+
}): void;
|
|
81
|
+
/** The current code as a plain, print-ready SVG. */
|
|
82
|
+
toQrSvg(opts?: Parameters<QrCityController['toQrSvg']>[0]): string;
|
|
83
|
+
toQrPng(opts?: Parameters<QrCityController['toQrPng']>[0]): Promise<Blob>;
|
|
84
|
+
play(): void;
|
|
85
|
+
pause(): void;
|
|
86
|
+
toPNG(opts?: {
|
|
87
|
+
scale?: number;
|
|
88
|
+
}): Promise<Blob>;
|
|
89
|
+
private create;
|
|
90
|
+
private destroy;
|
|
91
|
+
private report;
|
|
92
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<QrCityComponent, never>;
|
|
93
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<QrCityComponent, "qr-city-view", never, { "data": { "alias": "data"; "required": true; "isSignal": true; }; "cityStyle": { "alias": "cityStyle"; "required": false; "isSignal": true; }; "intro": { "alias": "intro"; "required": false; "isSignal": true; }; "pauseAtThreshold": { "alias": "pauseAtThreshold"; "required": false; "isSignal": true; }; "view": { "alias": "view"; "required": false; "isSignal": true; }; "errorCorrection": { "alias": "errorCorrection"; "required": false; "isSignal": true; }; "quality": { "alias": "quality"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "animate": { "alias": "animate"; "required": false; "isSignal": true; }; "maxPixelRatio": { "alias": "maxPixelRatio"; "required": false; "isSignal": true; }; "zoomable": { "alias": "zoomable"; "required": false; "isSignal": true; }; "maxZoom": { "alias": "maxZoom"; "required": false; "isSignal": true; }; "wheelZoom": { "alias": "wheelZoom"; "required": false; "isSignal": true; }; "fallback": { "alias": "fallback"; "required": false; "isSignal": true; }; "background": { "alias": "background"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "doorLabel": { "alias": "doorLabel"; "required": false; "isSignal": true; }; }, { "revealed": "revealed"; "introStageChange": "introStageChange"; "viewChange": "viewChange"; "zoomChange": "zoomChange"; "fallbackUsed": "fallbackUsed"; "qrError": "qrError"; }, never, never, true, never>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { QrCityComponent };
|
|
97
|
+
//# sourceMappingURL=qr-city-angular.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-city-angular.d.ts","sources":["../../src/qr-city.component.ts"],"mappings":";;;;AA+BA;;;;;;AAMG;AACH,cAMa,eAAe;;mBAEb,aAAA,CAAA,WAAA;;wBAEK,aAAA,CAAA,WAAA,CAAA,aAAA,GAAA,WAAA;;oBAEJ,aAAA,CAAA,WAAA,CAAA,WAAA;+BACW,aAAA,CAAA,WAAA;mBACZ,aAAA,CAAA,WAAA,CAAA,UAAA;8BACW,aAAA,CAAA,WAAA,CAAA,SAAA;sBACR,aAAA,CAAA,WAAA,CAAA,aAAA;0BACI,aAAA,CAAA,WAAA;sBACJ,aAAA,CAAA,WAAA;4BACM,aAAA,CAAA,WAAA;uBACL,aAAA,CAAA,WAAA;sBACD,aAAA,CAAA,WAAA;wBACE,aAAA,CAAA,WAAA;;uBAED,aAAA,CAAA,WAAA;yBACE,aAAA,CAAA,WAAA;oBACL,aAAA,CAAA,WAAA;wBACI,aAAA,CAAA,WAAA;;uBAGD,aAAA,CAAA,gBAAA;+BACQ,aAAA,CAAA,gBAAA,CAAA,gBAAA;yBACN,aAAA,CAAA,gBAAA,CAAA,UAAA;yBACA,aAAA,CAAA,gBAAA;;2BAEE,aAAA,CAAA,gBAAA,CAAA,KAAA;;sBAEL,aAAA,CAAA,gBAAA,CAAA,KAAA;AAEhB;AACA;AACA;;;AAIA;;AAwBA,sBAAkB,gBAAgB;iBACrB,gBAAgB;uBACV,UAAU;;;;;AA4B7B;AACA;AACA;AACA;AACA,kBAAc,UAAU;;AAAgC;AACxD;AACA;;AAAoD;;;;AAA+B;AACnF;AAAgC;;;;AAA+B;;;AACvB;;AAExC,mBAAe,UAAU,CAAC,gBAAgB;AAI1C,mBAAe,UAAU,CAAC,gBAAgB,kBAAkB,OAAO,CAAC,IAAI;AAGxE;AACA;;;QACkC,OAAO,CAAC,IAAI;AAI9C;AAsBA;AAKA;oDA/IW,eAAe;sDAAf,eAAe;AAoJ3B;;","names":[]}
|