@turnipxenon/pineapple 2.4.14 → 2.4.16
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/.idea/workspace.xml +64 -49
- package/.svelte-kit/__package__/app.postcss +15 -1
- package/.svelte-kit/__package__/components/DialogOverlay.svelte +48 -47
- package/.svelte-kit/__package__/components/dialog_manager/DialogManager.d.ts +8 -3
- package/.svelte-kit/__package__/components/dialog_manager/DialogManager.js +35 -11
- package/.svelte-kit/__package__/components/dialog_manager/DialogManagerStore.js +1 -1
- package/.svelte-kit/__package__/components/layouts/SeaweedBaseLayout.svelte +2 -2
- package/.svelte-kit/__package__/components/pineapple/PineappleBaseLayout.svelte +20 -7
- package/.svelte-kit/__package__/components/pineapple/PineappleBaseLayout.svelte.d.ts +3 -1
- package/.svelte-kit/__package__/components/pineapple/toast/Toast.svelte +1 -0
- package/.svelte-kit/__package__/scripts/pineapple_fiber/PineappleFiberParser.d.ts +2 -0
- package/.svelte-kit/__package__/scripts/pineapple_fiber/PineappleFiberParser.js +137 -0
- package/.svelte-kit/__package__/scripts/pineapple_fiber/PineappleWeaver.js +3 -134
- package/.svelte-kit/__package__/types/pineapple_fiber/DialogState.d.ts +2 -1
- package/.svelte-kit/__package__/types/pineapple_fiber/DialogState.js +1 -0
- package/.svelte-kit/ambient.d.ts +2 -0
- package/.svelte-kit/generated/server/internal.js +1 -1
- package/dist/app.postcss +15 -1
- package/dist/components/DialogOverlay.svelte +48 -47
- package/dist/components/dialog_manager/DialogManager.d.ts +8 -3
- package/dist/components/dialog_manager/DialogManager.js +35 -11
- package/dist/components/dialog_manager/DialogManagerStore.js +1 -1
- package/dist/components/layouts/SeaweedBaseLayout.svelte +2 -2
- package/dist/components/pineapple/PineappleBaseLayout.svelte +20 -7
- package/dist/components/pineapple/PineappleBaseLayout.svelte.d.ts +3 -1
- package/dist/components/pineapple/toast/Toast.svelte +1 -0
- package/dist/scripts/pineapple_fiber/PineappleFiberParser.d.ts +2 -0
- package/dist/scripts/pineapple_fiber/PineappleFiberParser.js +137 -0
- package/dist/scripts/pineapple_fiber/PineappleWeaver.js +3 -134
- package/dist/types/pineapple_fiber/DialogState.d.ts +2 -1
- package/dist/types/pineapple_fiber/DialogState.js +1 -0
- package/package.json +1 -1
- package/src/lib/app.postcss +15 -1
- package/src/lib/components/DialogOverlay.svelte +56 -54
- package/src/lib/components/dialog_manager/DialogManager.ts +38 -12
- package/src/lib/components/dialog_manager/DialogManagerStore.ts +1 -1
- package/src/lib/components/layouts/SeaweedBaseLayout.svelte +2 -2
- package/src/lib/components/pineapple/PineappleBaseLayout.svelte +21 -6
- package/src/lib/components/pineapple/toast/Toast.svelte +1 -0
- package/src/lib/scripts/pineapple_fiber/PineappleFiberParser.ts +177 -0
- package/src/lib/scripts/pineapple_fiber/PineappleWeaver.ts +3 -176
- package/src/lib/types/pineapple_fiber/DialogState.ts +2 -1
- package/src/routes/(pineapple)/+page.svelte +2 -2
- package/src/routes/(pineapple)/pineapple/+page.svelte +19 -1
- package/src/routes/(pineapple)/pineapple/TestDialog.yarn +7 -0
- package/vite.config.ts +2 -1
- package/.svelte-kit/__package__/components/pineapple/overlay_manager/OverlayManager.d.ts +0 -0
- package/.svelte-kit/__package__/components/pineapple/overlay_manager/OverlayManager.js +0 -1
- package/dist/components/pineapple/overlay_manager/OverlayManager.d.ts +0 -0
- package/dist/components/pineapple/overlay_manager/OverlayManager.js +0 -1
- 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 {
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
105
|
+
* @param newDialogTree
|
|
97
106
|
*/
|
|
98
|
-
setDialogTree = (
|
|
99
|
-
this.
|
|
107
|
+
setDialogTree = (newDialogTree: DialogDetail[]) => {
|
|
108
|
+
this.currentDialogTree = newDialogTree;
|
|
100
109
|
|
|
101
110
|
this.dialogMessageMap.clear();
|
|
102
|
-
|
|
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(
|
|
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-
|
|
70
|
-
--dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-
|
|
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
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
export let showDialogByDefault = false;
|
|
3
|
+
|
|
2
4
|
// For auto dark/light mode
|
|
3
5
|
import { AppBar, AppShell, autoModeWatcher, LightSwitch } from "@skeletonlabs/skeleton";
|
|
4
6
|
import RandomizedBackground from "$pkg/components/RandomizedBackground.svelte";
|
|
@@ -12,8 +14,10 @@
|
|
|
12
14
|
// import DialogOverlay from "$lib/components/DialogOverlay.svelte";
|
|
13
15
|
import AresLogo from "$pkg/assets/characters/ares/ares_logo.webp";
|
|
14
16
|
import FABIcon from "$pkg/assets/placeholder/placeholder_circle.png";
|
|
15
|
-
import
|
|
17
|
+
import CloseIcon from "$pkg/assets/icons/close.svg";
|
|
18
|
+
import { dialogManager, enableDialogueOverlay } from "$pkg/components/dialog_manager/DialogManagerStore";
|
|
16
19
|
import Toast from "$pkg/components/pineapple/toast/Toast.svelte";
|
|
20
|
+
import DialogOverlay from "$pkg/components/DialogOverlay.svelte";
|
|
17
21
|
// todo: clean up all these imports!
|
|
18
22
|
|
|
19
23
|
let pages: BreadcrumbData[] = [];
|
|
@@ -55,6 +59,8 @@
|
|
|
55
59
|
enableDialogueOverlay.subscribe((value) => {
|
|
56
60
|
enableDialogueOverlayValue = value;
|
|
57
61
|
});
|
|
62
|
+
|
|
63
|
+
enableDialogueOverlay.set(showDialogByDefault);
|
|
58
64
|
</script>
|
|
59
65
|
|
|
60
66
|
<!-- App Shell -->
|
|
@@ -64,9 +70,13 @@
|
|
|
64
70
|
|
|
65
71
|
<!--todo: turn off hidden when it's time-->
|
|
66
72
|
<button type="button" class="fab" on:click={()=>{
|
|
67
|
-
|
|
73
|
+
dialogManager.toggleDialogOverlay()
|
|
68
74
|
}}>
|
|
69
|
-
|
|
75
|
+
{#if (enableDialogueOverlayValue)}
|
|
76
|
+
<img class="img-icon" src={CloseIcon} alt="interactive floating action button represented as a turnip">
|
|
77
|
+
{:else }
|
|
78
|
+
<img src={FABIcon} alt="interactive floating action button represented as a turnip">
|
|
79
|
+
{/if}
|
|
70
80
|
</button>
|
|
71
81
|
|
|
72
82
|
<AppShell>
|
|
@@ -107,6 +117,8 @@
|
|
|
107
117
|
|
|
108
118
|
<Toast></Toast>
|
|
109
119
|
|
|
120
|
+
<DialogOverlay></DialogOverlay>
|
|
121
|
+
|
|
110
122
|
<div class="default-page-container">
|
|
111
123
|
<slot />
|
|
112
124
|
<div class="footer-space" />
|
|
@@ -126,8 +138,8 @@
|
|
|
126
138
|
|
|
127
139
|
<style lang="postcss">
|
|
128
140
|
:root {
|
|
129
|
-
--dialog-
|
|
130
|
-
--dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-
|
|
141
|
+
--dialog-start-pad: clamp(0em, 5vw, 2em);
|
|
142
|
+
--dialog-box-width: min(calc(50em + 4em), calc(100vw - var(--dialog-start-pad) - var(--theme-border-base)));
|
|
131
143
|
--dialog-box-height: clamp(15em, 50vw, 18em);
|
|
132
144
|
|
|
133
145
|
/** FAB icon margin/position calculation origin:
|
|
@@ -147,7 +159,6 @@
|
|
|
147
159
|
.default-page-container {
|
|
148
160
|
@apply flex justify-center items-center;
|
|
149
161
|
margin-top: 4em;
|
|
150
|
-
margin-left: clamp(1em, 15vw, 10em);
|
|
151
162
|
margin-right: 1em;
|
|
152
163
|
flex-direction: column;
|
|
153
164
|
z-index: 0;
|
|
@@ -197,6 +208,10 @@
|
|
|
197
208
|
border-radius: 50%;
|
|
198
209
|
}
|
|
199
210
|
|
|
211
|
+
.fab > img {
|
|
212
|
+
width: 100%;
|
|
213
|
+
}
|
|
214
|
+
|
|
200
215
|
.fab:dir(ltr) {
|
|
201
216
|
right: var(--fab-margin);
|
|
202
217
|
}
|
|
@@ -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) {
|
|
@@ -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>
|
|
22
|
-
<button on:click={createGoToFunction("portfolio")}><h2>
|
|
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">
|