210 lines
5.4 KiB
TypeScript
210 lines
5.4 KiB
TypeScript
import { get as getStore } from 'svelte/store'
|
|
import * as wn from 'webnative'
|
|
import * as uint8arrays from 'uint8arrays'
|
|
import type { CID } from 'multiformats/cid'
|
|
import type { PuttableUnixTree, File as WNFile } from 'webnative/fs/types'
|
|
import type { Metadata } from 'webnative/fs/metadata'
|
|
|
|
import { filesystemStore } from '$src/stores'
|
|
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
|
import { addNotification } from '$lib/notifications'
|
|
|
|
export type Image = {
|
|
cid: string
|
|
ctime: number
|
|
name: string
|
|
private: boolean
|
|
size: number
|
|
src: string
|
|
}
|
|
|
|
export type Gallery = {
|
|
publicImages: Image[] | null
|
|
privateImages: Image[] | null
|
|
selectedArea: AREAS
|
|
loading: boolean
|
|
}
|
|
|
|
interface GalleryFile extends PuttableUnixTree, WNFile {
|
|
cid: CID
|
|
content: Uint8Array
|
|
header: {
|
|
content: Uint8Array
|
|
metadata: Metadata
|
|
}
|
|
}
|
|
|
|
type Link = {
|
|
size: number
|
|
}
|
|
|
|
export const GALLERY_DIRS = {
|
|
[AREAS.PUBLIC]: ['public', 'gallery'],
|
|
[AREAS.PRIVATE]: ['private', 'gallery']
|
|
}
|
|
const FILE_SIZE_LIMIT = 5
|
|
|
|
/**
|
|
* Get images from the user's WNFS and construct the `src` value for the images
|
|
*/
|
|
export const getJSONFromWNFS: () => Promise<void> = async () => {
|
|
try {
|
|
// Set loading: true on the galleryStore
|
|
galleryStore.update(store => ({ ...store, loading: true }))
|
|
|
|
const { selectedArea } = getStore(galleryStore)
|
|
const isPrivate = selectedArea === AREAS.PRIVATE
|
|
const fs = getStore(filesystemStore)
|
|
|
|
// Set path to either private or public gallery dir
|
|
const path = wn.path.directory(...GALLERY_DIRS[selectedArea])
|
|
|
|
// Get list of links for files in the gallery dir
|
|
const links = await fs.ls(path)
|
|
|
|
const images = await Promise.all(
|
|
Object.entries(links).map(async ([name]) => {
|
|
const file = await fs.get(
|
|
wn.path.file(...GALLERY_DIRS[selectedArea], `${name}`)
|
|
)
|
|
|
|
// The CID for private files is currently located in `file.header.content`,
|
|
// whereas the CID for public files is located in `file.cid`
|
|
const cid = isPrivate
|
|
? (file as GalleryFile).header.content.toString()
|
|
: (file as GalleryFile).cid.toString()
|
|
|
|
// Create a base64 string to use as the image `src`
|
|
const src = `data:image/jpeg;base64, ${uint8arrays.toString(
|
|
(file as GalleryFile).content,
|
|
'base64'
|
|
)}`
|
|
|
|
return {
|
|
cid,
|
|
ctime: (file as GalleryFile).header.metadata.unixMeta.ctime,
|
|
name,
|
|
private: isPrivate,
|
|
size: (links[name] as Link).size,
|
|
src
|
|
}
|
|
})
|
|
)
|
|
|
|
// Sort images by ctime(created at date)
|
|
// NOTE: this will eventually be controlled via the UI
|
|
images.sort((a, b) => b.ctime - a.ctime)
|
|
|
|
// Push images to the galleryStore
|
|
galleryStore.update(store => ({
|
|
...store,
|
|
...(isPrivate
|
|
? {
|
|
privateImages: images
|
|
}
|
|
: {
|
|
publicImages: images
|
|
}),
|
|
loading: false
|
|
}))
|
|
} catch (error) {
|
|
console.error(error)
|
|
galleryStore.update(store => ({
|
|
...store,
|
|
loading: false
|
|
}))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload an image to the user's private or public WNFS
|
|
* @param image
|
|
*/
|
|
export const uploadImageToWNFS: (
|
|
image: File
|
|
) => Promise<void> = async image => {
|
|
try {
|
|
const { selectedArea } = getStore(galleryStore)
|
|
const fs = getStore(filesystemStore)
|
|
|
|
// Reject files over 5MB
|
|
const imageSizeInMB = image.size / (1024 * 1024)
|
|
if (imageSizeInMB > FILE_SIZE_LIMIT) {
|
|
throw new Error('Image can be no larger than 5MB')
|
|
}
|
|
|
|
// Reject the upload if the image already exists in the directory
|
|
const imageExists = await fs.exists(
|
|
wn.path.file(...GALLERY_DIRS[selectedArea], image.name)
|
|
)
|
|
if (imageExists) {
|
|
throw new Error(`${image.name} image already exists`)
|
|
}
|
|
|
|
// Create a sub directory and add some content
|
|
await fs.write(
|
|
wn.path.file(...GALLERY_DIRS[selectedArea], image.name),
|
|
image
|
|
)
|
|
|
|
// Announce the changes to the server
|
|
await fs.publish()
|
|
|
|
addNotification(`${image.name} image has been published`, 'success')
|
|
} catch (error) {
|
|
addNotification(error.message, 'error')
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete an image from the user's private or public WNFS
|
|
* @param name
|
|
*/
|
|
export const deleteImageFromWNFS: (
|
|
name: string
|
|
) => Promise<void> = async name => {
|
|
try {
|
|
const { selectedArea } = getStore(galleryStore)
|
|
const fs = getStore(filesystemStore)
|
|
|
|
const imageExists = await fs.exists(
|
|
wn.path.file(...GALLERY_DIRS[selectedArea], name)
|
|
)
|
|
|
|
if (imageExists) {
|
|
// Remove images from server
|
|
await fs.rm(wn.path.file(...GALLERY_DIRS[selectedArea], name))
|
|
|
|
// Announce the changes to the server
|
|
await fs.publish()
|
|
|
|
addNotification(`${name} image has been deleted`, 'success')
|
|
|
|
// Refetch images and update galleryStore
|
|
await getJSONFromWNFS()
|
|
} else {
|
|
throw new Error(`${name} image has already been deleted`)
|
|
}
|
|
} catch (error) {
|
|
addNotification(error.message, 'error')
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle uploads made by interacting with the file input directly
|
|
*/
|
|
export const handleFileInput: (
|
|
files: FileList
|
|
) => Promise<void> = async files => {
|
|
await Promise.all(
|
|
Array.from(files).map(async file => {
|
|
await uploadImageToWNFS(file)
|
|
})
|
|
)
|
|
|
|
// Refetch images and update galleryStore
|
|
await getJSONFromWNFS()
|
|
}
|