Skip to content

Commit 7ede903

Browse files
committed
frontend/explorer: combine files filter and terminal bar
1 parent 26f9474 commit 7ede903

File tree

9 files changed

+253
-169
lines changed

9 files changed

+253
-169
lines changed

src/packages/frontend/_projects.sass

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@
7070
.cc-project-files-create-dropdown
7171
> .dropdown.btn-group
7272
display: flex
73+
flex: 1 0 auto
7374

7475
.cc-project-files-path-nav
75-
min-width: 35vw
76+
flex: 1 0 auto
7677
> div,
7778
> nav
7879
background-color: $COL_GRAY_LLL

src/packages/frontend/account/avatar/users-viewing.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { Loading } from "@cocalc/frontend/components";
1616
import { cmp } from "@cocalc/util/misc";
1717
import { Avatar } from "./avatar";
1818

19-
// How frequently all UsersViewing componenents are completely updated.
19+
// How frequently all UsersViewing components are completely updated.
2020
// This is only needed to ensure that faces fade out; any newly added faces
2121
// will still be displayed instantly. Also, updating more frequently updates
2222
// the line positions in the tooltip.

src/packages/frontend/components/search-input.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ interface Props {
4141
}
4242

4343
export const SearchInput: React.FC<Props> = React.memo((props) => {
44-
const [value, set_value] = useState<string>(
44+
const [value, setValue] = useState<string>(
4545
props.value ?? props.default_value ?? "",
4646
);
4747
// if value changes, we update as well!
48-
useEffect(() => set_value(props.value ?? ""), [props.value]);
48+
useEffect(() => setValue(props.value ?? ""), [props.value]);
4949

5050
const [ctrl_down, set_ctrl_down] = useState<boolean>(false);
5151
const [shift_down, set_shift_down] = useState<boolean>(false);
@@ -70,7 +70,7 @@ export const SearchInput: React.FC<Props> = React.memo((props) => {
7070
}
7171

7272
function clear_value(): void {
73-
set_value("");
73+
setValue("");
7474
props.on_change?.("", get_opts());
7575
props.on_clear?.();
7676
}
@@ -140,7 +140,7 @@ export const SearchInput: React.FC<Props> = React.memo((props) => {
140140
onChange={(e) => {
141141
e.preventDefault();
142142
const value = e.target?.value ?? "";
143-
set_value(value);
143+
setValue(value);
144144
props.on_change?.(value, get_opts());
145145
if (!value) clear_value();
146146
}}

src/packages/frontend/project/explorer/explorer.tsx

+139-65
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
import { Alert } from "antd";
6+
import { Alert, Radio, Space, Tooltip } from "antd";
77
import * as immutable from "immutable";
8+
import * as _ from "lodash";
89
import React from "react";
910
import { FormattedMessage } from "react-intl";
10-
import * as underscore from "underscore";
1111

1212
import { UsersViewing } from "@cocalc/frontend/account/avatar/users-viewing";
1313
import { Button, ButtonGroup, Col, Row } from "@cocalc/frontend/antd-bootstrap";
1414
import {
15-
TypedMap,
1615
project_redux_name,
1716
rclass,
1817
redux,
1918
rtypes,
19+
TypedMap,
2020
} from "@cocalc/frontend/app-framework";
2121
import { ShallowTypedMap } from "@cocalc/frontend/app-framework/ShallowTypedMap";
2222
import {
@@ -41,14 +41,16 @@ import {
4141
} from "@cocalc/frontend/project_configuration";
4242
import { ProjectActions } from "@cocalc/frontend/project_store";
4343
import { ProjectMap, ProjectStatus } from "@cocalc/frontend/todo-types";
44+
import { unreachable } from "@cocalc/util/misc";
4445
import AskNewFilename from "../ask-filename";
4546
import { useProjectContext } from "../context";
4647
import { AccessErrors } from "./access-errors";
4748
import { ActionBar } from "./action-bar";
4849
import { ActionBox } from "./action-box";
4950
import { FetchDirectoryErrors } from "./fetch-directory-errors";
5051
import { FileListing } from "./file-listing";
51-
import { default_ext } from "./file-listing/utils";
52+
import { TerminalModeDisplay } from "./file-listing/terminal-mode-display";
53+
import { default_ext, TERM_MODE_CHAR } from "./file-listing/utils";
5254
import { MiniTerminal } from "./mini-terminal";
5355
import { MiscSideButtons } from "./misc-side-buttons";
5456
import { NewButton } from "./new-button";
@@ -148,11 +150,10 @@ export function Explorer() {
148150
const Explorer0 = rclass(
149151
class Explorer extends React.Component<ReactProps & ReduxProps, State> {
150152
newFileRef = React.createRef<any>();
151-
searchBarRef = React.createRef<any>();
153+
searchAndTerminalBar = React.createRef<any>();
152154
fileListingRef = React.createRef<any>();
153155
currentDirectoryRef = React.createRef<any>();
154156
miscButtonsRef = React.createRef<any>();
155-
miniterminalRef = React.createRef<any>();
156157

157158
static reduxProps = ({ name }) => {
158159
return {
@@ -412,7 +413,7 @@ const Explorer0 = rclass(
412413
return (
413414
<ActivityDisplay
414415
trunc={80}
415-
activity={underscore.values(this.props.activity)}
416+
activity={_.values(this.props.activity)}
416417
on_clear={() => this.props.actions.clear_all_activity()}
417418
style={{ top: "100px" }}
418419
/>
@@ -565,10 +566,17 @@ const Explorer0 = rclass(
565566
flexFlow: IS_MOBILE ? undefined : "row wrap",
566567
justifyContent: "space-between",
567568
alignItems: "stretch",
569+
marginBottom: "15px",
568570
}}
569571
>
570-
<div>
571-
<div style={{ display: "flex" }}>
572+
<div
573+
style={{
574+
flex: "3 1 auto",
575+
display: "flex",
576+
flexDirection: "column",
577+
}}
578+
>
579+
<div style={{ display: "flex", flex: "1 1 auto" }}>
572580
<SelectComputeServerForFileExplorer
573581
project_id={this.props.project_id}
574582
key="compute-server"
@@ -603,67 +611,32 @@ const Explorer0 = rclass(
603611
style={{
604612
flex: "0 1 auto",
605613
margin: "0 10px",
606-
marginBottom: "15px",
607614
}}
608615
className="cc-project-files-create-dropdown"
609616
>
610617
{this.render_new_file()}
611618
</div>
612619
)}
613620
{!IS_MOBILE && (
614-
<div
615-
ref={this.searchBarRef}
616-
style={{
617-
flex: "1 0 20%",
618-
minWidth: "15em",
619-
}}
620-
>
621-
<SearchBar
622-
project_id={this.props.project_id}
623-
key={this.props.current_path}
624-
file_search={this.props.file_search}
625-
actions={this.props.actions}
626-
current_path={this.props.current_path}
627-
selected_file={
628-
visible_listing != undefined
629-
? visible_listing[this.props.selected_file_index || 0]
630-
: undefined
631-
}
632-
selected_file_index={this.props.selected_file_index}
633-
file_creation_error={this.props.file_creation_error}
634-
num_files_displayed={
635-
visible_listing != undefined
636-
? visible_listing.length
637-
: undefined
638-
}
639-
create_file={this.create_file}
640-
create_folder={this.create_folder}
641-
/>
642-
</div>
621+
<SearchTerminalBar
622+
ref={this.searchAndTerminalBar}
623+
actions={this.props.actions}
624+
current_path={this.props.current_path}
625+
file_search={this.props.file_search}
626+
visible_listing={visible_listing}
627+
selected_file_index={this.props.selected_file_index}
628+
file_creation_error={this.props.file_creation_error}
629+
create_file={this.create_file}
630+
create_folder={this.create_folder}
631+
/>
643632
)}
644-
<>
645-
<div
646-
style={{
647-
flex: "0 1 auto",
648-
marginBottom: "15px",
649-
}}
650-
>
651-
<UsersViewing project_id={this.props.project_id} />
652-
</div>
653-
{!IS_MOBILE && (
654-
<div
655-
ref={this.miniterminalRef}
656-
style={{ flex: "1 0 auto", marginBottom: "15px" }}
657-
>
658-
<MiniTerminal
659-
current_path={this.props.current_path}
660-
project_id={this.props.project_id}
661-
actions={this.props.actions}
662-
show_close_x={true}
663-
/>
664-
</div>
665-
)}
666-
</>
633+
<div
634+
style={{
635+
flex: "0 1 auto",
636+
}}
637+
>
638+
<UsersViewing project_id={this.props.project_id} />
639+
</div>
667640
</div>
668641
);
669642
}
@@ -714,6 +687,11 @@ const Explorer0 = rclass(
714687
/>
715688
);
716689
}
690+
render_terminal_mode() {
691+
if (this.props.file_search[0] === TERM_MODE_CHAR) {
692+
return <TerminalModeDisplay />;
693+
}
694+
}
717695

718696
render() {
719697
let project_is_running: boolean,
@@ -763,7 +741,7 @@ const Explorer0 = rclass(
763741
alignItems: "stretch",
764742
};
765743

766-
// be careful with adding height:'100%'. it could cause flex to miscalc. see #3904
744+
// be careful with adding height:'100%'. it could cause flex to miscalculate. see #3904
767745
return (
768746
<div className={"smc-vfill"}>
769747
<div
@@ -777,6 +755,7 @@ const Explorer0 = rclass(
777755
{this.render_error()}
778756
{this.render_activity()}
779757
{this.render_control_row(visible_listing)}
758+
{this.render_terminal_mode()}
780759
{this.props.ext_selection != null && (
781760
<AskNewFilename project_id={this.props.project_id} />
782761
)}
@@ -806,6 +785,7 @@ const Explorer0 = rclass(
806785
<Row>{this.render_files_action_box(file_map)}</Row>
807786
) : undefined}
808787
</div>
788+
809789
<div
810790
ref={this.fileListingRef}
811791
className="smc-vfill"
@@ -832,14 +812,108 @@ const Explorer0 = rclass(
832812
open={this.props.explorerTour}
833813
project_id={this.props.project_id}
834814
newFileRef={this.newFileRef}
835-
searchBarRef={this.searchBarRef}
815+
searchAndTerminalBar={this.searchAndTerminalBar}
836816
fileListingRef={this.fileListingRef}
837817
currentDirectoryRef={this.currentDirectoryRef}
838818
miscButtonsRef={this.miscButtonsRef}
839-
miniterminalRef={this.miniterminalRef}
840819
/>
841820
</div>
842821
);
843822
}
844823
},
845824
);
825+
826+
const SearchTerminalBar = React.forwardRef(
827+
(
828+
{
829+
current_path,
830+
file_search,
831+
actions,
832+
visible_listing,
833+
selected_file_index,
834+
file_creation_error,
835+
create_file,
836+
create_folder,
837+
}: {
838+
ref: React.Ref<any>;
839+
current_path: string;
840+
file_search: string;
841+
actions: ProjectActions;
842+
visible_listing: ListingItem[] | undefined;
843+
selected_file_index?: number;
844+
file_creation_error?: string;
845+
create_file: (ext?: string, switch_over?: boolean) => void;
846+
create_folder: (switch_over?: boolean) => void;
847+
},
848+
ref: React.LegacyRef<HTMLDivElement> | undefined,
849+
) => {
850+
const [mode, setMode] = React.useState<"search" | "terminal">("search");
851+
852+
function renderTerminal() {
853+
return (
854+
<MiniTerminal
855+
current_path={current_path}
856+
actions={actions}
857+
show_close_x={true}
858+
/>
859+
);
860+
}
861+
862+
function renderSearch() {
863+
return (
864+
<SearchBar
865+
key={current_path}
866+
file_search={file_search}
867+
actions={actions}
868+
current_path={current_path}
869+
selected_file={
870+
visible_listing != undefined
871+
? visible_listing[selected_file_index || 0]
872+
: undefined
873+
}
874+
selected_file_index={selected_file_index}
875+
file_creation_error={file_creation_error}
876+
num_files_displayed={
877+
visible_listing != undefined ? visible_listing.length : undefined
878+
}
879+
create_file={create_file}
880+
create_folder={create_folder}
881+
/>
882+
);
883+
}
884+
885+
function renderBar() {
886+
switch (mode) {
887+
case "search":
888+
return renderSearch();
889+
case "terminal":
890+
return renderTerminal();
891+
default:
892+
unreachable(mode);
893+
}
894+
}
895+
896+
return (
897+
<Space.Compact style={{ flex: "1 1 auto" }}>
898+
<Radio.Group
899+
ref={ref}
900+
value={mode}
901+
onChange={(e) => setMode(e.target.value)}
902+
style={{ whiteSpace: "nowrap" }}
903+
>
904+
<Tooltip title="Click to change the search box to filter files by their name">
905+
<Radio.Button value="search">
906+
<Icon name="search" />
907+
</Radio.Button>
908+
</Tooltip>
909+
<Tooltip title="Click to change the search box to run commands in the terminal">
910+
<Radio.Button value="terminal" style={{ borderRadius: 0 }}>
911+
<Icon name="terminal" />
912+
</Radio.Button>
913+
</Tooltip>
914+
</Radio.Group>
915+
{renderBar()}
916+
</Space.Compact>
917+
);
918+
},
919+
);

0 commit comments

Comments
 (0)