loading and saving to localstorage and filesystem
This commit is contained in:
parent
033688dde4
commit
408b3d156d
8 changed files with 186 additions and 18 deletions
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.png" />
|
<link rel="icon" href="/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Starsy ̋̄— Star System Generator</title>
|
<title>Starsy — Star System Generator</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="theme-inverse">
|
<body class="theme-inverse">
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
94
src/App.vue
94
src/App.vue
|
@ -8,13 +8,53 @@
|
||||||
|
|
||||||
<section id="settings">
|
<section id="settings">
|
||||||
<ObjectSettings v-if="selectedObject" />
|
<ObjectSettings v-if="selectedObject" />
|
||||||
<Tips>
|
<AppMenu default-slot="tips">
|
||||||
<li>Edit planets by clicking directly inside the graphic or in the table below.</li>
|
<template #tips>
|
||||||
<li>Drag planets around to change their distance.</li>
|
<ul>
|
||||||
<li>The last removed planet can be restored from the table.</li>
|
<li>Edit planets by clicking directly inside the graphic or in the table below.</li>
|
||||||
<li><strong>ONLY THE LAST</strong> removed planet can be restored.</li>
|
<li>Drag planets around to change their distance.</li>
|
||||||
<li>Click on a planet to get more tips.</li>
|
<li>The last removed planet can be restored from the table.</li>
|
||||||
</Tips>
|
<li><strong>ONLY THE LAST</strong> removed planet can be restored.</li>
|
||||||
|
<li>Click on a planet to get more tips.</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
<template #load>
|
||||||
|
<p><i>(Careful! Loading will overwrite the current state!)</i></p>
|
||||||
|
<p>
|
||||||
|
<b>Local Storage</b>
|
||||||
|
<br />
|
||||||
|
<ul>
|
||||||
|
<li :key="name" v-for="{name, star, objects } in storageInfo">
|
||||||
|
{{ name }} ("{{ star }}", {{ objects }} objects)
|
||||||
|
<button @click="replaceCurrent(loadPreset(name))">load</button>
|
||||||
|
<button @click="deletePreset(name)" v-if="name !== 'example'">delete</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>File System</b>
|
||||||
|
<br />
|
||||||
|
<input type="file" @change="loadJSONFile($event)" />
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #save>
|
||||||
|
<p>
|
||||||
|
<label>
|
||||||
|
Save current system as:
|
||||||
|
<input placeholder="fancy star system" v-model="currentName" />
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Local Storage </b>
|
||||||
|
<button @click="savePreset(star, objects)">save as "{{ currentName }}"</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>File System </b>
|
||||||
|
<a :href="fileBlob" :download="`${currentName}.json`">download as {{ currentName }}.json</a>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #x></template>
|
||||||
|
</AppMenu>
|
||||||
<SystemSettings />
|
<SystemSettings />
|
||||||
<ObjectList />
|
<ObjectList />
|
||||||
</section>
|
</section>
|
||||||
|
@ -22,20 +62,52 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue'
|
import { computed } from 'vue'
|
||||||
import exampleData from './example-data'
|
|
||||||
import Headline from './components/Headline.vue'
|
import Headline from './components/Headline.vue'
|
||||||
import SystemDiagram from './components/SystemDiagram.vue'
|
import SystemDiagram from './components/SystemDiagram.vue'
|
||||||
|
import AppMenu from './components/AppMenu.vue'
|
||||||
import Tips from './components/Tips.vue'
|
import Tips from './components/Tips.vue'
|
||||||
import SystemSettings from './components/SystemSettings.vue'
|
import SystemSettings from './components/SystemSettings.vue'
|
||||||
import ObjectList from './components/ObjectList.vue'
|
import ObjectList from './components/ObjectList.vue'
|
||||||
import ObjectSettings from './components/ObjectSettings.vue'
|
import ObjectSettings from './components/ObjectSettings.vue'
|
||||||
import useObjects from './useObjects'
|
|
||||||
|
|
||||||
const { selectedObject } = useObjects()
|
import useObjects from './useObjects'
|
||||||
|
import useStorage from './useStorage'
|
||||||
|
|
||||||
|
const { star, objects, selectedObject, replaceCurrent } = useObjects()
|
||||||
const labelFonts = ['xolonium', 'douar', 'lack']
|
const labelFonts = ['xolonium', 'douar', 'lack']
|
||||||
const themes = ['default', 'retro', 'inverse', 'paper']
|
const themes = ['default', 'retro', 'inverse', 'paper']
|
||||||
|
|
||||||
|
const {
|
||||||
|
storageInfo,
|
||||||
|
loadPreset,
|
||||||
|
savePreset,
|
||||||
|
deletePreset,
|
||||||
|
currentName,
|
||||||
|
} = useStorage(star, objects)
|
||||||
|
|
||||||
|
const fileBlob = computed(() => {
|
||||||
|
const jsonFileData = JSON.stringify({ star, objects })
|
||||||
|
return `data:text/json;charset=utf-8,${encodeURIComponent(jsonFileData)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
function loadJSONFile (event) {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = evt => {
|
||||||
|
try {
|
||||||
|
const preset = JSON.parse(evt.target.result)
|
||||||
|
replaceCurrent(preset)
|
||||||
|
} catch {
|
||||||
|
alert('Failed to read file. Are you sure, it is a valid Starsy JSON file?')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsText(file)
|
||||||
|
}
|
||||||
|
|
||||||
function setTheme (theme) {
|
function setTheme (theme) {
|
||||||
const classes = document.body.className.split(' ')
|
const classes = document.body.className.split(' ')
|
||||||
const currentTheme = classes.find(c => c.startsWith('theme-'))
|
const currentTheme = classes.find(c => c.startsWith('theme-'))
|
||||||
|
|
17
src/app.css
17
src/app.css
|
@ -88,15 +88,15 @@ button.small {
|
||||||
background-size: 24px;
|
background-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tip {
|
.info {
|
||||||
width: calc(100% - 4em);
|
width: calc(100% - 4em);
|
||||||
margin-left: -1em;
|
margin-left: -1em;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
border: 2px solid #8888;
|
border: 2px solid #8888;
|
||||||
border-left-width: 1em;
|
border-left-width: 1em;
|
||||||
}
|
}
|
||||||
.tip > header { font-weight: bold; }
|
.info > header { font-weight: bold; }
|
||||||
.tip > li { margin-top: .75em; }
|
.info > li { margin-top: .75em; }
|
||||||
|
|
||||||
body.theme-retro {
|
body.theme-retro {
|
||||||
--bg-app: #4B4839;
|
--bg-app: #4B4839;
|
||||||
|
@ -150,6 +150,10 @@ text.tilted { transform: rotate(-45deg) translate(0, 100%); transform-origin: le
|
||||||
h1 {
|
h1 {
|
||||||
font-family: xolonium;
|
font-family: xolonium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a { color: #AAF; }
|
||||||
|
.theme-paper a { color: #33A; }
|
||||||
|
|
||||||
#app > header {
|
#app > header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -354,3 +358,10 @@ h1 {
|
||||||
color: var(--fg-app);
|
color: var(--fg-app);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.app-menu-button {
|
||||||
|
margin: 0 1em 0 0;
|
||||||
|
padding: .2em 1em;
|
||||||
|
}
|
||||||
|
.highlighted {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
28
src/components/AppMenu.vue
Normal file
28
src/components/AppMenu.vue
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<div class="info">
|
||||||
|
<header>
|
||||||
|
<button v-for="(_, slot) in slots"
|
||||||
|
:key="slot"
|
||||||
|
@click="shownSlot = slot"
|
||||||
|
class="app-menu-button"
|
||||||
|
:class="{ highlighted: shownSlot === slot }"
|
||||||
|
>
|
||||||
|
{{ slot }}
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<slot :name="shownSlot"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, useSlots } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
defaultSlot: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const shownSlot = ref(props.defaultSlot || Object.keys(slots)[0] || 'default')
|
||||||
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ul class="tip">
|
<ul class="info">
|
||||||
<header>
|
<header>
|
||||||
Tips:
|
Tips:
|
||||||
<button @click="tipsShown = !tipsShown" v-if="collapsible">
|
<button @click="tipsShown = !tipsShown" v-if="collapsible">
|
||||||
|
|
|
@ -10,8 +10,7 @@ import {
|
||||||
MAX_AMOUNT_RINGS,
|
MAX_AMOUNT_RINGS,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import { steepCurve } from './utils'
|
import { steepCurve } from './utils'
|
||||||
import exampleData from './example-data.js'
|
import exampleData from './example-data/sol'
|
||||||
|
|
||||||
|
|
||||||
const star = reactive(exampleData.star)
|
const star = reactive(exampleData.star)
|
||||||
const objects = reactive(exampleData.objects)
|
const objects = reactive(exampleData.objects)
|
||||||
|
@ -99,6 +98,18 @@ export default function useObjects () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomizeObject (object) {
|
function randomizeObject (object) {
|
||||||
|
console.log('randomize', object)
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceCurrent (preset) {
|
||||||
|
const { star: newStar, objects: newObjects } = preset
|
||||||
|
|
||||||
|
Object.keys(star).forEach(key => {
|
||||||
|
star[key] = newStar[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
objects.length = 0
|
||||||
|
newObjects.forEach(object => objects.push(object))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -113,5 +124,6 @@ export default function useObjects () {
|
||||||
restoreDeleted,
|
restoreDeleted,
|
||||||
randomizeObject,
|
randomizeObject,
|
||||||
autoName,
|
autoName,
|
||||||
|
replaceCurrent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
45
src/useStorage.js
Normal file
45
src/useStorage.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
export default function useStarsyStorage(star, objects) {
|
||||||
|
const store = useStorage('starsy', {
|
||||||
|
example: { star, objects }
|
||||||
|
})
|
||||||
|
|
||||||
|
const storageInfo = computed(() => {
|
||||||
|
return Object.keys(store.value).map(name => {
|
||||||
|
const { star, objects } = store.value[name]
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
star: star.designation,
|
||||||
|
objects: objects.length,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentName = ref('example')
|
||||||
|
|
||||||
|
function loadPreset(name) {
|
||||||
|
return store.value[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePreset(star, objects) {
|
||||||
|
const name = currentName.value
|
||||||
|
store.value[name] = { star, objects }
|
||||||
|
console.log('saved preset', name, store.value[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePreset(name) {
|
||||||
|
delete store.value[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
storageInfo,
|
||||||
|
loadPreset,
|
||||||
|
savePreset,
|
||||||
|
deletePreset,
|
||||||
|
currentName,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue