Skip to content

Commit f2dc679

Browse files
authored
Blueprint Tracker fixes (#31)
1 parent fcbac08 commit f2dc679

File tree

5 files changed

+111
-28
lines changed

5 files changed

+111
-28
lines changed

components/Blueprints/Settings.vue

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,32 @@
5151
<p class="mb-2 text-sm transition duration-500">{{ blueprints.length ?? 0 }}/10 accounts</p>
5252

5353
<ol class="flex w-full flex-col items-center justify-start gap-1">
54-
<li v-for="(account, index) in blueprints" class="w-full">
54+
<li
55+
v-for="(account, index) in blueprints"
56+
class="w-full"
57+
:class="{ 'du-tooltip': userStore.isUnsavedAccount && Number(route.query.a) !== index }"
58+
data-tip="Save your current account first!"
59+
>
5560
<button
5661
class="flex w-full cursor-pointer flex-col items-center justify-center rounded-lg py-1 transition duration-500 hover:duration-300"
57-
:class="
62+
:class="[
5863
route.query.a === index.toString()
5964
? 'bg-neutral-200 hover:bg-neutral-300/75 dark:bg-neutral-700 dark:hover:bg-neutral-600'
60-
: 'bg-neutral-100/25 hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700'
61-
"
65+
: 'bg-neutral-100/25 hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700',
66+
{ 'pointer-events-none opacity-50': userStore.isUnsavedAccount && Number(route.query.a) !== index }
67+
]"
6268
type="button"
6369
@click="goToAccount(index)"
6470
>
65-
<h5 class="justfiy-center inline-flex items-center font-medium transition duration-500">
71+
<h5 v-if="userStore.user" class="justfiy-center inline-flex items-center font-medium transition duration-500">
6672
{{ getObjectKey(account) }}
6773
<span class="du-tooltip ms-2" data-tip="Edit Name">
68-
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('editName', accountIndex)">
74+
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('editName', index)">
6975
<img class="size-4 select-none transition duration-500 dark:invert" src="/ui/pencil.svg" alt="Edit account name" />
7076
</button>
7177
</span>
72-
<span class="du-tooltip" data-tip="Delete">
73-
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('delete', accountIndex)">
78+
<span v-if="userStore.user.blueprints.length > 1" class="du-tooltip" data-tip="Delete">
79+
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('delete', index)">
7480
<img class="size-4 select-none transition duration-500 dark:invert" src="/ui/trash.svg" alt="Delete account" />
7581
</button>
7682
</span>
@@ -83,7 +89,7 @@
8389
</li>
8490
</ol>
8591
<button
86-
v-if="blueprints.length < 10"
92+
v-if="!userStore.isUnsavedAccount && blueprints.length < 10"
8793
class="mt-1 flex w-full cursor-pointer flex-col items-center justify-center rounded-lg py-2 transition hover:bg-neutral-100 dark:hover:bg-neutral-700"
8894
type="button"
8995
@click="emit('createNew')"
@@ -100,7 +106,6 @@
100106
<script setup lang="ts">
101107
const props = defineProps<{
102108
close: boolean;
103-
accountIndex: number;
104109
data: BlueprintAllShip[] | undefined;
105110
isOwner: boolean | undefined;
106111
}>();
@@ -154,6 +159,7 @@ function getTotalTP(ships: Record<string, (string | number)[]>[]) {
154159
}
155160
156161
function goToAccount(index: number) {
162+
if (userStore.isUnsavedAccount) return;
157163
void router.push(`/modules/blueprint-tracker?a=${index}`);
158164
}
159165
</script>

