Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions components/Blueprints/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,32 @@
<p class="mb-2 text-sm transition duration-500">{{ blueprints.length ?? 0 }}/10 accounts</p>

<ol class="flex w-full flex-col items-center justify-start gap-1">
<li v-for="(account, index) in blueprints" class="w-full">
<li
v-for="(account, index) in blueprints"
class="w-full"
:class="{ 'du-tooltip': userStore.isUnsavedAccount && Number(route.query.a) !== index }"
data-tip="Save your current account first!"
>
<button
class="flex w-full cursor-pointer flex-col items-center justify-center rounded-lg py-1 transition duration-500 hover:duration-300"
:class="
:class="[
route.query.a === index.toString()
? 'bg-neutral-200 hover:bg-neutral-300/75 dark:bg-neutral-700 dark:hover:bg-neutral-600'
: 'bg-neutral-100/25 hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700'
"
: 'bg-neutral-100/25 hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700',
{ 'pointer-events-none opacity-50': userStore.isUnsavedAccount && Number(route.query.a) !== index }
]"
type="button"
@click="goToAccount(index)"
>
<h5 class="justfiy-center inline-flex items-center font-medium transition duration-500">
<h5 v-if="userStore.user" class="justfiy-center inline-flex items-center font-medium transition duration-500">
{{ getObjectKey(account) }}
<span class="du-tooltip ms-2" data-tip="Edit Name">
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('editName', accountIndex)">
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('editName', index)">
<img class="size-4 select-none transition duration-500 dark:invert" src="/ui/pencil.svg" alt="Edit account name" />
</button>
</span>
<span class="du-tooltip" data-tip="Delete">
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('delete', accountIndex)">
<span v-if="userStore.user.blueprints.length > 1" class="du-tooltip" data-tip="Delete">
<button class="fo-btn fo-btn-circle fo-btn-text size-6 min-h-6" type="button" @click.stop="emit('delete', index)">
<img class="size-4 select-none transition duration-500 dark:invert" src="/ui/trash.svg" alt="Delete account" />
</button>
</span>
Expand All @@ -83,7 +89,7 @@
</li>
</ol>
<button
v-if="blueprints.length < 10"
v-if="!userStore.isUnsavedAccount && blueprints.length < 10"
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"
type="button"
@click="emit('createNew')"
Expand All @@ -100,7 +106,6 @@
<script setup lang="ts">
const props = defineProps<{
close: boolean;
accountIndex: number;
data: BlueprintAllShip[] | undefined;
isOwner: boolean | undefined;
}>();
Expand Down Expand Up @@ -154,6 +159,7 @@ function getTotalTP(ships: Record<string, (string | number)[]>[]) {
}

function goToAccount(index: number) {
if (userStore.isUnsavedAccount) return;
void router.push(`/modules/blueprint-tracker?a=${index}`);
}
</script>
Expand Down
8 changes: 6 additions & 2 deletions components/Blueprints/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
>
<BlueprintsSettings
:close="closeSettings"
:account-index="accountIndex"
:data="data"
:is-owner="isOwner"
@edit-name="(acc) => (editName = acc)"
Expand Down Expand Up @@ -175,7 +174,10 @@ onMounted(() => window.addEventListener("scroll", detectSticky));
onBeforeUnmount(() => window.removeEventListener("scroll", detectSticky));

function createNewAccount() {
void router.push({ query: { ...route.query, a: props.accountIndex + 1 } });
if (!userStore.user) return;

userStore.createNewAccount = true;
void router.push({ query: { ...route.query, a: userStore.user.blueprints.length } });
}

