move object logic to composable

main
koehr 2 years ago
parent 1cdf93c48f
commit 72dd514322

@ -4,23 +4,10 @@
@select:font="setFont($event)"
@select:theme="setTheme($event)"
/>
<SystemDiagram v-bind="{ star, objects, selectedObject }"
@select="selectObject"
@update="updateSelectedObject"
/>
<SystemDiagram />
<section id="settings">
<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)"
/>
<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>
@ -28,8 +15,8 @@
<li>The last removed planet can be restored from the table.</li>
<li><strong>ONLY THE LAST</strong> removed planet can be restored.</li>
</Tips>
<SystemSettings v-model:designation="star.designation" v-model:radius="star.radius" />
<ObjectList v-bind="{ objects, deletedObject, addObject, editObject, deleteObject, restoreDeleted }" />
<SystemSettings />
<ObjectList />
</section>
</template>
@ -43,89 +30,12 @@ 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 { MAX_DISTANCE_PLANET } from './constants'
const star = reactive({
designation: 'Sol',
radius: 400,
})
import useObjects from './useObjects'
const objects = reactive(exampleData)
const { selectedObject } = useObjects()
const labelFonts = ['xolonium', 'douar', 'lack']
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) {
const classes = document.body.className.split(' ')
const currentTheme = classes.find(c => c.startsWith('theme-'))

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

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

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

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

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

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

@ -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,
}
}
Loading…
Cancel
Save