components/Blueprints/Toolbar.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
>
77
<BlueprintsSettings
88
:close="closeSettings"
9-
:account-index="accountIndex"
109
:data="data"
1110
:is-owner="isOwner"
1211
@edit-name="(acc) => (editName = acc)"
@@ -175,7 +174,10 @@ onMounted(() => window.addEventListener("scroll", detectSticky));
175174
onBeforeUnmount(() => window.removeEventListener("scroll", detectSticky));
176175
177176
function createNewAccount() {
178-
void router.push({ query: { ...route.query, a: props.accountIndex + 1 } });
177+
if (!userStore.user) return;
178+
179+
userStore.createNewAccount = true;
180+
void router.push({ query: { ...route.query, a: userStore.user.blueprints.length } });
179181
}
180182
181183
async function saveBlueprints() {
@@ -202,6 +204,7 @@ async function saveBlueprints() {
202204
203205
if (fetchSuccess) {
204206
success.value = true;
207+
userStore.isUnsavedAccount = false;
205208
setTimeout(() => {
206209
userStore.hasUnsavedChanges = false;
207210
if (newBlueprints && userStore.user) userStore.user.blueprints = newBlueprints;
@@ -254,6 +257,7 @@ async function deleteAccount() {
254257
if (!fetchSuccess && error) return console.error(error);
255258
256259
deleteSuccess.value = true;
260+
void router.replace({ query: { ...route.query, a: userStore.user.blueprints.length - 2 } });
257261
258262
setTimeout(() => {
259263
deleteModal.value = undefined;

pages/modules/blueprint-tracker.vue

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const accountIndex = ref(0);
6363
watch(
6464
data,
6565
(val) => {
66-
if (userStore.user?.uid === route.query.u && val) userStore.blueprintsAutosave = val;
66+
if (val && userStore.user?.uid === route.query.u) userStore.blueprintsAutosave = val;
6767
},
6868
{ deep: true }
6969
);
@@ -140,15 +140,20 @@ onMounted(() => {
140140
showVariants.value = localStorage.getItem("variants") === "true";
141141
});
142142
143-
async function getBlueprints(data: AllShip[]) {
144-
const {
145-
success,
146-
error,
147-
content,
148-
lastSaved: bpLastSaved
149-
} = await $fetch("/api/getBlueprints", { method: "POST", body: { uid: route.query.u ?? userStore.user?.uid, accountIndex: accountIndex.value } });
150-
151-
if (!success && error) console.error(error);
143+
async function getAccount(data: AllShip[]): Promise<BlueprintAllShip[] | undefined> {
144+
// prettier-ignore
145+
const { success, error, content, lastSaved: bpLastSaved } =
146+
await $fetch("/api/getBlueprints", { method: "POST", body: { uid: route.query.u ?? userStore.user?.uid, accountIndex: accountIndex.value } });
147+
148+
if (!success && error) {
149+
console.error(error);
150+
if (route.query.a !== "0") {
151+
await router.replace({ query: { ...route.query, a: 0 } });
152+
userStore.hasUnsavedChanges = false;
153+
window.location.reload();
154+
return;
155+
}
156+
}
152157
153158
if (success && content && bpLastSaved) {
154159
lastSaved.value = bpLastSaved;
@@ -167,9 +172,14 @@ async function getBlueprints(data: AllShip[]) {
167172
return result as BlueprintAllShip;
168173
});
169174
}
175+
}
176+
177+
function createAccount(data: AllShip[]): BlueprintAllShip[] {
178+
if (userStore.user && !userStore.user.blueprints.some((account) => getObjectKey(account) === "Unnamed" && getObjectValue(account).length === 0)) userStore.user.blueprints.push({ Unnamed: [] });
170179
171180
lastSaved.value = new Date().toISOString().slice(0, 10);
172-
if (userStore.user) userStore.user.blueprints.push({ Unnamed: [] });
181+
userStore.createNewAccount = false;
182+
userStore.isUnsavedAccount = true;
173183
return data.map((ship) => {
174184
const result: Record<any, any> = {
175185
...ship,
@@ -184,6 +194,22 @@ async function getBlueprints(data: AllShip[]) {
184194
});
185195
}
186196
197+
async function getBlueprints(data: AllShip[]): Promise<BlueprintAllShip[]> {
198+
if (route.query.u === undefined && !userStore.user)
199+
return await waitUntil(
200+
() => Boolean(route.query.u ?? userStore.user),
201+
async () => await getBlueprints(data),
202+
new Promise((resolve) => resolve([]))
203+
);
204+
205+
if (!userStore.createNewAccount) {
206+
const account = await getAccount(data);
207+
if (account) return account;
208+
}
209+
210+
return createAccount(data);
211+
}
212+
187213
watch(
188214
() => route.query,
189215
async (val) => {
@@ -197,11 +223,17 @@ watch(
197223
}
198224
);
199225
200-
watch(isOwner, async (val) => {
201-
if (!userStore.shipData) return;
226+
onMounted(() => {
227+
const stop = watch(isOwner, async (val) => {
228+
if (!userStore.shipData) return;
202229
203-
if (val) return (data.value = userStore.blueprintsAutosave ?? (await getBlueprints(userStore.shipData)));
204-
data.value = await getBlueprints(userStore.shipData);
230+
if (val) {
231+
data.value = userStore.blueprintsAutosave ?? (await getBlueprints(userStore.shipData));
232+
return stop();
233+
}
234+
data.value = await getBlueprints(userStore.shipData);
235+
stop();
236+
});
205237
});
206238
207239
function getTotalTP(ships: BlueprintAllShip[]) {

stores/user.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export const useUserStore = defineStore("userStore", () => {
88
// blueprint tracker
99
const blueprintsAutosave = ref<BlueprintAllShip[]>();
1010
const hasUnsavedChanges = ref(false);
11+
const createNewAccount = ref(false);
12+
const isUnsavedAccount = ref(false);
1113

1214
async function getUser(createUserIfFail = true) {
1315
const uid = localStorage.getItem("uid");
@@ -60,5 +62,5 @@ export const useUserStore = defineStore("userStore", () => {
6062
void fetchShipData();
6163
}
6264

63-
return { isDarkMode, alert, user, shipData, shipDifficulties, blueprintsAutosave, hasUnsavedChanges, getUser, init };
65+
return { isDarkMode, alert, user, shipData, shipDifficulties, blueprintsAutosave, hasUnsavedChanges, createNewAccount, isUnsavedAccount, getUser, init };
6466
});

utils/functions.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ export function getRandomItem<T>(array: T[] | string) {
2121
return array[getRandomInt(0, array.length - 1)];
2222
}
2323

24+
/**
25+
* Waits until a condition is fulfilled before executing a callback.
26+
*
27+
* @param condition - Function that returns a boolean indicating whether the condition is fulfilled.
28+
* @param callback - Function to execute once the condition is fulfilled.
29+
* @param defaultValue - Default value to return if the condition is not fulfilled within the specified timeout. Defaults to undefined.
30+
* @param timeout - Maximum time, in milliseconds, to wait for the condition to be fulfilled. Defaults to 1000ms.
31+
* @param interval - Time, in milliseconds, between checks of the condition. Defaults to 100ms.
32+
* @returns The result of the callback function.
33+
*/
34+
export async function waitUntil<T>(condition: () => boolean, callback: () => T, defaultValue: T, timeout?: number, interval?: number): Promise<T>;
35+
/**
36+
* Waits until a condition is fulfilled before executing a callback.
37+
*
38+
* @param condition - Function that returns a boolean indicating whether the condition is fulfilled.
39+
* @param callback - Function to execute once the condition is fulfilled.
40+
* @param timeout - Maximum time, in milliseconds, to wait for the condition to be fulfilled. Defaults to 1000ms.
41+
* @param interval - Time, in milliseconds, between checks of the condition. Defaults to 100ms.
42+
* @returns The result of the callback function.
43+
*/
44+
export async function waitUntil<T>(condition: () => boolean, callback: () => T, timeout?: number, interval?: number): Promise<T | undefined>;
45+
export async function waitUntil<T>(condition: () => boolean, callback: () => T, defaultValue?: T, timeout = 1000, interval = 100): Promise<T | undefined> {
46+
return new Promise((resolve) => {
47+
const timeoutId = setTimeout(() => {
48+
// eslint-disable-next-line no-use-before-define
49+
clearInterval(intervalId);
50+
resolve(defaultValue);
51+
}, timeout);
52+
53+
const intervalId = setInterval(() => {
54+
if (condition()) {
55+
clearTimeout(timeoutId);
56+
clearInterval(intervalId);
57+
resolve(callback());
58+
}
59+
}, interval);
60+
});
61+
}
62+
2463
const dateOptions: Readonly<Record<string, Record<string, string>>> = {
2564
full: { dateStyle: "long", timeZone: "UTC" },
2665
numeric: { dateStyle: "short", timeZone: "UTC" }

0 commit comments

Comments
 (0)