Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
__pycache__
node_modules
.mypy_cache
.pytest_cache
.ruff_cache
.venv
data/
2 changes: 2 additions & 0 deletions static/components/item-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ window.app.component('item-card', {
return 'In Stock'
},
itemImgUrl() {
if (!this.item.images || !this.item.images.length) return null
if (isURLimg(this.item.images[0])) return this.item.images[0]
return isBase64String(this.item.images[0])
? this.item.images[0]
: `/api/v1/assets/${this.item.images[0]}/thumbnail`
Expand Down
41 changes: 41 additions & 0 deletions static/components/photo-gallery-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,39 @@ window.app.component('photo-gallery-form', {
}
},

addFromUrl() {
if (!this.canAdd) {
LNbits.utils.notifyError(`Maximum ${this.max} photos allowed`)
return
}

this.$q
.dialog({
title: 'Add Photo from URL',
color: 'secondary',
prompt: {
model: '',
type: 'text',
isValid: isURLimg,
label: 'Image URL',
attrs: {
placeholder: 'https://example.com/photo.jpg'
}
},
cancel: true,
persistent: true
})
.onOk(url => {
const preview = url.trim()
this.gallery.push({
file: null,
preview,
assetId: null,
isNew: true
})
})
},

remove(index) {
URL.revokeObjectURL(this.gallery[index].preview)
this.gallery.splice(index, 1)
Expand All @@ -111,6 +144,14 @@ window.app.component('photo-gallery-form', {
<div>
<div class="float-right">
<q-btn
outline
color="secondary"
class="q-mb-sm q-mr-sm"
:disable="!canAdd"
label="Add URL"
@click="addFromUrl"
></q-btn>
<q-btn
label="Add Photo"
color="secondary"
:disable="!canAdd"
Expand Down
55 changes: 19 additions & 36 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,10 @@ window.app = Vue.createApp({
this.itemDialog.gallery = item.images.map(id => {
return {
assetId: id,
preview: isBase64String(id) ? id : `/api/v1/assets/${id}/thumbnail`,
preview:
isBase64String(id) || isURLimg(id)
? id
: `/api/v1/assets/${id}/thumbnail`,
file: null,
isNew: false
}
Expand Down Expand Up @@ -531,9 +534,9 @@ window.app = Vue.createApp({
this.itemDialog.data.inventory_id = this.inventory.id
try {
const assetIds = await Promise.all(
this.itemDialog.gallery
.filter(p => p.file)
.map(p => this.uploadPhoto(p.file))
this.itemDialog.gallery.map(p =>
p.file ? this.uploadPhoto(p.file) : p.preview
)
)
if (assetIds.includes(null)) {
LNbits.utils.notifyError('One or more photo uploads failed')
Expand All @@ -555,40 +558,20 @@ window.app = Vue.createApp({
}
},
async updateItem(data) {
const filesToUpload = this.itemDialog.gallery
.filter(
p =>
(p.isNew && p.file) ||
(!p.isNew && p.assetId && isBase64String(p.assetId))
)
.map(p => {
if (p.isNew && p.file) return p.file
if (!p.isNew && p.assetId && isBase64String(p.assetId))
return base64ToFile(p.assetId)
return null
const assetIds = await Promise.all(
this.itemDialog.gallery.map(p => {
if (p.file) return this.uploadPhoto(p.file)
if (p.assetId && isBase64String(p.assetId)) {
return this.uploadPhoto(base64ToFile(p.assetId))
}
return p.assetId || p.preview
})
.filter(Boolean)

let uploadedAssetIds = []
if (filesToUpload.length > 0) {
try {
uploadedAssetIds = await Promise.all(
filesToUpload.map(file => this.uploadPhoto(file))
)
} catch (error) {
LNbits.utils.notifyError('Failed to upload photos')
return
}
)
if (assetIds.includes(null)) {
LNbits.utils.notifyError('One or more photo uploads failed')
return
}

// Asset IDs from gallery that are not new and not base64
const existingAssetIds = this.itemDialog.gallery
.filter(p => !p.isNew && p.assetId && !isBase64String(p.assetId))
.map(p => p.assetId)

const finalIds = [...existingAssetIds, ...uploadedAssetIds]

data.images = toCsv(finalIds)
data.images = toCsv(assetIds)

try {
const {data: updatedItem} = await LNbits.api.request(
Expand Down
15 changes: 12 additions & 3 deletions static/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ function mapItems(obj) {
}
obj.tags = fromCsv(obj.tags)
obj.omit_tags = fromCsv(obj.omit_tags)
obj.images = isBase64String(obj.images)
? fromCsv(obj.images, '|||')
: fromCsv(obj.images)
obj.images =
isBase64String(obj.images) || obj.images?.includes('|||')
? fromCsv(obj.images, '|||')
: fromCsv(obj.images)
return obj
}

Expand Down Expand Up @@ -51,3 +52,11 @@ function isBase64String(str) {
if (typeof str !== 'string') return false
return str.includes('data:') && str.includes('base64')
}
function isURLimg(str) {
if (typeof str !== 'string') return false
try {
return ['http:', 'https:'].includes(new URL(str.trim()).protocol)
} catch {
return false
}
}
33 changes: 17 additions & 16 deletions templates/inventory/manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,17 @@
},
async addItem() {
this.itemDialog.data.inventory_id = this.inventory.id
const base64List = await Promise.all(
this.itemDialog.gallery.map(p => fileToBase64(p.file))
const newPhotos = await Promise.all(
this.itemDialog.gallery.map(p =>
p.file ? fileToBase64(p.file) : p.preview
)
)

try {
this.itemDialog.data.images = toCsv(base64List, '|||')
this.itemDialog.data.images = toCsv(
newPhotos,
newPhotos.some(isBase64String) ? '|||' : ','
)
const {data} = await LNbits.api.request(
'POST',
`/inventory/api/v1/managers/${this.manager.id}/item`,
Expand All @@ -385,21 +391,16 @@
}
},
async updateItem(data) {
const newPhotos = this.itemDialog.gallery.filter(p => p.isNew && p.file)
let newBase64 = []

if (newPhotos.length > 0) {
newBase64 = await Promise.all(
newPhotos.map(p => fileToBase64(p.file))
const finalImages = await Promise.all(
this.itemDialog.gallery.map(p =>
p.file ? fileToBase64(p.file) : p.preview
)
}

const finalImages = [
...this.itemDialog.gallery.filter(p => !p.isNew).map(p => p.file),
...newBase64
]
)

data.images = toCsv(finalImages, '|||')
data.images = toCsv(
finalImages,
finalImages.some(isBase64String) ? '|||' : ','
)

try {
const {data: updatedItem} = await LNbits.api.request(
Expand Down
Loading