@turnipxenon/pineapple 2.4.14 → 2.4.15

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.
Files changed (49) hide show
  1. package/.idea/workspace.xml +64 -49
  2. package/.svelte-kit/__package__/app.postcss +15 -1
  3. package/.svelte-kit/__package__/components/DialogOverlay.svelte +48 -47
  4. package/.svelte-kit/__package__/components/dialog_manager/DialogManager.d.ts +8 -3
  5. package/.svelte-kit/__package__/components/dialog_manager/DialogManager.js +35 -11
  6. package/.svelte-kit/__package__/components/dialog_manager/DialogManagerStore.js +1 -1
  7. package/.svelte-kit/__package__/components/layouts/SeaweedBaseLayout.svelte +2 -2
  8. package/.svelte-kit/__package__/components/pineapple/PineappleBaseLayout.svelte +18 -6
  9. package/.svelte-kit/__package__/components/pineapple/toast/Toast.svelte +1 -0
  10. package/.svelte-kit/__package__/scripts/pineapple_fiber/PineappleFiberParser.d.ts +2 -0
  11. package/.svelte-kit/__package__/scripts/pineapple_fiber/PineappleFiberParser.js +137 -0
  12. package/.svelte-kit/__package__/scripts/pineapple_fiber/PineappleWeaver.js +3 -134
  13. package/.svelte-kit/__package__/types/pineapple_fiber/DialogState.d.ts +2 -1
  14. package/.svelte-kit/__package__/types/pineapple_fiber/DialogState.js +1 -0
  15. package/.svelte-kit/ambient.d.ts +2 -0
  16. package/.svelte-kit/generated/server/internal.js +1 -1
  17. package/dist/app.postcss +15 -1
  18. package/dist/components/DialogOverlay.svelte +48 -47
  19. package/dist/components/dialog_manager/DialogManager.d.ts +8 -3
  20. package/dist/components/dialog_manager/DialogManager.js +35 -11
  21. package/dist/components/dialog_manager/DialogManagerStore.js +1 -1
  22. package/dist/components/layouts/SeaweedBaseLayout.svelte +2 -2
  23. package/dist/components/pineapple/PineappleBaseLayout.svelte +18 -6
  24. package/dist/components/pineapple/toast/Toast.svelte +1 -0
  25. package/dist/scripts/pineapple_fiber/PineappleFiberParser.d.ts +2 -0
  26. package/dist/scripts/pineapple_fiber/PineappleFiberParser.js +137 -0
  27. package/dist/scripts/pineapple_fiber/PineappleWeaver.js +3 -134
  28. package/dist/types/pineapple_fiber/DialogState.d.ts +2 -1
  29. package/dist/types/pineapple_fiber/DialogState.js +1 -0
  30. package/package.json +1 -1
  31. package/src/lib/app.postcss +15 -1
  32. package/src/lib/components/DialogOverlay.svelte +56 -54
  33. package/src/lib/components/dialog_manager/DialogManager.ts +38 -12
  34. package/src/lib/components/dialog_manager/DialogManagerStore.ts +1 -1
  35. package/src/lib/components/layouts/SeaweedBaseLayout.svelte +2 -2
  36. package/src/lib/components/pineapple/PineappleBaseLayout.svelte +19 -6
  37. package/src/lib/components/pineapple/toast/Toast.svelte +1 -0
  38. package/src/lib/scripts/pineapple_fiber/PineappleFiberParser.ts +177 -0
  39. package/src/lib/scripts/pineapple_fiber/PineappleWeaver.ts +3 -176
  40. package/src/lib/types/pineapple_fiber/DialogState.ts +2 -1
  41. package/src/routes/(pineapple)/+page.svelte +2 -2
  42. package/src/routes/(pineapple)/pineapple/+page.svelte +19 -1
  43. package/src/routes/(pineapple)/pineapple/TestDialog.yarn +7 -0
  44. package/vite.config.ts +2 -1
  45. package/.svelte-kit/__package__/components/pineapple/overlay_manager/OverlayManager.d.ts +0 -0
  46. package/.svelte-kit/__package__/components/pineapple/overlay_manager/OverlayManager.js +0 -1
  47. package/dist/components/pineapple/overlay_manager/OverlayManager.d.ts +0 -0
  48. package/dist/components/pineapple/overlay_manager/OverlayManager.js +0 -1
  49. package/src/lib/components/pineapple/overlay_manager/OverlayManager.ts +0 -0
