move object logic to composable

This commit is contained in:
koehr 2022-01-06 18:49:48 +01:00
parent 1cdf93c48f
commit 72dd514322
8 changed files with 239 additions and 239 deletions

View file

@ -4,23 +4,10 @@
@select:font="setFont($event)" @select:font="setFont($event)"
@select:theme="setTheme($event)" @select:theme="setTheme($event)"
/> />
<SystemDiagram v-bind="{ star, objects, selectedObject }" <SystemDiagram />
@select="selectObject"
@update="updateSelectedObject"
/>
<section id="settings"> <section id="settings">
<ObjectSettings v-if="selectedObject" <ObjectSettings v-if="selectedObject" />
v-model:name="selectedObject.name"
v-model:distance="selectedObject.distance"
v-model:type="selectedObject.type"
v-model:radius="selectedObject.radius"
v-model:rings="selectedObject.rings"
v-model:satellites="selectedObject.satellites"
:auto-name="autoName(selectedObject)"
@delete="deleteObject"
@close="editObject(null)"
/>
<Tips> <Tips>
<li>Edit planets by clicking directly inside the graphic or in the table below.</li> <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>Drag planets around to change their distance.</li>
@ -28,8 +15,8 @@
<li>The last removed planet can be restored from the table.</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><strong>ONLY THE LAST</strong> removed planet can be restored.</li>
</Tips> </Tips>
<SystemSettings v-model:designation="star.designation" v-model:radius="star.radius" /> <SystemSettings />
<ObjectList v-bind="{ objects, deletedObject, addObject, editObject, deleteObject, restoreDeleted }" /> <ObjectList />
</section> </section>
</template> </template>
@ -43,89 +30,12 @@ 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 { MAX_DISTANCE_PLANET } from './constants' import useObjects from './useObjects'
const star = reactive({ const { selectedObject } = useObjects()
designation: 'Sol',
radius: 400,
})
const objects = reactive(exampleData)
const labelFonts = ['xolonium', 'douar', 'lack'] const labelFonts = ['xolonium', 'douar', 'lack']
const themes = ['default', 'retro', 'inverse', 'paper'] const themes = ['default', 'retro', 'inverse', 'paper']
const selectedObject = ref(null)
const deletedObject = ref(null) // { index: Number, object: Object }
function addObject() {
const amount = objects.length
let distance = 100
if (amount) {
const lastObject = objects[amount - 1]
distance = Math.min(MAX_DISTANCE_PLANET, lastObject.distance + 2*lastObject.radius + 10)
}
objects.push({
type: 'planet',
name: `${star.designation}-${amount + 1}`,
radius: 1,
distance,
satellites: [],
rings: 0,
})
}
function editObject (object) {
if (object) {
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
}
selectedObject.value = object
}
function selectObject (object) {
selectedObject.value = object
}
function updateSelectedObject (payload) {
for (const key in payload) {
selectedObject.value[key] = payload[key]
}
}
function deleteObject (object) {
if (deletedObject.value) {
const lost = deletedObject.value.object.name
const confirmed = confirm(`
Attention! Only the LAST deleted object can be restored.
${lost} will be lost forever! Proceed anyway?`
)
if (!confirmed) return
}
if (!object) object = selectedObject.value
const index = objects.indexOf(object)
console.debug('deleting object at index', index)
if (index >= 0) objects.splice(index, 1)
if (object === selectedObject.value) selectedObject.value = null
deletedObject.value = { index, object }
}
function restoreDeleted () {
const { index, object } = deletedObject.value
console.debug('restoring deleted object', index)
objects.splice(index, 0, object)
deletedObject.value = null
}
function autoName (obj) {
const index = objects.indexOf(obj)
return `${star.designation}-${index}`
}
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-'))

View file

@ -210,6 +210,9 @@ h1 {
padding: .25em 1em; padding: .25em 1em;
border-bottom: 2px solid var(--fg-app); border-bottom: 2px solid var(--fg-app);
} }
#object-list tr.selected {
font-weight: bold;
}
#object-list .cell { #object-list .cell {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;

View file

