save images
This commit is contained in:
parent
c66914239d
commit
4a6ad11d16
7 changed files with 182 additions and 8 deletions
7
src-tauri/Cargo.lock
generated
7
src-tauri/Cargo.lock
generated
|
@ -1512,6 +1512,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -3770,6 +3776,7 @@ dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
|
"http-range",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = ["protocol-asset"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
|
"core:image:allow-from-path",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
"fs:allow-write-text-file",
|
"fs:allow-write-text-file",
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"assetProtocol": {
|
||||||
|
"enable": true,
|
||||||
|
"scope": ["*/**"]
|
||||||
|
},
|
||||||
|
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|
|
@ -8,6 +8,11 @@ export const FieldTypes = [
|
||||||
|
|
||||||
export type FieldType = typeof FieldTypes[number]
|
export type FieldType = typeof FieldTypes[number]
|
||||||
|
|
||||||
|
export interface Image {
|
||||||
|
src: string
|
||||||
|
alt?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Column {
|
export interface Column {
|
||||||
name: string
|
name: string
|
||||||
type: FieldType
|
type: FieldType
|
||||||
|
|
|
@ -11,6 +11,13 @@ import {
|
||||||
type Row as DataRow,
|
type Row as DataRow,
|
||||||
type Column as DataColumn,
|
type Column as DataColumn,
|
||||||
} from 'src/types/data'
|
} from 'src/types/data'
|
||||||
|
import ImageEditor from './ImageEditor.vue'
|
||||||
|
|
||||||
|
interface EditingRow {
|
||||||
|
data: DataRow
|
||||||
|
index: number
|
||||||
|
field: string
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
columns: DataColumn[],
|
columns: DataColumn[],
|
||||||
|
@ -20,6 +27,8 @@ const emits = defineEmits<{
|
||||||
}>()
|
}>()
|
||||||
const model = defineModel<DataRow[]>({ required: true })
|
const model = defineModel<DataRow[]>({ required: true })
|
||||||
const editingRows = ref([])
|
const editingRows = ref([])
|
||||||
|
const editingImage = ref(false)
|
||||||
|
const editingImageRow = ref(null as EditingRow | null)
|
||||||
|
|
||||||
const onRowEditSave = (event: { newData: any, index: number }) => {
|
const onRowEditSave = (event: { newData: any, index: number }) => {
|
||||||
let { newData, index } = event
|
let { newData, index } = event
|
||||||
|
@ -45,9 +54,28 @@ const onDeleteRow = (event: Event, slotProps: { index: number }) => {
|
||||||
model.value.splice(slotProps.index, 1)
|
model.value.splice(slotProps.index, 1)
|
||||||
emits('dirty')
|
emits('dirty')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onEditImage = (event: Event, slotProps: { data: DataRow, index: number, field: string }) => {
|
||||||
|
event.preventDefault()
|
||||||
|
editingImage.value = true
|
||||||
|
editingImageRow.value = slotProps
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSaveImage = (image: string) => {
|
||||||
|
const row = { ...model.value[editingImageRow.value!.index] } as any
|
||||||
|
row[editingImageRow.value!.field] = {
|
||||||
|
src: image,
|
||||||
|
}
|
||||||
|
model.value[editingImageRow.value!.index] = row
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
|
ImageEditor(
|
||||||
|
v-model:visible='editingImage'
|
||||||
|
v-model:row='editingImageRow'
|
||||||
|
@saved='onSaveImage'
|
||||||
|
)
|
||||||
DataTable.data-editor(
|
DataTable.data-editor(
|
||||||
@row-edit-save='onRowEditSave'
|
@row-edit-save='onRowEditSave'
|
||||||
v-model:editingRows='editingRows'
|
v-model:editingRows='editingRows'
|
||||||
|
@ -70,9 +98,8 @@ DataTable.data-editor(
|
||||||
v-if='col.type === "image"'
|
v-if='col.type === "image"'
|
||||||
)
|
)
|
||||||
Image(
|
Image(
|
||||||
v-if='slotProps.data.image'
|
v-if='slotProps.data[col.name]'
|
||||||
:src='slotProps.data.image.src'
|
:src='slotProps.data[col.name].src'
|
||||||
:alt='slotProps.data.image.alt'
|
|
||||||
preview
|
preview
|
||||||
)
|
)
|
||||||
span(
|
span(
|
||||||
|
@ -83,17 +110,20 @@ DataTable.data-editor(
|
||||||
)
|
)
|
||||||
span {{ slotProps.data[col.name] }}
|
span {{ slotProps.data[col.name] }}
|
||||||
template(
|
template(
|
||||||
#editor='{ data, field }'
|
#editor='slotProps'
|
||||||
)
|
)
|
||||||
div(
|
div(
|
||||||
v-if='col.type === "image"'
|
v-if='col.type === "image"'
|
||||||
)
|
)
|
||||||
span Not Implemented
|
Button(
|
||||||
|
label='Edit Image'
|
||||||
|
:onClick='(e) => onEditImage(e, slotProps)'
|
||||||
|
)
|
||||||
div(
|
div(
|
||||||
v-else
|
v-else
|
||||||
)
|
)
|
||||||
InputText(
|
InputText(
|
||||||
v-model='data[field]'
|
v-model='slotProps.data[slotProps.field]'
|
||||||
fluid
|
fluid
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
|
|
127
src/views/Editor/ImageEditor.vue
Normal file
127
src/views/Editor/ImageEditor.vue
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
ref,
|
||||||
|
} from 'vue'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import Dialog from 'primevue/dialog'
|
||||||
|
import ImageComponent from 'primevue/image'
|
||||||
|
import InputGroup from 'primevue/inputgroup'
|
||||||
|
import InputGroupAddon from 'primevue/inputgroupaddon'
|
||||||
|
import InputText from 'primevue/inputtext'
|
||||||
|
|
||||||
|
import {
|
||||||
|
type Row as DataRow,
|
||||||
|
} from 'src/types/data'
|
||||||
|
import { useAppStore } from 'src/store'
|
||||||
|
|
||||||
|
const store = useAppStore()
|
||||||
|
|
||||||
|
const visible = defineModel<boolean>('visible', { default: false })
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'saved', val: string): void
|
||||||
|
}>()
|
||||||
|
const row = defineModel<DataRow>('row')
|
||||||
|
const filePath = ref(store.currentInventory.filePath.replace('.json', '/'))
|
||||||
|
const fileName = ref('')
|
||||||
|
const currentSrc = ref('')
|
||||||
|
const currentBlob = ref<Blob>()
|
||||||
|
|
||||||
|
const onBrowseForImage = async (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const val = await open({
|
||||||
|
multiple: false,
|
||||||
|
directory: false,
|
||||||
|
filters: [{ name: 'Image (svg, png, jpg, webp)', extensions: ['svg', 'png', 'jpg', 'jpeg', 'webp'] }],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!!val && !Array.isArray(val)) {
|
||||||
|
let res = await fetch(convertFileSrc(val), {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
let blob = await res.blob()
|
||||||
|
if (blob.type.includes('image')){
|
||||||
|
currentSrc.value = URL.createObjectURL(blob)
|
||||||
|
currentBlob.value = blob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToDataUrlViaCanvas(url: string, callback: (val: string) => void, outputFormat: string) {
|
||||||
|
const image = new Image()
|
||||||
|
image.onload = function() {
|
||||||
|
let canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
let dataUrl
|
||||||
|
canvas.height = (this as any).height
|
||||||
|
canvas.width = (this as any).width
|
||||||
|
ctx!.drawImage(this as any, 0, 0)
|
||||||
|
dataUrl = canvas.toDataURL(outputFormat)
|
||||||
|
callback(dataUrl)
|
||||||
|
canvas = null as any
|
||||||
|
}
|
||||||
|
image.src = url
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
convertToDataUrlViaCanvas(currentSrc.value, (val) => {
|
||||||
|
emit('saved', val)
|
||||||
|
visible.value = false
|
||||||
|
}, currentBlob.value!.type)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
Dialog(
|
||||||
|
v-model:visible='visible'
|
||||||
|
modal
|
||||||
|
header='Edit Image'
|
||||||
|
)
|
||||||
|
.content(
|
||||||
|
v-if='!currentSrc'
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
label='Browse For Image'
|
||||||
|
@click='onBrowseForImage'
|
||||||
|
)
|
||||||
|
.content(
|
||||||
|
v-else
|
||||||
|
)
|
||||||
|
InputGroup.image-group
|
||||||
|
ImageComponent(
|
||||||
|
:src='currentSrc'
|
||||||
|
)
|
||||||
|
template(#footer)
|
||||||
|
Button(
|
||||||
|
label='Save'
|
||||||
|
@click='onSave'
|
||||||
|
:disabled='!currentSrc'
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
label='Replace'
|
||||||
|
@click='onBrowseForImage'
|
||||||
|
:disabled='!currentSrc'
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
label='Cancel'
|
||||||
|
@click='onCancel'
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="sass">
|
||||||
|
.image-group
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
:deep(.p-image img)
|
||||||
|
max-width: 33vw
|
||||||
|
max-height: 33vh
|
||||||
|
</style>
|
Loading…
Add table
Reference in a new issue