@@ -6,7 +6,7 @@ import { writable } from "svelte/store";
6
6
  import type { DialogDetail } from "$pkg/types/pineapple_fiber/DialogDetail";
7
7
  import { DialogState } from "$pkg/types/pineapple_fiber/DialogState";
8
8
  import { tweened } from "svelte/motion";
9
- import { cubicOut } from "svelte/easing";
9
+ import { backOut } from "svelte/easing";
10
10
  import { PortraitType } from "$pkg/types/pineapple_fiber/PortraitType";
11
11
  import AresHappy from "$pkg/assets/characters/ares/ares_happy.webp";
12
12
  import AresBlushing from "$pkg/assets/characters/ares/ares_blushing.webp";
@@ -24,6 +24,7 @@ import {
24
24
  updateRate
25
25
  } from "$pkg/components/dialog_manager/DialogManagerStore";
26
26
  import { DialogProcessor } from "$pkg/components/dialog_manager/DialogProcessor";
27
+ import { parseYarn } from "$pkg/scripts/pineapple_fiber/PineappleFiberParser";
27
28
 
28
29
  const shouldDebugYarn = false;
29
30
 
@@ -31,7 +32,7 @@ export type OnSetDialogChoiceCallback = (newMessage: DialogDetail) => void;
31
32
 