@ -4,14 +4,15 @@
<th scope="col" v-for="col in columns">{{ col }}</th> <th scope="col" v-for="col in columns">{{ col }}</th>
<th scope="col">actions</th> <th scope="col">actions</th>
</tr> </tr>
<tr :class="{ deleted: i === deletedObject?.index }" v-for="o,i in objectList"> <tr v-for="o,i in objectList" :class="{ selected: o === selectedObject }">
<td v-for="value in values"> <td v-for="value in values">
<div class="cell">{{ o[value] }}</div> <div class="cell">{{ o[value] }}</div>
</td> </td>
<td><div class="cell">{{ o.satellites.length }}</div></td> <td><div class="cell">{{ o.satellites.length }}</div></td>
<td><div class="cell"> <td><div class="cell">
<button class="settings" @click="editObject(o)">&nbsp;</button> <button class="settings" title="Configure Object" @click="editObject(o)">&nbsp;</button>
<button class="delete" @click="deleteObject(o)">&nbsp;</button> <button class="dice" title="Randomize Object Values" @click="randomizeObject(o)">&nbsp;</button>
<button class="delete" title="Delete Object" @click="deleteObject(o)">&nbsp;</button>
</div></td> </div></td>
<button v-if="i === deletedObject?.index" <button v-if="i === deletedObject?.index"
class="deleted-overlay" class="deleted-overlay"
@ -20,30 +21,41 @@
RESTORE DELETED OBJECT RESTORE DELETED OBJECT
</button> </button>
</tr> </tr>
<button class="add" @click="addObject">&nbsp;</button> <button class="add" title="Add New Object" @click="addObject">&nbsp;</button>
</table> </table>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import useObjects from '../useObjects'
const props = defineProps({ const {
objects: Array, objects,
deletedObject: [Object, null], selectedObject,
addObject: Function, deletedObject,
editObject: Function, addObject,
deleteObject: Function, deleteObject,
restoreDeleted: Function, restoreDeleted,
}) randomizeObject,
} = useObjects()
const columns = ['Δ', 'Name', 'Type', 'Radius', 'Rings', 'Satellites'] const columns = ['Δ', 'Name', 'Type', 'Radius', 'Rings', 'Satellites']
const values = ['distance', 'name', 'type', 'radius', 'rings'] const values = ['distance', 'name', 'type', 'radius', 'rings']
const objectList = computed(() => { const objectList = computed(() => {
if (!props.deletedObject) return props.objects if (!deletedObject.value) return objects
const { index, object } = props.deletedObject const { index, object } = deletedObject.value
const objects = [ ...props.objects ] const objects_ = [ ...objects ]
objects.splice(index, 0, object) objects_.splice(index, 0, object)
return objects return objects_
}) })
function editObject (object) {
if (object) {
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
}
selectedObject.value = object
}
</script> </script>

View file

