@novely/core 0.22.2 → 0.23.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/dist/index.d.ts +19 -10
- package/dist/index.global.js +134 -43
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +147 -53
- package/dist/index.js.map +1 -1
- package/package.json +62 -62
package/dist/index.d.ts
CHANGED
|
@@ -214,6 +214,7 @@ interface CharacterHandle {
|
|
|
214
214
|
emotion: (emotion: string, render: boolean) => void;
|
|
215
215
|
append: (className?: string, style?: string, restoring?: boolean) => void;
|
|
216
216
|
remove: (className?: string, style?: string, duration?: number, restoring?: boolean) => Promise<void>;
|
|
217
|
+
animate: (timeout: number, classes: string[]) => void;
|
|
217
218
|
emotions: Record<string, HTMLImageElement[]>;
|
|
218
219
|
}
|
|
219
220
|
type AudioHandle = {
|
|
@@ -223,23 +224,23 @@ type AudioHandle = {
|
|
|
223
224
|
};
|
|
224
225
|
type Renderer = {
|
|
225
226
|
misc: {
|
|
226
|
-
/**
|
|
227
|
-
* Function to preload images async and await for all images to load or fail
|
|
228
|
-
* @param images Set of images to load
|
|
229
|
-
*/
|
|
230
|
-
preloadImagesBlocking: (images: Set<string>) => Promise<PromiseSettledResult<unknown>[]>;
|
|
231
227
|
/**
|
|
232
228
|
* Function to preload image sync
|
|
233
229
|
* @param image Image URL
|
|
234
230
|
* @returns Image URL
|
|
235
231
|
*/
|
|
236
232
|
preloadImage: <T extends string>(image: T) => T;
|
|
233
|
+
/**
|
|
234
|
+
* Function to preload image async
|
|
235
|
+
* @param image Image URL
|
|
236
|
+
* @returns Promise
|
|
237
|
+
*/
|
|
238
|
+
preloadImageBlocking: (image: string) => Promise<void>;
|
|
237
239
|
/**
|
|
238
240
|
* Function to preload audio
|
|
239
|
-
* @param type kind of audio
|
|
240
241
|
* @param source <url> pointing to the audio
|
|
241
242
|
*/
|
|
242
|
-
preloadAudioBlocking: (
|
|
243
|
+
preloadAudioBlocking: (source: string) => Promise<void>;
|
|
243
244
|
};
|
|
244
245
|
ui: {
|
|
245
246
|
/**
|
|
@@ -308,7 +309,6 @@ type Renderer = {
|
|
|
308
309
|
};
|
|
309
310
|
store: unknown;
|
|
310
311
|
setStore: unknown;
|
|
311
|
-
getCharacter: (character: string) => CharacterHandle | undefined;
|
|
312
312
|
};
|
|
313
313
|
removeContext: (context: string) => void;
|
|
314
314
|
};
|
|
@@ -418,6 +418,11 @@ interface NovelyInit<Languages extends string, Characters extends Record<string,
|
|
|
418
418
|
* @default 799
|
|
419
419
|
*/
|
|
420
420
|
throttleTimeout?: number;
|
|
421
|
+
/**
|
|
422
|
+
* Limits how many assets can be downloaded parallelly
|
|
423
|
+
* @default 15
|
|
424
|
+
*/
|
|
425
|
+
parallelAssetsDownloadLimit?: number;
|
|
421
426
|
/**
|
|
422
427
|
* Custom language detector
|
|
423
428
|
* @param languages Supported languages aka `languages: []` in the config
|
|
@@ -445,8 +450,12 @@ interface NovelyInit<Languages extends string, Characters extends Record<string,
|
|
|
445
450
|
* @default "lazy"
|
|
446
451
|
*/
|
|
447
452
|
preloadAssets?: 'lazy' | 'blocking';
|
|
453
|
+
/**
|
|
454
|
+
* Fetching function
|
|
455
|
+
*/
|
|
456
|
+
fetch?: typeof fetch;
|
|
448
457
|
}
|
|
449
|
-
declare const novely: <Languages extends string, Characters extends Record<string, Character<Languages>>, StateScheme extends State, DataScheme extends Data>({ characters, storage, storageDelay, renderer: createRenderer, initialScreen, translation, languages, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, }: NovelyInit<Languages, Characters, StateScheme, DataScheme>) => {
|
|
458
|
+
declare const novely: <Languages extends string, Characters extends Record<string, Character<Languages>>, StateScheme extends State, DataScheme extends Data>({ characters, storage, storageDelay, renderer: createRenderer, initialScreen, translation, languages, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request }: NovelyInit<Languages, Characters, StateScheme, DataScheme>) => {
|
|
450
459
|
/**
|
|
451
460
|
* Function to set game script
|
|
452
461
|
*/
|
|
@@ -472,7 +481,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
|
|
|
472
481
|
/**
|
|
473
482
|
* Unwraps translatable content to a string value
|
|
474
483
|
*/
|
|
475
|
-
unwrap(content: string | (() => string) |
|
|
484
|
+
unwrap(content: string | (() => string) | Exclude<Record<Languages, string | (() => string)>, Record<string, string>> | Record<Languages, string>): string;
|
|
476
485
|
/**
|
|
477
486
|
* Cancel data loading, hide UI, ignore page change events
|
|
478
487
|
* Data updates still will work in case Novely already was loaded
|
package/dist/index.global.js
CHANGED
|
@@ -174,11 +174,45 @@ var Novely = (() => {
|
|
|
174
174
|
]);
|
|
175
175
|
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
176
176
|
var DEFAULT_TYPEWRITER_SPEED = "Medium";
|
|
177
|
+
var HOWLER_SUPPORTED_FILE_FORMATS = /* @__PURE__ */ new Set([
|
|
178
|
+
"mp3",
|
|
179
|
+
"mpeg",
|
|
180
|
+
"opus",
|
|
181
|
+
"ogg",
|
|
182
|
+
"oga",
|
|
183
|
+
"wav",
|
|
184
|
+
"aac",
|
|
185
|
+
"caf",
|
|
186
|
+
"m4a",
|
|
187
|
+
"m4b",
|
|
188
|
+
"mp4",
|
|
189
|
+
"weba",
|
|
190
|
+
"webm",
|
|
191
|
+
"dolby",
|
|
192
|
+
"flac"
|
|
193
|
+
]);
|
|
194
|
+
var SUPPORTED_IMAGE_FILE_FORMATS = /* @__PURE__ */ new Set([
|
|
195
|
+
"apng",
|
|
196
|
+
"avif",
|
|
197
|
+
"gif",
|
|
198
|
+
"jpg",
|
|
199
|
+
"jpeg",
|
|
200
|
+
"jfif",
|
|
201
|
+
"pjpeg",
|
|
202
|
+
"pjp",
|
|
203
|
+
"png",
|
|
204
|
+
"svg",
|
|
205
|
+
"webp",
|
|
206
|
+
"bmp"
|
|
207
|
+
]);
|
|
177
208
|
var MAIN_CONTEXT_KEY = "$MAIN";
|
|
178
209
|
|
|
179
210
|
// src/shared.ts
|
|
180
211
|
var STACK_MAP = /* @__PURE__ */ new Map();
|
|
181
212
|
|
|
213
|
+
// ../../node_modules/.pnpm/esm-env@1.0.0/node_modules/esm-env/prod-ssr.js
|
|
214
|
+
var DEV = false;
|
|
215
|
+
|
|
182
216
|
// src/utils.ts
|
|
183
217
|
var matchAction = ({ getContext, push, forward }, values) => {
|
|
184
218
|
return (action, props, { ctx, data }) => {
|
|
@@ -187,9 +221,13 @@ var Novely = (() => {
|
|
|
187
221
|
ctx: context,
|
|
188
222
|
data,
|
|
189
223
|
push() {
|
|
224
|
+
if (context.meta.preview)
|
|
225
|
+
return;
|
|
190
226
|
push(context);
|
|
191
227
|
},
|
|
192
228
|
forward() {
|
|
229
|
+
if (context.meta.preview)
|
|
230
|
+
return;
|
|
193
231
|
forward(context);
|
|
194
232
|
}
|
|
195
233
|
}, props);
|
|
@@ -298,6 +336,9 @@ var Novely = (() => {
|
|
|
298
336
|
var isSkippedDuringRestore = (item) => {
|
|
299
337
|
return SKIPPED_DURING_RESTORE.has(item);
|
|
300
338
|
};
|
|
339
|
+
var isAudioAction = (action) => {
|
|
340
|
+
return AUDIO_ACTIONS.has(action);
|
|
341
|
+
};
|
|
301
342
|
var noop = () => {
|
|
302
343
|
};
|
|
303
344
|
var isAction = (element) => {
|
|
@@ -520,6 +561,53 @@ var Novely = (() => {
|
|
|
520
561
|
};
|
|
521
562
|
return useStack;
|
|
522
563
|
};
|
|
564
|
+
var mapSet = (set, fn) => {
|
|
565
|
+
return [...set].map(fn);
|
|
566
|
+
};
|
|
567
|
+
var isImageAsset = (asset) => {
|
|
568
|
+
return isString(asset) && isCSSImage(asset);
|
|
569
|
+
};
|
|
570
|
+
var getUrlFileExtension = (address) => {
|
|
571
|
+
try {
|
|
572
|
+
const { pathname } = new URL(address, location.href);
|
|
573
|
+
return pathname.split(".").at(-1).split("!")[0].split(":")[0];
|
|
574
|
+
} catch (error) {
|
|
575
|
+
if (DEV) {
|
|
576
|
+
console.error(new Error(`Could not construct URL "${address}".`, { cause: error }));
|
|
577
|
+
}
|
|
578
|
+
return "";
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
var fetchContentType = async (request, url) => {
|
|
582
|
+
try {
|
|
583
|
+
const response = await request(url, {
|
|
584
|
+
method: "HEAD"
|
|
585
|
+
});
|
|
586
|
+
return response.headers.get("Content-Type") || "";
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (DEV) {
|
|
589
|
+
console.error(new Error(`Failed to fetch file at "${url}"`, { cause: error }));
|
|
590
|
+
}
|
|
591
|
+
return "";
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
var getResourseType = async (request, url) => {
|
|
595
|
+
const extension = getUrlFileExtension(url);
|
|
596
|
+
if (HOWLER_SUPPORTED_FILE_FORMATS.has(extension)) {
|
|
597
|
+
return "audio";
|
|
598
|
+
}
|
|
599
|
+
if (SUPPORTED_IMAGE_FILE_FORMATS.has(extension)) {
|
|
600
|
+
return "image";
|
|
601
|
+
}
|
|
602
|
+
const contentType = await fetchContentType(request, url);
|
|
603
|
+
if (contentType.includes("audio")) {
|
|
604
|
+
return "audio";
|
|
605
|
+
}
|
|
606
|
+
if (contentType.includes("image")) {
|
|
607
|
+
return "image";
|
|
608
|
+
}
|
|
609
|
+
return "other";
|
|
610
|
+
};
|
|
523
611
|
|
|
524
612
|
// src/global.ts
|
|
525
613
|
var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
|
|
@@ -715,11 +803,6 @@ var Novely = (() => {
|
|
|
715
803
|
|
|
716
804
|
// src/novely.ts
|
|
717
805
|
var import_p_limit = __toESM(require_p_limit(), 1);
|
|
718
|
-
|
|
719
|
-
// ../../node_modules/.pnpm/esm-env@1.0.0/node_modules/esm-env/prod-ssr.js
|
|
720
|
-
var DEV = false;
|
|
721
|
-
|
|
722
|
-
// src/novely.ts
|
|
723
806
|
var novely = ({
|
|
724
807
|
characters,
|
|
725
808
|
storage = localStorageStorage({ key: "novely-game-storage" }),
|
|
@@ -736,9 +819,12 @@ var Novely = (() => {
|
|
|
736
819
|
getLanguage: getLanguage2 = getLanguage,
|
|
737
820
|
overrideLanguage = false,
|
|
738
821
|
askBeforeExit = true,
|
|
739
|
-
preloadAssets = "lazy"
|
|
822
|
+
preloadAssets = "lazy",
|
|
823
|
+
parallelAssetsDownloadLimit = 15,
|
|
824
|
+
fetch: request = fetch
|
|
740
825
|
}) => {
|
|
741
826
|
const limitScript = (0, import_p_limit.default)(1);
|
|
827
|
+
const limitAssetsDownload = (0, import_p_limit.default)(parallelAssetsDownloadLimit);
|
|
742
828
|
const story = {};
|
|
743
829
|
const times = /* @__PURE__ */ new Set();
|
|
744
830
|
const ASSETS_TO_PRELOAD = /* @__PURE__ */ new Set();
|
|
@@ -754,7 +840,23 @@ var Novely = (() => {
|
|
|
754
840
|
Object.assign(story, flattenStory(part));
|
|
755
841
|
if (preloadAssets === "blocking" && ASSETS_TO_PRELOAD.size > 0) {
|
|
756
842
|
renderer.ui.showScreen("loading");
|
|
757
|
-
|
|
843
|
+
const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
|
|
844
|
+
const list = mapSet(ASSETS_TO_PRELOAD, (asset) => {
|
|
845
|
+
return limitAssetsDownload(async () => {
|
|
846
|
+
const type = await getResourseType(request, asset);
|
|
847
|
+
switch (type) {
|
|
848
|
+
case "audio": {
|
|
849
|
+
await preloadAudioBlocking(asset);
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
case "image": {
|
|
853
|
+
await preloadImageBlocking(asset);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
await Promise.allSettled(list);
|
|
758
860
|
}
|
|
759
861
|
const screen = renderer.ui.getScreen();
|
|
760
862
|
const nextScreen = scriptCalled ? screen : initialScreen;
|
|
@@ -775,13 +877,25 @@ var Novely = (() => {
|
|
|
775
877
|
return limitScript(() => scriptBase(part));
|
|
776
878
|
};
|
|
777
879
|
const action = new Proxy({}, {
|
|
778
|
-
get(_,
|
|
880
|
+
get(_, action2) {
|
|
779
881
|
return (...props) => {
|
|
780
882
|
if (preloadAssets === "blocking") {
|
|
781
|
-
if (
|
|
883
|
+
if (action2 === "showBackground") {
|
|
884
|
+
if (isImageAsset(props[0])) {
|
|
885
|
+
ASSETS_TO_PRELOAD.add(props[0]);
|
|
886
|
+
}
|
|
887
|
+
if (props[0] && typeof props[0] === "object") {
|
|
888
|
+
for (const value of Object.values(props[0])) {
|
|
889
|
+
if (!isImageAsset(value))
|
|
890
|
+
continue;
|
|
891
|
+
ASSETS_TO_PRELOAD.add(value);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (isAudioAction(action2) && isString(props[0])) {
|
|
782
896
|
ASSETS_TO_PRELOAD.add(props[0]);
|
|
783
897
|
}
|
|
784
|
-
if (
|
|
898
|
+
if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
|
|
785
899
|
const images = characters[props[0]].emotions[props[1]];
|
|
786
900
|
if (Array.isArray(images)) {
|
|
787
901
|
for (const asset of images) {
|
|
@@ -792,7 +906,7 @@ var Novely = (() => {
|
|
|
792
906
|
}
|
|
793
907
|
}
|
|
794
908
|
}
|
|
795
|
-
return [
|
|
909
|
+
return [action2, ...props];
|
|
796
910
|
};
|
|
797
911
|
}
|
|
798
912
|
});
|
|
@@ -912,12 +1026,11 @@ var Novely = (() => {
|
|
|
912
1026
|
return;
|
|
913
1027
|
let latest = save2 || $.get().saves.at(-1);
|
|
914
1028
|
if (!latest) {
|
|
915
|
-
$.set({
|
|
916
|
-
saves: [initial],
|
|
917
|
-
data: klona(defaultData),
|
|
918
|
-
meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
|
|
919
|
-
});
|
|
920
1029
|
latest = klona(initial);
|
|
1030
|
+
$.update((prev) => {
|
|
1031
|
+
prev.saves.push(latest);
|
|
1032
|
+
return prev;
|
|
1033
|
+
});
|
|
921
1034
|
}
|
|
922
1035
|
const context = renderer.getContext(MAIN_CONTEXT_KEY);
|
|
923
1036
|
const stack = useStack(context);
|
|
@@ -1021,7 +1134,7 @@ var Novely = (() => {
|
|
|
1021
1134
|
ctx.meta.preview = true;
|
|
1022
1135
|
const processor = createQueueProcessor(queue);
|
|
1023
1136
|
await processor.run((action2, props) => {
|
|
1024
|
-
if (
|
|
1137
|
+
if (isAudioAction(action2))
|
|
1025
1138
|
return;
|
|
1026
1139
|
if (action2 === "vibrate")
|
|
1027
1140
|
return;
|
|
@@ -1272,10 +1385,10 @@ var Novely = (() => {
|
|
|
1272
1385
|
ctx.vibrate(pattern);
|
|
1273
1386
|
push();
|
|
1274
1387
|
},
|
|
1275
|
-
next({
|
|
1388
|
+
next({ push }) {
|
|
1276
1389
|
push();
|
|
1277
1390
|
},
|
|
1278
|
-
animateCharacter({ ctx,
|
|
1391
|
+
animateCharacter({ ctx, push }, [character, timeout, ...classes]) {
|
|
1279
1392
|
if (DEV && classes.length === 0) {
|
|
1280
1393
|
throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
|
|
1281
1394
|
}
|
|
@@ -1284,30 +1397,8 @@ var Novely = (() => {
|
|
|
1284
1397
|
}
|
|
1285
1398
|
if (ctx.meta.preview)
|
|
1286
1399
|
return;
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
const char = ctx.getCharacter(character);
|
|
1290
|
-
if (!char)
|
|
1291
|
-
return;
|
|
1292
|
-
const target = char.canvas;
|
|
1293
|
-
if (!target)
|
|
1294
|
-
return;
|
|
1295
|
-
const classNames = classes.filter((className) => !target.classList.contains(className));
|
|
1296
|
-
target.classList.add(...classNames);
|
|
1297
|
-
const removeClassNames = () => {
|
|
1298
|
-
target.classList.remove(...classNames);
|
|
1299
|
-
};
|
|
1300
|
-
const timeoutId = setTimeout(removeClassNames, timeout);
|
|
1301
|
-
clear(() => {
|
|
1302
|
-
removeClassNames();
|
|
1303
|
-
clearTimeout(timeoutId);
|
|
1304
|
-
});
|
|
1305
|
-
};
|
|
1306
|
-
handler.key = "@@internal-animate-character";
|
|
1307
|
-
match("custom", [handler], {
|
|
1308
|
-
ctx,
|
|
1309
|
-
data: data2
|
|
1310
|
-
});
|
|
1400
|
+
ctx.character(character).animate(timeout, classes);
|
|
1401
|
+
push();
|
|
1311
1402
|
},
|
|
1312
1403
|
text({ ctx, data: data2, forward }, text) {
|
|
1313
1404
|
const string = text.map((content) => unwrap2(content, data2)).join(" ");
|