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" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Starsy ̋̄— Star System Generator</title>
|
||||
<title>Starsy — Star System Generator</title>
|
||||
</head>
|
||||
<body class="theme-inverse">
|
||||
<div id="app"></div>
|
||||
|
|
94
src/App.vue
94
src/App.vue
|
@ -8,13 +8,53 @@
|
|||
|
||||
<section id="settings">
|
||||
<ObjectSettings v-if="selectedObject" />
|
||||
<Tips>
|
||||
<li>Edit planets by clicking directly inside the graphic or in the table below.</li>
|
||||
<li>Drag planets around to change their distance.</li>
|
||||
<li>The last removed planet can be restored from the table.</li>
|
||||
<li><strong>ONLY THE LAST</strong> removed planet can be restored.</li>
|
||||
<li>Click on a planet to get more tips.</li>
|
||||
</Tips>
|
||||
<AppMenu default-slot="tips">
|
||||
<template #tips>
|
||||
<ul>
|
||||
<li>Edit planets by clicking directly inside the graphic or in the table below.</li>
|
||||
<li>Drag planets around to change their distance.</li>
|
||||
<li>The last removed planet can be restored from the table.</li>
|
||||
<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 />
|
||||
<ObjectList />
|
||||
</section>
|
||||
|
@ -22,20 +62,52 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import exampleData from './example-data'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import Headline from './components/Headline.vue'
|
||||
import SystemDiagram from './components/SystemDiagram.vue'
|
||||
import AppMenu from './components/AppMenu.vue'
|
||||
import Tips from './components/Tips.vue'
|
||||
import SystemSettings from './components/SystemSettings.vue'
|
||||
import ObjectList from './components/ObjectList.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 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) {
|
||||
const classes = document.body.className.split(' ')
|
||||
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;
|
||||
}
|
||||
|
||||
.tip {
|
||||
.info {
|
||||
width: calc(100% - 4em);
|
||||
margin-left: -1em;
|
||||
padding: 1em 2em;
|
||||
border: 2px solid #8888;
|
||||
border-left-width: 1em;
|
||||
}
|
||||
.tip > header { font-weight: bold; }
|
||||
.tip > li { margin-top: .75em; }
|
||||
.info > header { font-weight: bold; }
|
||||
.info > li { margin-top: .75em; }
|
||||
|
||||
body.theme-retro {
|
||||
--bg-app: #4B4839;
|
||||
|
@ -150,6 +150,10 @@ text.tilted { transform: rotate(-45deg) translate(0, 100%); transform-origin: le
|
|||
h1 {
|
||||
font-family: xolonium;
|
||||
}
|
||||
|
||||
a { color: #AAF; }
|
||||
.theme-paper a { color: #33A; }
|
||||
|
||||
#app > header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -354,3 +358,10 @@ h1 {
|
|||
color: var(--fg-app);
|
||||
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>
|
||||
<ul class="tip">
|
||||
<ul class="info">
|
||||
<header>
|
||||
Tips:
|
||||
<button @click="tipsShown = !tipsShown" v-if="collapsible">
|
||||
|
|
|
@ -10,8 +10,7 @@ import {
|
|||
MAX_AMOUNT_RINGS,
|
||||
} from './constants'
|
||||
import { steepCurve } from './utils'
|
||||
import exampleData from './example-data.js'
|
||||
|
||||
import exampleData from './example-data/sol'
|
||||
|
||||
const star = reactive(exampleData.star)
|
||||
const objects = reactive(exampleData.objects)
|
||||
|
@ -99,6 +98,18 @@ export default function useObjects () {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -113,5 +124,6 @@ export default function useObjects () {
|
|||
restoreDeleted,
|
||||
randomizeObject,
|
||||
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