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

278 lines
8.9 KiB
Vue
Raw Normal View History

2021-06-21 04:29:55 +00:00
<template>
<Modal :show="show" @close="$emit('closeDialog', 'Saves')">
<template v-slot:header>
<h2>Saves Manager</h2>
</template>
<template v-slot:body>
<div v-sortable="{ update, handle: '.handle' }">
<save
v-for="(save, index) in saves"
:key="index"
:save="save"
@open="openSave(save.id)"
@export="exportSave(save.id)"
@editSave="name => editSave(save.id, name)"
@duplicate="duplicateSave(save.id)"
@delete="deleteSave(save.id)"
/>
</div>
</template>
<template v-slot:footer>
<div class="modal-footer">
<TextField
:value="saveToImport"
@submit="importSave"
@change="importSave"
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"
closeOnSelect
@change="newFromPreset"
placeholder="Select preset"
class="presets"
:value="[]"
/>
</div>
</div>
<div class="footer">
<div style="flex-grow: 1"></div>
<button
class="button modal-default-button"
@click="$emit('closeDialog', 'Saves')"
>
Close
</button>
</div>
</div>
</template>
</Modal>
2021-06-21 04:29:55 +00:00
</template>
<script lang="ts">
import player from "@/game/player";
import settings from "@/game/settings";
import state from "@/game/state";
import { PlayerData } from "@/typings/player";
import { getUniqueID, loadSave, newSave, save } from "@/util/save";
import { defineComponent } from "vue";
2021-06-21 04:29:55 +00:00
export default defineComponent({
name: "SavesManager",
props: {
show: Boolean
},
emits: ["closeDialog"],
data() {
let bankContext = require.context("raw-loader!../../../saves", true, /\.txt$/);
let bank = 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;
}, []);
return {
importingFailed: false,
saves: {}, // Gets populated when the modal is opened
saveToImport: "",
bank
} as {
importingFailed: boolean;
saves: Record<
string,
Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }
>;
saveToImport: string;
bank: Array<{ label: string; value: string }>;
};
},
watch: {
show(newValue) {
if (newValue) {
this.loadSaveData();
}
}
},
methods: {
loadSaveData() {
this.saves = settings.saves.reduce(
(
acc: Record<
string,
Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }
>,
curr: string
) => {
try {
const save = localStorage.getItem(curr);
if (save == null) {
acc[curr] = { error: `Save with id "${curr}" doesn't exist`, id: curr };
} else {
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(save))));
acc[curr].id = curr;
}
} catch (error) {
console.warn(`Can't load save with id "${curr}"`, error);
acc[curr] = { error, id: curr };
}
return acc;
},
{}
);
},
exportSave(id: string) {
let saveToExport;
if (player.id === id) {
save();
saveToExport = state.saveToExport;
} else {
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id]))));
}
2021-06-21 04:29:55 +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);
},
duplicateSave(id: string) {
if (player.id === id) {
save();
}
2021-06-21 04:29:55 +00:00
const playerData = { ...this.saves[id], id: getUniqueID() };
localStorage.setItem(
playerData.id,
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
2021-06-21 04:29:55 +00:00
settings.saves.push(playerData.id);
this.saves[playerData.id] = playerData;
},
deleteSave(id: string) {
settings.saves = settings.saves.filter((save: string) => save !== id);
localStorage.removeItem(id);
delete this.saves[id];
},
openSave(id: string) {
this.saves[player.id].time = player.time;
2021-08-25 00:48:07 +00:00
save();
loadSave(this.saves[id]);
},
newSave() {
const playerData = newSave();
this.saves[playerData.id] = playerData;
},
newFromPreset(preset: string) {
const playerData = JSON.parse(decodeURIComponent(escape(atob(preset))));
playerData.id = getUniqueID();
localStorage.setItem(
playerData.id,
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
2021-06-21 04:29:55 +00:00
settings.saves.push(playerData.id);
this.saves[playerData.id] = playerData;
},
editSave(id: string, newName: string) {
this.saves[id].name = newName;
if (player.id === id) {
player.name = newName;
save();
} else {
localStorage.setItem(
id,
btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id]))))
);
}
},
importSave(text: string) {
this.saveToImport = text;
if (text) {
this.$nextTick(() => {
try {
const playerData = JSON.parse(decodeURIComponent(escape(atob(text))));
if (typeof playerData !== "object") {
this.importingFailed = true;
return;
}
const id = getUniqueID();
playerData.id = id;
localStorage.setItem(
id,
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
this.saves[id] = playerData;
this.saveToImport = "";
this.importingFailed = false;
2021-06-21 04:29:55 +00:00
settings.saves.push(id);
} catch (e) {
this.importingFailed = true;
}
});
} else {
this.importingFailed = false;
}
},
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>