Skip to content
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
55 changes: 53 additions & 2 deletions components/files/DownloadDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'w-full rounded-md border bg-autonomi-surface px-3 py-2 text-sm text-autonomi-text focus:outline-none',
filenameError ? 'border-red-500 focus:border-red-500' : 'border-autonomi-border focus:border-autonomi-blue',
]"
@input="onFilenameInput"
@keyup.enter="confirm"
/>
<p v-if="filenameError" class="mt-1 text-xs text-red-400">{{ filenameError }}</p>
Expand All @@ -57,17 +58,23 @@
</template>

<script setup lang="ts">
import { useFilesStore } from '~/stores/files'
import { filenameError as checkFilename } from '~/utils/validators'

const props = defineProps<{ open: boolean }>()
const emit = defineEmits<{
close: []
download: [address: string, filename: string]
}>()

import { filenameError as checkFilename } from '~/utils/validators'
const filesStore = useFilesStore()

const inputEl = ref<HTMLInputElement | null>(null)
const address = ref('')
const filename = ref('')
/** Tracks whether the user has edited the filename since the last prefill,
* so a subsequent address change doesn't clobber their typing. */
const filenameDirty = ref(false)

const filenameError = computed(() => checkFilename(filename.value))

Expand All @@ -77,17 +84,61 @@ const valid = computed(() => {
return isHex && filename.value.trim().length > 0 && filenameError.value === null
})

/** Prior upload that matches the currently-typed address, if any. Drives
* both the filename prefill and the extension-append fallback on submit. */
const matchedEntry = computed(() => {
const addr = address.value.trim()
if (!/^(0x)?[0-9a-fA-F]{8,}$/.test(addr)) return undefined
return filesStore.findUploadByAddress(addr)
})

watch(() => props.open, (val) => {
if (val) {
address.value = ''
filename.value = ''
filenameDirty.value = false
nextTick(() => inputEl.value?.focus())
}
})

watch(matchedEntry, (match) => {
if (!match) return
if (filenameDirty.value) return
filename.value = match.name
})

function onFilenameInput() {
filenameDirty.value = true
}

function confirm() {
if (!valid.value) return
emit('download', address.value.trim(), filename.value.trim())
const typed = filename.value.trim()
const final = appendExtensionIfNeeded(typed, matchedEntry.value?.name)
emit('download', address.value.trim(), final)
emit('close')
}

/** If the user typed a filename without an extension and we know the
* original extension from a history match, append it silently. Keeps
* "screenshot" → "screenshot.png" without nagging, while leaving
* deliberate unextensioned names alone when there's no match to copy from. */
function appendExtensionIfNeeded(typed: string, originalName?: string): string {
if (!originalName) return typed
if (hasExtension(typed)) return typed
const origExt = extensionOf(originalName)
if (!origExt) return typed
return `${typed}.${origExt}`
}

function hasExtension(name: string): boolean {
const dot = name.lastIndexOf('.')
return dot > 0 && dot < name.length - 1
}

function extensionOf(name: string): string | null {
const dot = name.lastIndexOf('.')
if (dot <= 0 || dot === name.length - 1) return null
return name.slice(dot + 1)
}
</script>
37 changes: 29 additions & 8 deletions pages/files.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@
<th class="cursor-pointer px-4 py-2.5 hover:text-autonomi-text" @click="toggleUploadSort('cost')">
Cost {{ uploadSortIndicator('cost') }}
</th>
<th class="px-4 py-2.5">Address</th>
<th class="cursor-pointer px-4 py-2.5 hover:text-autonomi-text" @click="toggleUploadSort('date')">
Date {{ uploadSortIndicator('date') }}
</th>
<th class="px-4 py-2.5">Address</th>
<th class="w-px px-2 py-2.5"><span class="sr-only">Actions</span></th>
</tr>
</thead>
Expand Down Expand Up @@ -134,6 +134,7 @@
<span v-if="file.gas_cost" class="block text-[10px] text-autonomi-muted/60">+ {{ file.gas_cost }} gas</span>
</template>
</td>
<td class="px-4 py-2.5 text-autonomi-muted">{{ formatDate(file.date) }}</td>
<td class="px-4 py-2.5">
<span
v-if="file.public_address"
Expand All @@ -144,24 +145,39 @@
<span class="rounded bg-autonomi-blue/15 px-1 py-px text-[9px] font-sans uppercase tracking-wider">Public</span>
{{ truncateAddress(file.public_address, 8, 6) }}
</span>
<span
<div
v-else-if="file.data_map_file"
class="cursor-pointer font-mono text-xs text-autonomi-muted hover:text-autonomi-blue"
:title="`Reveal ${datamapBasename(file.data_map_file)} in its folder`"
@click.stop="openFolder(file.data_map_file)"
class="flex items-center gap-1.5"
>
{{ datamapBasename(file.data_map_file) }}
</span>
<span
class="cursor-pointer font-mono text-xs text-autonomi-muted hover:text-autonomi-blue hover:underline"
title="Reveal datamap file in Finder/Explorer"
@click.stop="openFolder(file.data_map_file)"
>
{{ datamapBasename(file.data_map_file) }}
</span>
<button
type="button"
class="rounded p-0.5 text-autonomi-muted/60 hover:text-autonomi-blue hover:bg-autonomi-surface"
title="Copy datamap file path"
@click.stop="copyDatamapPath(file.data_map_file!)"
>
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M7 3.5A1.5 1.5 0 018.5 2h3.879a1.5 1.5 0 011.06.44l3.122 3.12A1.5 1.5 0 0117 6.622V12.5a1.5 1.5 0 01-1.5 1.5h-1v-3.379a3 3 0 00-.879-2.121L10.5 5.379A3 3 0 008.379 4.5H7v-1z" />
<path d="M4.5 6A1.5 1.5 0 003 7.5v9A1.5 1.5 0 004.5 18h7a1.5 1.5 0 001.5-1.5v-5.879a1.5 1.5 0 00-.44-1.06L9.44 6.439A1.5 1.5 0 008.378 6H4.5z" />
</svg>
</button>
</div>
<span
v-else-if="file.address"
class="cursor-pointer font-mono text-xs text-autonomi-muted hover:text-autonomi-blue"
title="Copy network address"
@click.stop="copyAddress(file.address)"
>
{{ truncateAddress(file.address, 8, 6) }}
</span>
<span v-else class="text-autonomi-muted">-</span>
</td>
<td class="px-4 py-2.5 text-autonomi-muted">{{ formatDate(file.date) }}</td>
<td class="px-2 py-2.5 text-right whitespace-nowrap">
<span v-if="isSettled(file)" class="inline-flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
<button
Expand Down Expand Up @@ -1033,6 +1049,11 @@ function copyPublicAddress(addr: string) {
toastStore.add('Public address copied — share to let others download this file', 'info')
}

function copyDatamapPath(path: string) {
navigator.clipboard.writeText(path)
toastStore.add('Datamap path copied to clipboard', 'info')
}

function datamapBasename(path: string): string {
return path.split(/[\\/]/).pop() ?? path
}
Expand Down