Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
sawka committed Feb 7, 2025
2 parents 8b4c296 + e018e7b commit c5f45c3
Show file tree
Hide file tree
Showing 66 changed files with 1,153 additions and 162 deletions.
7 changes: 4 additions & 3 deletions cmd/wsh/cmd/wshcmd-file.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,14 @@ func fileInfoRun(cmd *cobra.Command, args []string) error {

info, err := wshclient.FileInfoCommand(RpcClient, fileData, &wshrpc.RpcOpts{Timeout: DefaultFileTimeout})
err = convertNotFoundErr(err)
if err == fs.ErrNotExist {
return fmt.Errorf("%s: no such file", path)
}
if err != nil {
return fmt.Errorf("getting file info: %w", err)
}

if info.NotFound {
return fmt.Errorf("%s: no such file", path)
}

WriteStdout("name:\t%s\n", info.Name)
if info.Mode != 0 {
WriteStdout("mode:\t%s\n", info.Mode.String())
Expand Down
1 change: 1 addition & 0 deletions frontend/app/block/blockframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
"--magnified-block-blur": `${magnifiedBlockBlur}px`,
} as React.CSSProperties
}
inert={preview ? "1" : undefined}
>
<BlockMask nodeModel={nodeModel} />
{preview || viewModel == null ? null : (
Expand Down
172 changes: 142 additions & 30 deletions frontend/app/view/preview/directorypreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import dayjs from "dayjs";
import { PrimitiveAtom, atom, useAtom, useAtomValue, useSetAtom } from "jotai";
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useDrag, useDrop } from "react-dnd";
import { quote as shellQuote } from "shell-quote";
import { debounce } from "throttle-debounce";
import "./directorypreview.scss";
Expand Down Expand Up @@ -650,34 +651,6 @@ function TableBody({
[setRefreshVersion, conn]
);

const displayRow = useCallback(
(row: Row<FileInfo>, idx: number) => (
<div
ref={(el) => (rowRefs.current[idx] = el)}
className={clsx("dir-table-body-row", { focused: focusIndex === idx })}
key={row.id}
onDoubleClick={() => {
const newFileName = row.getValue("path") as string;
model.goHistory(newFileName);
setSearch("");
}}
onClick={() => setFocusIndex(idx)}
onContextMenu={(e) => handleFileContextMenu(e, row.original)}
>
{row.getVisibleCells().map((cell) => (
<div
className={clsx("dir-table-body-cell", "col-" + cell.column.id)}
key={cell.id}
style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
))}
</div>
),
[setSearch, handleFileContextMenu, setFocusIndex, focusIndex]
);

return (
<div className="dir-table-body" ref={bodyRef}>
{search !== "" && (
Expand All @@ -693,13 +666,110 @@ function TableBody({
<div className="dummy dir-table-body-row" ref={dummyLineRef}>
<div className="dir-table-body-cell">dummy-data</div>
</div>
{table.getTopRows().map(displayRow)}
{table.getCenterRows().map((row, idx) => displayRow(row, idx + table.getTopRows().length))}
{table.getTopRows().map((row, idx) => (
<TableRow
model={model}
row={row}
focusIndex={focusIndex}
setFocusIndex={setFocusIndex}
setSearch={setSearch}
idx={idx}
handleFileContextMenu={handleFileContextMenu}
ref={(el) => (rowRefs.current[idx] = el)}
key={idx}
/>
))}
{table.getCenterRows().map((row, idx) => (
<TableRow
model={model}
row={row}
focusIndex={focusIndex}
setFocusIndex={setFocusIndex}
setSearch={setSearch}
idx={idx + table.getTopRows().length}
handleFileContextMenu={handleFileContextMenu}
ref={(el) => (rowRefs.current[idx] = el)}
key={idx}
/>
))}
</div>
</div>
);
}

type TableRowProps = {
model: PreviewModel;
row: Row<FileInfo>;
focusIndex: number;
setFocusIndex: (_: number) => void;
setSearch: (_: string) => void;
idx: number;
handleFileContextMenu: (e: any, finfo: FileInfo) => Promise<void>;
};

const TableRow = React.forwardRef(function (
{ model, row, focusIndex, setFocusIndex, setSearch, idx, handleFileContextMenu }: TableRowProps,
ref: React.RefObject<HTMLDivElement>
) {
const dirPath = useAtomValue(model.normFilePath);
const connection = useAtomValue(model.connection);
const formatRemoteUri = useCallback(
(path: string) => {
let conn: string;
if (!connection) {
conn = "local";
} else {
conn = connection;
}
return `wsh://${conn}/${path}`;
},
[connection]
);

const dragItem: DraggedFile = {
relName: row.getValue("name") as string,
absParent: dirPath,
uri: formatRemoteUri(row.getValue("path") as string),
};
const [{ isDragging }, drag, dragPreview] = useDrag(
() => ({
type: "FILE_ITEM",
canDrag: true,
item: () => dragItem,
collect: (monitor) => {
return {
isDragging: monitor.isDragging(),
};
},
}),
[dragItem]
);

return (
<div
className={clsx("dir-table-body-row", { focused: focusIndex === idx })}
onDoubleClick={() => {
const newFileName = row.getValue("path") as string;
model.goHistory(newFileName);
setSearch("");
}}
onClick={() => setFocusIndex(idx)}
onContextMenu={(e) => handleFileContextMenu(e, row.original)}
ref={drag}
>
{row.getVisibleCells().map((cell) => (
<div
className={clsx("dir-table-body-cell", "col-" + cell.column.id)}
key={cell.id}
style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
))}
</div>
);
});

const MemoizedTableBody = React.memo(
TableBody,
(prev, next) => prev.table.options.data == next.table.options.data
Expand Down Expand Up @@ -830,6 +900,48 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
middleware: [offset(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)],
});

const [, drop] = useDrop(
() => ({
accept: "FILE_ITEM", //a name of file drop type
canDrop: (_, monitor) => {
const dragItem = monitor.getItem<DraggedFile>();
// drop if not current dir is the parent directory of the dragged item
// requires absolute path
if (monitor.isOver({ shallow: false }) && dragItem.absParent !== dirPath) {
return true;
}
return false;
},
drop: async (draggedFile: DraggedFile, monitor) => {
if (!monitor.didDrop()) {
const timeoutYear = 31536000000; // one year
const opts: FileCopyOpts = {
timeout: timeoutYear,
recursive: true,
};
const desturi = await model.formatRemoteUri(dirPath, globalStore.get);
const data: CommandFileCopyData = {
srcuri: draggedFile.uri,
desturi,
opts,
};
try {
await RpcApi.FileCopyCommand(TabRpcClient, data, { timeout: timeoutYear });
} catch (e) {
console.log("copy failed:", e);
}
model.refreshCallback();
}
},
// TODO: mabe add a hover option?
}),
[dirPath, model.formatRemoteUri, model.refreshCallback]
);

useEffect(() => {
drop(refs.reference);
}, [refs.reference]);

const dismiss = useDismiss(context);
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);

Expand Down
16 changes: 9 additions & 7 deletions frontend/layout/lib/TileLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
} from "./types";
import { determineDropDirection } from "./utils";

const tileItemType = "TILE_ITEM";

export interface TileLayoutProps {
/**
* The atom containing the layout tree state.
Expand Down Expand Up @@ -59,14 +61,16 @@ function TileLayoutComponent({ tabAtom, contents, getCursorPoint }: TileLayoutPr
const setReady = useSetAtom(layoutModel.ready);
const isResizing = useAtomValue(layoutModel.isResizing);

const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({
const { activeDrag, dragClientOffset, dragItemType } = useDragLayer((monitor) => ({
activeDrag: monitor.isDragging(),
dragClientOffset: monitor.getClientOffset(),
dragItemType: monitor.getItemType(),
}));

useEffect(() => {
setActiveDrag(activeDrag);
}, [setActiveDrag, activeDrag]);
const activeTileDrag = activeDrag && dragItemType == tileItemType;
setActiveDrag(activeTileDrag);
}, [activeDrag, dragItemType]);

const checkForCursorBounds = useCallback(
debounce(100, (dragClientOffset: XYCoord) => {
Expand Down Expand Up @@ -214,8 +218,6 @@ interface DisplayNodeProps {
node: LayoutNode;
}

const dragItemType = "TILE_ITEM";

/**
* The draggable and displayable portion of a leaf node in a layout tree.
*/
Expand All @@ -230,7 +232,7 @@ const DisplayNode = ({ layoutModel, node }: DisplayNodeProps) => {

const [{ isDragging }, drag, dragPreview] = useDrag(
() => ({
type: dragItemType,
type: tileItemType,
canDrag: () => !(isEphemeral || isMagnified),
item: () => node,
collect: (monitor) => ({
Expand Down Expand Up @@ -358,7 +360,7 @@ const OverlayNode = memo(({ node, layoutModel }: OverlayNodeProps) => {

const [, drop] = useDrop(
() => ({
accept: dragItemType,
accept: tileItemType,
canDrop: (_, monitor) => {
const dragItem = monitor.getItem<LayoutNode>();
if (monitor.isOver({ shallow: true }) && dragItem.id !== node.id) {
Expand Down
6 changes: 6 additions & 0 deletions frontend/types/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,12 @@ declare global {
}

type SuggestionsFnType = (query: string, reqContext: SuggestionRequestContext) => Promise<FetchSuggestionsResponse>;

type DraggedFile = {
uri: string;
absParent: string;
relName: string;
};
}

export {};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"vite-plugin-static-copy": "^2.2.0",
"vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.4"
"vitest": "^3.0.5"
},
"dependencies": {
"@floating-ui/react": "^0.27.3",
Expand Down
5 changes: 4 additions & 1 deletion pkg/remote/connparse/connparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ func ParseURI(uri string) (*Connection, error) {
}
}

addPrecedingSlash := true

if scheme == "" {
scheme = ConnectionTypeWsh
addPrecedingSlash = false
if len(rest) != len(uri) {
// This accounts for when the uri starts with "//", which would get trimmed in the first split.
parseWshPath()
Expand All @@ -152,7 +155,7 @@ func ParseURI(uri string) (*Connection, error) {
}
if strings.HasPrefix(remotePath, "/~") {
remotePath = strings.TrimPrefix(remotePath, "/")
} else if len(remotePath) > 1 && !windowsDriveRegex.MatchString(remotePath) && !strings.HasPrefix(remotePath, "/") && !strings.HasPrefix(remotePath, "~") && !strings.HasPrefix(remotePath, "./") && !strings.HasPrefix(remotePath, "../") && !strings.HasPrefix(remotePath, ".\\") && !strings.HasPrefix(remotePath, "..\\") && remotePath != ".." {
} else if addPrecedingSlash && (len(remotePath) > 1 && !windowsDriveRegex.MatchString(remotePath) && !strings.HasPrefix(remotePath, "/") && !strings.HasPrefix(remotePath, "~") && !strings.HasPrefix(remotePath, "./") && !strings.HasPrefix(remotePath, "../") && !strings.HasPrefix(remotePath, ".\\") && !strings.HasPrefix(remotePath, "..\\") && remotePath != "..") {
remotePath = "/" + remotePath
}
}
Expand Down
44 changes: 44 additions & 0 deletions pkg/remote/connparse/connparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,50 @@ func TestParseURI_WSHCurrentPath(t *testing.T) {
if c.GetFullURI() != expected {
t.Fatalf("expected full URI to be %q, got %q", expected, c.GetFullURI())
}

cstr = "path/to/file"
c, err = connparse.ParseURI(cstr)
if err != nil {
t.Fatalf("failed to parse URI: %v", err)
}
expected = "path/to/file"
if c.Path != expected {
t.Fatalf("expected path to be %q, got %q", expected, c.Path)
}
expected = "current"
if c.Host != expected {
t.Fatalf("expected host to be %q, got %q", expected, c.Host)
}
expected = "wsh"
if c.Scheme != expected {
t.Fatalf("expected scheme to be %q, got %q", expected, c.Scheme)
}
expected = "wsh://current/path/to/file"
if c.GetFullURI() != expected {
t.Fatalf("expected full URI to be %q, got %q", expected, c.GetFullURI())
}

cstr = "/etc/path/to/file"
c, err = connparse.ParseURI(cstr)
if err != nil {
t.Fatalf("failed to parse URI: %v", err)
}
expected = "/etc/path/to/file"
if c.Path != expected {
t.Fatalf("expected path to be %q, got %q", expected, c.Path)
}
expected = "current"
if c.Host != expected {
t.Fatalf("expected host to be %q, got %q", expected, c.Host)
}
expected = "wsh"
if c.Scheme != expected {
t.Fatalf("expected scheme to be %q, got %q", expected, c.Scheme)
}
expected = "wsh://current/etc/path/to/file"
if c.GetFullURI() != expected {
t.Fatalf("expected full URI to be %q, got %q", expected, c.GetFullURI())
}
}

func TestParseURI_WSHCurrentPathWindows(t *testing.T) {
Expand Down
1 change: 0 additions & 1 deletion pkg/util/tarcopy/tarcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ func TarCopyDest(ctx context.Context, cancel context.CancelCauseFunc, ch <-chan
pipeReader, pipeWriter := io.Pipe()
iochan.WriterChan(ctx, pipeWriter, ch, func() {
gracefulClose(pipeWriter, tarCopyDestName, pipeWriterName)
cancel(nil)
}, cancel)
tarReader := tar.NewReader(pipeReader)
defer func() {
Expand Down
Loading

0 comments on commit c5f45c3

Please sign in to comment.