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
11 changes: 10 additions & 1 deletion src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,16 @@ export async function setup() {

if (fileSystem instanceof TauriFileSystem) await setupTauriFileSystem()

if (fileSystem instanceof LocalFileSystem) fileSystem.setRootName('fileSystemPolyfill')
if (fileSystem instanceof LocalFileSystem) {
fileSystem.setRootName('fileSystemPolyfill')

// On browsers without the File System Access API (e.g. Safari, Firefox, Android Chrome) the
// entire project lives in IndexedDB. Request persistent storage so the browser doesn't evict
// it, which otherwise causes saved files to silently disappear.
if (navigator.storage?.persist) {
navigator.storage.persist().catch(() => {})
}
}

setupTypescript()
setupLang()
Expand Down
37 changes: 35 additions & 2 deletions src/components/Windows/Presets/Presets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,32 @@ const validationError: ComputedRef<string | null> = computed(() => {
return null
})

function openFileInput(fieldId: string) {
const input = document.getElementById(`preset-file-input-${fieldId}`)

if (input instanceof HTMLInputElement) input.click()
}

async function onFileInputChange(event: Event, fieldId: string) {
const input = <HTMLInputElement>event.target

const file = input.files?.[0]

if (!file) return

const content = new Uint8Array(await file.arrayBuffer())

// Store a fake file handle matching the shape that preset scripts (and loadPresetFile) expect:
// `name` + `content` (binary, read by createFile) + `text()` (read by e.g. the Block Model script).
createPresetOptions.value[fieldId] = {
name: file.name,
content,
async text() {
return new TextDecoder().decode(content)
},
}
}

async function create() {
if (validationError.value !== null) return

Expand Down Expand Up @@ -214,15 +240,22 @@ watch(filteredCategories, () => {
class="mb-6 flex bg-background"
v-slot="{ focus, blur }"
>
<input type="file" class="hidden" />
<input
:id="`preset-file-input-${fieldId}`"
type="file"
class="hidden"
:accept="fieldOptions.accept"
@change="(event: Event) => onFileInputChange(event, fieldId)"
/>

<button
class="flex align-center gap-2 text-text-secondary font-theme placeholder:text-text-secondary"
@mouseenter="focus"
@mouseleave="blur"
@click="openFileInput(fieldId)"
>
<Icon icon="image" class="no-fill" color="text-text-secondary" />
{{ fieldName }}
{{ createPresetOptions[fieldId]?.name ?? fieldName }}
</button>
</LabeledInput>

Expand Down
5 changes: 4 additions & 1 deletion src/libs/compiler/DashService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ export class DashService implements AsyncDisposable {
await sendAndWait(
{
action: 'setup',
config: this.project.config,
// Pass a plain, structured-clone-safe copy of the config. Safari's structured clone
// is stricter than Chromium's and throws DataCloneError on non-plain objects, which
// broke .mcaddon exports (the only export path that hands data to the Dash worker).
config: this.project.config ? JSON.parse(JSON.stringify(this.project.config)) : this.project.config,
mode,
configPath: join(this.project.path, 'config.json'),
compilerConfigPath,
Expand Down
9 changes: 8 additions & 1 deletion src/libs/fileSystem/LocalFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,14 @@ export class LocalFileSystem extends BaseFileSystem {

path = resolve('/', path)

await del(`localFileSystem/${this.rootName}${path}`)
// Remove the directory entry itself as well as every file and subdirectory nested inside of it.
// idb-keyval has no concept of folders, so deleting only the directory key would orphan its children.
const directoryKey = `localFileSystem/${this.rootName}${path}`
const childPrefix = `${directoryKey}/`

const childKeys = (await keys()).filter((key) => key.toString().startsWith(childPrefix))

await Promise.all([del(directoryKey), ...childKeys.map((key) => del(key))])

if (
this.pathsToWatch.find((watchPath) => path.startsWith(watchPath)) !== undefined &&
Expand Down