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
3 changes: 3 additions & 0 deletions src/packages/frontend/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@
"immutablejs",
"ipynb",
"isabs",
"isactive",
"isdir",
"isopen",
"issymlink",
"kernelspec",
"LLM",
"LLMs",
Expand Down
19 changes: 18 additions & 1 deletion src/packages/frontend/project/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
* License: MS-RSL – see LICENSE.md for details
*/

import { Context, createContext, useContext, useMemo, useState } from "react";
import {
Context,
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import * as immutable from "immutable";

import {
ProjectActions,
Expand Down Expand Up @@ -120,6 +128,15 @@ export function useProjectContextProvider({
// not each time the active tab is opened!
const manageStarredFiles = useStarredFilesManager(project_id);

// Sync starred files from conat to Redux store for use in computed values
useEffect(() => {
if (actions) {
actions.setState({
starred_files: immutable.List(manageStarredFiles.starred),
});
}
}, [manageStarredFiles.starred, actions]);

const kucalc = useTypedRedux("customize", "kucalc");
const onCoCalcCom = kucalc === KUCALC_COCALC_COM;
const onCoCalcDocker = kucalc === KUCALC_DISABLED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

// Show a file listing.

// cSpell:ignore issymlink

import { Alert, Spin } from "antd";
import * as immutable from "immutable";
import React, { useEffect, useRef, useState } from "react";
Expand All @@ -26,6 +24,7 @@ import useVirtuosoScrollHook from "@cocalc/frontend/components/virtuoso-scroll-h
import { WATCH_THROTTLE_MS } from "@cocalc/frontend/conat/listings";
import { ProjectActions } from "@cocalc/frontend/project_actions";
import { MainConfiguration } from "@cocalc/frontend/project_configuration";
import { useStarredFilesManager } from "@cocalc/frontend/project/page/flyouts/store";
import * as misc from "@cocalc/util/misc";
import { FileRow } from "./file-row";
import { ListingHeader } from "./listing-header";
Expand Down Expand Up @@ -87,6 +86,7 @@ export const FileListing: React.FC<Props> = ({
isRunning,
}: Props) => {
const [starting, setStarting] = useState<boolean>(false);
const { starred, setStarredPath } = useStarredFilesManager(project_id);

const prev_current_path = usePrevious(current_path);

Expand Down Expand Up @@ -136,6 +136,10 @@ export const FileListing: React.FC<Props> = ({
const checked = checked_files.has(misc.path_to_file(current_path, name));
const color = misc.rowBackground({ index, checked });
const { is_public } = file_map[name];
const fullPath = misc.path_to_file(current_path, name);
// For directories, add trailing slash to match flyout convention
const pathForStar = isdir ? `${fullPath}/` : fullPath;
const isStarred = starred.includes(pathForStar);

return (
<FileRow
Expand All @@ -159,6 +163,13 @@ export const FileListing: React.FC<Props> = ({
no_select={shift_is_down}
link_target={link_target}
computeServerId={computeServerId}
isStarred={isStarred}
onToggleStar={(path, starState) => {
// For directories, ensure trailing slash
const normalizedPath =
isdir && !path.endsWith("/") ? `${path}/` : path;
setStarredPath(normalizedPath, starState);
}}
/>
);
}
Expand Down
36 changes: 32 additions & 4 deletions src/packages/frontend/project/explorer/file-listing/file-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ interface Props {
// if given, include a little 'server' tag in this color, and tooltip etc using id.
// Also important for download and preview links!
computeServerId?: number;
isStarred?: boolean;
onToggleStar?: (path: string, starred: boolean) => void;
}

export const FileRow: React.FC<Props> = React.memo((props) => {
Expand Down Expand Up @@ -176,6 +178,29 @@ export const FileRow: React.FC<Props> = React.memo((props) => {
}
}

function render_star() {
if (!props.onToggleStar) return null;
const path = full_path();
const starred = props.isStarred ?? false;
const iconName = starred ? "star-filled" : "star";

return (
<Icon
name={iconName}
onClick={(e) => {
e?.preventDefault();
e?.stopPropagation();
props.onToggleStar?.(path, !starred);
}}
style={{
cursor: "pointer",
fontSize: "14pt",
color: starred ? COLORS.STAR : COLORS.GRAY_L,
}}
/>
);
}

function full_path() {
return misc.path_to_file(props.current_path, props.name);
}
Expand Down Expand Up @@ -235,12 +260,12 @@ export const FileRow: React.FC<Props> = React.memo((props) => {
return (
<TimeAgo
date={new Date(props.time * 1000).toISOString()}
style={{ color: "#666" }}
style={{ color: COLORS.GRAY_M }}
/>
);
} catch (error) {
return (
<div style={{ color: "#666", display: "inline" }}>
<div style={{ color: COLORS.GRAY_M, display: "inline" }}>
Invalid Date Time
</div>
);
Expand Down Expand Up @@ -365,14 +390,17 @@ export const FileRow: React.FC<Props> = React.memo((props) => {
<Col sm={2} xs={12} onClick={handle_click}>
{render_icon()}
</Col>
<Col sm={1} xs={6} style={{ textAlign: "center" }}>
{render_star()}
</Col>
<Col sm={10} xs={24} onClick={handle_click}>
<VisibleXS>
<span style={{ marginLeft: "16px" }} />
</VisibleXS>
{render_name()}
</Col>
<Col
sm={8}
sm={7}
xs={24}
style={{
paddingRight:
Expand All @@ -388,7 +416,7 @@ export const FileRow: React.FC<Props> = React.memo((props) => {
<DirectorySize size={props.size} />
</>
) : (
<span className="pull-right" style={{ color: "#666" }}>
<span className="pull-right" style={{ color: COLORS.GRAY_M }}>
{render_download_button(url)}
{render_view_button(url, props.name)}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import React from "react";
import { TypedMap } from "@cocalc/frontend/app-framework";
import { Icon, Gap, VisibleMDLG } from "@cocalc/frontend/components";
import { COLORS } from "@cocalc/util/theme";
import { Col, Row } from "antd";

// TODO: Flatten active_file_sort for easy PureComponent use
Expand All @@ -16,7 +17,7 @@ interface Props {

const row_style: React.CSSProperties = {
cursor: "pointer",
color: "#666",
color: COLORS.GRAY_M,
backgroundColor: "#fafafa",
border: "1px solid #eee",
borderRadius: "4px",
Expand All @@ -31,7 +32,7 @@ export const ListingHeader: React.FC<Props> = (props: Props) => {

function render_sort_link(
column_name: string,
display_name: string,
display_name: string | React.JSX.Element,
marginLeft?,
) {
return (
Expand All @@ -45,7 +46,11 @@ export const ListingHeader: React.FC<Props> = (props: Props) => {
e.preventDefault();
return sort_by(column_name);
}}
style={{ color: "#428bca", fontWeight: "bold" }}
style={{
color: COLORS.FG_BLUE,
fontWeight: "bold",
whiteSpace: "nowrap",
}}
>
{display_name}
<Gap />
Expand All @@ -70,10 +75,20 @@ export const ListingHeader: React.FC<Props> = (props: Props) => {
<Col sm={2} xs={6}>
{render_sort_link("type", "Type", "-4px")}
</Col>
<Col sm={1} xs={6} style={{ textAlign: "center" }}>
{render_sort_link(
"starred",
<Icon
name="star-filled"
style={{ color: COLORS.FG_BLUE, fontSize: "12pt" }}
/>,
"0px",
)}
</Col>
<Col sm={10} xs={24}>
{render_sort_link("name", "Name", "-4px")}
</Col>
<Col sm={8} xs={12}>
<Col sm={7} xs={12}>
{render_sort_link("time", "Date Modified", "2px")}
<span className="pull-right">
{render_sort_link("size", "Size/Download/View")}
Expand Down
19 changes: 15 additions & 4 deletions src/packages/frontend/project/page/flyouts/file-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ const FILE_ITEM_OPENED_STYLE: CSS = {
const FILE_ITEM_ACTIVE_STYLE: CSS = {
...FILE_ITEM_OPENED_STYLE,
color: COLORS.PROJECT.FIXED_LEFT_OPENED,
};
} as const;

const FILE_ITEM_ACTIVE_STYLE_2: CSS = {
...FILE_ITEM_ACTIVE_STYLE,
backgroundColor: COLORS.GRAY_L0,
};
} as const;

const FILE_ITEM_STYLE: CSS = {
flex: "1",
Expand Down Expand Up @@ -105,7 +105,7 @@ const CLOSE_ICON_STYLE: CSS = {
top: "1px",
position: "relative",
paddingBottom: "1px",
};
} as const;

interface Item {
isopen?: boolean;
Expand Down Expand Up @@ -305,12 +305,23 @@ export const FileListItem = React.memo((props: Readonly<FileListItemProps>) => {

const icon: IconName = isStarred ? "star-filled" : "star";

// In "files" mode, always show yellow star when starred
// In "active" mode, only show yellow star when file is also open
const starColor =
mode === "files"
? isStarred
? COLORS.STAR
: COLORS.GRAY_L
: isStarred && item.isopen
? COLORS.STAR
: COLORS.GRAY_L;

return (
<Icon
name={icon}
style={{
...ICON_STYLE,
color: isStarred && item.isopen ? COLORS.STAR : COLORS.GRAY_L,
color: starColor,
}}
onClick={(e: React.MouseEvent) => {
e?.stopPropagation();
Expand Down
13 changes: 11 additions & 2 deletions src/packages/frontend/project/page/flyouts/files-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ export function FilesHeader(props: Readonly<Props>): React.JSX.Element {
);
}

function renderSortButton(name: string, display: string): React.JSX.Element {
function renderSortButton(
name: string,
display: string | React.JSX.Element,
): React.JSX.Element {
const isActive = activeFileSort.get("column_name") === name;
const direction = isActive ? (
<Icon
Expand Down Expand Up @@ -319,7 +322,9 @@ export function FilesHeader(props: Readonly<Props>): React.JSX.Element {
<FormattedMessage
id="page.flyouts.files.stale-directory.description"
defaultMessage={"To update, <A>start this project</A>."}
description={"to update the outdated information in a file directory listing of a project"}
description={
"to update the outdated information in a file directory listing of a project"
}
values={{
A: (c) => (
<a
Expand Down Expand Up @@ -386,6 +391,10 @@ export function FilesHeader(props: Readonly<Props>): React.JSX.Element {
}}
>
<Radio.Group size="small">
{renderSortButton(
"starred",
<Icon name="star-filled" style={{ fontSize: "10pt" }} />,
)}
{renderSortButton("name", "Name")}
{renderSortButton("size", "Size")}
{renderSortButton("time", "Time")}
Expand Down
29 changes: 27 additions & 2 deletions src/packages/frontend/project/page/flyouts/files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function FilesFlyout({
isRunning: projectIsRunning,
project_id,
actions,
manageStarredFiles,
} = useProjectContext();
const isMountedRef = useIsMountedRef();
const rootRef = useRef<HTMLDivElement>(null as any);
Expand Down Expand Up @@ -239,6 +240,21 @@ export function FilesFlyout({
const aExt = a.name.split(".").pop() ?? "";
const bExt = b.name.split(".").pop() ?? "";
return aExt.localeCompare(bExt);
case "starred":
const pathA = path_to_file(current_path, a.name);
const pathB = path_to_file(current_path, b.name);
const starPathA = a.isdir ? `${pathA}/` : pathA;
const starPathB = b.isdir ? `${pathB}/` : pathB;
const starredA = manageStarredFiles.starred.includes(starPathA);
const starredB = manageStarredFiles.starred.includes(starPathB);

if (starredA && !starredB) {
return -1;
} else if (!starredA && starredB) {
return 1;
} else {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
default:
console.warn(`flyout/files: unknown sort column ${col}`);
return 0;
Expand All @@ -257,7 +273,7 @@ export function FilesFlyout({
}

if (activeFileSort.get("is_descending")) {
procFiles.reverse(); // inplace op
procFiles.reverse(); // in-place op
}

const isEmpty = procFiles.length === 0;
Expand Down Expand Up @@ -451,7 +467,7 @@ export function FilesFlyout({
window.getSelection()?.removeAllRanges();
const file = directoryFiles[index];

// doubleclick straight to open file
// double click straight to open file
if (e.detail === 2) {
setPrevSelected(index);
open(e, index);
Expand Down Expand Up @@ -578,6 +594,9 @@ export function FilesFlyout({
: checked_files.includes(
path_to_file(current_path, directoryFiles[index].name),
);
const fullPath = path_to_file(current_path, item.name);
const pathForStar = item.isdir ? `${fullPath}/` : fullPath;
const isStarred = manageStarredFiles.starred.includes(pathForStar);
return (
<FileListItem
mode="files"
Expand Down Expand Up @@ -606,6 +625,12 @@ export function FilesFlyout({
toggleSelected(index, item.name, nextState);
}}
checked_files={checked_files}
isStarred={isStarred}
onStar={(starState: boolean) => {
const normalizedPath =
item.isdir && !fullPath.endsWith("/") ? `${fullPath}/` : fullPath;
manageStarredFiles.setStarredPath(normalizedPath, starState);
}}
/>
);
}
Expand Down
Loading