Skip to content

Commit

Permalink
File replace mutations (#336)
Browse files Browse the repository at this point in the history
  • Loading branch information
benmerckx authored Oct 25, 2023
1 parent 7f0bf53 commit b3e0605
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 181 deletions.
1 change: 1 addition & 0 deletions src/backend/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export class Database implements Syncable {
)
return
case MutationType.FileRemove:
if (mutation.replace) return
case MutationType.Remove:
return this.store(EntryRow({entryId: mutation.entryId}).delete())
case MutationType.Discard:
Expand Down
7 changes: 6 additions & 1 deletion src/backend/data/ChangeSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,12 @@ export class ChangeSetCreator {
return [{type: ChangeType.Upload, file: mutation.file, url: mutation.url}]
}

fileRemoveChanges({file: entryFile}: FileRemoveMutation): Array<Change> {
fileRemoveChanges({
file: entryFile,
replace
}: FileRemoveMutation): Array<Change> {
// Removing the binary file from the media instance happens in Server
if (replace) return []
return [{type: ChangeType.Delete, file: entryFile}]
}

Expand Down
13 changes: 12 additions & 1 deletion src/core/EntryFilenames.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {JsonLoader} from 'alinea/backend'
import {Config} from './Config.js'
import {EntryPhase} from './EntryRow.js'
import {EntryPhase, EntryRow} from './EntryRow.js'
import {Workspace} from './Workspace.js'
import {values} from './util/Objects.js'
import {join} from './util/Paths.js'
Expand Down Expand Up @@ -96,3 +96,14 @@ export function entryFileName(
if (!root) throw new Error(`Root "${entry.root}" does not exist`)
return join(contentDir, entry.root, entryFilepath(config, entry, parentPaths))
}

export function entryFile(config: Config, entry: EntryRow) {
const workspace = config.workspaces[entry.workspace]
if (!workspace)
throw new Error(`Workspace "${entry.workspace}" does not exist`)
const filePath = entry.filePath
const {source: contentDir} = Workspace.data(workspace)
const root = Workspace.roots(workspace)[entry.root]
if (!root) throw new Error(`Root "${entry.root}" does not exist`)
return join(contentDir, entry.root, filePath)
}
1 change: 1 addition & 0 deletions src/core/Mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,5 @@ export interface FileRemoveMutation {
file: string
workspace: string
location: string
replace: boolean
}
3 changes: 2 additions & 1 deletion src/dashboard/atoms/EntryEditorAtoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ export function createEntryEditor(entryData: EntryData) {
entryId: published.entryId,
workspace: published.workspace,
location: (published.data as MediaFile).location,
file: entryFile(published)
file: entryFile(published),
replace: false
}
return set(mutateAtom, mutation).catch(error => {
set(
Expand Down
86 changes: 65 additions & 21 deletions src/dashboard/hook/UseUploads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {entryFileName, entryFilepath} from 'alinea/core/EntryFilenames'
import {createId} from 'alinea/core/Id'
import {Mutation, MutationType} from 'alinea/core/Mutation'
import {MediaFile} from 'alinea/core/media/MediaSchema'
import {base64} from 'alinea/core/util/Encoding'
import {generateKeyBetween} from 'alinea/core/util/FractionalIndexing'
import {
Expand All @@ -22,9 +23,9 @@ import {
normalize
} from 'alinea/core/util/Paths'
import {rgba, toHex} from 'color2k'
import {useSetAtom} from 'jotai'
import {atom, useAtom, useSetAtom} from 'jotai'
import pLimit from 'p-limit'
import {useState} from 'react'
import {useEffect} from 'react'
import smartcrop from 'smartcrop'
import {rgbaToThumbHash, thumbHashToAverageRGBA} from 'thumbhash'
import {useMutate} from '../atoms/DbAtoms.js'
Expand All @@ -42,6 +43,8 @@ export enum UploadStatus {
}

export interface UploadDestination {
// Use to overwrite files
entryId?: string
parentId?: string
workspace: string
root: string
Expand All @@ -62,6 +65,7 @@ export interface Upload {
height?: number
result?: Media.File
error?: Error
replace?: {entry: EntryRow; entryFile: string}
}

const defaultTasker = pLimit(Infinity)
Expand Down Expand Up @@ -175,22 +179,51 @@ async function process(
return {...upload, info, status: UploadStatus.Uploaded}
}
case UploadStatus.Uploaded: {
const {file, entry} = await createEntry(upload)
const {replace} = upload
const info = upload.info!
await mutate(
{
type: MutationType.Create,
entryId: entry.entryId,
file,
entry
},
{
type: MutationType.Upload,
entryId: entry.entryId,
url: info.previewUrl,
file: info.location
}
)
const {file, entry} = await createEntry(upload)
if (replace) {
await mutate(
{
type: MutationType.Edit,
entryId: replace.entry.entryId,
file: replace.entryFile,
entry: {
...replace.entry,
data: {...entry.data, title: replace.entry.title}
}
},
{
type: MutationType.Upload,
entryId: entry.entryId,
url: info.previewUrl,
file: info.location
},
{
type: MutationType.FileRemove,
entryId: replace.entry.entryId,
file: replace.entryFile,
workspace: replace.entry.workspace,
location: (replace.entry.data as MediaFile).location,
replace: true
}
)
} else {
await mutate(
{
type: MutationType.Create,
entryId: entry.entryId,
file,
entry
},
{
type: MutationType.Upload,
entryId: entry.entryId,
url: info.previewUrl,
file: info.location
}
)
}
return {...upload, result: entry, status: UploadStatus.Done}
}
case UploadStatus.Done:
Expand Down Expand Up @@ -221,15 +254,22 @@ function createBatch(mutate: (...mutations: Array<Mutation>) => Promise<void>) {
}
}

const uploadsAtom = atom<Array<Upload>>([])

export function useUploads(onSelect?: (entry: EntryRow) => void) {
const config = useConfig()
const graph = useGraph()
const {cnx: client} = useSession()
const mutate = useMutate()
const setErrorAtom = useSetAtom(errorAtom)
const [uploads, setUploads] = useState<Array<Upload>>([])
const [uploads, setUploads] = useAtom(uploadsAtom)
const batch = createBatch(mutate)

useEffect(() => {
// Clear upload list on unmount
return () => setUploads([])
}, [])

async function batchMutations(...mutations: Array<Mutation>) {
await batch(...mutations)
}
Expand Down Expand Up @@ -350,9 +390,13 @@ export function useUploads(onSelect?: (entry: EntryRow) => void) {
}
}

async function upload(files: FileList, to: UploadDestination) {
const uploads = Array.from(files).map(file => {
return {id: createId(), file, to, status: UploadStatus.Queued}
async function upload(
files: Array<File>,
to: UploadDestination,
replace?: {entry: EntryRow; entryFile: string}
) {
const uploads: Array<Upload> = Array.from(files).map(file => {
return {id: createId(), file, to, replace, status: UploadStatus.Queued}
})
setUploads(current => [...uploads, ...current])
return Promise.all(uploads.map(uploadFile))
Expand Down
Loading

0 comments on commit b3e0605

Please sign in to comment.