32
33
  export class DialogManager {
33
34
  dialogMessageMap: Map<string, DialogDetail> = new Map();
34
- dialogMessageList: DialogDetail[] = [];
35
+ currentDialogTree: DialogDetail[] = [];
35
36
  fullCurrentMessage: string = defaultDialogMessage[0].textContent;
36
37
  currentMessageMeta: DialogDetail = defaultDialogMessage[0];
37
38
  currentMessage = writable("");
@@ -41,9 +42,10 @@ export class DialogManager {
41
42
  currentPortrait = writable();
42
43
  portraitMap: Map<string, any> = new Map();
43
44
  currentState = DialogState.Visible;
45
+ currentReadableState = writable(this.currentState);
44
46
  hidePercent = tweened(100, {
45
47
  duration: 400,
46
- easing: cubicOut
48
+ easing: backOut
47
49
  }); // 100 = 100%
48
50
  skipNextActiveTime = 0;
49
51
  dialogProcessor = new DialogProcessor();
@@ -51,16 +53,23 @@ export class DialogManager {
51
53
  _setDialogChoiceQueue: DialogDetail[] = [];
52
54
  _setDialogChoiceMutex = false;
53
55
  onSetDialogListeners: OnSetDialogChoiceCallback[] = [];
56
+ enableDialogueOverlayCache = false;
54
57
 
55
58
  constructor() {
56
59
  enableDialogueOverlay.subscribe((value) => {
57
60
  // todo: investigate why we cant put setDialogDefault inside the then clause
58
61
  // ISSUE #82 https://github.com/TurnipXenon/pineapple/issues/82
62
+ this.enableDialogueOverlayCache = value;
59
63
  if (value) {
60
- this.hidePercent.set(0);
61
- this.setDialogToDefault();
64
+ this.hidePercent.set(0).then(() => {
65
+ this.currentState = DialogState.Visible;
66
+ this.currentReadableState.set(this.currentState);
67
+ });
62
68
  } else {
63
- this.hidePercent.set(100);
69
+ this.hidePercent.set(100).then(() => {
70
+ this.currentState = DialogState.Invisible;
71
+ this.currentReadableState.set(this.currentState);
72
+ });
64
73
  this.setDialogTree([{ textContent: "" }]);
65
74
  }
66
75
  });
@@ -93,13 +102,13 @@ export class DialogManager {
93
102
  * sets the possible dialog that might appear on the Dialog UI
94
103
  * note that it overwrites the previous tree and does not append on it due to the possibility
95
104
  * of node name conflicts
96
- * @param newMessageList
105
+ * @param newDialogTree
97
106
  */
98
- setDialogTree = (newMessageList: DialogDetail[]) => {
99
- this.dialogMessageList = newMessageList;
107
+ setDialogTree = (newDialogTree: DialogDetail[]) => {
108
+ this.currentDialogTree = newDialogTree;
100
109
 
101
110
  this.dialogMessageMap.clear();
102
- newMessageList.map((value) => {
111
+ newDialogTree.map((value) => {
103
112
  if (value.dialogId) {
104
113
  this.dialogMessageMap.set(value.dialogId!, value);
105
114
  }
@@ -118,7 +127,7 @@ export class DialogManager {
118
127
  this.portraitMap.set(PortraitType.AresYay.toString(), AresYay);
119
128
  }
120
129
 
121
- this.setDialogChoice(newMessageList[0]);
130
+ this.setDialogChoice(newDialogTree[0]);
122
131
  };
123
132
 
124
133
  /**
@@ -265,7 +274,7 @@ export class DialogManager {
265
274
  while (
266
275
  this.fullCurrentMessage[this.currentIndex] == "<" &&
267
276
  this.currentIndex + 1 < this.fullCurrentMessage.length
268
- ) {
277
+ ) {
269
278
  // find valid character, trap with closing
270
279
  this.currentIndex = this.fullCurrentMessage.indexOf(">", this.currentIndex) + 1;
271
280
  // normalize
@@ -279,4 +288,21 @@ export class DialogManager {
279
288
 
280
289
  window.requestAnimationFrame(this.update);
281
290
  };
291
+
292
+ enableDialogOverlay(enable: boolean) {
293
+ enableDialogueOverlay.set(enable);
294
+ }
295
+
296
+ toggleDialogOverlay() {
297
+ enableDialogueOverlay.set(!this.enableDialogueOverlayCache);
298
+ };
299
+
300
+ async parseAndSetDialogTree(dialogYarn: string): Promise<DialogDetail[]> {
301
+ return parseYarn(dialogYarn)
302
+ .then((dialogTree) => {
303
+ console.log(dialogTree);
304
+ this.setDialogTree(dialogTree);
305
+ return dialogTree;
306
+ });
307
+ }
282
308
  }
@@ -32,7 +32,7 @@ export const updateRate: number = 40 / 1000; // *at least* 40ms per letter
32
32
  // todo: if we go through doing yarn to typescript, move this!
33
33
  export const defaultDialogMessage: DialogDetail[] = [
34
34
  {
35
- textContent: `<p>Have you drank water? Or perhaps, you've checked out <a target="_blank" class="external-link" href="http://crouton.net">one of the best webpages</a> out there?`
35
+ textContent: `<p>I don't really have anything to say. Have you drank water? Or perhaps, you've checked out <a target="_blank" class="external-link" href="http://crouton.net">one of the best webpages</a> out there?`
36
36
  }
37
37
  ];
38
38
 
@@ -66,8 +66,8 @@
66
66
 
67
67
  <style lang="postcss">
68
68
  :root {
69
- --dialog-left-pad: clamp(0em, 5vw, 2em);
70
- --dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-left-pad) - var(--theme-border-base)));
69
+ --dialog-start-pad: clamp(0em, 5vw, 2em);
70
+ --dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-start-pad) - var(--theme-border-base)));
71
71
  --dialog-box-height: clamp(15em, 50vw, 18em);
72
72
  }
73
73
 
@@ -12,8 +12,10 @@
12
12
  // import DialogOverlay from "$lib/components/DialogOverlay.svelte";
13
13
  import AresLogo from "$pkg/assets/characters/ares/ares_logo.webp";
14
14
  import FABIcon from "$pkg/assets/placeholder/placeholder_circle.png";
15
- import { enableDialogueOverlay } from "$pkg/components/dialog_manager/DialogManagerStore";
15
+ import CloseIcon from "$pkg/assets/icons/close.svg";
16
+ import { dialogManager, enableDialogueOverlay } from "$pkg/components/dialog_manager/DialogManagerStore";
16
17
  import Toast from "$pkg/components/pineapple/toast/Toast.svelte";
18
+ import DialogOverlay from "$pkg/components/DialogOverlay.svelte";
17
19
  // todo: clean up all these imports!
18
20
 
19
21
  let pages: BreadcrumbData[] = [];
@@ -55,6 +57,8 @@
55
57
  enableDialogueOverlay.subscribe((value) => {
56
58
  enableDialogueOverlayValue = value;
57
59
  });
60
+
61
+ enableDialogueOverlay.set(false);
58
62
  </script>
59
63
 
60
64
  <!-- App Shell -->
@@ -64,9 +68,13 @@
64
68
 
65
69
  <!--todo: turn off hidden when it's time-->
66
70
  <button type="button" class="fab" on:click={()=>{
67
- enableDialogueOverlay.set(!enableDialogueOverlayValue);
71
+ dialogManager.toggleDialogOverlay()
68
72
  }}>
69
- <img src={FABIcon} alt="interactive floating action button represented as a turnip">
73
+ {#if (enableDialogueOverlayValue)}
74
+ <img class="img-icon" src={CloseIcon} alt="interactive floating action button represented as a turnip">
75
+ {:else }
76
+ <img src={FABIcon} alt="interactive floating action button represented as a turnip">
77
+ {/if}
70
78
  </button>
71
79
 
72
80
  <AppShell>
@@ -107,6 +115,8 @@
107
115
 
108
116
  <Toast></Toast>
109
117
 
118
+ <DialogOverlay></DialogOverlay>
119
+
110
120
  <div class="default-page-container">
111
121
  <slot />
112
122
  <div class="footer-space" />
@@ -126,8 +136,8 @@
126
136
 
127
137
  <style lang="postcss">
128
138
  :root {
129
- --dialog-left-pad: clamp(0em, 5vw, 2em);
130
- --dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-left-pad) - var(--theme-border-base)));
139
+ --dialog-start-pad: clamp(0em, 5vw, 2em);
140
+ --dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-start-pad) - var(--theme-border-base)));
131
141
  --dialog-box-height: clamp(15em, 50vw, 18em);
132
142
 
133
143
  /** FAB icon margin/position calculation origin:
@@ -147,7 +157,6 @@
147
157
  .default-page-container {
148
158
  @apply flex justify-center items-center;
149
159
  margin-top: 4em;
150
- margin-left: clamp(1em, 15vw, 10em);
151
160
  margin-right: 1em;
152
161
  flex-direction: column;
153
162
  z-index: 0;
@@ -197,6 +206,10 @@
197
206
  border-radius: 50%;
198
207
  }
199
208
 
209
+ .fab > img {
210
+ width: 100%;
211
+ }
212
+
200
213
  .fab:dir(ltr) {
201
214
  right: var(--fab-margin);
202
215
  }
@@ -101,6 +101,7 @@
101
101
  position: fixed;
102
102
  /* 12em = this component's margin (4em) + fab margin + width (8em) */
103
103
  max-width: calc(100vw - 12em);
104
+ z-index: 100;
104
105
  }
105
106
 
106
107
  .toast-positioner:dir(ltr) {
@@ -0,0 +1,177 @@
1
+ import type { DialogDetail } from "$pkg/types/pineapple_fiber/DialogDetail";
2
+ import { PortraitType } from "$pkg/types/pineapple_fiber/PortraitType";
3
+
4
+ interface ChoiceDetail {
5
+ name: string;
6
+ jumpNode: string;
7
+ }
8
+
9
+ const shouldDebug = false;
10
+
11
+ export const parseYarn = async (fileContent: string): Promise<DialogDetail[]> => {
12
+ const dialogDetailList: DialogDetail[] = [];
13
+
14
+ fileContent.split("===").map((unparsedNode) => {
15
+ // todo: detect empty nodes
16
+ // todo: improve the code readability
17
+ if (unparsedNode.trim() === "") {
18
+ return;
19
+ }
20
+
21
+ const dialogDetails: DialogDetail = {
22
+ // todo: dissect the line below and give comments because it is complex
23
+ dialogId: unparsedNode
24
+ .slice(unparsedNode.indexOf("title: "))
25
+ .split("\n")[0]
26
+ .split(" ")
27
+ .pop()
28
+ ?.trim(),
29
+ portraitType: PortraitType.AresNeutral,
30
+ textContent: "" // will be filled later below
31
+ };
32
+ let portraitUnset = true;
33
+
34
+ // parse the PineappleFiber metatags
35
+ const unparsedBody = unparsedNode.split("---").pop()!.trim();
36
+ const bodyList = unparsedBody.split("\n");
37
+ let contentIndexStart = 0;
38
+ for (let index = 0; index < bodyList.length; index++) {
39
+ const possibleTagPair = bodyList[index].split(": ");
40
+ const possibleTagName = possibleTagPair[0].toLowerCase();
41
+
42
+ if (!["portrait"].includes(possibleTagName)) {
43
+ break;
44
+ }
45
+
46
+ contentIndexStart = index + 1; // increase count for each viable tah
47
+ switch (possibleTagName) {
48
+ case "portrait":
49
+ portraitUnset = false;
50
+ // todo: implement a way to match the appropriate portrait based on the metatag
51
+ // from https://stackoverflow.com/a/17381004/17836168
52
+ dialogDetails.portraitType =
53
+ PortraitType[possibleTagPair[1].trim() as keyof typeof PortraitType];
54
+ if (shouldDebug) {
55
+ console.log(
56
+ "Portrait detected:",
57
+ possibleTagPair[1],
58
+ " => ",
59
+ dialogDetails.portraitType
60
+ );
61
+ }
62
+ break;
63
+ }
64
+ }
65
+
66
+ // portrait check
67
+ if (portraitUnset) {
68
+ console.warn(`Portrait missing for node: ${dialogDetails.dialogId}`);
69
+ dialogDetails.portraitType = PortraitType.AresNeutral;
70
+ }
71
+
72
+ // remove the metatags from the body
73
+ const unprocessedContent = bodyList.slice(contentIndexStart).join("\n");
74
+ const contentPair = unprocessedContent.split("<ChoiceBreak>");
75
+
76
+ if (contentPair.length === 2) {
77
+ // parse for the choice names in the options
78
+ enum ChoiceParsingState {
79
+ Free, // detects for the next option
80
+ Line // previously detected an option, will try to detect for the next option
81
+ }
82
+
83
+ let parsingState: ChoiceParsingState = ChoiceParsingState.Free;
84
+ const currentChoiceDetail: ChoiceDetail = { jumpNode: "", name: "" };
85
+ const choiceList: ChoiceDetail[] = [];
86
+ let shouldSkipChoices = false;
87
+
88
+ const checkChoiceForSave = () => {
89
+ if (currentChoiceDetail.name !== "") {
90
+ choiceList.push({
91
+ name: currentChoiceDetail.name,
92
+ jumpNode: currentChoiceDetail.jumpNode
93
+ });
94
+ }
95
+ };
96
+
97
+ contentPair[1]
98
+ .trim()
99
+ .split("\n")
100
+ .filter((line) => {
101
+ const trimmedLine = line.trim();
102
+ if (shouldSkipChoices) {
103
+ return false;
104
+ }
105
+
106
+ if (trimmedLine.startsWith("// ignore the rest")) {
107
+ shouldSkipChoices = true;
108
+ return false;
109
+ }
110
+
111
+ return true;
112
+ })
113
+ .map((line) => {
114
+ const trimmedLine = line.trim();
115
+
116
+ switch (parsingState) {
117
+ case ChoiceParsingState.Free:
118
+ if (trimmedLine.startsWith("->")) {
119
+ // save previous choice
120
+ checkChoiceForSave();
121
+
122
+ // write start of new choice
123
+ currentChoiceDetail.name = trimmedLine.split(" ").pop()!;
124
+ parsingState = ChoiceParsingState.Line;
125
+ }
126
+ break;
127
+
128
+ case ChoiceParsingState.Line:
129
+ if (trimmedLine.startsWith("<<jump")) {
130
+ const jumpNode = trimmedLine.split(" ").pop()!;
131
+ currentChoiceDetail.jumpNode = jumpNode.slice(0, jumpNode.length - 2); // remove ">>"
132
+ parsingState = ChoiceParsingState.Free;
133
+ }
134
+ break;
135
+
136
+ default:
137
+ console.error(`Unimplemented parsing state: ${parsingState}`);
138
+ break;
139
+ }
140
+ });
141
+ checkChoiceForSave();
142
+
143
+ dialogDetails.textContent = contentPair[0];
144
+
145
+ // handle choice start tags to a href
146
+ choiceList.forEach((choiceDetail) => {
147
+ const keyword = `<choice ${choiceDetail.name}>`;
148
+ while (dialogDetails.textContent.includes(keyword)) {
149
+ dialogDetails.textContent = dialogDetails.textContent.replace(
150
+ keyword,
151
+ `<a class="choice-${choiceDetail.jumpNode} dialog-choice" title="Click to continue the dialog">`
152
+ );
153
+ }
154
+ });
155
+
156
+ const externalKeyword = "<a href=";
157
+ while (dialogDetails.textContent.includes(externalKeyword)) {
158
+ dialogDetails.textContent = dialogDetails.textContent.replace(
159
+ externalKeyword,
160
+ "<a target=\"_blank\" class=\"external-link\" href="
161
+ ); // make all external tags with a custom cursor
162
+ }
163
+
164
+ const choiceEndKeyword = "</choice>";
165
+ while (dialogDetails.textContent.includes(choiceEndKeyword)) {
166
+ dialogDetails.textContent = dialogDetails.textContent.replace(choiceEndKeyword, "</a>"); // convert all choice end tags to a tags
167
+ }
168
+ } else {
169
+ // assume only one which indicates it is choiceless
170
+ dialogDetails.textContent = unprocessedContent;
171
+ }
172
+
173
+ dialogDetailList.push(dialogDetails);
174
+ });
175
+
176
+ return dialogDetailList;
177
+ };
@@ -11,190 +11,17 @@ import { getAllFiles } from "$pkg/scripts/util/FileManagement";
11
11
  import fs, { readFileSync } from "fs";
12
12
  import type { DialogDetail } from "$pkg/types/pineapple_fiber/DialogDetail";
13
13
  import { PortraitType } from "$pkg/types/pineapple_fiber/PortraitType";
14
-
15
- // import { getAllFiles } from "../util/FileManagement";
16
- // import fs, { readFileSync } from "fs";
17
- // import type { DialogDetail } from "../../types/pineapple_fiber/DialogDetail";
18
- // import { PortraitType } from "../../types/pineapple_fiber/PortraitType";
19
-
20
- interface ChoiceDetail {
21
- name: string;
22
- jumpNode: string;
23
- }
24
-
25
- const shouldDebug = false;
14
+ import { parseYarn } from "$pkg/scripts/pineapple_fiber/PineappleFiberParser";
26
15
 
27
16
  const pineappleWeaverRun = () => {
28
17
  console.info("Starting Pineapple Weaver.");
29
18
  const BASE_PATH = "./src/routes";
30
19
  getAllFiles(BASE_PATH, (path: string): boolean => {
31
20
  return path.split(".").pop() === "yarn";
32
- }).map((filePath) => {
21
+ }).map(async (filePath) => {
33
22
  console.info(`Converting: ${filePath}`);
34
23
  const fileContent = readFileSync(filePath, "utf-8");
35
- const dialogDetailList: DialogDetail[] = [];
36
-
37
- fileContent.split("===").map((unparsedNode) => {
38
- // todo: detect empty nodes
39
- // todo: improve the code readability
40
- if (unparsedNode.trim() === "") {
41
- return;
42
- }
43
-
44
- const dialogDetails: DialogDetail = {
45
- // todo: dissect the line below and give comments because it is complex
46
- dialogId: unparsedNode
47
- .slice(unparsedNode.indexOf("title: "))
48
- .split("\n")[0]
49
- .split(" ")
50
- .pop()
51
- ?.trim(),
52
- portraitType: PortraitType.AresNeutral,
53
- textContent: "" // will be filled later below
54
- };
55
- let portraitUnset = true;
56
-
57
- // parse the PineappleFiber metatags
58
- const unparsedBody = unparsedNode.split("---").pop()!.trim();
59
- const bodyList = unparsedBody.split("\n");
60
- let contentIndexStart = 0;
61
- for (let index = 0; index < bodyList.length; index++) {
62
- const possibleTagPair = bodyList[index].split(": ");
63
- const possibleTagName = possibleTagPair[0].toLowerCase();
64
-
65
- if (!["portrait"].includes(possibleTagName)) {
66
- break;
67
- }
68
-
69
- contentIndexStart = index + 1; // increase count for each viable tah
70
- switch (possibleTagName) {
71
- case "portrait":
72
- portraitUnset = false;
73
- // todo: implement a way to match the appropriate portrait based on the metatag
74
- // from https://stackoverflow.com/a/17381004/17836168
75
- dialogDetails.portraitType =
76
- PortraitType[possibleTagPair[1].trim() as keyof typeof PortraitType];
77
- if (shouldDebug) {
78
- console.log(
79
- "Portrait detected:",
80
- possibleTagPair[1],
81
- " => ",
82
- dialogDetails.portraitType
83
- );
84
- }
85
- break;
86
- }
87
- }
88
-
89
- // portrait check
90
- if (portraitUnset) {
91
- console.warn(`Portrait missing for node: ${dialogDetails.dialogId}`);
92
- dialogDetails.portraitType = PortraitType.AresNeutral;
93
- }
94
-
95
- // remove the metatags from the body
96
- const unprocessedContent = bodyList.slice(contentIndexStart).join("\n");
97
- const contentPair = unprocessedContent.split("<ChoiceBreak>");
98
-
99
- if (contentPair.length === 2) {
100
- // parse for the choice names in the options
101
- enum ChoiceParsingState {
102
- Free, // detects for the next option
103
- Line // previously detected an option, will try to detect for the next option
104
- }
105
-
106
- let parsingState: ChoiceParsingState = ChoiceParsingState.Free;
107
- const currentChoiceDetail: ChoiceDetail = { jumpNode: "", name: "" };
108
- const choiceList: ChoiceDetail[] = [];
109
- let shouldSkipChoices = false;
110
-
111
- const checkChoiceForSave = () => {
112
- if (currentChoiceDetail.name !== "") {
113
- choiceList.push({
114
- name: currentChoiceDetail.name,
115
- jumpNode: currentChoiceDetail.jumpNode
116
- });
117
- }
118
- };
119
-
120
- contentPair[1]
121
- .trim()
122
- .split("\n")
123
- .filter((line) => {
124
- const trimmedLine = line.trim();
125
- if (shouldSkipChoices) {
126
- return false;
127
- }
128
-
129
- if (trimmedLine.startsWith("// ignore the rest")) {
130
- shouldSkipChoices = true;
131
- return false;
132
- }
133
-
134
- return true;
135
- })
136
- .map((line) => {
137
- const trimmedLine = line.trim();
138
-
139
- switch (parsingState) {
140
- case ChoiceParsingState.Free:
141
- if (trimmedLine.startsWith("->")) {
142
- // save previous choice
143
- checkChoiceForSave();
144
-
145
- // write start of new choice
146
- currentChoiceDetail.name = trimmedLine.split(" ").pop()!;
147
- parsingState = ChoiceParsingState.Line;
148
- }
149
- break;
150
-
151
- case ChoiceParsingState.Line:
152
- if (trimmedLine.startsWith("<<jump")) {
153
- const jumpNode = trimmedLine.split(" ").pop()!;
154
- currentChoiceDetail.jumpNode = jumpNode.slice(0, jumpNode.length - 2); // remove ">>"
155
- parsingState = ChoiceParsingState.Free;
156
- }
157
- break;
158
-
159
- default:
160
- console.error(`Unimplemented parsing state: ${parsingState}`);
161
- break;
162
- }
163
- });
164
- checkChoiceForSave();
165
-
166
- dialogDetails.textContent = contentPair[0];
167
-
168
- // handle choice start tags to a href
169
- choiceList.forEach((choiceDetail) => {
170
- const keyword = `<choice ${choiceDetail.name}>`;
171
- while (dialogDetails.textContent.includes(keyword)) {
172
- dialogDetails.textContent = dialogDetails.textContent.replace(
173
- keyword,
174
- `<a class="choice-${choiceDetail.jumpNode} dialog-choice" title="Click to continue the dialog">`
175
- );
176
- }
177
- });
178
-
179
- const externalKeyword = "<a href=";
180
- while (dialogDetails.textContent.includes(externalKeyword)) {
181
- dialogDetails.textContent = dialogDetails.textContent.replace(
182
- externalKeyword,
183
- '<a target="_blank" class="external-link" href='
184
- ); // make all external tags with a custom cursor
185
- }
186
-
187
- const choiceEndKeyword = "</choice>";
188
- while (dialogDetails.textContent.includes(choiceEndKeyword)) {
189
- dialogDetails.textContent = dialogDetails.textContent.replace(choiceEndKeyword, "</a>"); // convert all choice end tags to a tags
190
- }
191
- } else {
192
- // assume only one which indicates it's choiceless
193
- dialogDetails.textContent = unprocessedContent;
194
- }
195
-
196
- dialogDetailList.push(dialogDetails);
197
- });
24
+ const dialogDetailList: DialogDetail[] = await parseYarn(fileContent);
198
25
 
199
26
  const dialogDetailToString = (detail: DialogDetail): string => {
200
27
  if (detail.portraitType === undefined) {
@@ -1,5 +1,6 @@
1
1
  export enum DialogState {
2
2
  Invisible,
3
3
  Visible,
4
- Busy
4
+ Busy,
5
+ Inherit
5
6
  }
@@ -18,8 +18,8 @@
18
18
  <h1 class="mb-8">Directory</h1>
19
19
 
20
20
  <div class="btn-group-vertical variant-filled-secondary">
21
- <button on:click={createGoToFunction("pineapple")}><h2>Personal</h2></button>
22
- <button on:click={createGoToFunction("portfolio")}><h2>Portfolio</h2></button>
21
+ <button on:click={createGoToFunction("pineapple")}><h2>Pineapple playground</h2></button>
22
+ <button on:click={createGoToFunction("portfolio")}><h2>Seaweed playrground</h2></button>
23
23
  </div>
24
24
  </div>
25
25
 
@@ -2,6 +2,9 @@
2
2
  import { PUBLIC_CRINGE_USERNAME } from "$env/static/public";
3
3
  import { showComponentInToast, showTextInToast } from "$pkg/components/pineapple/toast/Toast";
4
4
  import TestCard from "$pkg/components/pineapple/toast/custom-toast/TestCustomToast.svelte";
5
+ import TestDialogYarn from "./TestDialog.yarn?raw";
6
+ import { dialogManager } from "$pkg";
7
+
5
8
 
6
9
  let testingQueueNumber = 1;
7
10
  const testingRandomPhrases = [
@@ -9,6 +12,19 @@
9
12
  "Niko the Baikal seal",
10
13
  "Niko the Baikal seal\nfrom Toba Aquarium"
11
14
  ];
15
+ const testDialogYarn = TestDialogYarn;
16
+
17
+ let parsed = false;
18
+ const onTestDialogClick = () => {
19
+ if (!parsed) {
20
+ dialogManager.parseAndSetDialogTree(testDialogYarn).then(() => {
21
+ dialogManager.toggleDialogOverlay();
22
+ });
23
+ parsed = true;
24
+ } else {
25
+ dialogManager.toggleDialogOverlay();
26
+ }
27
+ };
12
28
  </script>
13
29
 
14
30
  <svelte:head>
@@ -21,7 +37,6 @@
21
37
  </svelte:head>
22
38
 
23
39
  <div class="card default-card">
24
-
25
40
  <button
26
41
  class="btn variant-filled-secondary"
27
42
  on:click={() => {
@@ -33,6 +48,9 @@
33
48
  showTextInToast(`${testingQueueNumber} ${testingRandomPhrases[testingQueueNumber]}`);
34
49
  testingQueueNumber = (testingQueueNumber + 1) % testingRandomPhrases.length;
35
50
  }}><h3>Handy toast</h3></button>
51
+ <button
52
+ class="btn variant-filled-secondary"
53
+ on:click={onTestDialogClick}><h3>Test dialog</h3></button>
36
54
  </div>
37
55
 
38
56
  <style lang="postcss">
@@ -0,0 +1,7 @@
1
+ title: ErrorStart
2
+ position: 0,0
3
+ ---
4
+ Portrait: AresDisappointed
5
+ <p>Huh... We didn't find that page. Sorry about that. You can go back to our <a href="/">homepage.</a><br>
6
+ Gomen oomf chan o(TヘTo)</p>
7
+ ===
package/vite.config.ts CHANGED
@@ -2,5 +2,6 @@ import { sveltekit } from "@sveltejs/kit/vite";
2
2
  import { defineConfig } from "vite";
3
3
 
4
4
  export default defineConfig({
5
- plugins: [sveltekit()]
5
+ plugins: [sveltekit()],
6
+ assetsInclude: ['**/*.yarn']
6
7
  });