@ -8,51 +8,50 @@
<section class="main"> <section class="main">
<div> <div>
<input type="text" class="big" <input type="text" class="big"
:value="name" :value="selectedObject.name"
@input="checkName($event.target.value)" @input="update('name', $event.target.value)"
/> />
</div> </div>
<div> <div>
Distance Δ: Distance Δ:
<input type="number" :min="MIN_DISTANCE_PLANET" :max="MAX_DISTANCE_PLANET" <input type="number" :min="MIN_DISTANCE_PLANET" :max="MAX_DISTANCE_PLANET"
:value="distance" :value="selectedObject.distance"
@input="update('distance', $event.target.value)" @input="update('distance', $event.target.value)"
/> />
</div> </div>
<div> <div>
Radius r: Radius r:
<input type="number" :min="MIN_SIZE_PLANET" :max="MAX_SIZE_PLANET" <input type="number" :min="MIN_SIZE_PLANET" :max="MAX_SIZE_PLANET"
:value="radius" :value="selectedObject.radius"
@input="update('radius', $event.target.value)" @input="update('radius', $event.target.value)"
/> />
</div> </div>
<div> <div>
Rings: Rings:
<input type="number" :min="MIN_AMOUNT_RINGS" :max="MAX_AMOUNT_RINGS" <input type="number" :min="MIN_AMOUNT_RINGS" :max="MAX_AMOUNT_RINGS"
:value="rings" :value="selectedObject.rings"
@input="update('rings', $event.target.value)" @input="update('rings', $event.target.value)"
/> />
</div> </div>
<button class="close" title="close" @click="$emit('close')">&nbsp;</button> <button class="close" title="close" @click="close">&nbsp;</button>
</section> </section>
<SatelliteSettings :satellites="satellites" @update:satellites="update('satellites', $event)" /> <SatelliteSettings :satellites="selectedObject.satellites" @update:satellites="update('satellites', $event)" />
<section class="additional-options"> <section class="additional-options">
Other options: Other options:
<button class="cta danger" @click="$emit('delete')">REMOVE OBJECT</button> <button class="cta danger" @click="deleteObject()">REMOVE OBJECT</button>
</section> </section>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import useObjects from '../useObjects'
import Tips from './Tips.vue' import Tips from './Tips.vue'
import SatelliteSettings from './SatelliteSettings.vue' import SatelliteSettings from './SatelliteSettings.vue'
import { import {
MIN_SIZE_STAR,
MAX_SIZE_STAR,
MIN_SIZE_PLANET, MIN_SIZE_PLANET,
MAX_SIZE_PLANET, MAX_SIZE_PLANET,
MIN_DISTANCE_PLANET, MIN_DISTANCE_PLANET,
@ -61,36 +60,22 @@ import {
MAX_AMOUNT_RINGS, MAX_AMOUNT_RINGS,
} from '../constants' } from '../constants'
const props = defineProps({ const {
distance: Number, selectedObject,
name: String, deleteObject,
type: String, updateSelectedObject,
radius: Number, } = useObjects()
rings: Number,
satellites: Array,
autoName: String, // auto generated name, like Sol-3
})
const emit = defineEmits([
'update:distance',
'update:name',
'update:type',
'update:radius',
'update:rings',
'update:satellites',
'delete',
'close',
])
const numberTargets = ['distance', 'radius', 'rings'] const numberTargets = ['distance', 'radius', 'rings']
function update (target, value) { function update (target, value) {
console.debug('updating', target, 'with', value) console.debug('updating', target, 'with', value)
if (numberTargets.indexOf(target) >= 0) value = parseInt(value) if (numberTargets.indexOf(target) >= 0) value = parseInt(value)
emit(`update:${target}`, value)
updateSelectedObject({ [target]: value })
} }
function checkName (name) { function close () {
if (!name.trim().length) name = props.autoName selectedObject.value = null
update('name', name)
} }
</script> </script>

View file

@ -34,26 +34,15 @@
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { steepCurve } from '../utils' import useObjects from '../useObjects'
import {
MIN_SIZE_PLANET,
MAX_SIZE_PLANET,
MIN_DISTANCE_PLANET,
MAX_DISTANCE_PLANET,
} from '../constants'
const props = defineProps({ const {
star: Object, star,
objects: Array, starCX,
selectedObject: Object, objects,
}) selectedObject,
updateSelectedObject,
const emit = defineEmits([ 'select', 'update' ]) } = useObjects()
const starCX = computed(() => {
const r = props.star.radius
return -1 * r * steepCurve(r, 50, 0.955)
})
const draggedObject = ref(null) const draggedObject = ref(null)
const draggingDelta = ref(0) const draggingDelta = ref(0)
@ -69,12 +58,8 @@ function stopDragging (event) {
event.target.removeEventListener('pointerup', stopDragging) event.target.removeEventListener('pointerup', stopDragging)
console.debug('stop draggin', draggedObject.value.name) console.debug('stop draggin', draggedObject.value.name)
let distance = draggedObject.value.distance + draggingDelta.value const distance = draggedObject.value.distance + draggingDelta.value
updateSelectedObject({ distance })
if (distance < MIN_DISTANCE_PLANET) distance = MIN_DISTANCE_PLANET
if (distance > MAX_DISTANCE_PLANET) distance = MAX_DISTANCE_PLANET
emit('update', { distance })
dragStart = 0 dragStart = 0
draggingDelta.value = 0 draggingDelta.value = 0
@ -88,8 +73,7 @@ function updateDelta (event) {
function startDragging (event, object) { function startDragging (event, object) {
console.debug('start draggin', object.name) console.debug('start draggin', object.name)
selectedObject.value = object
emit('select', object)
// we can savely assume that the windows width is not changing while dragging // we can savely assume that the windows width is not changing while dragging
pixelFactor = 1000 / document.body.offsetWidth pixelFactor = 1000 / document.body.offsetWidth
@ -104,23 +88,16 @@ function startDragging (event, object) {
} }
function resizeObject (event) { function resizeObject (event) {
if (!props.selectedObject) return if (!selectedObject.value) return
event.preventDefault() event.preventDefault()
let radius = props.selectedObject.radius let radius = selectedObject.value.radius
radius = radius + event.deltaY * -0.01 radius = radius + event.deltaY * -0.01
if (event.deltaY > 0) radius = Math.floor(radius) if (event.deltaY > 0) radius = Math.floor(radius)
else radius = Math.ceil(radius) else radius = Math.ceil(radius)
if (radius < MIN_SIZE_PLANET) radius = MIN_SIZE_PLANET updateSelectedObject({ radius })
if (radius > MAX_SIZE_PLANET) radius = MAX_SIZE_PLANET
emit('update', { radius })
}
function onDragEnter (event) {
console.log('SystemDiagram onDragEnter', event)
} }
</script> </script>

View file

@ -4,20 +4,17 @@
<menu id="system-settings"> <menu id="system-settings">
<label> <label>
Name Name
<input type="text" <input type="text" v-model="star.designation" />
:value="designation"
@input="update('designation', $event.target.value)"
/>
</label> </label>
<label> <label>
Star Size Star Size
<input type="range" <input type="range"
:min="MIN_SIZE_STAR" :min="MIN_SIZE_STAR"
:max="MAX_SIZE_STAR" :max="MAX_SIZE_STAR"
:value="radius" :value="star.radius"
@input="update('radius', $event.target.value)" @input="updateRadius($event.target.value)"
/> />
({{ radius }}) ({{ star.radius }})
</label> </label>
</menu> </menu>
</header> </header>
@ -25,22 +22,15 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import useObjects from '../useObjects'
import { import {
MIN_SIZE_STAR, MIN_SIZE_STAR,
MAX_SIZE_STAR, MAX_SIZE_STAR,
} from '../constants' } from '../constants'
const props = defineProps({ const { star } = useObjects()
designation: String,
radius: Number,
})
const emit = defineEmits([
'update:designation',
'update:radius',
])
function update (target, value) { function updateRadius (radius) {
if (target === 'radius') value = parseInt(value) star.radius = parseInt(radius)
emit(`update:${target}`, value)
} }
</script> </script>

View file

@ -1,38 +1,44 @@
export default [ export default {
{ type: 'planet', name: 'Mercury', radius: 1, distance: 100, satellites: [], rings: 0 }, star: {
{ type: 'planet', name: 'Venus', radius: 4, distance: 120, satellites: [], rings: 0 }, designation: 'Sol',
{ type: 'planet', name: 'Terra', radius: 4, distance: 140, satellites: [ radius: 400,
{ name: 'ISS', radius: 1, type: 'station' }, },
{ name: 'Luna', radius: 2, type: 'moon' }, objects: [
], rings: 0 }, { type: 'planet', name: 'Mercury', designation: 'Sol-1', radius: 1, distance: 100, satellites: [], rings: 0 },
{ type: 'planet', name: 'Mars', radius: 2, distance: 160, satellites: [ { type: 'planet', name: 'Venus', designation: 'Sol-2', radius: 4, distance: 120, satellites: [], rings: 0 },
{ name: 'MTO', radius: 1, type: 'station' }, { type: 'planet', name: 'Terra', designation: 'Sol-3', radius: 4, distance: 140, satellites: [
{ name: 'Phobos', radius: 1, type: 'moon' }, { name: 'ISS', radius: 1, type: 'station' },
{ name: 'Daimos', radius: 1, type: 'moon' }, { name: 'Luna', radius: 2, type: 'moon' },
], rings: 0 }, ], rings: 0 },
{ type: 'planet', name: 'Jupiter', radius: 40, distance: 260, satellites: [ { type: 'planet', name: 'Mars', designation: 'Sol-4', radius: 2, distance: 160, satellites: [
{ name: 'Io', radius: 2, type: 'moon' }, { name: 'MTO', radius: 1, type: 'station' },
{ name: 'Europa', radius: 2, type: 'moon' }, { name: 'Phobos', radius: 1, type: 'moon' },
{ name: 'Ganymede', radius: 4, type: 'moon' }, { name: 'Daimos', radius: 1, type: 'moon' },
{ name: 'Callisto', radius: 3, type: 'moon' }, ], rings: 0 },
], rings: 1 }, { type: 'planet', name: 'Jupiter', designation: 'Sol-5', radius: 40, distance: 260, satellites: [
{ type: 'planet', name: 'Saturn', radius: 36, distance: 410, satellites: [ { name: 'Io', radius: 2, type: 'moon' },
{ name: 'Mimas', radius: 1, type: 'moon' }, { name: 'Europa', radius: 2, type: 'moon' },
{ name: 'Enceladus', radius: 1, type: 'moon' }, { name: 'Ganymede', radius: 4, type: 'moon' },
{ name: 'Tethys', radius: 1, type: 'moon' }, { name: 'Callisto', radius: 3, type: 'moon' },
{ name: 'Dione', radius: 1, type: 'moon' }, ], rings: 1 },
{ name: 'Rhea', radius: 1, type: 'moon' }, { type: 'planet', name: 'Saturn', designation: 'Sol-6', radius: 36, distance: 410, satellites: [
{ name: 'Titan', radius: 3, type: 'moon' }, { name: 'Mimas', radius: 1, type: 'moon' },
{ name: 'Iapetus', radius: 1, type: 'moon' }, { name: 'Enceladus', radius: 1, type: 'moon' },
], rings: 5 }, { name: 'Tethys', radius: 1, type: 'moon' },
{ type: 'planet', name: 'Uranus', radius: 16, distance: 680, satellites: [ { name: 'Dione', radius: 1, type: 'moon' },
{ name: 'Miranda', radius: 1, type: 'moon' }, { name: 'Rhea', radius: 1, type: 'moon' },
{ name: 'Ariel', radius: 1, type: 'moon' }, { name: 'Titan', radius: 3, type: 'moon' },
{ name: 'Umbriel', radius: 1, type: 'moon' }, { name: 'Iapetus', radius: 1, type: 'moon' },
{ name: 'Titania', radius: 1, type: 'moon' }, ], rings: 5 },
{ name: 'Oberon', radius: 1, type: 'moon' }, { type: 'planet', name: 'Uranus', designation: 'Sol-7', radius: 16, distance: 680, satellites: [
], rings: 2 }, { name: 'Miranda', radius: 1, type: 'moon' },
{ type: 'planet', name: 'Neptune', radius: 15, distance: 950, satellites: [ { name: 'Ariel', radius: 1, type: 'moon' },
{ name: 'Triton', radius: 1, type: 'moon' }, { name: 'Umbriel', radius: 1, type: 'moon' },
], rings: 0 }, { name: 'Titania', radius: 1, type: 'moon' },
] { name: 'Oberon', radius: 1, type: 'moon' },
], rings: 2 },
{ type: 'planet', name: 'Neptune', designation: 'Sol-8', radius: 15, distance: 950, satellites: [
{ name: 'Triton', radius: 1, type: 'moon' },
], rings: 0 },
],
}

117
src/useObjects.js Normal file
View file

@ -0,0 +1,117 @@
import { reactive, ref, computed } from 'vue'
import {
MIN_SIZE_STAR,
MAX_SIZE_STAR,
MIN_SIZE_PLANET,
MAX_SIZE_PLANET,
MIN_DISTANCE_PLANET,
MAX_DISTANCE_PLANET,
MIN_AMOUNT_RINGS,
MAX_AMOUNT_RINGS,
} from './constants'
import { steepCurve } from './utils'
import exampleData from './example-data.js'
const star = reactive(exampleData.star)
const objects = reactive(exampleData.objects)
const selectedObject = ref(null)
const deletedObject = ref(null) // { index: Number, object: Object }
const starCX = computed(() => {
const r = star.radius
return -1 * r * steepCurve(r, 50, 0.955)
})
export default function useObjects () {
function addObject() {
const amount = objects.length
let distance = 100
if (amount) {
const lastObject = objects[amount - 1]
distance = Math.min(MAX_DISTANCE_PLANET, lastObject.distance + 2*lastObject.radius + 10)
}
objects.push({
type: 'planet',
name: `${star.designation}-${amount + 1}`,
radius: 1,
distance,
satellites: [],
rings: 0,
})
}
function updateSelectedObject (payload) {
if (payload.name && !payload.name.trim().length) {
payload.name = selectedObject.value.designation
}
if (payload.distance) {
payload.distance = Math.max(MIN_DISTANCE_PLANET, payload.distance)
payload.distance = Math.min(MAX_DISTANCE_PLANET, payload.distance)
}
if (payload.radius) {
payload.radius = Math.max(MIN_SIZE_PLANET, payload.radius)
payload.radius = Math.min(MAX_SIZE_PLANET, payload.radius)
}
if (payload.rings) {
payload.rings = Math.max(MIN_AMOUNT_RINGS, payload.rings)
payload.rings = Math.min(MAX_AMOUNT_RINGS, payload.rings)
}
for (const key in payload) {
selectedObject.value[key] = payload[key]
}
}
function deleteObject (object) {
if (deletedObject.value) {
const lost = deletedObject.value.object.name
const confirmed = confirm(`
Attention! Only the LAST deleted object can be restored.
${lost} will be lost forever! Proceed anyway?`
)
if (!confirmed) return
}
if (!object) object = selectedObject.value
const index = objects.indexOf(object)
console.debug('deleting object at index', index)
if (index >= 0) objects.splice(index, 1)
if (object === selectedObject.value) selectedObject.value = null
deletedObject.value = { index, object }
}
function restoreDeleted () {
const { index, object } = deletedObject.value
console.debug('restoring deleted object', index)
objects.splice(index, 0, object)
deletedObject.value = null
}
function autoName (object) {
const index = objects.indexOf(object)
return `${star.designation}-${index}`
}
function randomizeObject (object) {
}
return {
star,
starCX,
objects,
selectedObject,
deletedObject,
addObject,
deleteObject,
updateSelectedObject,
restoreDeleted,
randomizeObject,
autoName,
}
}