file saving & exiting
This commit is contained in:
parent
0a55fc8c88
commit
61a8f56d13
8 changed files with 151 additions and 24 deletions
|
@ -9,6 +9,7 @@
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
|
"fs:allow-write-text-file",
|
||||||
"fs:default"
|
"fs:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,9 @@ main
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
header
|
header
|
||||||
margin-bottom: 1rem
|
margin-bottom: 1rem
|
||||||
|
.ellipsis
|
||||||
|
display: inline-block
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
white-space: nowrap
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { definePreset } from '@primeuix/themes'
|
||||||
import Aura from '@primeuix/themes/aura'
|
import Aura from '@primeuix/themes/aura'
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config'
|
||||||
import ToastService from 'primevue/toastservice'
|
import ToastService from 'primevue/toastservice'
|
||||||
|
import Tooltip from 'primevue/tooltip'
|
||||||
import 'primeicons/primeicons.css'
|
import 'primeicons/primeicons.css'
|
||||||
|
|
||||||
import { router } from './router'
|
import { router } from './router'
|
||||||
|
@ -12,7 +13,6 @@ import App from './App.vue'
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
|
||||||
const themePreset = definePreset(Aura, {
|
const themePreset = definePreset(Aura, {
|
||||||
semantic: {
|
semantic: {
|
||||||
primary: {
|
primary: {
|
||||||
|
@ -44,4 +44,5 @@ app
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.use(ToastService)
|
.use(ToastService)
|
||||||
|
.directive('tooltip', Tooltip)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|
|
@ -17,12 +17,14 @@ export const useAppStore = defineStore('appStore', {
|
||||||
} as InventoryState,
|
} as InventoryState,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
fileTree: (state) => fileTree(state.currentInventory),
|
fileTree(state) {
|
||||||
fileDirectory: (state) => {
|
return fileTree(state.currentInventory)
|
||||||
|
},
|
||||||
|
fileDirectory(state) {
|
||||||
const split = fileTree(state.currentInventory)
|
const split = fileTree(state.currentInventory)
|
||||||
return state.currentInventory.filePath.replace(split[split.length - 1], '')
|
return state.currentInventory.filePath.replace(split[split.length - 1], '')
|
||||||
},
|
},
|
||||||
fileName: (state) => {
|
fileName(state) {
|
||||||
const split = fileTree(state.currentInventory)
|
const split = fileTree(state.currentInventory)
|
||||||
return split[split.length - 1]
|
return split[split.length - 1]
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,35 +15,40 @@ import {
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
columns: DataColumn[],
|
columns: DataColumn[],
|
||||||
}>()
|
}>()
|
||||||
const model = defineModel<DataRow[]>()
|
const emits = defineEmits<{
|
||||||
|
(e: 'dirty'): void
|
||||||
|
}>()
|
||||||
|
const model = defineModel<DataRow[]>({ required: true })
|
||||||
const editingRows = ref([])
|
const editingRows = ref([])
|
||||||
|
|
||||||
const onRowEditSave = (event: { newData: any, index: number }) => {
|
const onRowEditSave = (event: { newData: any, index: number }) => {
|
||||||
let { newData, index } = event
|
let { newData, index } = event
|
||||||
model.value![index] = newData
|
model.value[index] = newData
|
||||||
|
emits('dirty')
|
||||||
}
|
}
|
||||||
|
|
||||||
const onAddRow = (event: Event) => {
|
const onAddRow = (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
model.value!.push({ _id: uuidv7() })
|
model.value.push({ _id: uuidv7() })
|
||||||
console.log(model.value)
|
emits('dirty')
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
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(() => {
|
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 }) => {
|
const onDeleteRow = (event: Event, slotProps: { index: number }) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
model.value!.splice(slotProps.index, 1)
|
model.value.splice(slotProps.index, 1)
|
||||||
|
emits('dirty')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
DataTable(
|
DataTable.data-editor(
|
||||||
@row-edit-save='onRowEditSave'
|
@row-edit-save='onRowEditSave'
|
||||||
v-model:editingRows='editingRows'
|
v-model:editingRows='editingRows'
|
||||||
:value='model'
|
:value='model'
|
||||||
|
@ -93,6 +98,7 @@ DataTable(
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
rowEditor
|
rowEditor
|
||||||
|
header='Actions'
|
||||||
style='width:10%;min-width:8rem;'
|
style='width:10%;min-width:8rem;'
|
||||||
bodyStyle='text-align:center;'
|
bodyStyle='text-align:center;'
|
||||||
)
|
)
|
||||||
|
@ -157,4 +163,7 @@ Button(
|
||||||
:deep(.p-image)
|
:deep(.p-image)
|
||||||
img
|
img
|
||||||
max-width: 16rem
|
max-width: 16rem
|
||||||
|
:deep(td:last-of-type)
|
||||||
|
display: flex
|
||||||
|
padding-left: 8px
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
<script setup lang="ts">
|
<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 Tabs from 'primevue/tabs'
|
||||||
import TabList from 'primevue/tablist'
|
import TabList from 'primevue/tablist'
|
||||||
import Tab from 'primevue/tab'
|
import Tab from 'primevue/tab'
|
||||||
|
@ -14,13 +21,71 @@ import DataEditor from './DataEditor.vue'
|
||||||
import FieldEditor from './FieldEditor.vue'
|
import FieldEditor from './FieldEditor.vue'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const data = ref([...appStore.currentInventory.data?.rows || []])
|
const data = ref(appStore.currentInventory.data?.rows)
|
||||||
const fields = ref([...appStore.currentInventory.data?.columns || []])
|
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>
|
</script>
|
||||||
|
|
||||||
<template lang="pug">
|
<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
|
TabList
|
||||||
Tab(value='data-editor') Data Editor
|
Tab(value='data-editor') Data Editor
|
||||||
Tab(value='field-editor') Field Editor
|
Tab(value='field-editor') Field Editor
|
||||||
|
@ -28,14 +93,22 @@ Tabs(value='data-editor')
|
||||||
TabPanel(value='field-editor')
|
TabPanel(value='field-editor')
|
||||||
FieldEditor(
|
FieldEditor(
|
||||||
v-model='fields'
|
v-model='fields'
|
||||||
|
@dirty='onDirty'
|
||||||
)
|
)
|
||||||
TabPanel(value='data-editor')
|
TabPanel(value='data-editor')
|
||||||
DataEditor(
|
DataEditor(
|
||||||
v-model='data'
|
v-model='data'
|
||||||
:columns='fields'
|
:columns='fields'
|
||||||
|
@dirty='onDirty'
|
||||||
)
|
)
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="sass">
|
<style scoped lang="sass">
|
||||||
|
.tabs
|
||||||
|
margin-top: 1rem
|
||||||
|
.context
|
||||||
|
display: flex
|
||||||
|
gap: 1rem
|
||||||
|
.ellipsis
|
||||||
|
max-width: 8rem
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { v1 as uuidv1 } from 'uuid'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import DataTable from 'primevue/datatable'
|
import DataTable from 'primevue/datatable'
|
||||||
import Column from 'primevue/column'
|
import Column from 'primevue/column'
|
||||||
|
@ -11,18 +12,40 @@ import {
|
||||||
type Column as DataColumn,
|
type Column as DataColumn,
|
||||||
} from 'src/types/data'
|
} from 'src/types/data'
|
||||||
|
|
||||||
const model = defineModel<DataColumn[]>()
|
const emits = defineEmits<{
|
||||||
|
(e: 'dirty'): void
|
||||||
|
}>()
|
||||||
|
const model = defineModel<DataColumn[]>({ required: true })
|
||||||
const editingFields = ref([])
|
const editingFields = ref([])
|
||||||
|
|
||||||
const onFieldEditSave = (event: { newData: any, index: number }) => {
|
const onFieldEditSave = (event: { newData: any, index: number }) => {
|
||||||
let { newData, index } = event
|
let { newData, index } = event
|
||||||
model.value![index] = newData
|
model.value[index] = newData
|
||||||
console.log(model.value)
|
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>
|
</script>
|
||||||
|
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
DataTable(
|
DataTable.field-editor(
|
||||||
@row-edit-save='onFieldEditSave'
|
@row-edit-save='onFieldEditSave'
|
||||||
v-model:editingRows='editingFields'
|
v-model:editingRows='editingFields'
|
||||||
:value='model'
|
:value='model'
|
||||||
|
@ -61,13 +84,14 @@ DataTable(
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
rowEditor
|
rowEditor
|
||||||
|
header='Actions'
|
||||||
style='width:10%;min-width:8rem;'
|
style='width:10%;min-width:8rem;'
|
||||||
bodyStyle='text-align:center;'
|
bodyStyle='text-align:center;'
|
||||||
)
|
)
|
||||||
template(
|
template(
|
||||||
#body='slotProps'
|
#body='slotProps'
|
||||||
)
|
)
|
||||||
Button(
|
Button.edit(
|
||||||
icon='pi pi-pencil'
|
icon='pi pi-pencil'
|
||||||
aria-label='Row Edit'
|
aria-label='Row Edit'
|
||||||
rounded
|
rounded
|
||||||
|
@ -83,6 +107,9 @@ DataTable(
|
||||||
rounded
|
rounded
|
||||||
severity='danger'
|
severity='danger'
|
||||||
variant='text'
|
variant='text'
|
||||||
|
:onClick=`(e) => {
|
||||||
|
onDeleteField(e, slotProps)
|
||||||
|
}`
|
||||||
)
|
)
|
||||||
template(
|
template(
|
||||||
#editor='slotProps'
|
#editor='slotProps'
|
||||||
|
@ -107,7 +134,16 @@ DataTable(
|
||||||
slotProps.editorCancelCallback(e)
|
slotProps.editorCancelCallback(e)
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
Button(
|
||||||
|
icon='pi pi-plus'
|
||||||
|
aria-label='Add New Field'
|
||||||
|
label='Add New Field'
|
||||||
|
@click='onAddField'
|
||||||
|
)
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="sass">
|
<style scoped lang="sass">
|
||||||
|
:deep(td:last-of-type)
|
||||||
|
display: flex
|
||||||
|
padding-left: 8px
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -38,7 +38,7 @@ const onBrowse = async (e: Event) => {
|
||||||
|
|
||||||
const onCreate = async (e: Event) => {
|
const onCreate = async (e: Event) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
appStore.currentInventory.filePath = `${filePath}/${fileName}.json`
|
appStore.currentInventory.filePath = `${filePath.value}/${fileName.value}.json`
|
||||||
appStore.currentInventory.data = {
|
appStore.currentInventory.data = {
|
||||||
columns: [
|
columns: [
|
||||||
{ name: 'name', type: 'text' },
|
{ name: 'name', type: 'text' },
|
||||||
|
|
Loading…
Add table
Reference in a new issue