async function saveBlueprints() {
Expand All @@ -202,6 +204,7 @@ async function saveBlueprints() {

if (fetchSuccess) {
success.value = true;
userStore.isUnsavedAccount = false;
setTimeout(() => {
userStore.hasUnsavedChanges = false;
if (newBlueprints && userStore.user) userStore.user.blueprints = newBlueprints;
Expand Down Expand Up @@ -254,6 +257,7 @@ async function deleteAccount() {
if (!fetchSuccess && error) return console.error(error);

deleteSuccess.value = true;
void router.replace({ query: { ...route.query, a: userStore.user.blueprints.length - 2 } });

setTimeout(() => {
deleteModal.value = undefined;
Expand Down
62 changes: 47 additions & 15 deletions pages/modules/blueprint-tracker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const accountIndex = ref(0);
watch(
data,
(val) => {
if (userStore.user?.uid === route.query.u && val) userStore.blueprintsAutosave = val;
if (val && userStore.user?.uid === route.query.u) userStore.blueprintsAutosave = val;
},
{ deep: true }
);
Expand Down Expand Up @@ -140,15 +140,20 @@ onMounted(() => {
showVariants.value = localStorage.getItem("variants") === "true";
});

async function getBlueprints(data: AllShip[]) {
const {
success,
error,
content,
lastSaved: bpLastSaved
} = await $fetch("/api/getBlueprints", { method: "POST", body: { uid: route.query.u ?? userStore.user?.uid, accountIndex: accountIndex.value } });

if (!success && error) console.error(error);
async function getAccount(data: AllShip[]): Promise<BlueprintAllShip[] | undefined> {
// prettier-ignore
const { success, error, content, lastSaved: bpLastSaved } =
await $fetch("/api/getBlueprints", { method: "POST", body: { uid: route.query.u ?? userStore.user?.uid, accountIndex: accountIndex.value } });

if (!success && error) {
console.error(error);
if (route.query.a !== "0") {
await router.replace({ query: { ...route.query, a: 0 } });
userStore.hasUnsavedChanges = false;
window.location.reload();
return;
}
}

if (success && content && bpLastSaved) {
lastSaved.value = bpLastSaved;
Expand All @@ -167,9 +172,14 @@ async function getBlueprints(data: AllShip[]) {
return result as BlueprintAllShip;
});
}
}

function createAccount(data: AllShip[]): BlueprintAllShip[] {
if (userStore.user && !userStore.user.blueprints.some((account) => getObjectKey(account) === "Unnamed" && getObjectValue(account).length === 0)) userStore.user.blueprints.push({ Unnamed: [] });

lastSaved.value = new Date().toISOString().slice(0, 10);
if (userStore.user) userStore.user.blueprints.push({ Unnamed: [] });
userStore.createNewAccount = false;
userStore.isUnsavedAccount = true;
return data.map((ship) => {
const result: Record<any, any> = {
...ship,
Expand All @@ -184,6 +194,22 @@ async function getBlueprints(data: AllShip[]) {
});
}

async function getBlueprints(data: AllShip[]): Promise<BlueprintAllShip[]> {
if (route.query.u === undefined && !userStore.user)
return await waitUntil(
() => Boolean(route.query.u ?? userStore.user),
async () => await getBlueprints(data),
new Promise((resolve) => resolve([]))
);

if (!userStore.createNewAccount) {
const account = await getAccount(data);
if (account) return account;
}

return createAccount(data);
}

watch(
() => route.query,
async (val) => {
Expand All @@ -197,11 +223,17 @@ watch(
}
);

watch(isOwner, async (val) => {
if (!userStore.shipData) return;
onMounted(() => {
const stop = watch(isOwner, async (val) => {
if (!userStore.shipData) return;

if (val) return (data.value = userStore.blueprintsAutosave ?? (await getBlueprints(userStore.shipData)));
data.value = await getBlueprints(userStore.shipData);
if (val) {
data.value = userStore.blueprintsAutosave ?? (await getBlueprints(userStore.shipData));
return stop();
}
data.value = await getBlueprints(userStore.shipData);
stop();
});
});

function getTotalTP(ships: BlueprintAllShip[]) {
Expand Down
4 changes: 3 additions & 1 deletion stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const useUserStore = defineStore("userStore", () => {
// blueprint tracker
const blueprintsAutosave = ref<BlueprintAllShip[]>();
const hasUnsavedChanges = ref(false);
const createNewAccount = ref(false);
const isUnsavedAccount = ref(false);

async function getUser(createUserIfFail = true) {
const uid = localStorage.getItem("uid");
Expand Down Expand Up @@ -60,5 +62,5 @@ export const useUserStore = defineStore("userStore", () => {
void fetchShipData();
}

return { isDarkMode, alert, user, shipData, shipDifficulties, blueprintsAutosave, hasUnsavedChanges, getUser, init };
return { isDarkMode, alert, user, shipData, shipDifficulties, blueprintsAutosave, hasUnsavedChanges, createNewAccount, isUnsavedAccount, getUser, init };
});
39 changes: 39 additions & 0 deletions utils/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@ export function getRandomItem<T>(array: T[] | string) {
return array[getRandomInt(0, array.length - 1)];
}

/**
* Waits until a condition is fulfilled before executing a callback.
*
* @param condition - Function that returns a boolean indicating whether the condition is fulfilled.
* @param callback - Function to execute once the condition is fulfilled.
* @param defaultValue - Default value to return if the condition is not fulfilled within the specified timeout. Defaults to undefined.
* @param timeout - Maximum time, in milliseconds, to wait for the condition to be fulfilled. Defaults to 1000ms.
* @param interval - Time, in milliseconds, between checks of the condition. Defaults to 100ms.
* @returns The result of the callback function.
*/
export async function waitUntil<T>(condition: () => boolean, callback: () => T, defaultValue: T, timeout?: number, interval?: number): Promise<T>;
/**
* Waits until a condition is fulfilled before executing a callback.
*
* @param condition - Function that returns a boolean indicating whether the condition is fulfilled.
* @param callback - Function to execute once the condition is fulfilled.
* @param timeout - Maximum time, in milliseconds, to wait for the condition to be fulfilled. Defaults to 1000ms.
* @param interval - Time, in milliseconds, between checks of the condition. Defaults to 100ms.
* @returns The result of the callback function.
*/
export async function waitUntil<T>(condition: () => boolean, callback: () => T, timeout?: number, interval?: number): Promise<T | undefined>;
export async function waitUntil<T>(condition: () => boolean, callback: () => T, defaultValue?: T, timeout = 1000, interval = 100): Promise<T | undefined> {
return new Promise((resolve) => {
const timeoutId = setTimeout(() => {
// eslint-disable-next-line no-use-before-define
clearInterval(intervalId);
resolve(defaultValue);
}, timeout);

const intervalId = setInterval(() => {
if (condition()) {
clearTimeout(timeoutId);
clearInterval(intervalId);
resolve(callback());
}
}, interval);
});
}

const dateOptions: Readonly<Record<string, Record<string, string>>> = {
full: { dateStyle: "long", timeZone: "UTC" },
numeric: { dateStyle: "short", timeZone: "UTC" }
Expand Down