samengine 1.9.0 → 1.10.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 +201 -0
- package/README.md +203 -0
- package/dist/config/buildconfig.d.ts +146 -0
- package/dist/config/buildconfig.js +115 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.js +1 -0
- package/dist/core.d.ts +17 -0
- package/dist/core.js +24 -0
- package/dist/html.d.ts +29 -0
- package/dist/html.js +20 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -2
- package/dist/input.d.ts +51 -0
- package/dist/input.js +44 -3
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +6 -2
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +8 -1
- package/dist/nonbrowser/getversion.d.ts +13 -0
- package/dist/nonbrowser/getversion.js +35 -0
- package/dist/nonbrowser/ghresolver.d.ts +1 -0
- package/dist/nonbrowser/ghresolver.js +7 -0
- package/dist/nonbrowser/index.d.ts +9 -0
- package/dist/nonbrowser/index.js +9 -0
- package/dist/nonbrowser/internal/buildhelper.d.ts +42 -0
- package/dist/nonbrowser/internal/buildhelper.js +144 -0
- package/dist/nonbrowser/internal/cli/argparser.d.ts +20 -0
- package/dist/nonbrowser/internal/cli/argparser.js +36 -0
- package/dist/nonbrowser/internal/cli/main.d.ts +13 -0
- package/dist/nonbrowser/internal/cli/main.js +262 -0
- package/dist/nonbrowser/internal/config.d.ts +9 -0
- package/dist/nonbrowser/internal/config.js +40 -0
- package/dist/nonbrowser/internal/exporthtml.d.ts +37 -0
- package/dist/nonbrowser/internal/exporthtml.js +622 -0
- package/dist/nonbrowser/internal/projcreator/downloadZip.d.ts +4 -0
- package/dist/nonbrowser/internal/projcreator/downloadZip.js +83 -0
- package/dist/nonbrowser/internal/projcreator/main.d.ts +1 -0
- package/dist/nonbrowser/internal/projcreator/main.js +81 -0
- package/dist/nonbrowser/utils.d.ts +8 -0
- package/dist/nonbrowser/utils.js +18 -0
- package/dist/physics/collision.d.ts +33 -0
- package/dist/physics/collision.js +27 -0
- package/dist/physics/physicsEngine.d.ts +18 -0
- package/dist/physics/physicsEngine.js +18 -0
- package/dist/physics/physicsObject.d.ts +20 -0
- package/dist/physics/physicsObject.js +20 -0
- package/dist/renderer.d.ts +85 -2
- package/dist/renderer.js +86 -7
- package/dist/samegui/index.d.ts +49 -0
- package/dist/samegui/index.js +137 -0
- package/dist/save.d.ts +30 -0
- package/dist/save.js +25 -0
- package/dist/sound/audioplayer.d.ts +39 -0
- package/dist/sound/audioplayer.js +39 -5
- package/dist/storage/index.d.ts +57 -0
- package/dist/storage/index.js +89 -0
- package/dist/text/index.d.ts +14 -0
- package/dist/text/index.js +58 -0
- package/dist/texture.d.ts +100 -0
- package/dist/texture.js +75 -41
- package/dist/types/button.d.ts +25 -0
- package/dist/types/button.js +22 -0
- package/dist/types/circle.d.ts +26 -0
- package/dist/types/circle.js +21 -7
- package/dist/types/color.d.ts +17 -0
- package/dist/types/color.js +11 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/rectangle.d.ts +29 -0
- package/dist/types/rectangle.js +23 -6
- package/dist/types/triangle.d.ts +23 -0
- package/dist/types/triangle.js +20 -6
- package/dist/types/vector2d.d.ts +42 -0
- package/dist/types/vector2d.js +39 -11
- package/dist/types/vector3d.d.ts +38 -0
- package/dist/types/vector3d.js +35 -11
- package/dist/utils/index.d.ts +13 -4
- package/dist/utils/index.js +26 -2
- package/dist/utils/logger/index.d.ts +24 -0
- package/dist/utils/logger/index.js +44 -0
- package/dist/utils/math.d.ts +18 -0
- package/dist/utils/math.js +18 -4
- package/package.json +40 -10
- package/dist/utils/jsonc-parser.d.ts +0 -4
- package/dist/utils/jsonc-parser.js +0 -166
- package/dist/utils/markdown.d.ts +0 -41
- package/dist/utils/markdown.js +0 -699
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// HTML Overlay
|
|
2
|
+
import { hash } from "../utils/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Tiny immediate-mode HTML overlay for debug tools and simple in-game menus.
|
|
5
|
+
*
|
|
6
|
+
* Call `begin()` before emitting UI for the frame, then call `button`, `text`,
|
|
7
|
+
* and `checkbox` in a stable order, and finally call `end()`. Elements are
|
|
8
|
+
* backed by real DOM nodes but identified by hashed labels or explicit ids.
|
|
9
|
+
*/
|
|
10
|
+
export class HtmlUI {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.elements = new Map();
|
|
13
|
+
this.clicked = new Map();
|
|
14
|
+
this.frameIds = [];
|
|
15
|
+
this.valueMap = new Map();
|
|
16
|
+
this.root = document.createElement("div");
|
|
17
|
+
this.root.style.position = "absolute";
|
|
18
|
+
this.root.style.inset = "0";
|
|
19
|
+
this.root.style.pointerEvents = "none";
|
|
20
|
+
document.body.appendChild(this.root);
|
|
21
|
+
this.panel = document.createElement("div");
|
|
22
|
+
this.panel.style.position = "absolute";
|
|
23
|
+
this.panel.style.left = "20px";
|
|
24
|
+
this.panel.style.top = "20px";
|
|
25
|
+
this.panel.style.padding = "10px";
|
|
26
|
+
this.panel.style.background = "rgba(30,30,30,0.9)";
|
|
27
|
+
this.panel.style.color = "white";
|
|
28
|
+
this.panel.style.pointerEvents = "auto";
|
|
29
|
+
this.panel.style.borderRadius = "6px";
|
|
30
|
+
this.root.appendChild(this.panel);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Starts a new UI frame and records which controls are used this frame.
|
|
34
|
+
*/
|
|
35
|
+
begin() {
|
|
36
|
+
this.frameIds = [];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ends the UI frame and clears one-frame button click states.
|
|
40
|
+
*/
|
|
41
|
+
end() {
|
|
42
|
+
// reset clicks AFTER frame
|
|
43
|
+
for (const id of this.frameIds) {
|
|
44
|
+
this.clicked.set(id, false);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
getButton(id) {
|
|
48
|
+
let e = this.elements.get(id);
|
|
49
|
+
if (!e) {
|
|
50
|
+
const btn = document.createElement("button");
|
|
51
|
+
btn.style.display = "block";
|
|
52
|
+
btn.style.marginBottom = "6px";
|
|
53
|
+
btn.style.width = "100%";
|
|
54
|
+
btn.style.padding = "6px";
|
|
55
|
+
btn.style.background = "#444";
|
|
56
|
+
btn.style.color = "white";
|
|
57
|
+
btn.style.border = "none";
|
|
58
|
+
btn.style.cursor = "pointer";
|
|
59
|
+
this.panel.appendChild(btn);
|
|
60
|
+
e = { el: btn, type: "button" };
|
|
61
|
+
this.elements.set(id, e);
|
|
62
|
+
}
|
|
63
|
+
return e.el;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates or updates a button.
|
|
67
|
+
*
|
|
68
|
+
* @returns `true` during the frame after the DOM button was clicked.
|
|
69
|
+
*/
|
|
70
|
+
button(label, idOverride) {
|
|
71
|
+
const id = hash(idOverride ?? label);
|
|
72
|
+
this.frameIds.push(id);
|
|
73
|
+
const btn = this.getButton(id);
|
|
74
|
+
btn.textContent = label;
|
|
75
|
+
// wichtig: nur EIN handler (kein stacking!)
|
|
76
|
+
btn.onclick = () => {
|
|
77
|
+
this.clicked.set(id, true);
|
|
78
|
+
};
|
|
79
|
+
return this.clicked.get(id) === true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Creates or updates a text label in the overlay panel.
|
|
83
|
+
*/
|
|
84
|
+
text(label, idOverride) {
|
|
85
|
+
const id = hash(idOverride ?? label);
|
|
86
|
+
this.frameIds.push(id);
|
|
87
|
+
let e = this.elements.get(id);
|
|
88
|
+
if (!e) {
|
|
89
|
+
const div = document.createElement("div");
|
|
90
|
+
div.style.marginBottom = "6px";
|
|
91
|
+
this.panel.appendChild(div);
|
|
92
|
+
e = { el: div, type: "text" };
|
|
93
|
+
this.elements.set(id, e);
|
|
94
|
+
}
|
|
95
|
+
e.el.textContent = label;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates or updates a checkbox and returns its current value.
|
|
99
|
+
*
|
|
100
|
+
* `defaultValue` is only used the first time the checkbox id appears.
|
|
101
|
+
*/
|
|
102
|
+
checkbox(label, defaultValue, idOverride) {
|
|
103
|
+
const id = hash(idOverride ?? label);
|
|
104
|
+
this.frameIds.push(id);
|
|
105
|
+
// initial state only once
|
|
106
|
+
if (!this.valueMap.has(id)) {
|
|
107
|
+
this.valueMap.set(id, defaultValue);
|
|
108
|
+
}
|
|
109
|
+
const current = this.valueMap.get(id);
|
|
110
|
+
let e = this.elements.get(id);
|
|
111
|
+
if (!e) {
|
|
112
|
+
const wrapper = document.createElement("label");
|
|
113
|
+
wrapper.style.display = "flex";
|
|
114
|
+
wrapper.style.alignItems = "center";
|
|
115
|
+
wrapper.style.gap = "8px";
|
|
116
|
+
wrapper.style.marginBottom = "6px";
|
|
117
|
+
wrapper.style.cursor = "pointer";
|
|
118
|
+
const input = document.createElement("input");
|
|
119
|
+
input.type = "checkbox";
|
|
120
|
+
const text = document.createElement("span");
|
|
121
|
+
wrapper.appendChild(input);
|
|
122
|
+
wrapper.appendChild(text);
|
|
123
|
+
this.panel.appendChild(wrapper);
|
|
124
|
+
e = { el: wrapper, type: "checkbox" };
|
|
125
|
+
this.elements.set(id, e);
|
|
126
|
+
}
|
|
127
|
+
const wrapper = e.el;
|
|
128
|
+
const input = wrapper.querySelector("input");
|
|
129
|
+
const text = wrapper.querySelector("span");
|
|
130
|
+
text.textContent = label;
|
|
131
|
+
input.checked = current;
|
|
132
|
+
input.onchange = () => {
|
|
133
|
+
this.valueMap.set(id, input.checked);
|
|
134
|
+
};
|
|
135
|
+
return current;
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/save.d.ts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Old save data shape used by the legacy save helpers.
|
|
3
|
+
*
|
|
4
|
+
* @deprecated Use samengine/storage instead
|
|
5
|
+
*/
|
|
1
6
|
export type SaveData = Record<string, any>;
|
|
7
|
+
/**
|
|
8
|
+
* localStorage key used by the legacy save helpers.
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Use samengine/storage instead
|
|
11
|
+
*/
|
|
2
12
|
export declare let SAVE_KEY: string;
|
|
13
|
+
/**
|
|
14
|
+
* Stores save data as JSON under `SAVE_KEY`.
|
|
15
|
+
*
|
|
16
|
+
* @deprecated Use samengine/storage instead
|
|
17
|
+
*/
|
|
3
18
|
export declare function saveGame(data: SaveData): void;
|
|
19
|
+
/**
|
|
20
|
+
* Loads and parses JSON save data from `SAVE_KEY`.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Use samengine/storage instead
|
|
23
|
+
*/
|
|
4
24
|
export declare function loadGame(): SaveData | null;
|
|
25
|
+
/**
|
|
26
|
+
* Removes the current save entry from localStorage.
|
|
27
|
+
*
|
|
28
|
+
* @deprecated Use samengine/storage instead
|
|
29
|
+
*/
|
|
5
30
|
export declare function clearSave(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Downloads the current save data as `savegame.json`.
|
|
33
|
+
*
|
|
34
|
+
* @deprecated Use samengine/storage instead
|
|
35
|
+
*/
|
|
6
36
|
export declare function exportSave(): void;
|
package/dist/save.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
// Please rename the SaveKey
|
|
2
|
+
/**
|
|
3
|
+
* localStorage key used by the legacy save helpers.
|
|
4
|
+
*
|
|
5
|
+
* @deprecated Use samengine/storage instead
|
|
6
|
+
*/
|
|
2
7
|
export let SAVE_KEY = "my_game_save";
|
|
8
|
+
/**
|
|
9
|
+
* Stores save data as JSON under `SAVE_KEY`.
|
|
10
|
+
*
|
|
11
|
+
* @deprecated Use samengine/storage instead
|
|
12
|
+
*/
|
|
3
13
|
export function saveGame(data) {
|
|
4
14
|
try {
|
|
5
15
|
const json = JSON.stringify(data);
|
|
@@ -10,6 +20,11 @@ export function saveGame(data) {
|
|
|
10
20
|
console.error("Save failed:", err);
|
|
11
21
|
}
|
|
12
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Loads and parses JSON save data from `SAVE_KEY`.
|
|
25
|
+
*
|
|
26
|
+
* @deprecated Use samengine/storage instead
|
|
27
|
+
*/
|
|
13
28
|
export function loadGame() {
|
|
14
29
|
try {
|
|
15
30
|
const json = localStorage.getItem(SAVE_KEY);
|
|
@@ -22,10 +37,20 @@ export function loadGame() {
|
|
|
22
37
|
return null;
|
|
23
38
|
}
|
|
24
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Removes the current save entry from localStorage.
|
|
42
|
+
*
|
|
43
|
+
* @deprecated Use samengine/storage instead
|
|
44
|
+
*/
|
|
25
45
|
export function clearSave() {
|
|
26
46
|
localStorage.removeItem(SAVE_KEY);
|
|
27
47
|
console.log("Save cleared!");
|
|
28
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Downloads the current save data as `savegame.json`.
|
|
51
|
+
*
|
|
52
|
+
* @deprecated Use samengine/storage instead
|
|
53
|
+
*/
|
|
29
54
|
export function exportSave() {
|
|
30
55
|
try {
|
|
31
56
|
const json = localStorage.getItem(SAVE_KEY);
|
|
@@ -1,17 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Audio based sound manager for short effects and looping music.
|
|
3
|
+
*
|
|
4
|
+
* Sounds are decoded once and cached by URL. In normal builds, audio files are
|
|
5
|
+
* loaded with `fetch(url)`. Single-file exports can provide `window.__loadResource`
|
|
6
|
+
* to resolve embedded resources before falling back to normal fetch.
|
|
7
|
+
*/
|
|
1
8
|
export declare class SoundSystem {
|
|
2
9
|
private ctx;
|
|
3
10
|
private bufferCache;
|
|
4
11
|
private activeSources;
|
|
5
12
|
private masterGain;
|
|
13
|
+
/**
|
|
14
|
+
* Creates or reuses an `AudioContext` and connects a master gain node.
|
|
15
|
+
*
|
|
16
|
+
* Browsers often require user interaction before audio can play. If playback
|
|
17
|
+
* is blocked or suspended, call `resume()` from a click/key handler.
|
|
18
|
+
*/
|
|
6
19
|
constructor();
|
|
20
|
+
/**
|
|
21
|
+
* Loads, decodes, and caches an audio file.
|
|
22
|
+
*
|
|
23
|
+
* @param url Audio URL or embedded resource key.
|
|
24
|
+
* @returns Decoded `AudioBuffer`.
|
|
25
|
+
*/
|
|
7
26
|
load(url: string): Promise<AudioBuffer>;
|
|
27
|
+
/**
|
|
28
|
+
* Plays an audio file and returns the active source node.
|
|
29
|
+
*
|
|
30
|
+
* @param url Audio URL or embedded resource key.
|
|
31
|
+
* @param options Per-playback settings such as volume, loop, and speed.
|
|
32
|
+
*/
|
|
8
33
|
play(url: string, options?: {
|
|
9
34
|
volume?: number;
|
|
10
35
|
loop?: boolean;
|
|
11
36
|
playbackRate?: number;
|
|
12
37
|
}): Promise<AudioBufferSourceNode>;
|
|
38
|
+
/**
|
|
39
|
+
* Stops every source started through this `SoundSystem`.
|
|
40
|
+
*/
|
|
13
41
|
stopAll(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Sets the master volume for all current and future sounds.
|
|
44
|
+
*
|
|
45
|
+
* `1` is full volume, `0` is silent. Values above `1` amplify.
|
|
46
|
+
*/
|
|
14
47
|
setVolume(v: number): void;
|
|
48
|
+
/**
|
|
49
|
+
* Resumes the underlying `AudioContext` if the browser suspended it.
|
|
50
|
+
*/
|
|
15
51
|
resume(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Suspends the underlying `AudioContext` without clearing loaded buffers.
|
|
54
|
+
*/
|
|
16
55
|
pause(): Promise<void>;
|
|
17
56
|
}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Audio based sound manager for short effects and looping music.
|
|
3
|
+
*
|
|
4
|
+
* Sounds are decoded once and cached by URL. In normal builds, audio files are
|
|
5
|
+
* loaded with `fetch(url)`. Single-file exports can provide `window.__loadResource`
|
|
6
|
+
* to resolve embedded resources before falling back to normal fetch.
|
|
7
|
+
*/
|
|
1
8
|
export class SoundSystem {
|
|
9
|
+
/**
|
|
10
|
+
* Creates or reuses an `AudioContext` and connects a master gain node.
|
|
11
|
+
*
|
|
12
|
+
* Browsers often require user interaction before audio can play. If playback
|
|
13
|
+
* is blocked or suspended, call `resume()` from a click/key handler.
|
|
14
|
+
*/
|
|
2
15
|
constructor() {
|
|
3
16
|
this.bufferCache = new Map();
|
|
4
17
|
this.activeSources = new Set();
|
|
@@ -8,7 +21,12 @@ export class SoundSystem {
|
|
|
8
21
|
this.masterGain.gain.value = 1;
|
|
9
22
|
this.masterGain.connect(this.ctx.destination);
|
|
10
23
|
}
|
|
11
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Loads, decodes, and caches an audio file.
|
|
26
|
+
*
|
|
27
|
+
* @param url Audio URL or embedded resource key.
|
|
28
|
+
* @returns Decoded `AudioBuffer`.
|
|
29
|
+
*/
|
|
12
30
|
async load(url) {
|
|
13
31
|
if (this.bufferCache.has(url)) {
|
|
14
32
|
return this.bufferCache.get(url);
|
|
@@ -38,7 +56,12 @@ export class SoundSystem {
|
|
|
38
56
|
this.bufferCache.set(url, buffer);
|
|
39
57
|
return buffer;
|
|
40
58
|
}
|
|
41
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Plays an audio file and returns the active source node.
|
|
61
|
+
*
|
|
62
|
+
* @param url Audio URL or embedded resource key.
|
|
63
|
+
* @param options Per-playback settings such as volume, loop, and speed.
|
|
64
|
+
*/
|
|
42
65
|
async play(url, options = {}) {
|
|
43
66
|
const buffer = await this.load(url);
|
|
44
67
|
const source = this.ctx.createBufferSource();
|
|
@@ -56,7 +79,9 @@ export class SoundSystem {
|
|
|
56
79
|
};
|
|
57
80
|
return source;
|
|
58
81
|
}
|
|
59
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Stops every source started through this `SoundSystem`.
|
|
84
|
+
*/
|
|
60
85
|
stopAll() {
|
|
61
86
|
for (const s of this.activeSources) {
|
|
62
87
|
try {
|
|
@@ -66,16 +91,25 @@ export class SoundSystem {
|
|
|
66
91
|
}
|
|
67
92
|
this.activeSources.clear();
|
|
68
93
|
}
|
|
69
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Sets the master volume for all current and future sounds.
|
|
96
|
+
*
|
|
97
|
+
* `1` is full volume, `0` is silent. Values above `1` amplify.
|
|
98
|
+
*/
|
|
70
99
|
setVolume(v) {
|
|
71
100
|
this.masterGain.gain.value = v;
|
|
72
101
|
}
|
|
73
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Resumes the underlying `AudioContext` if the browser suspended it.
|
|
104
|
+
*/
|
|
74
105
|
async resume() {
|
|
75
106
|
if (this.ctx.state === "suspended") {
|
|
76
107
|
await this.ctx.resume();
|
|
77
108
|
}
|
|
78
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Suspends the underlying `AudioContext` without clearing loaded buffers.
|
|
112
|
+
*/
|
|
79
113
|
async pause() {
|
|
80
114
|
if (this.ctx.state === "running") {
|
|
81
115
|
await this.ctx.suspend();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper shape for values that may receive expiration metadata in the future.
|
|
3
|
+
*
|
|
4
|
+
* The current `StorageLib` methods store raw values directly, so this type is
|
|
5
|
+
* mainly useful when callers want to manage their own `expiresAt` convention.
|
|
6
|
+
*/
|
|
7
|
+
export type StoredValue<T> = {
|
|
8
|
+
value: T;
|
|
9
|
+
expiresAt?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Small typed wrapper around `localStorage`.
|
|
13
|
+
*
|
|
14
|
+
* Values are serialized with `JSON.stringify` and read with `JSON.parse`. If a
|
|
15
|
+
* value cannot be parsed, `get` returns `null` instead of throwing.
|
|
16
|
+
*/
|
|
17
|
+
export declare class StorageLib {
|
|
18
|
+
/**
|
|
19
|
+
* Stores a JSON-serializable value under a key.
|
|
20
|
+
*/
|
|
21
|
+
static set<T>(key: string, value: T): void;
|
|
22
|
+
/**
|
|
23
|
+
* Reads and parses a value from localStorage.
|
|
24
|
+
*
|
|
25
|
+
* @returns The parsed value or `null` when the key is missing or invalid JSON.
|
|
26
|
+
*/
|
|
27
|
+
static get<T>(key: string): T | null;
|
|
28
|
+
/**
|
|
29
|
+
* Removes a single key from localStorage.
|
|
30
|
+
*/
|
|
31
|
+
static remove(key: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Clears the complete browser localStorage for the current origin.
|
|
34
|
+
*
|
|
35
|
+
* Be careful: this affects all keys on the same domain, not only samengine
|
|
36
|
+
* keys.
|
|
37
|
+
*/
|
|
38
|
+
static clear(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Checks whether a key exists in localStorage.
|
|
41
|
+
*/
|
|
42
|
+
static has(key: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Exports the complete localStorage content as a JSON string.
|
|
45
|
+
*
|
|
46
|
+
* Existing JSON values are parsed back into objects; non-JSON values are kept
|
|
47
|
+
* as strings.
|
|
48
|
+
*/
|
|
49
|
+
static exportToJson(pretty?: boolean): string;
|
|
50
|
+
/**
|
|
51
|
+
* Imports a JSON object into localStorage.
|
|
52
|
+
*
|
|
53
|
+
* @param json JSON object string, usually created by `exportToJson`.
|
|
54
|
+
* @param overwrite If false, existing keys are preserved.
|
|
55
|
+
*/
|
|
56
|
+
static importFromJson(json: string, overwrite?: boolean): void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small typed wrapper around `localStorage`.
|
|
3
|
+
*
|
|
4
|
+
* Values are serialized with `JSON.stringify` and read with `JSON.parse`. If a
|
|
5
|
+
* value cannot be parsed, `get` returns `null` instead of throwing.
|
|
6
|
+
*/
|
|
7
|
+
export class StorageLib {
|
|
8
|
+
/**
|
|
9
|
+
* Stores a JSON-serializable value under a key.
|
|
10
|
+
*/
|
|
11
|
+
static set(key, value) {
|
|
12
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads and parses a value from localStorage.
|
|
16
|
+
*
|
|
17
|
+
* @returns The parsed value or `null` when the key is missing or invalid JSON.
|
|
18
|
+
*/
|
|
19
|
+
static get(key) {
|
|
20
|
+
const item = localStorage.getItem(key);
|
|
21
|
+
if (!item) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(item);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Removes a single key from localStorage.
|
|
33
|
+
*/
|
|
34
|
+
static remove(key) {
|
|
35
|
+
localStorage.removeItem(key);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Clears the complete browser localStorage for the current origin.
|
|
39
|
+
*
|
|
40
|
+
* Be careful: this affects all keys on the same domain, not only samengine
|
|
41
|
+
* keys.
|
|
42
|
+
*/
|
|
43
|
+
static clear() {
|
|
44
|
+
localStorage.clear();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Checks whether a key exists in localStorage.
|
|
48
|
+
*/
|
|
49
|
+
static has(key) {
|
|
50
|
+
return localStorage.getItem(key) !== null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Exports the complete localStorage content as a JSON string.
|
|
54
|
+
*
|
|
55
|
+
* Existing JSON values are parsed back into objects; non-JSON values are kept
|
|
56
|
+
* as strings.
|
|
57
|
+
*/
|
|
58
|
+
static exportToJson(pretty = true) {
|
|
59
|
+
const data = {};
|
|
60
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
61
|
+
const key = localStorage.key(i);
|
|
62
|
+
if (!key)
|
|
63
|
+
continue;
|
|
64
|
+
const value = localStorage.getItem(key);
|
|
65
|
+
try {
|
|
66
|
+
data[key] = value ? JSON.parse(value) : null;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
data[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return JSON.stringify(data, null, pretty ? 2 : 0);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Imports a JSON object into localStorage.
|
|
76
|
+
*
|
|
77
|
+
* @param json JSON object string, usually created by `exportToJson`.
|
|
78
|
+
* @param overwrite If false, existing keys are preserved.
|
|
79
|
+
*/
|
|
80
|
+
static importFromJson(json, overwrite = true) {
|
|
81
|
+
const data = JSON.parse(json);
|
|
82
|
+
for (const [key, value] of Object.entries(data)) {
|
|
83
|
+
if (!overwrite && localStorage.getItem(key) !== null) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Rect } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* For Text Input: Use canvasinput-ts
|
|
4
|
+
*/
|
|
5
|
+
export interface TextStyle {
|
|
6
|
+
color: string;
|
|
7
|
+
font: string;
|
|
8
|
+
size: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Rendert Text innerhalb eines Rechtecks mit automatischem Umbruch.
|
|
12
|
+
* Lange Wörter werden ebenfalls umgebrochen.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderTextBox(ctx: CanvasRenderingContext2D, rect: Rect, text: string, style: TextStyle, padding?: number): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendert Text innerhalb eines Rechtecks mit automatischem Umbruch.
|
|
3
|
+
* Lange Wörter werden ebenfalls umgebrochen.
|
|
4
|
+
*/
|
|
5
|
+
export function renderTextBox(ctx, rect, text, style, padding = 4) {
|
|
6
|
+
ctx.fillStyle = style.color;
|
|
7
|
+
ctx.font = `${style.size}px ${style.font}`;
|
|
8
|
+
ctx.textBaseline = "top";
|
|
9
|
+
const maxWidth = rect.width - padding * 2;
|
|
10
|
+
const lineHeight = style.size * 1.2;
|
|
11
|
+
let y = rect.y + padding;
|
|
12
|
+
const paragraphs = text.split("\n");
|
|
13
|
+
for (const paragraph of paragraphs) {
|
|
14
|
+
let line = "";
|
|
15
|
+
const words = paragraph.split(" ");
|
|
16
|
+
for (let word of words) {
|
|
17
|
+
// Wort zu lang → innerhalb des Wortes umbrechen
|
|
18
|
+
while (ctx.measureText(word).width > maxWidth) {
|
|
19
|
+
let chunk = "";
|
|
20
|
+
for (const char of word) {
|
|
21
|
+
const test = chunk + char;
|
|
22
|
+
if (ctx.measureText(test).width > maxWidth) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
chunk = test;
|
|
26
|
+
}
|
|
27
|
+
// Höhenbegrenzung
|
|
28
|
+
if (y + lineHeight > rect.y + rect.height) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
ctx.fillText(chunk, rect.x + padding, y);
|
|
32
|
+
y += lineHeight;
|
|
33
|
+
word = word.substring(chunk.length);
|
|
34
|
+
}
|
|
35
|
+
const testLine = line.length > 0
|
|
36
|
+
? `${line} ${word}`
|
|
37
|
+
: word;
|
|
38
|
+
if (ctx.measureText(testLine).width > maxWidth) {
|
|
39
|
+
if (y + lineHeight > rect.y + rect.height) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
ctx.fillText(line, rect.x + padding, y);
|
|
43
|
+
y += lineHeight;
|
|
44
|
+
line = word;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
line = testLine;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (line.length > 0) {
|
|
51
|
+
if (y + lineHeight > rect.y + rect.height) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
ctx.fillText(line, rect.x + padding, y);
|
|
55
|
+
y += lineHeight;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|