Profectus-Demo/src/components/system/SavesManager.vue

264 lines
7.6 KiB
Vue
Raw Normal View History

2021-06-20 23:29:55 -05:00
<template>
2022-01-24 22:25:34 -06:00
<Modal v-model="isOpen" ref="modal">
<template v-slot:header>
<h2>Saves Manager</h2>
</template>
<template v-slot:body>
2022-01-24 22:25:34 -06:00
<Draggable
:list="settings.saves"
handle=".handle"
v-if="unref(modal?.isOpen)"
:itemKey="(save: string) => save"
>
<template #item="{ element }">
<Save
:save="saves[element]"
@open="openSave(element)"
@export="exportSave(element)"
@editName="name => editSave(element, name)"
@duplicate="duplicateSave(element)"
@delete="deleteSave(element)"
/>
</template>
</Draggable>
</template>
<template v-slot:footer>
<div class="modal-footer">
2022-01-13 22:25:47 -06:00
<Text
v-model="saveToImport"
title="Import Save"
placeholder="Paste your save here!"
:class="{ importingFailed }"
/>
<div class="field">
<span class="field-title">Create Save</span>
<div class="field-buttons">
<button class="button" @click="newSave">New Game</button>
<Select
v-if="Object.keys(bank).length > 0"
:options="bank"
2022-01-13 22:25:47 -06:00
:modelValue="[]"
@update:modelValue="preset => newFromPreset(preset as string)"
closeOnSelect
placeholder="Select preset"
class="presets"
/>
</div>
</div>
<div class="footer">
<div style="flex-grow: 1"></div>
2022-01-13 22:25:47 -06:00
<button class="button modal-default-button" @click="isOpen = false">
Close
</button>
</div>
</div>
</template>
</Modal>
2021-06-20 23:29:55 -05:00
</template>
2022-01-13 22:25:47 -06:00
<script setup lang="ts">
import Modal from "@/components/system/Modal.vue";
import player, { PlayerData } from "@/game/player";
import settings from "@/game/settings";
2022-01-24 22:25:34 -06:00
import { getUniqueID, loadSave, save, newSave } from "@/util/save";
import { ComponentPublicInstance, computed, nextTick, reactive, ref, unref, watch } from "vue";
2022-01-13 22:25:47 -06:00
import Select from "../fields/Select.vue";
import Text from "../fields/Text.vue";
import Save from "./Save.vue";
2022-01-24 22:25:34 -06:00
import Draggable from "vuedraggable";
2021-06-20 23:29:55 -05:00
2022-01-13 22:25:47 -06:00
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
2021-06-20 23:29:55 -05:00
2022-01-13 22:25:47 -06:00
const isOpen = ref(false);
2022-01-24 22:25:34 -06:00
const modal = ref<ComponentPublicInstance<typeof Modal> | null>(null);
2021-06-20 23:29:55 -05:00
2022-01-13 22:25:47 -06:00
defineExpose({
open() {
isOpen.value = true;
}
});
2021-06-20 23:29:55 -05:00
2022-01-13 22:25:47 -06:00
const importingFailed = ref(false);
const saveToImport = ref("");
2021-06-20 23:29:55 -05:00
2022-01-13 22:25:47 -06:00
watch(saveToImport, save => {
if (save) {
nextTick(() => {
try {
const playerData = JSON.parse(decodeURIComponent(escape(atob(save))));
if (typeof playerData !== "object") {
importingFailed.value = true;
return;
}
const id = getUniqueID();
playerData.id = id;
localStorage.setItem(
id,
2022-01-13 22:25:47 -06:00
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
2022-01-13 22:25:47 -06:00
saveToImport.value = "";
importingFailed.value = false;
settings.saves.push(id);
} catch (e) {
importingFailed.value = true;
}
2022-01-13 22:25:47 -06:00
});
} else {
importingFailed.value = false;
}
});
let bankContext = require.context("raw-loader!../../../saves", true, /\.txt$/);
let bank = ref(
bankContext.keys().reduce((acc: Array<{ label: string; value: string }>, curr) => {
// .slice(2, -4) strips the leading ./ and the trailing .txt
acc.push({
label: curr.slice(2, -4),
value: bankContext(curr).default
});
return acc;
}, [])
);
2021-06-20 23:29:55 -05:00
2022-01-24 22:25:34 -06:00
const cachedSaves = reactive<Record<string, LoadablePlayerData>>({});
function getCachedSave(id: string) {
if (!(id in cachedSaves)) {
const save = localStorage.getItem(id);
if (save == null) {
cachedSaves[id] = { error: `Save with id "${id}" doesn't exist`, id };
} else {
try {
cachedSaves[id] = JSON.parse(decodeURIComponent(escape(atob(save))));
cachedSaves[id].id = id;
} catch (error) {
cachedSaves[id] = { error, id };
}
}
2022-01-24 22:25:34 -06:00
}
return cachedSaves[id];
2022-01-13 22:25:47 -06:00
}
2022-01-24 22:25:34 -06:00
// Wipe cache whenever the modal is opened
watch(isOpen, isOpen => {
if (isOpen) {
Object.keys(cachedSaves).forEach(key => delete cachedSaves[key]);
}
});
const saves = computed(() =>
settings.saves.reduce((acc: Record<string, LoadablePlayerData>, curr: string) => {
acc[curr] = getCachedSave(curr);
return acc;
}, {})
);
2022-01-13 22:25:47 -06:00
function exportSave(id: string) {
let saveToExport;
if (player.id === id) {
saveToExport = save();
} else {
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(saves.value[id]))));
}
2022-01-13 22:25:47 -06:00
// Put on clipboard. Using the clipboard API asks for permissions and stuff
const el = document.createElement("textarea");
el.value = saveToExport;
document.body.appendChild(el);
el.select();
el.setSelectionRange(0, 99999);
document.execCommand("copy");
document.body.removeChild(el);
}
function duplicateSave(id: string) {
if (player.id === id) {
save();
}
const playerData = { ...saves.value[id], id: getUniqueID() };
localStorage.setItem(
playerData.id,
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
settings.saves.push(playerData.id);
}
function deleteSave(id: string) {
settings.saves = settings.saves.filter((save: string) => save !== id);
localStorage.removeItem(id);
}
function openSave(id: string) {
2022-01-24 22:23:30 -06:00
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
saves.value[player.id]!.time = player.time;
2022-01-13 22:25:47 -06:00
save();
2022-01-24 22:23:30 -06:00
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
loadSave(saves.value[id]!);
2022-01-13 22:25:47 -06:00
}
function newFromPreset(preset: string) {
const playerData = JSON.parse(decodeURIComponent(escape(atob(preset))));
playerData.id = getUniqueID();
localStorage.setItem(
playerData.id,
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
settings.saves.push(playerData.id);
}
function editSave(id: string, newName: string) {
2022-01-24 22:25:34 -06:00
const currSave = saves.value[id];
if (currSave) {
currSave.name = newName;
if (player.id === id) {
player.name = newName;
save();
} else {
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(currSave)))));
}
2022-01-13 22:25:47 -06:00
}
}
2021-06-20 23:29:55 -05:00
</script>
<style scoped>
.field form,
.field .field-title,
.field .field-buttons {
margin: 0;
2021-06-20 23:29:55 -05:00
}
.field-buttons {
display: flex;
2021-06-20 23:29:55 -05:00
}
.field-buttons .field {
margin: 0;
margin-left: 8px;
2021-06-20 23:29:55 -05:00
}
.modal-footer {
margin-top: -20px;
}
.footer {
display: flex;
margin-top: 20px;
2021-06-20 23:29:55 -05:00
}
</style>
<style>
.importingFailed input {
color: red;
2021-06-20 23:29:55 -05:00
}
.field-buttons .v-select {
width: 220px;
2021-07-24 17:08:52 -05:00
}
.presets .vue-select[aria-expanded="true"] vue-dropdown {
visibility: hidden;
2021-07-24 17:08:52 -05:00
}
2021-06-20 23:29:55 -05:00
</style>