file saving & exiting

This commit is contained in:
lightling 2025-03-02 18:14:27 -05:00
parent 0a55fc8c88
commit 61a8f56d13
Signed by: lightling
GPG key ID: F1F29650D537C773
8 changed files with 151 additions and 24 deletions

View file

@ -9,6 +9,7 @@
"core:default",
"opener:default",
"dialog:default",
"fs:allow-write-text-file",
"fs:default"
]
}
}

View file

@ -36,4 +36,9 @@ main
<style lang="sass">
header
margin-bottom: 1rem
.ellipsis
display: inline-block
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
</style>

View file

@ -4,6 +4,7 @@ import { definePreset } from '@primeuix/themes'
import Aura from '@primeuix/themes/aura'
import PrimeVue from 'primevue/config'
import ToastService from 'primevue/toastservice'
import Tooltip from 'primevue/tooltip'
import 'primeicons/primeicons.css'
import { router } from './router'
@ -12,7 +13,6 @@ import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
const themePreset = definePreset(Aura, {
semantic: {
primary: {
@ -44,4 +44,5 @@ app
},
})
.use(ToastService)
.directive('tooltip', Tooltip)
.mount('#app')

View file

@ -17,12 +17,14 @@ export const useAppStore = defineStore('appStore', {
} as InventoryState,
}),
getters: {
fileTree: (state) => fileTree(state.currentInventory),
fileDirectory: (state) => {
fileTree(state) {
return fileTree(state.currentInventory)
},
fileDirectory(state) {
const split = fileTree(state.currentInventory)
return state.currentInventory.filePath.replace(split[split.length - 1], '')
},
fileName: (state) => {
fileName(state) {
const split = fileTree(state.currentInventory)
return split[split.length - 1]
},

View file

@ -15,35 +15,40 @@ import {
const props = defineProps<{
columns: DataColumn[],
}>()
const model = defineModel<DataRow[]>()
const emits = defineEmits<{
(e: 'dirty'): void
}>()
const model = defineModel<DataRow[]>({ required: true })
const editingRows = ref([])
const onRowEditSave = (event: { newData: any, index: number }) => {
let { newData, index } = event
model.value![index] = newData
model.value[index] = newData
emits('dirty')
}
const onAddRow = (event: Event) => {
event.preventDefault()
model.value!.push({ _id: uuidv7() })
console.log(model.value)
model.value.push({ _id: uuidv7() })
emits('dirty')
requestAnimationFrame(() => {
(document.querySelector('tr:last-of-type button.edit') as HTMLButtonElement).click()
(document.querySelector('.data-editor tr:last-of-type button.edit') as HTMLButtonElement).click()
requestAnimationFrame(() => {
(document.querySelector('tr:last-of-type input') as HTMLInputElement | undefined)?.focus()
(document.querySelector('.data-editor tr:last-of-type input') as HTMLInputElement | undefined)?.select()
})
})
}
const onDeleteRow = (event: Event, slotProps: { index: number }) => {
event.preventDefault()
model.value!.splice(slotProps.index, 1)
model.value.splice(slotProps.index, 1)
emits('dirty')
}
</script>
<template lang="pug">
DataTable(
DataTable.data-editor(
@row-edit-save='onRowEditSave'
v-model:editingRows='editingRows'
:value='model'
@ -93,6 +98,7 @@ DataTable(
)
Column(
rowEditor
header='Actions'
style='width:10%;min-width:8rem;'
bodyStyle='text-align:center;'
)
@ -157,4 +163,7 @@ Button(
:deep(.p-image)
img
max-width: 16rem
:deep(td:last-of-type)
display: flex
padding-left: 8px
</style>

View file

@ -1,5 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
computed,
ref,
} from 'vue'
import { useRouter } from 'vue-router'
import { useToast } from 'primevue'
import { writeTextFile } from '@tauri-apps/plugin-fs'
import Menubar from 'primevue/menubar'
import Tabs from 'primevue/tabs'
import TabList from 'primevue/tablist'
import Tab from 'primevue/tab'
@ -14,13 +21,71 @@ import DataEditor from './DataEditor.vue'
import FieldEditor from './FieldEditor.vue'
const appStore = useAppStore()
const router = useRouter()
const toast = useToast()
const data = ref([...appStore.currentInventory.data?.rows || []])
const fields = ref([...appStore.currentInventory.data?.columns || []])
const data = ref(appStore.currentInventory.data?.rows)
const fields = ref(appStore.currentInventory.data?.columns)
const dirty = ref(false)
const fileDirectory = ref(appStore.fileDirectory)
const fileName = ref(appStore.fileName)
const onSave = async () => {
try {
await writeTextFile(appStore.currentInventory.filePath, JSON.stringify({
rows: data.value,
columns: fields.value,
}, null, 2))
} catch (err) {
toast.add({
severity: 'error',
summary: 'The file could not be saved (permission issue?)',
detail: err,
})
}
dirty.value = false
}
const onQuit = () => {
// todo: should prompt to confirm
router.push('/')
}
const menu = computed(() => ([
{
label: 'Save',
icon: 'pi pi-save',
command: onSave,
disabled: !dirty.value,
},
{
label: 'Exit',
icon: 'pi pi-sign-out',
command: onQuit,
},
]))
const onDirty = () => {
dirty.value = true
}
</script>
<template lang="pug">
Tabs(value='data-editor')
Menubar(
:model='menu'
)
template(#end)
.context
span Editing for
span.ellipsis(
v-tooltip.bottom='fileDirectory'
)
span {{ fileDirectory }}
span.ellipsis(
v-tooltip.bottom='fileName'
)
span {{ fileName }}
Tabs.tabs(value='data-editor')
TabList
Tab(value='data-editor') Data Editor
Tab(value='field-editor') Field Editor
@ -28,14 +93,22 @@ Tabs(value='data-editor')
TabPanel(value='field-editor')
FieldEditor(
v-model='fields'
@dirty='onDirty'
)
TabPanel(value='data-editor')
DataEditor(
v-model='data'
:columns='fields'
@dirty='onDirty'
)
</template>
<style scoped lang="sass">
.tabs
margin-top: 1rem
.context
display: flex
gap: 1rem
.ellipsis
max-width: 8rem
</style>

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import { v1 as uuidv1 } from 'uuid'
import Button from 'primevue/button'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
@ -11,18 +12,40 @@ import {
type Column as DataColumn,
} from 'src/types/data'
const model = defineModel<DataColumn[]>()
const emits = defineEmits<{
(e: 'dirty'): void
}>()
const model = defineModel<DataColumn[]>({ required: true })
const editingFields = ref([])
const onFieldEditSave = (event: { newData: any, index: number }) => {
let { newData, index } = event
model.value![index] = newData
console.log(model.value)
model.value[index] = newData
emits('dirty')
}
const onAddField = (event: Event) => {
event.preventDefault()
model.value.push({ name: uuidv1().split('-')[0], type: 'text' })
emits('dirty')
requestAnimationFrame(() => {
(document.querySelector('.field-editor tr:last-of-type button.edit') as HTMLButtonElement).click()
requestAnimationFrame(() => {
(document.querySelector('.field-editor tr:last-of-type input') as HTMLInputElement | undefined)?.select()
})
})
}
const onDeleteField = (event: Event, slotProps: { index: number }) => {
event.preventDefault()
model.value.splice(slotProps.index, 1)
emits('dirty')
}
</script>
<template lang="pug">
DataTable(
DataTable.field-editor(
@row-edit-save='onFieldEditSave'
v-model:editingRows='editingFields'
:value='model'
@ -61,13 +84,14 @@ DataTable(
)
Column(
rowEditor
header='Actions'
style='width:10%;min-width:8rem;'
bodyStyle='text-align:center;'
)
template(
#body='slotProps'
)
Button(
Button.edit(
icon='pi pi-pencil'
aria-label='Row Edit'
rounded
@ -83,6 +107,9 @@ DataTable(
rounded
severity='danger'
variant='text'
:onClick=`(e) => {
onDeleteField(e, slotProps)
}`
)
template(
#editor='slotProps'
@ -107,7 +134,16 @@ DataTable(
slotProps.editorCancelCallback(e)
}`
)
Button(
icon='pi pi-plus'
aria-label='Add New Field'
label='Add New Field'
@click='onAddField'
)
</template>
<style scoped lang="sass">
:deep(td:last-of-type)
display: flex
padding-left: 8px
</style>

View file

@ -38,7 +38,7 @@ const onBrowse = async (e: Event) => {
const onCreate = async (e: Event) => {
e.preventDefault()
appStore.currentInventory.filePath = `${filePath}/${fileName}.json`
appStore.currentInventory.filePath = `${filePath.value}/${fileName.value}.json`
appStore.currentInventory.data = {
columns: [
{ name: 'name', type: 'text' },