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

264 lines
7.3 KiB
Vue
Raw Normal View History

2021-06-21 04:29:55 +00:00
<template>
2022-01-14 04:25:47 +00:00
<Modal v-model="isOpen">
<template v-slot:header>
<h2>Saves Manager</h2>
</template>
<template v-slot:body>
<div v-sortable="{ update, handle: '.handle' }">
2022-01-14 04:25:47 +00:00
<Save
v-for="(save, index) in saves"
:key="index"
:save="save"
@open="openSave(save.id)"
@export="exportSave(save.id)"
2022-01-14 04:25:47 +00:00
@editName="name => editSave(save.id, name)"
@duplicate="duplicateSave(save.id)"
@delete="deleteSave(save.id)"
/>
</div>
</template>
<template v-slot:footer>
<div class="modal-footer">
2022-01-14 04:25:47 +00: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-14 04:25:47 +00: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-14 04:25:47 +00:00
<button class="button modal-default-button" @click="isOpen = false">
Close
</button>
</div>
</div>
</template>
</Modal>
2021-06-21 04:29:55 +00:00
</template>
2022-01-14 04:25:47 +00: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-14 04:25:47 +00:00
import { getUniqueID, loadSave, save, newSave as createNewSave } from "@/util/save";
import { nextTick, ref, watch } from "vue";
import Select from "../fields/Select.vue";
import Text from "../fields/Text.vue";
import Save from "./Save.vue";
import vSortable from "vue-sortable";
2021-06-21 04:29:55 +00:00
2022-01-14 04:25:47 +00:00
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
2021-06-21 04:29:55 +00:00
2022-01-14 04:25:47 +00:00
const isOpen = ref(false);
2021-06-21 04:29:55 +00:00
2022-01-14 04:25:47 +00:00
defineExpose({
open() {
isOpen.value = true;
}
});
2021-06-21 04:29:55 +00:00
2022-01-14 04:25:47 +00:00
const importingFailed = ref(false);
const saveToImport = ref("");
2021-06-21 04:29:55 +00:00
2022-01-14 04:25:47 +00:00
watch(isOpen, isOpen => {
if (isOpen) {
loadSaveData();
}
});
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-14 04:25:47 +00:00
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
2022-01-14 04:25:47 +00:00
saves.value[id] = playerData;
saveToImport.value = "";
importingFailed.value = false;
settings.saves.push(id);
} catch (e) {
importingFailed.value = true;
}
2022-01-14 04:25:47 +00: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-21 04:29:55 +00:00
2022-01-14 04:25:47 +00:00
const saves = ref<Record<string, LoadablePlayerData>>({});
function loadSaveData() {
saves.value = settings.saves.reduce((acc: Record<string, LoadablePlayerData>, curr: string) => {
try {
const save = localStorage.getItem(curr);
if (save == null) {
acc[curr] = { error: `Save with id "${curr}" doesn't exist`, id: curr };
} else {
2022-01-14 04:25:47 +00:00
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(save))));
acc[curr].id = curr;
}
2022-01-14 04:25:47 +00:00
} catch (error) {
console.warn(`Can't load save with id "${curr}"`, error);
acc[curr] = { error, id: curr };
}
2022-01-14 04:25:47 +00:00
return acc;
}, {});
}
function exportSave(id: string) {
let saveToExport;
if (player.id === id) {
saveToExport = save();
} else {
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(saves.value[id]))));
}
2022-01-14 04:25:47 +00: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);
saves.value[playerData.id] = playerData;
}
function deleteSave(id: string) {
settings.saves = settings.saves.filter((save: string) => save !== id);
localStorage.removeItem(id);
delete saves.value[id];
}
function openSave(id: string) {
saves.value[player.id].time = player.time;
save();
loadSave(saves.value[id]);
}
function newSave() {
const playerData = createNewSave();
saves.value[playerData.id] = playerData;
}
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);
saves.value[playerData.id] = playerData;
}
function editSave(id: string, newName: string) {
saves.value[id].name = newName;
if (player.id === id) {
player.name = newName;
save();
} else {
localStorage.setItem(
id,
btoa(unescape(encodeURIComponent(JSON.stringify(saves.value[id]))))
);
}
}
function update(e: { newIndex: number; oldIndex: number }) {
settings.saves.splice(e.newIndex, 0, settings.saves.splice(e.oldIndex, 1)[0]);
}
2021-06-21 04:29:55 +00:00
</script>
<style scoped>
.field form,
.field .field-title,
.field .field-buttons {
margin: 0;
2021-06-21 04:29:55 +00:00
}
.field-buttons {
display: flex;
2021-06-21 04:29:55 +00:00
}
.field-buttons .field {
margin: 0;
margin-left: 8px;
2021-06-21 04:29:55 +00:00
}
.modal-footer {
margin-top: -20px;
}
.footer {
display: flex;
margin-top: 20px;
2021-06-21 04:29:55 +00:00
}
</style>
<style>
.importingFailed input {
color: red;
2021-06-21 04:29:55 +00:00
}
.field-buttons .v-select {
width: 220px;
2021-07-24 22:08:52 +00:00
}
.presets .vue-select[aria-expanded="true"] vue-dropdown {
visibility: hidden;
2021-07-24 22:08:52 +00:00
}
2021-06-21 04:29:55 +00:00
</style>