mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
* feat(file-drop): add element owner preparation plumbing * Fix terminal and chat file drop destination ownership * fix runtime terminal drop ownership and queued chat retries * fix(file-drop): preserve feedback acceptance and destination ordering * fix(file-drop): attach chat and composer files at the drop surface * fix(file-drop): preserve project destinations and live chat availability * fix(file-drop): keep path resolution in filesystem namespace * test(file-drop): use filesystem path bridge in PR3 fixtures * fix(file-drop): bind terminal drops to pane elements * fix(file-drop): compare terminal pane identity across public views * test(file-drop): align composer lifecycle with element-owned drops * Fix interrupted chat composition and refuse unused quick-create drops * Type the quick-create drop regression transfer * fix(file-drop): let the explorer, project sidebar, tab strip and editor own OS drops STA-6940 PR5. The file explorer tree, the project sidebar, each editor group's tab strip and its editor area now take OS file drops on their own element through the shared owner hook, with identity from their own render (the explorer's shown workspace and target row, the tab strip's and editor group's worktree and group) instead of the active worktree. The explorer and sidebar broadcast subscribers and their legacy drop markers are deleted; editor-open moves out of useGlobalFileDrop into editor-dropped-file-open, which the legacy unmarked-chrome route still uses until PR6. * fix(file-drop): keep editor delivery tied to its destination group * fix(file-drop): guard delayed opens and share floating ownership * fix(file-drop): remove legacy OS drop routing (STA-6940 PR6/6) * test(file-drop): finish owner test and comment cleanup * Clarify dropped-file default editor destination * Retain UI test namespace for terminal resume coverage
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { measureClipboardTextByteLength } from './clipboard-text'
|
|
|
|
export const ORCA_INTERNAL_FILE_DRAG_TYPE = 'text/x-orca-file-path'
|
|
|
|
export const NATIVE_FILE_DROP_MAX_PATHS = 256
|
|
export const NATIVE_FILE_DROP_MAX_PATH_BYTES = 256 * 1024
|
|
|
|
export type NativeFileDropRejectedPayload = {
|
|
byteLength: number
|
|
pathCount: number
|
|
reason: NativeFileDropRejectionReason
|
|
target: 'rejected'
|
|
/** Why every file in a `temp-copy-failed` drop went uncopied, when they share one reason. */
|
|
commonReason?: NativeFileDropCopyFailureReason
|
|
}
|
|
|
|
// Why tokens: the renderer owns the localized copy; main never sends display text.
|
|
export const NATIVE_FILE_DROP_COPY_FAILURE_REASONS = [
|
|
'missing',
|
|
'permission-denied',
|
|
'changed',
|
|
'out-of-space',
|
|
'storage-unavailable',
|
|
'storage-not-private',
|
|
'copy-failed',
|
|
'timed-out',
|
|
'busy',
|
|
// Too big to copy, so agents in terminals and composers can't be given it.
|
|
'too-large',
|
|
'storage-full'
|
|
] as const
|
|
|
|
export type NativeFileDropCopyFailureReason = (typeof NATIVE_FILE_DROP_COPY_FAILURE_REASONS)[number]
|
|
|
|
/** What path validation alone can reject a drop for. */
|
|
export type NativeFileDropSizeRejectionReason = 'paths-too-large' | 'too-many-paths'
|
|
|
|
/** `unresolved-paths`: the OS handed us file items no path could be read from
|
|
* (promised/virtual files), which used to be swallowed with no feedback.
|
|
* `temp-copy-failed`: main could not copy a macOS drag-temp file; only main sends it. */
|
|
export type NativeFileDropRejectionReason =
|
|
| NativeFileDropSizeRejectionReason
|
|
| 'unresolved-paths'
|
|
| 'temp-copy-failed'
|
|
|
|
export type NativeFileDropPathValidation =
|
|
| { byteLength: number; pathCount: number; status: 'accepted' }
|
|
| {
|
|
byteLength: number
|
|
pathCount: number
|
|
reason: NativeFileDropSizeRejectionReason
|
|
status: 'rejected'
|
|
}
|
|
|
|
function getDataTransferTypes(
|
|
types: Iterable<string> | ArrayLike<string> | null | undefined
|
|
): string[] {
|
|
return types ? Array.from(types) : []
|
|
}
|
|
|
|
export function hasNativeFileDragTypes(
|
|
types: Iterable<string> | ArrayLike<string> | null | undefined
|
|
): boolean {
|
|
const values = getDataTransferTypes(types)
|
|
return values.includes('Files') && !values.includes(ORCA_INTERNAL_FILE_DRAG_TYPE)
|
|
}
|
|
|
|
export function validateNativeFileDropPaths(
|
|
paths: readonly string[],
|
|
options: {
|
|
maxPathBytes?: number
|
|
maxPaths?: number
|
|
} = {}
|
|
): NativeFileDropPathValidation {
|
|
const pathCount = paths.length
|
|
const maxPaths = options.maxPaths ?? NATIVE_FILE_DROP_MAX_PATHS
|
|
if (pathCount > maxPaths) {
|
|
return {
|
|
byteLength: 0,
|
|
pathCount,
|
|
reason: 'too-many-paths',
|
|
status: 'rejected'
|
|
}
|
|
}
|
|
|
|
const maxPathBytes = options.maxPathBytes ?? NATIVE_FILE_DROP_MAX_PATH_BYTES
|
|
let byteLength = 0
|
|
for (const path of paths) {
|
|
const measurement = measureClipboardTextByteLength(path, {
|
|
stopAfterBytes: maxPathBytes - byteLength
|
|
})
|
|
byteLength += measurement.byteLength
|
|
if (byteLength > maxPathBytes) {
|
|
return {
|
|
byteLength,
|
|
pathCount,
|
|
reason: 'paths-too-large',
|
|
status: 'rejected'
|
|
}
|
|
}
|
|
}
|
|
|
|
return { byteLength, pathCount, status: 'accepted' }
|
|
}
|
|
|
|
export function createRejectedNativeFileDropPayload(
|
|
validation: Extract<NativeFileDropPathValidation, { status: 'rejected' }>
|
|
): NativeFileDropRejectedPayload {
|
|
return {
|
|
byteLength: validation.byteLength,
|
|
pathCount: validation.pathCount,
|
|
reason: validation.reason,
|
|
target: 'rejected'
|
|
}
|
|
}
|