feat: add workspace files to chat from Files and Changes
* feat(app): open changed files from context menu * fix(app): preserve changed-file row behavior * feat(app): add changed files to open chats * feat(app): share file actions across explorer and changes * feat(app): attach workspace files to focused chat * feat(app): drag workspace files into chat * refactor(app): use file actions menu for changed files * fix(app): open changed-file actions on right click * test(app): cover direct file attachment flow * fix(app): enable add to chat on native * fix(app): align workspace file pane rows * refactor(app): simplify workspace file attachments * fix(app): preserve uploaded file draft attachments --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
co-authored by
Mohamed Boudra
parent
42ee5a5949
commit
bd937850b3
@@ -1,4 +1,5 @@
|
||||
import type { ImageAttachment } from "@/composer/types";
|
||||
import type { WorkspaceFileDragPayload } from "@/attachments/workspace-file-drag";
|
||||
|
||||
export interface DroppedFileItem {
|
||||
kind: "web-file";
|
||||
@@ -18,4 +19,5 @@ export type DroppedItem = DroppedFileItem | DroppedPathItem;
|
||||
export interface FileDropSink {
|
||||
onFiles: (images: ImageAttachment[]) => void;
|
||||
onGenericFiles?: (items: DroppedItem[]) => void;
|
||||
onWorkspaceFile?: (payload: WorkspaceFileDragPayload) => void;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
} from "@/attachments/file-types";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
import type { DroppedItem, DroppedPathItem, FileDropSink } from "./types";
|
||||
import {
|
||||
parseWorkspaceFileDragPayload,
|
||||
WORKSPACE_FILE_DRAG_MIME,
|
||||
} from "@/attachments/workspace-file-drag";
|
||||
|
||||
type DesktopDragDropPayload =
|
||||
| { type: "enter"; paths: string[] }
|
||||
@@ -185,7 +189,11 @@ export function useDropListeners({
|
||||
if (disabledRef.current) return;
|
||||
|
||||
dragCounter.current++;
|
||||
if (e.dataTransfer?.types.includes("Files")) {
|
||||
if (suppressed.value || !hasSink.value) return;
|
||||
const types = new Set(e.dataTransfer?.types ?? []);
|
||||
const acceptsWorkspaceFile =
|
||||
types.has(WORKSPACE_FILE_DRAG_MIME) && Boolean(getSink()?.onWorkspaceFile);
|
||||
if (types.has("Files") || acceptsWorkspaceFile) {
|
||||
isDragging.value = true;
|
||||
}
|
||||
}
|
||||
@@ -197,7 +205,11 @@ export function useDropListeners({
|
||||
if (!e.dataTransfer) return;
|
||||
// Only advertise "copy" when the drop would actually be accepted, so the cursor doesn't
|
||||
// promise a drop that the handler then discards (suppressed/archived/no consumer mounted).
|
||||
const canAccept = !disabledRef.current && !suppressed.value && hasSink.value;
|
||||
const types = new Set(e.dataTransfer.types);
|
||||
const acceptsWorkspaceFile =
|
||||
types.has(WORKSPACE_FILE_DRAG_MIME) && Boolean(getSink()?.onWorkspaceFile);
|
||||
const acceptsDrop = types.has("Files") || acceptsWorkspaceFile;
|
||||
const canAccept = acceptsDrop && !disabledRef.current && !suppressed.value && hasSink.value;
|
||||
e.dataTransfer.dropEffect = canAccept ? "copy" : "none";
|
||||
}
|
||||
|
||||
@@ -225,6 +237,14 @@ export function useDropListeners({
|
||||
const sink = getSink();
|
||||
if (!sink) return;
|
||||
|
||||
const serializedWorkspaceFile = e.dataTransfer?.getData(WORKSPACE_FILE_DRAG_MIME);
|
||||
if (serializedWorkspaceFile && sink.onWorkspaceFile) {
|
||||
const payload = parseWorkspaceFileDragPayload(serializedWorkspaceFile);
|
||||
if (payload) {
|
||||
sink.onWorkspaceFile(payload);
|
||||
}
|
||||
}
|
||||
|
||||
const files = Array.from(e.dataTransfer?.files ?? []);
|
||||
const genericItems: DroppedItem[] = files.map((file) => ({
|
||||
kind: "web-file",
|
||||
|
||||
Reference in New Issue
Block a user