@novely/core 0.34.0 → 0.36.0-beta.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.js CHANGED
@@ -47,6 +47,7 @@ var MAIN_CONTEXT_KEY = "$MAIN";
47
47
 
48
48
  // src/shared.ts
49
49
  var STACK_MAP = /* @__PURE__ */ new Map();
50
+ var CUSTOM_ACTION_MAP = /* @__PURE__ */ new Map();
50
51
  var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
51
52
 
52
53
  // src/utils.ts
@@ -81,9 +82,16 @@ function klona(val) {
81
82
  }
82
83
 
83
84
  // src/utils.ts
84
- var matchAction = ({ getContext, push, forward }, values) => {
85
+ var matchAction = ({ getContext, onBeforeActionCall, push, forward }, values) => {
85
86
  return (action, props, { ctx, data }) => {
86
87
  const context = typeof ctx === "string" ? getContext(ctx) : ctx;
88
+ if (action !== "say") {
89
+ onBeforeActionCall({
90
+ action,
91
+ props,
92
+ ctx: context
93
+ });
94
+ }
87
95
  return values[action]({
88
96
  ctx: context,
89
97
  data,
@@ -248,7 +256,7 @@ var getOppositeAction = (action) => {
248
256
  var getActionsFromPath = (story, path, filter) => {
249
257
  let current = story;
250
258
  let precurrent;
251
- let ignoreNested = false;
259
+ let ignoreNestedBefore = null;
252
260
  let index = 0;
253
261
  let skipPreserve = void 0;
254
262
  const skip = /* @__PURE__ */ new Set();
@@ -269,11 +277,11 @@ var getActionsFromPath = (story, path, filter) => {
269
277
  if (isNumber(val)) {
270
278
  index++;
271
279
  let startIndex = 0;
272
- if (ignoreNested) {
273
- const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
280
+ if (ignoreNestedBefore) {
281
+ const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), ignoreNestedBefore);
274
282
  if (prev) {
275
283
  startIndex = prev[1];
276
- ignoreNested = false;
284
+ ignoreNestedBefore = null;
277
285
  }
278
286
  }
279
287
  for (let i = startIndex; i <= val; i++) {
@@ -308,7 +316,7 @@ var getActionsFromPath = (story, path, filter) => {
308
316
  current = story[val];
309
317
  } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
310
318
  current = blocks.pop();
311
- ignoreNested = true;
319
+ ignoreNestedBefore = type.slice(0, -5);
312
320
  }
313
321
  }
314
322
  return {
@@ -492,6 +500,9 @@ var fetchContentType = async (request, url) => {
492
500
  }
493
501
  };
494
502
  var getResourseType = async (request, url) => {
503
+ if (!isCSSImage(url)) {
504
+ return "other";
505
+ }
495
506
  const extension = getUrlFileExtension(url);
496
507
  if (HOWLER_SUPPORTED_FILE_FORMATS.has(extension)) {
497
508
  return "audio";
@@ -605,6 +616,68 @@ var nextPath = (path) => {
605
616
  }
606
617
  return path;
607
618
  };
619
+ var isBlockingAction = (action) => {
620
+ return isUserRequiredAction(action) || isSkippedDuringRestore(action[0]) && action[0] !== "vibrate";
621
+ };
622
+ var collectActionsBeforeBlockingAction = ({ path, refer }) => {
623
+ const collection = [];
624
+ let action = refer(path);
625
+ while (true) {
626
+ if (action == void 0) {
627
+ const { exitImpossible } = exitPath({
628
+ path,
629
+ refer
630
+ });
631
+ if (exitImpossible) {
632
+ break;
633
+ }
634
+ }
635
+ if (!action) {
636
+ break;
637
+ }
638
+ if (isBlockingAction(action)) {
639
+ const [name, ...props] = action;
640
+ if (name === "choice") {
641
+ const choiceProps = props;
642
+ for (let i = 0; i < choiceProps.length; i++) {
643
+ const branchContent = choiceProps[i];
644
+ if (!Array.isArray(branchContent))
645
+ continue;
646
+ const virtualPath = klona(path);
647
+ virtualPath.push(["choice", i], [null, 0]);
648
+ const innerActions = collectActionsBeforeBlockingAction({
649
+ path: virtualPath,
650
+ refer
651
+ });
652
+ collection.push(...innerActions);
653
+ }
654
+ } else if (name === "condition") {
655
+ const conditionProps = props;
656
+ const conditions = Object.keys(conditionProps[1]);
657
+ for (const condition of conditions) {
658
+ const virtualPath = klona(path);
659
+ virtualPath.push(["condition", condition], [null, 0]);
660
+ const innerActions = collectActionsBeforeBlockingAction({
661
+ path: virtualPath,
662
+ refer
663
+ });
664
+ collection.push(...innerActions);
665
+ }
666
+ }
667
+ break;
668
+ }
669
+ collection.push(action);
670
+ if (action[0] === "jump") {
671
+ path = [["jump", action[1]], [null, 0]];
672
+ } else if (action[0] == "block") {
673
+ path.push(["block", action[1]], [null, 0]);
674
+ } else {
675
+ nextPath(path);
676
+ }
677
+ action = refer(path);
678
+ }
679
+ return collection;
680
+ };
608
681
 
609
682
  // src/novely.ts
610
683
  import { dequal } from "dequal/lite";
@@ -751,6 +824,76 @@ var replace = (input, data, pluralization, actions, pr) => {
751
824
  });
752
825
  };
753
826
 
827
+ // src/custom-action.ts
828
+ var createCustomActionNode = (id) => {
829
+ const div = document.createElement("div");
830
+ div.setAttribute("data-id", id);
831
+ return div;
832
+ };
833
+ var getCustomActionHolder = (ctx, fn) => {
834
+ const cached = CUSTOM_ACTION_MAP.get(ctx.id + fn.key);
835
+ if (cached) {
836
+ return cached;
837
+ }
838
+ const holder = {
839
+ cleanup: noop,
840
+ node: null,
841
+ fn,
842
+ localData: {}
843
+ };
844
+ CUSTOM_ACTION_MAP.set(ctx.id + fn.key, holder);
845
+ return holder;
846
+ };
847
+ var handleCustomAction = (ctx, fn, { lang, state, setMountElement, setClear, remove: renderersRemove }) => {
848
+ const holder = getCustomActionHolder(ctx, fn);
849
+ const flags = {
850
+ ...ctx.meta
851
+ };
852
+ const getDomNodes = (insert = true) => {
853
+ if (holder.node || !insert) {
854
+ return {
855
+ element: holder.node,
856
+ root: ctx.root
857
+ };
858
+ }
859
+ holder.node = insert ? createCustomActionNode(fn.key) : null;
860
+ setMountElement(holder.node);
861
+ return {
862
+ element: holder.node,
863
+ root: ctx.root
864
+ };
865
+ };
866
+ const clear = (func) => {
867
+ setClear(holder.cleanup = () => {
868
+ func();
869
+ holder.node = null;
870
+ holder.cleanup = noop;
871
+ setMountElement(null);
872
+ setClear(noop);
873
+ });
874
+ };
875
+ const data = (updatedData) => {
876
+ if (updatedData) {
877
+ return holder.localData = updatedData;
878
+ }
879
+ return holder.localData;
880
+ };
881
+ const remove = () => {
882
+ holder.cleanup();
883
+ renderersRemove();
884
+ };
885
+ return fn({
886
+ flags,
887
+ lang,
888
+ state,
889
+ data,
890
+ clear,
891
+ remove,
892
+ rendererContext: ctx,
893
+ getDomNodes
894
+ });
895
+ };
896
+
754
897
  // src/storage.ts
755
898
  var localStorageStorage = (options) => {
756
899
  return {
@@ -825,6 +968,26 @@ var novely = ({
825
968
  const intime = (value) => {
826
969
  return times.add(value), value;
827
970
  };
971
+ const handleAssetsPreloading = async () => {
972
+ const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
973
+ const list = mapSet(ASSETS_TO_PRELOAD, (asset) => {
974
+ return limitAssetsDownload(async () => {
975
+ const type = await getResourseType(request, asset);
976
+ switch (type) {
977
+ case "audio": {
978
+ await preloadAudioBlocking(asset);
979
+ break;
980
+ }
981
+ case "image": {
982
+ await preloadImageBlocking(asset);
983
+ break;
984
+ }
985
+ }
986
+ });
987
+ });
988
+ await Promise.allSettled(list);
989
+ ASSETS_TO_PRELOAD.clear();
990
+ };
828
991
  const scriptBase = async (part) => {
829
992
  Object.assign(story, flattenStory(part));
830
993
  let loadingIsShown = false;
@@ -836,31 +999,14 @@ var novely = ({
836
999
  if (!loadingIsShown) {
837
1000
  renderer.ui.showLoading();
838
1001
  }
839
- const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
840
- const list = mapSet(ASSETS_TO_PRELOAD, (asset) => {
841
- return limitAssetsDownload(async () => {
842
- const type = await getResourseType(request, asset);
843
- switch (type) {
844
- case "audio": {
845
- await preloadAudioBlocking(asset);
846
- break;
847
- }
848
- case "image": {
849
- await preloadImageBlocking(asset);
850
- break;
851
- }
852
- }
853
- });
854
- });
855
- await Promise.allSettled(list);
1002
+ await handleAssetsPreloading();
856
1003
  }
857
- ASSETS_TO_PRELOAD.clear();
858
1004
  await dataLoaded.promise;
859
1005
  renderer.ui.hideLoading();
860
1006
  if (!initialScreenWasShown) {
861
1007
  initialScreenWasShown = true;
862
1008
  if (initialScreen === "game") {
863
- restore();
1009
+ restore(void 0);
864
1010
  } else {
865
1011
  renderer.ui.showScreen(initialScreen);
866
1012
  }
@@ -869,6 +1015,33 @@ var novely = ({
869
1015
  const script = (part) => {
870
1016
  return limitScript(() => scriptBase(part));
871
1017
  };
1018
+ const checkAndAddToPreloadingList = (action2, props) => {
1019
+ if (action2 === "showBackground") {
1020
+ if (isImageAsset(props[0])) {
1021
+ ASSETS_TO_PRELOAD.add(props[0]);
1022
+ }
1023
+ if (props[0] && typeof props[0] === "object") {
1024
+ for (const value of Object.values(props[0])) {
1025
+ if (!isImageAsset(value))
1026
+ continue;
1027
+ ASSETS_TO_PRELOAD.add(value);
1028
+ }
1029
+ }
1030
+ }
1031
+ if (isAudioAction(action2) && isString(props[0])) {
1032
+ ASSETS_TO_PRELOAD.add(props[0]);
1033
+ }
1034
+ if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
1035
+ const images = characters[props[0]].emotions[props[1]];
1036
+ if (Array.isArray(images)) {
1037
+ for (const asset of images) {
1038
+ ASSETS_TO_PRELOAD.add(asset);
1039
+ }
1040
+ } else {
1041
+ ASSETS_TO_PRELOAD.add(images);
1042
+ }
1043
+ }
1044
+ };
872
1045
  const action = new Proxy({}, {
873
1046
  get(_, action2) {
874
1047
  if (action2 in renderer.actions) {
@@ -876,31 +1049,7 @@ var novely = ({
876
1049
  }
877
1050
  return (...props) => {
878
1051
  if (preloadAssets === "blocking") {
879
- if (action2 === "showBackground") {
880
- if (isImageAsset(props[0])) {
881
- ASSETS_TO_PRELOAD.add(props[0]);
882
- }
883
- if (props[0] && typeof props[0] === "object") {
884
- for (const value of Object.values(props[0])) {
885
- if (!isImageAsset(value))
886
- continue;
887
- ASSETS_TO_PRELOAD.add(value);
888
- }
889
- }
890
- }
891
- if (isAudioAction(action2) && isString(props[0])) {
892
- ASSETS_TO_PRELOAD.add(props[0]);
893
- }
894
- if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
895
- const images = characters[props[0]].emotions[props[1]];
896
- if (Array.isArray(images)) {
897
- for (const asset of images) {
898
- ASSETS_TO_PRELOAD.add(asset);
899
- }
900
- } else {
901
- ASSETS_TO_PRELOAD.add(images);
902
- }
903
- }
1052
+ checkAndAddToPreloadingList(action2, props);
904
1053
  }
905
1054
  return [action2, ...props];
906
1055
  };
@@ -1027,7 +1176,12 @@ var novely = ({
1027
1176
  return prev.saves.push(save2), prev;
1028
1177
  });
1029
1178
  }
1030
- restore(save2);
1179
+ const context = renderer.getContext(MAIN_CONTEXT_KEY);
1180
+ const stack = useStack(context);
1181
+ stack.value = save2;
1182
+ context.meta.restoring = context.meta.goingBack = false;
1183
+ renderer.ui.showScreen("game");
1184
+ render(context);
1031
1185
  };
1032
1186
  const set = (save2, ctx) => {
1033
1187
  const stack = useStack(ctx || MAIN_CONTEXT_KEY);
@@ -1072,7 +1226,7 @@ var novely = ({
1072
1226
  }
1073
1227
  const [action2, fn] = element;
1074
1228
  if (action2 === "custom") {
1075
- context.clearCustom(fn);
1229
+ getCustomActionHolder(context, fn).cleanup();
1076
1230
  }
1077
1231
  }
1078
1232
  }
@@ -1082,8 +1236,8 @@ var novely = ({
1082
1236
  data: latest[1]
1083
1237
  });
1084
1238
  }
1085
- const lastQueueItem = queue.at(-1) || [];
1086
- const lastQueueItemRequiresUserAction = isSkippedDuringRestore(lastQueueItem[0]) || isUserRequiredAction(lastQueueItem);
1239
+ const lastQueueItem = queue.at(-1);
1240
+ const lastQueueItemRequiresUserAction = lastQueueItem && isBlockingAction(lastQueueItem);
1087
1241
  await run((item) => {
1088
1242
  if (!latest)
1089
1243
  return;
@@ -1197,6 +1351,9 @@ var novely = ({
1197
1351
  }
1198
1352
  return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1199
1353
  };
1354
+ const clearCustomAction = (ctx, fn) => {
1355
+ getCustomActionHolder(ctx, fn).cleanup();
1356
+ };
1200
1357
  const renderer = createRenderer({
1201
1358
  mainContextKey: MAIN_CONTEXT_KEY,
1202
1359
  characters,
@@ -1210,6 +1367,7 @@ var novely = ({
1210
1367
  preview,
1211
1368
  removeContext,
1212
1369
  getStateFunction,
1370
+ clearCustomAction,
1213
1371
  languages,
1214
1372
  storageData,
1215
1373
  coreData,
@@ -1246,6 +1404,40 @@ var novely = ({
1246
1404
  matchActionOptions.push(ctx);
1247
1405
  if (!ctx.meta.preview)
1248
1406
  interactivity(true);
1407
+ },
1408
+ onBeforeActionCall({ action: action2, props, ctx }) {
1409
+ if (preloadAssets !== "automatic")
1410
+ return;
1411
+ if (ctx.meta.preview || ctx.meta.restoring)
1412
+ return;
1413
+ if (!isBlockingAction([action2, ...props]))
1414
+ return;
1415
+ try {
1416
+ const collection = collectActionsBeforeBlockingAction({
1417
+ path: nextPath(klona(useStack(ctx).value[0])),
1418
+ refer
1419
+ });
1420
+ for (const [action3, ...props2] of collection) {
1421
+ checkAndAddToPreloadingList(action3, props2);
1422
+ }
1423
+ const { preloadAudioBlocking, preloadImage } = renderer.misc;
1424
+ ASSETS_TO_PRELOAD.forEach(async (asset) => {
1425
+ ASSETS_TO_PRELOAD.delete(asset);
1426
+ const type = await getResourseType(request, asset);
1427
+ switch (type) {
1428
+ case "audio": {
1429
+ preloadAudioBlocking(asset);
1430
+ break;
1431
+ }
1432
+ case "image": {
1433
+ preloadImage(asset);
1434
+ break;
1435
+ }
1436
+ }
1437
+ });
1438
+ } catch (cause) {
1439
+ console.error(cause);
1440
+ }
1249
1441
  }
1250
1442
  };
1251
1443
  const match = matchAction(matchActionOptions, {
@@ -1453,19 +1645,31 @@ var novely = ({
1453
1645
  forward
1454
1646
  );
1455
1647
  },
1456
- custom({ ctx, push }, [handler]) {
1457
- if (handler.requireUserAction) {
1648
+ custom({ ctx, push }, [fn]) {
1649
+ if (fn.requireUserAction) {
1458
1650
  ctx.clearBlockingActions(void 0);
1459
1651
  }
1460
- const result = ctx.custom(handler, () => {
1461
- if (ctx.meta.restoring)
1462
- return;
1463
- if (handler.requireUserAction && !ctx.meta.preview) {
1652
+ const state = getStateFunction(ctx);
1653
+ const lang = storageData.get().meta[0];
1654
+ const result = handleCustomAction(ctx, fn, {
1655
+ ...ctx.custom(fn),
1656
+ state,
1657
+ lang
1658
+ });
1659
+ const next2 = () => {
1660
+ if (fn.requireUserAction && !ctx.meta.preview) {
1464
1661
  enmemory(ctx);
1465
1662
  interactivity(true);
1466
1663
  }
1467
1664
  push();
1468
- });
1665
+ };
1666
+ if (!ctx.meta.restoring) {
1667
+ if (isPromise(result)) {
1668
+ result.then(next2);
1669
+ } else {
1670
+ next2();
1671
+ }
1672
+ }
1469
1673
  return result;
1470
1674
  },
1471
1675
  vibrate({ ctx, push }, pattern) {
@@ -1515,6 +1719,9 @@ var novely = ({
1515
1719
  render(ctx);
1516
1720
  },
1517
1721
  preload({ ctx, push }, [source]) {
1722
+ if (DEV2 && preloadAssets !== "lazy") {
1723
+ console.error(`You do not need a preload action becase "preloadAssets" strategy was set to "${preloadAssets}"`);
1724
+ }
1518
1725
  if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
1519
1726
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
1520
1727
  }