Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File replace mutations #336

Merged
merged 2 commits into from
Oct 25, 2023
Merged
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
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