Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/compute/cloud-filesystem/cloud-filesystems.tsx
Views: 687
/*1Component that shows a list of all cloud file systems:23- in a project4- associated to an account5*/67import { useEffect, useRef, useState } from "react";8import { editCloudFilesystem, getCloudFilesystems } from "./api";9import useCounter from "@cocalc/frontend/app-framework/counter-hook";10import ShowError from "@cocalc/frontend/components/error";11import type { CloudFilesystem as CloudFilesystemType } from "@cocalc/util/db-schema/cloud-filesystems";12import { Button, Spin } from "antd";13import CreateCloudFilesystem from "./create";14import CloudFilesystem from "./cloud-filesystem";15import { Icon } from "@cocalc/frontend/components/icon";16import { A } from "@cocalc/frontend/components/A";17import RefreshButton from "@cocalc/frontend/components/refresh";18import { cmp } from "@cocalc/util/misc";19import {20SortableList,21SortableItem,22DragHandle,23} from "@cocalc/frontend/components/sortable-list";24// import {25// get_local_storage,26// set_local_storage,27// } from "@cocalc/frontend/misc/local-storage";28import { useTypedRedux } from "@cocalc/frontend/app-framework";2930export type CloudFilesystems = {31[id: number]: CloudFilesystemType;32};3334interface Props {35// if not given, shows global list across all projects you collab on36project_id?: string;37}3839export default function CloudFilesystems({ project_id }: Props) {40const { val: counter, inc: refresh } = useCounter();41const [error, setError] = useState<string>("");42const [refreshing, setRefreshing] = useState<boolean>(false);43const [cloudFilesystems, setCloudFilesystems] =44useState<CloudFilesystems | null>(null);45const scheduledRefresh = useRef<boolean>(false);4647// todo -- other sorts later48// const [sortBy, setSortBy] = useState<49// "id" | "title" | "custom" | "edited" | "state"50// >((get_local_storage(`cloudfs-${project_id}`) ?? "custom") as any);51const sortBy: string = "custom";5253const [ids, setIds] = useState<number[]>([]);54const account_id = useTypedRedux("account", "account_id");5556const updateIds = (cloudFilesystems: CloudFilesystems | null) => {57if (cloudFilesystems == null) {58setIds([]);59return;60}61const c = Object.values(cloudFilesystems);62c.sort((x, y) => {63const d = -cmp(x.position ?? 0, y.position ?? 0);64if (d) return d;65return -cmp(x.id ?? 0, y.id ?? 0);66});67const ids = c.map(({ id }) => id);68setIds(ids);69};7071useEffect(() => {72(async () => {73try {74setRefreshing(true);75const cloudFilesystems: CloudFilesystems = {};76for (const cloudFilesystem of await getCloudFilesystems({77project_id,78})) {79cloudFilesystems[cloudFilesystem.id] = cloudFilesystem;80}81setCloudFilesystems(cloudFilesystems);82updateIds(cloudFilesystems);8384if (!scheduledRefresh.current) {85// if a file system is currently being deleted, we refresh86// again in 30s.87for (const { deleting } of Object.values(cloudFilesystems)) {88if (deleting) {89setTimeout(() => {90scheduledRefresh.current = false;91refresh();92}, 30000);93scheduledRefresh.current = true;94break;95}96}97}98} catch (err) {99setError(`${err}`);100} finally {101setRefreshing(false);102}103})();104}, [counter]);105106if (cloudFilesystems == null) {107return <Spin />;108}109110const renderItem = (id) => {111const cloudFilesystem = cloudFilesystems[id];112113return (114<div style={{ display: "flex" }}>115{sortBy == "custom" && account_id == cloudFilesystem.account_id && (116<div117style={{118fontSize: "20px",119color: "#888",120display: "flex",121justifyContent: "center",122flexDirection: "column",123marginRight: "5px",124}}125>126<DragHandle id={id} />127</div>128)}129<CloudFilesystem130style={{ marginBottom: "10px" }}131key={`${id}`}132cloudFilesystem={cloudFilesystem}133refresh={refresh}134showProject={project_id == null}135editable={account_id == cloudFilesystem.account_id}136/>137</div>138);139};140141const v: JSX.Element[] = [];142for (const id of ids) {143v.push(144<SortableItem key={`${id}`} id={id}>145{renderItem(id)}146</SortableItem>,147);148}149150return (151<div>152<RefreshButton153refresh={refresh}154style={{ position: "absolute", right: 0 }}155refreshing={refreshing}156/>157<h2 style={{ textAlign: "center" }}>Cloud File Systems</h2>158<div style={{ textAlign: "center" }}>159<Button160href="https://youtu.be/zYoldE2yS3I"161target="_new"162style={{ marginRight: "15px" }}163>164<Icon name="youtube" style={{ color: "red" }} />165Short Demo166</Button>167<Button168href="https://youtu.be/uk5eA5piQEo"169target="_new"170style={{ marginRight: "15px" }}171>172<Icon name="youtube" style={{ color: "red" }} />173Long Demo174</Button>175<Button176href="https://doc.cocalc.com/cloud_file_system.html"177target="_new"178>179<Icon name="external-link" />180Docs181</Button>182</div>183<p184style={{185maxWidth: "700px",186margin: "15px auto",187fontSize: "11pt",188color: "#666",189}}190>191<A href="https://doc.cocalc.com/cloud_file_system.html">192CoCalc Cloud File Systems{" "}193</A>194are scalable distributed POSIX shared file systems with fast local195caching. Use them simultaneously from all compute servers in this196project. There are no limits on how much data you can store. You do not197specify the size of a cloud file system in advance. The cost per GB is198typically much less than a compute server disk, but you pay network199usage and operations.200</p>201202<div style={{ margin: "5px 0" }}>203{project_id204? ""205: "All Cloud File Systems you own across your projects are listed below."}206</div>207<ShowError error={error} setError={setError} />208{project_id != null && cloudFilesystems != null && (209<CreateCloudFilesystem210project_id={project_id}211cloudFilesystems={cloudFilesystems}212refresh={refresh}213/>214)}215<SortableList216disabled={sortBy != "custom"}217items={ids}218Item={({ id }) => renderItem(id)}219onDragStop={(oldIndex, newIndex) => {220let position;221if (newIndex == ids.length - 1) {222const last = cloudFilesystems[ids[ids.length - 1]];223// putting it at the bottom, so subtract 1 from very bottom position224position = (last.position ?? last.id) - 1;225} else {226// putting it above what was at position newIndex.227if (newIndex == 0) {228// very top229const first = cloudFilesystems[ids[0]];230// putting it at the bottom, so subtract 1 from very bottom position231position = (first.position ?? first.id) + 1;232} else {233// not at the very top: between two234let x, y;235if (newIndex > oldIndex) {236x = cloudFilesystems[ids[newIndex]];237y = cloudFilesystems[ids[newIndex + 1]];238} else {239x = cloudFilesystems[ids[newIndex - 1]];240y = cloudFilesystems[ids[newIndex]];241}242243const x0 = x.position ?? x.id;244const y0 = y.position ?? y.id;245// TODO: yes, positions could get too close like with compute servers246position = (x0 + y0) / 2;247}248}249// update UI250const id = ids[oldIndex];251const cur = { ...cloudFilesystems[id], position };252const cloudFilesystems1 = { ...cloudFilesystems, [id]: cur };253setCloudFilesystems(cloudFilesystems1);254updateIds(cloudFilesystems1);255// update Database256(async () => {257try {258await editCloudFilesystem({ id, position });259} catch (err) {260console.warn(err);261}262})();263}}264>265{v}266</SortableList>267</div>268);269}270271272