narraleaf-react 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
- ![](./docs/nlr-logo-banner.png)
1
+ ![](docs/nlr-logo-banner.png)
2
2
 
3
3
  # NarraLeaf-React
4
4
 
5
- English | [简体中文](./docs/README.zh-CN.md)
5
+ English | [简体中文](docs/README.zh-CN.md)
6
6
 
7
7
  A React visual novel player framework
8
8
 
@@ -34,7 +34,7 @@ Read more in [🛠React.NarraLeaf.com](https://react.narraleaf.com)
34
34
  ### Example
35
35
 
36
36
  ```bash
37
- npx create-react-app my-first-narraleaf-app --template my-first-narraleaf-app
37
+ npx create-react-app nlr-app --template my-first-narraleaf-app
38
38
  ```
39
39
 
40
40
  to start
@@ -43,39 +43,11 @@ to start
43
43
  npm start
44
44
  ```
45
45
 
46
- ### Performance
47
-
48
- Please enable image cache for a better performance.
49
- Narraleaf-React tries to cache the images before showing them, but it is recommended to enable cache on your server.
50
-
51
- for NextJS, add this to your `next.config.js`:
52
-
53
- ```js
54
- /** @type {import('next').NextConfig} */
55
- const nextConfig = {
56
- async headers() {
57
- return [
58
- {
59
- source: '/YOUR_IMAGE_ENDPOINT/(.*)', // ex: /static/images/(.*)
60
- headers: [
61
- {
62
- key: 'Cache-Control',
63
- value: 'public, max-age=31536000, immutable',
64
- },
65
- ],
66
- }
67
- ]
68
- }
69
- };
70
-
71
- export default nextConfig;
72
- ```
73
-
74
46
  ## License
75
47
 
76
48
  > NarraLeaf-React is licensed under the MPL License.
77
- >
78
- > We updated the license to MPL on 2024-9-24.
49
+ >
50
+ > We updated the license to MPL on 2024-9-24.
79
51
 
80
52
  ## Contributing
81
53
 
@@ -11,6 +11,7 @@ import type { Sound } from "../elements/sound";
11
11
  import type { Script } from "../elements/script";
12
12
  import { Sentence } from "../elements/character/sentence";
13
13
  import type { TransformDefinitions } from "../elements/transform/type";
14
+ import { Image } from "../elements/image";
14
15
  export declare const CharacterActionTypes: {
15
16
  readonly say: "character:say";
16
17
  readonly action: "character:action";
@@ -56,9 +57,10 @@ export declare const ImageActionTypes: {
56
57
  readonly setTransition: "image:setTransition";
57
58
  readonly applyTransition: "image:applyTransition";
58
59
  readonly flush: "image:flush";
60
+ readonly initWearable: "image:initWearable";
59
61
  };
60
62
  export type ImageActionContentType = {
61
- [K in typeof ImageActionTypes[keyof typeof ImageActionTypes]]: K extends "image:setSrc" ? [string] : K extends "image:setPosition" ? [CommonDisplayable["position"], Transform<TransformDefinitions.ImageTransformProps>] : K extends "image:show" ? [void, Transform<TransformDefinitions.ImageTransformProps>] : K extends "image:hide" ? [void, Transform<TransformDefinitions.ImageTransformProps>] : K extends "image:applyTransform" ? [void, Transform<TransformDefinitions.ImageTransformProps>, string] : K extends "image:init" ? [Scene?] : K extends "image:dispose" ? [] : K extends "image:setTransition" ? [ITransition | null] : K extends "image:applyTransition" ? [ITransition] : K extends "image:flush" ? [] : any;
63
+ [K in typeof ImageActionTypes[keyof typeof ImageActionTypes]]: K extends "image:setSrc" ? [string] : K extends "image:setPosition" ? [CommonDisplayable["position"], Transform<TransformDefinitions.ImageTransformProps>] : K extends "image:show" ? [void, Transform<TransformDefinitions.ImageTransformProps>] : K extends "image:hide" ? [void, Transform<TransformDefinitions.ImageTransformProps>] : K extends "image:applyTransform" ? [void, Transform<TransformDefinitions.ImageTransformProps>, string] : K extends "image:init" ? [Scene?] : K extends "image:dispose" ? [] : K extends "image:setTransition" ? [ITransition | null] : K extends "image:applyTransition" ? [ITransition] : K extends "image:flush" ? [] : K extends "image:initWearable" ? [Image] : any;
62
64
  };
63
65
  export declare const ConditionActionTypes: {
64
66
  readonly action: "condition:action";
@@ -17,6 +17,7 @@ export declare class ImageAction<T extends typeof ImageActionTypes[keyof typeof
17
17
  readonly setTransition: "image:setTransition";
18
18
  readonly applyTransition: "image:applyTransition";
19
19
  readonly flush: "image:flush";
20
+ readonly initWearable: "image:initWearable";
20
21
  };
21
22
  executeAction(state: GameState): CalledActionResult | Awaitable<CalledActionResult, any>;
22
23
  }
@@ -2,8 +2,11 @@ import { ContentNode } from "../action/tree/actionTree";
2
2
  import { LogicAction } from "../action/logicAction";
3
3
  import { Action } from "../action/action";
4
4
  import { Chained, Proxied } from "../action/chain";
5
+ import { Awaitable } from "../../../util/data";
6
+ import { CalledActionResult } from "../gameTypes";
5
7
  export declare class TypedAction<ContentType extends Record<string, any> = Record<string, any>, T extends keyof ContentType & string = keyof ContentType & string, Callee extends LogicAction.GameElement = LogicAction.GameElement> extends Action<ContentType[T], Callee, T> {
6
8
  callee: Callee;
7
9
  constructor(callee: Proxied<Callee, Chained<LogicAction.Actions, Callee>>, type: T, contentNode: ContentNode<ContentType[T]>);
8
10
  unknownType(): void;
11
+ resolveAwaitable<T extends CalledActionResult = any>(handler: (resolve: ((value: T) => void), awaitable: Awaitable<CalledActionResult, T>) => Promise<void> | void, awaitable?: Awaitable<CalledActionResult, T>): Awaitable<CalledActionResult, T>;
9
12
  }
@@ -22,7 +22,7 @@ import { ControlAction } from "../action/actions/controlAction";
22
22
  import { Text } from "../elements/text";
23
23
  import { TextAction } from "../action/actions/textAction";
24
24
  export declare namespace LogicAction {
25
- type Displayable = Text;
25
+ type Displayable = Text | Image;
26
26
  type GameElement = Character | Scene | Story | Image | Condition | Script | Menu | Sound | Control | Text;
27
27
  type Actions = (TypedAction | CharacterAction | ConditionAction | ImageAction | SceneAction | ScriptAction | StoryAction | MenuAction | SoundAction | ControlAction | TextAction);
28
28
  type ActionTypes = Values<typeof CharacterActionTypes> | Values<typeof ConditionActionTypes> | Values<typeof ImageActionTypes> | Values<typeof SceneActionTypes> | Values<typeof ScriptActionTypes> | Values<typeof StoryActionTypes> | Values<typeof MenuActionTypes> | Values<typeof SoundAction.ActionTypes> | Values<typeof ControlAction.ActionTypes> | Values<typeof TextAction.ActionTypes>;
@@ -1,4 +1,3 @@
1
- import React from "react";
2
1
  import type { TransformDefinitions } from "../elements/transform/type";
3
2
  import { Actionable } from "../action/actionable";
4
3
  import { Transform } from "./transform/transform";
@@ -11,25 +10,21 @@ export type ImageConfig = {
11
10
  src: string | StaticImageData;
12
11
  display: boolean;
13
12
  disposed?: boolean;
13
+ wearables: Image[];
14
+ isWearable?: boolean;
14
15
  } & CommonDisplayable;
15
16
  export type ImageDataRaw = {
16
17
  state: Record<string, any>;
17
18
  };
18
19
  export type ImageEventTypes = {
19
- "event:image.init": [];
20
- "event:image.mount": [];
21
- "event:image.unmount": [];
22
- "event:image.ready": [React.MutableRefObject<HTMLDivElement | null>];
23
- "event:image.elementLoaded": [];
24
- "event:image.flush": [];
25
- "event:image.flushComponent": [];
26
20
  "event:displayable.applyTransition": [ITransition];
27
21
  "event:displayable.applyTransform": [Transform];
28
22
  "event:displayable.init": [];
23
+ "event:wearable.create": [Image];
29
24
  };
30
25
  export declare class Image extends Actionable<ImageDataRaw, Image> implements EventfulDisplayable {
31
- constructor(name: string, config: DeepPartial<ImageConfig>);
32
- constructor(config: DeepPartial<ImageConfig>);
26
+ constructor(name: string, config: Partial<ImageConfig>);
27
+ constructor(config?: DeepPartial<ImageConfig>);
33
28
  /**
34
29
  * Dispose the image
35
30
  *
@@ -109,5 +104,14 @@ export declare class Image extends Actionable<ImageDataRaw, Image> implements Ev
109
104
  hide(): Proxied<Image, Chained<LogicAction.Actions>>;
110
105
  hide(transform: Transform<TransformDefinitions.ImageTransformProps>): Proxied<Image, Chained<LogicAction.Actions>>;
111
106
  hide(transform: Partial<TransformDefinitions.CommonTransformProps>): Proxied<Image, Chained<LogicAction.Actions>>;
107
+ /**
108
+ * Add a wearable to the image
109
+ * @param children - Wearable image or images
110
+ */
111
+ addWearable(children: Image | Image[]): this;
112
+ /**
113
+ * Bind this image to a parent image as a wearable
114
+ */
115
+ bindWearable(parent: Image): this;
112
116
  copy(): Image;
113
117
  }
@@ -1,6 +1,6 @@
1
1
  import { Constructable } from "../action/constructable";
2
2
  import { Awaitable } from "../../../util/data";
3
- import { Background, EventfulDisplayable, ImageColor, ImageSrc } from "../types";
3
+ import { color, EventfulDisplayable, ImageColor, ImageSrc, StaticImageData } from "../types";
4
4
  import { LogicAction } from "../action/logicAction";
5
5
  import { Transform } from "../elements/transform/transform";
6
6
  import { IImageTransition, ITransition } from "../elements/transition/type";
@@ -38,7 +38,7 @@ type ChainedScene = Proxied<Scene, Chained<LogicAction.Actions>>;
38
38
  export type SceneDataRaw = {
39
39
  state: {
40
40
  backgroundMusic?: SoundDataRaw | null;
41
- background?: Background["background"];
41
+ background?: color | StaticImageData | null;
42
42
  };
43
43
  backgroundImageState?: ImageDataRaw | null;
44
44
  };
@@ -34,8 +34,8 @@ export type RawPosition = CommonPositionType | (Coord2DPosition & {
34
34
  xalign?: never;
35
35
  yalign?: never;
36
36
  }) | (AlignPosition & {
37
- x: never;
38
- y: never;
37
+ x?: never;
38
+ y?: never;
39
39
  });
40
40
  export type Unknown = typeof PositionUtils.Unknown;
41
41
  export type UnknownAble<T> = T | Unknown;
@@ -3,7 +3,7 @@ import type { DOMKeyframesDefinition } from "framer-motion";
3
3
  import { TransformDefinitions } from "./type";
4
4
  import Sequence = TransformDefinitions.Sequence;
5
5
  import SequenceProps = TransformDefinitions.SequenceProps;
6
- export type Transformers = "position" | "opacity" | "scale" | "rotation" | "display" | "src" | "backgroundColor" | "backgroundOpacity" | "transform" | "fontSize" | "fontColor";
6
+ export type Transformers = "position" | "opacity" | "scale" | "rotation" | "display" | "src" | "backgroundColor" | "backgroundOpacity" | "transform" | "fontColor";
7
7
  export type TransformHandler<T> = (value: T) => DOMKeyframesDefinition;
8
8
  export type TransformersMap = {
9
9
  "position": CommonDisplayable["position"];
@@ -15,7 +15,6 @@ export type TransformersMap = {
15
15
  "backgroundColor": Background["background"];
16
16
  "backgroundOpacity": number;
17
17
  "transform": TransformDefinitions.Types;
18
- "fontSize": number;
19
18
  "fontColor": color;
20
19
  };
21
20
  export declare class Transform<T extends TransformDefinitions.Types = object> {
@@ -4,6 +4,7 @@ import { ImageColor, ImageSrc } from "../../types";
4
4
  export type ElementProp<T extends Element = Element, U extends React.HTMLAttributes<T> = React.HTMLAttributes<T>> = React.JSX.IntrinsicAttributes & React.ClassAttributes<T> & React.HTMLAttributes<T> & U;
5
5
  export type ImgElementProp = ElementProp<HTMLImageElement, React.ImgHTMLAttributes<HTMLImageElement>>;
6
6
  export type SpanElementProp = ElementProp<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>;
7
+ export type DivElementProp = ElementProp<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>;
7
8
  export type CSSElementProp<T extends React.CSSProperties | DOMKeyframesDefinition> = ElementProp & {
8
9
  style: T;
9
10
  };
@@ -22,6 +22,9 @@ export interface SavedGame {
22
22
  }
23
23
  export type GameConfig = {
24
24
  player: {
25
+ /**
26
+ * The id of the container element for the game
27
+ */
25
28
  contentContainerId: string;
26
29
  /**
27
30
  * The aspect ratio of the game
@@ -36,7 +39,17 @@ export type GameConfig = {
36
39
  * The minimum width and height of the player in pixels
37
40
  */
38
41
  minHeight: number;
42
+ /**
43
+ * Base width of the player in pixels, Image scale will be calculated based on this value
44
+ *
45
+ * For 16/9, recommended value is 1920
46
+ */
39
47
  width: number;
48
+ /**
49
+ * Base height of the player in pixels, Image scale will be calculated based on this value
50
+ *
51
+ * For 16/9, recommended value is 1080
52
+ */
40
53
  height: number;
41
54
  /**
42
55
  * When player presses one of these keys, the game will skip the current action
@@ -50,6 +63,10 @@ export type GameConfig = {
50
63
  * higher value means faster skipping.
51
64
  */
52
65
  skipInterval: number;
66
+ /**
67
+ * The interval in milliseconds between each ratio update.
68
+ */
69
+ ratioUpdateInterval: number;
53
70
  };
54
71
  elements: {
55
72
  say: {
@@ -66,6 +83,12 @@ export type GameConfig = {
66
83
  */
67
84
  textInterval: number;
68
85
  use: SayComponent;
86
+ /**
87
+ * If true, the game will scale the dialog to fit the screen
88
+ *
89
+ * Text will look smaller when this is enabled
90
+ */
91
+ useAspectScale: boolean;
69
92
  };
70
93
  img: {
71
94
  /**
@@ -73,19 +96,49 @@ export type GameConfig = {
73
96
  */
74
97
  slowLoadWarning: boolean;
75
98
  slowLoadThreshold: number;
99
+ /**
100
+ * If true, when you press [GameConfig.player.skipKey], the game will skip the image transform
101
+ */
76
102
  allowSkipTransform: boolean;
103
+ /**
104
+ * If true, when you press [GameConfig.player.skipKey], the game will skip the image transition
105
+ */
77
106
  allowSkipTransition: boolean;
78
107
  };
79
108
  menu: {
80
109
  use: MenuComponent;
81
110
  };
82
111
  background: {
112
+ /**
113
+ * If true, when you press [GameConfig.player.skipKey], the game will skip the background transform
114
+ */
83
115
  allowSkipTransform: boolean;
116
+ /**
117
+ * If true, when you press [GameConfig.player.skipKey], the game will skip the background transition
118
+ */
84
119
  allowSkipTransition: boolean;
85
120
  };
86
121
  text: {
122
+ /**
123
+ * If true, when you press [GameConfig.player.skipKey], the game will skip the text transform
124
+ */
87
125
  allowSkipTransform: boolean;
126
+ /**
127
+ * If true, when you press [GameConfig.player.skipKey], the game will skip the text transition
128
+ */
88
129
  allowSkipTransition: boolean;
130
+ /**
131
+ * Base width of the dialog in pixels
132
+ *
133
+ * For 16/9, recommended value is 1920
134
+ */
135
+ width: number;
136
+ /**
137
+ * Base height of the dialog in pixels
138
+ *
139
+ * For 16/9, recommended value is 1080 * 0.2
140
+ */
141
+ height: number;
89
142
  };
90
143
  };
91
144
  elementStyles: {
@@ -94,6 +147,7 @@ export type GameConfig = {
94
147
  * Custom class for the say container
95
148
  * Ex: "rounded-md shadow-md" for rounded and shadowed container
96
149
  */
150
+ contentContainerClassName: string;
97
151
  containerClassName: string;
98
152
  nameTextClassName: string;
99
153
  textContainerClassName: string;
@@ -132,6 +186,10 @@ export type GameConfig = {
132
186
  debug: boolean;
133
187
  trace: boolean;
134
188
  };
189
+ /**
190
+ * If true, the game will show the inspector when you hover over the element
191
+ */
192
+ inspector: boolean;
135
193
  };
136
194
  };
137
195
  export type GameSettings = {
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { ImgElementProp } from "../../../nlcore/elements/transition/type";
3
+ export default function AspectScaleImage({ props, onLoad, id, Ref, }: Readonly<{
4
+ props: Omit<ImgElementProp, "onLoad">;
5
+ onLoad: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;
6
+ id?: string;
7
+ Ref?: React.RefObject<HTMLImageElement>;
8
+ }>): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ export default function SizeUpdateAnnouncer({ containerRef }: Readonly<{
3
+ containerRef: React.RefObject<HTMLDivElement>;
4
+ }>): null;
@@ -16,7 +16,6 @@ import { Text, TextEventTypes } from "../nlcore/elements/text";
16
16
  type PlayerStateElement = {
17
17
  texts: Clickable<TextElement>[];
18
18
  menus: Clickable<MenuElement, Choice>[];
19
- images: Image[];
20
19
  displayable: LogicAction.Displayable[];
21
20
  };
22
21
  export type PlayerState = {
@@ -31,7 +30,7 @@ export type PlayerStateData = {
31
30
  scenes: {
32
31
  sceneId: string;
33
32
  elements: {
34
- images: string[];
33
+ displayable: string[];
35
34
  };
36
35
  }[];
37
36
  };
@@ -65,10 +64,6 @@ export declare class GameState {
65
64
  scene: Scene;
66
65
  ele: PlayerStateElement;
67
66
  } | null;
68
- findElementByImage(image: Image): {
69
- scene: Scene;
70
- ele: PlayerStateElement;
71
- } | null;
72
67
  findElementByDisplayable(displayable: LogicAction.Displayable): {
73
68
  scene: Scene;
74
69
  ele: PlayerStateElement;
@@ -87,6 +82,7 @@ export declare class GameState {
87
82
  createText(id: string, sentence: Sentence, afterClick?: () => void, scene?: Scene): void;
88
83
  createMenu(menu: MenuData, afterChoose?: (choice: Choice) => void, scene?: Scene): void;
89
84
  createImage(image: Image, scene?: Scene): this;
85
+ createWearable(parent: Image, image: Image): Promise<any>;
90
86
  disposeImage(image: Image, scene?: Scene): this;
91
87
  createDisplayable(displayable: LogicAction.Displayable, scene?: Scene): this;
92
88
  disposeDisplayable(displayable: LogicAction.Displayable, scene?: Scene): this;
@@ -23,6 +23,6 @@ export declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
23
23
  errorInfo: null;
24
24
  };
25
25
  componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
26
- render(): string | number | boolean | React.JSX.Element | Iterable<React.ReactNode> | null | undefined;
26
+ render(): string | number | boolean | Iterable<React.ReactNode> | React.JSX.Element | null | undefined;
27
27
  }
28
28
  export {};
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { DivElementProp } from "../../nlcore/elements/transition/type";
3
+ type InspectStyle = {
4
+ border?: "solid" | "dashed" | "dotted";
5
+ borderWidth?: number;
6
+ color?: React.CSSProperties["color"];
7
+ };
8
+ declare function InspectDiv(props: Readonly<DivElementProp & {
9
+ children?: React.ReactNode;
10
+ tag?: string;
11
+ } & InspectStyle>): React.JSX.Element;
12
+ declare function InspectSpan(props: Readonly<DivElementProp & {
13
+ children?: React.ReactNode;
14
+ tag?: string;
15
+ } & InspectStyle>): React.JSX.Element;
16
+ declare function InspectImg(props: Readonly<DivElementProp & {
17
+ tag?: string;
18
+ } & InspectStyle>): React.JSX.Element;
19
+ declare function InspectButton(props: Readonly<DivElementProp & {
20
+ children?: React.ReactNode;
21
+ tag?: string;
22
+ } & InspectStyle>): React.JSX.Element;
23
+ declare function InspectFramerMotionDiv(props: Readonly<DivElementProp & {
24
+ children?: React.ReactNode;
25
+ tag?: string;
26
+ Ref?: React.RefObject<HTMLDivElement>;
27
+ } & InspectStyle>): React.JSX.Element;
28
+ declare const Inspect: {
29
+ Div: typeof InspectDiv;
30
+ Span: typeof InspectSpan;
31
+ Button: typeof InspectButton;
32
+ Img: typeof InspectImg;
33
+ mDiv: typeof InspectFramerMotionDiv;
34
+ };
35
+ export default Inspect;
@@ -12,6 +12,7 @@ type AspectRatioEvents = {
12
12
  "event:aspectRatio.update": [width: number, height: number];
13
13
  "event:aspectRatio.pause": [];
14
14
  "event:aspectRatio.resume": [];
15
+ "event:aspectRatio.requestUpdate": [];
15
16
  };
16
17
  declare class AspectRatio {
17
18
  static EventTypes: {
@@ -37,6 +38,8 @@ declare class AspectRatio {
37
38
  pause(): void;
38
39
  resume(): void;
39
40
  onUpdate(callback: (width: number, height: number) => void): () => void;
41
+ requestUpdate(): void;
42
+ onRequestedUpdate(callback: () => void): () => void;
40
43
  private triggerUpdate;
41
44
  }
42
45
  export declare function RatioProvider({ children }: {