diff --git a/src/packages/frontend/i18n/common.ts b/src/packages/frontend/i18n/common.ts index ee3d5a838f..9aff2417b0 100644 --- a/src/packages/frontend/i18n/common.ts +++ b/src/packages/frontend/i18n/common.ts @@ -306,7 +306,7 @@ export const labels = defineMessages({ }, latex_document: { id: "labels.latex_document", - defaultMessage: "LaTeX Document", + defaultMessage: "LaTeX", description: "Indicating a LaTeX Documents on a button label or frame title", }, @@ -578,6 +578,10 @@ export const labels = defineMessages({ id: "labels.color", defaultMessage: "Color", }, + create: { + id: "labels.create", + defaultMessage: "Create", + }, }); export const menu = defineMessages({ diff --git a/src/packages/frontend/project/new/add-ai-gen-btn.tsx b/src/packages/frontend/project/new/add-ai-gen-btn.tsx new file mode 100644 index 0000000000..e2fec495dd --- /dev/null +++ b/src/packages/frontend/project/new/add-ai-gen-btn.tsx @@ -0,0 +1,55 @@ +/* + * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. + * License: MS-RSL – see LICENSE.md for details + */ + +import { Col, Flex, Space } from "antd"; + +import { AIGenerateDocumentButton } from "@cocalc/frontend/project/page/home-page/ai-generate-document"; +import { Ext } from "@cocalc/frontend/project/page/home-page/ai-generate-examples"; + +interface Props { + btn: JSX.Element; + grid: [number | { flex: string }, number | { flex: string }]; + filename: string | undefined; + filenameChanged?: boolean; + mode: "full" | "flyout"; + ext: Ext; +} + +export function AiDocGenerateBtn({ btn, grid, ext, filename, mode }: Props) { + const isFlyout = mode === "flyout"; + const [sm, md] = grid; + + if (isFlyout) { + return ( + + + {btn} + + + + + + ); + } else { + return ( + // + + {btn} + + + + + ); + } +} diff --git a/src/packages/frontend/project/new/file-type-selector.tsx b/src/packages/frontend/project/new/file-type-selector.tsx index c06a015d98..a156130f4c 100644 --- a/src/packages/frontend/project/new/file-type-selector.tsx +++ b/src/packages/frontend/project/new/file-type-selector.tsx @@ -3,22 +3,26 @@ * License: MS-RSL – see LICENSE.md for details */ -import { Col, Flex, Modal, Row, Tag } from "antd"; +import { Col, Modal, Row, Tag } from "antd"; import { Gutter } from "antd/es/grid/row"; +import { throttle } from "lodash"; import type { ReactNode } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { Available } from "@cocalc/comm/project-configuration"; -import { CSS } from "@cocalc/frontend/app-framework"; +import { + CSS, + useEffect, + useRef, + useState, +} from "@cocalc/frontend/app-framework"; import { A } from "@cocalc/frontend/components/A"; import { Icon } from "@cocalc/frontend/components/icon"; import { Tip } from "@cocalc/frontend/components/tip"; import { computeServersEnabled } from "@cocalc/frontend/compute/config"; import { labels } from "@cocalc/frontend/i18n"; -import { useProjectContext } from "@cocalc/frontend/project/context"; -import { Ext } from "@cocalc/frontend/project/page/home-page/ai-generate-examples"; import { ProjectActions } from "@cocalc/frontend/project_actions"; -import { AIGenerateDocumentButton } from "../page/home-page/ai-generate-document"; +import { AiDocGenerateBtn } from "./add-ai-gen-btn"; import { DELAY_SHOW_MS, NEW_FILETYPE_ICONS } from "./consts"; import { JupyterNotebookButtons } from "./jupyter-buttons"; import { NewFileButton } from "./new-file-button"; @@ -50,57 +54,112 @@ interface Props { // Could be changed to auto adjust to a list of pre-defined button names. export function FileTypeSelector({ create_file, - create_folder, projectActions, availableFeatures, disabledFeatures, mode = "full", selectedExt, - children, filename, makeNewFilename, filenameChanged, }: Props) { - const { project_id } = useProjectContext(); const intl = useIntl(); + const mainDivRef = useRef(null); + const [width, setWidth] = useState(0); + const btnWidth = Math.max(100, (width - 50) / 5); + + useEffect(() => { + const main = mainDivRef.current; + if (main == null) return; + const resizeObserver = new ResizeObserver( + throttle( + (entries) => { + if (entries.length > 0) { + setWidth(entries[0].contentRect.width); + } + }, + 10, + { leading: false, trailing: true }, + ), + ); + resizeObserver.observe(main); + return () => { + resizeObserver.disconnect(); + }; + }, []); + if (!create_file) { return null; } const isFlyout = mode === "flyout"; const btnSize = isFlyout ? "small" : "large"; - - // col width of Antd's 24 grid system - const base = 6; - const md = isFlyout ? 24 : base; - const sm = isFlyout ? 24 : 2 * base; - const doubleMd = isFlyout ? 24 : 2 * base; - const doubleSm = isFlyout ? 24 : 4 * base; + const tipStyle: CSS = isFlyout ? { flex: "1 1 auto" } : { width: "100%" }; //{ flex: "1 1 auto" }; + + // Usually, there are supposed to be 5 columns, but it changes if the layout is tighter to 3 + const base = (n = 1) => { + // return { flex: `${n * 20}%` }; + return 5 * n; + }; + const md = isFlyout ? 24 : base(1); + const sm = isFlyout ? 24 : base(2); + const doubleMd = isFlyout ? 24 : base(2); + const doubleSm = isFlyout ? 24 : base(4); const y: Gutter = isFlyout ? 15 : 30; const gutter: [Gutter, Gutter] = [20, y / 2]; - const newRowStyle = { marginTop: `${y}px` }; + const newRowStyle: CSS = { marginTop: `${y}px` } as const; function btnActive(ext: string): boolean { if (!isFlyout) return false; return ext === selectedExt; } - function renderJupyterNotebook() { - if ( - !availableFeatures.jupyter_notebook && - !availableFeatures.sage && - !availableFeatures.latex - ) { - return; - } + function wrapBtn(btn: ReactNode) { + return ( + + {btn} + + ); + //return {btn}; + // return ( + //
+ // {btn} + //
+ // ); + } + + function makeRow(btns: ReactNode) { + return ( + + {btns} + + ); + // return ( + // + // {btns} + // + // // + // // {btns} + // // + // ); + } + function renderPopular() { return ( <>
- Popular Documents + Popular
- + {makeRow( makeNewFilename?.("ipynb")} after={ /* Those come after the main button, then the additional jupyter notebooks – to avoid jumpyness */ - [renderLaTeX(), renderQuarto(), renderMD()] + [ + renderLinuxTerminal(), + renderLaTeX(), + renderQuarto(), + renderTeaching(), + ] } - /> - + />, + )} ); } - function renderLinux() { + function renderLinuxTerminal() { + return wrapBtn( + + + , + ); + } + + function renderX11() { + if (!availableFeatures.x11) return null; + + return wrapBtn( + + + , + ); + } + + function renderUtilities() { if (disabledFeatures?.linux) return; + return ( <>
- Linux + Utilities
- - - - - - - {availableFeatures.x11 && ( - - - - - - )} - {create_folder != null && ( - - - - - - )} - - {children} - - + {makeRow( + <> + {renderChat()} + {renderX11()} + {renderStopwatchTimer()} + {renderTaskList()} + {/* {children} */} + , + )} ); } @@ -229,6 +275,7 @@ export function FileTypeSelector({ placement="left" icon={"cloud-server"} tip={"Affordable GPUs and high-end dedicated virtual machines."} + style={tipStyle} > -
- Teaching and Chat -
+ return wrapBtn( + documentation to learn more.`} + values={{ + A: (c) => ( + + {c} + + ), + }} + /> + } + > + + , + ); + } - - {!disabledFeatures?.course && ( - - documentation to learn more.`} - values={{ - A: (c) => ( - - {c} - - ), - }} - /> - } - > - - - - )} - {!disabledFeatures?.chat && ( - - documentation to learn more.`} - values={{ - A: (c) => ( - {c} - ), - }} - /> - } - > - - - - )} + function renderChat() { + if (disabledFeatures?.chat) return; - {/* {!disabledFeatures?.timers && ( - - - - - - )} */} - - + return wrapBtn( + documentation to learn more.`} + values={{ + A: (c) => {c}, + }} + /> + } + > + + , ); } @@ -396,62 +415,29 @@ export function FileTypeSelector({ }); } - return ( - - - - - + return wrapBtn( + + + , ); } - function addAiDocGenerate(btn: JSX.Element, ext: Ext) { - if (isFlyout) { - return ( - - - {btn} - - - - - - ); - } else { - return ( - - {btn} - - - ); - } - } - function renderQuarto() { if (mode !== "flyout") return null; if (!availableFeatures.qmd) return null; @@ -463,7 +449,7 @@ export function FileTypeSelector({ title="Quarto File" icon={NEW_FILETYPE_ICONS.qmd} tip="Quarto document with real-time preview." - style={mode === "flyout" ? { flex: "1 1 auto" } : undefined} + style={tipStyle} > ); + return addAiDocGenerate(btn, "tex"); } - function renderMD() { + function addAiDocGenerate(btn, ext) { + return wrapBtn( + , + ); + } + + function renderMarkdown() { const btn = ( + + , + "rmd", + ); + } + + function renderWhiteboard() { + return wrapBtn( + + + , + ); + } + function renderSlides() { const labelSlides = intl.formatMessage({ id: "new.file-type-selector.slides.title", defaultMessage: "Slides", description: "Short label on a buton to create a new slideshow file", }); + return wrapBtn( + + + , + ); + } + + function renderMiscellaneous() { + if (disabledFeatures?.md) return; + return ( <>
- Miscellaneous Documents + Miscellaneous
- - {availableFeatures.rmd && - addAiDocGenerate( - - - , - "rmd", - )} - - - - - - - - - - - - {renderSageWS()} - + {makeRow( + <> + {renderMarkdown()} + {renderRMarkdown()} + {renderWhiteboard()} + {renderSlides()} + {renderSageWS()} + , + )} ); } - function renderUtilities() { - const labelTaskList = intl.formatMessage({ - id: "new.file-type-selector.tasks.label", - defaultMessage: "Task List", - }); + function renderStopwatchTimer() { + if (disabledFeatures?.timers) return; const labelStopWatchTimer = intl.formatMessage({ id: "project.new.file-type-selector.timers.label", defaultMessage: "Stopwatch and Timer", }); - return ( - <> -
- Utilities -
- - - - - - - {!disabledFeatures?.timers && ( - - - - - - )} - - + return wrapBtn( + + + , + ); + } + + function renderTaskList() { + const labelTaskList = intl.formatMessage({ + id: "new.file-type-selector.tasks.label", + defaultMessage: "Task List", + }); + + return wrapBtn( + + + , ); } return ( -
- {renderJupyterNotebook()} - {renderLinux()} - {renderMarkdown()} - {renderTeachingSocial()} - {renderServers()} +
+ {renderPopular()} {renderUtilities()} + {renderMiscellaneous()} + {renderServers()}
); } diff --git a/src/packages/frontend/project/new/jupyter-buttons.tsx b/src/packages/frontend/project/new/jupyter-buttons.tsx index 2a443f9e43..3ad74ef11e 100644 --- a/src/packages/frontend/project/new/jupyter-buttons.tsx +++ b/src/packages/frontend/project/new/jupyter-buttons.tsx @@ -18,6 +18,7 @@ import { AIGenerateDocumentButton } from "../page/home-page/ai-generate-document import { ensure_project_running } from "../project-start-warning"; import { DELAY_SHOW_MS, NEW_FILETYPE_ICONS } from "./consts"; import { NewFileButton } from "./new-file-button"; +import { AiDocGenerateBtn } from "./add-ai-gen-btn"; /** * An incomplete mapping of Jupyter Kernel "language" names to a display name and file extension. @@ -51,7 +52,7 @@ interface JupyterNotebookButtonsProps { create_file: (ext: string) => void; btnSize: "small" | "large"; btnActive: (name: string) => boolean; - grid: [number, number]; + grid: [number | { flex: string }, number | { flex: string }]; filename: string; filenameChanged?: boolean; mode: "full" | "flyout"; @@ -259,23 +260,24 @@ export function JupyterNotebookButtons({ btns.push({ lang, btn: ( - - handleClick(kernelName)} - ext={ext} - size={btnSize} - active={btnActive("ipynb-sagemath")} - // mode={isFlyout ? "secondary" : undefined} - /> - +
+ + handleClick(kernelName)} + ext={ext} + size={btnSize} + active={btnActive("ipynb-sagemath")} + // mode={isFlyout ? "secondary" : undefined} + /> + +
), }); } @@ -287,7 +289,6 @@ export function JupyterNotebookButtons({ {btn} {btn} - - {btn} - - - - - - ); - } else { - return ( - - {btn} - - - ); - } + return ( + + ); } return ( diff --git a/src/packages/frontend/project/new/new-file-button.tsx b/src/packages/frontend/project/new/new-file-button.tsx index 442b8c660b..305254aa04 100644 --- a/src/packages/frontend/project/new/new-file-button.tsx +++ b/src/packages/frontend/project/new/new-file-button.tsx @@ -5,25 +5,26 @@ import { Button } from "antd"; +import { CSS } from "@cocalc/frontend/app-framework"; import { Icon, IconName } from "@cocalc/frontend/components/icon"; import { unreachable } from "@cocalc/util/misc"; import { COLORS } from "@cocalc/util/theme"; import { NEW_FILETYPE_ICONS, isNewFiletypeIconName } from "./consts"; -export const STYLE = { - marginRight: "5px", +export const STYLE: CSS = { marginBottom: "5px", whiteSpace: "normal", padding: "10px", height: "auto", + width: "100%", } as const; -const ICON_STYLE = { +const ICON_STYLE: CSS = { color: COLORS.FILE_ICON, fontSize: "125%", } as const; -const ICON_STYLE_LARGE = { +const ICON_STYLE_LARGE: CSS = { ...ICON_STYLE, fontSize: "200%", }; @@ -66,7 +67,7 @@ export function NewFileButton({ ); - const style = { + const style: CSS = { ...STYLE, ...(active ? { @@ -76,7 +77,7 @@ export function NewFileButton({ : {}), ...(mode === "secondary" ? { padding: "5px" } : { width: "100%" }), ...(active && mode === "secondary" ? {} : undefined), - ...(size == "large" ? { minHeight: "125px" } : undefined), + ...(size === "large" ? { minHeight: "125px" } : undefined), }; function renderBody() { diff --git a/src/packages/frontend/project/new/new-file-page.tsx b/src/packages/frontend/project/new/new-file-page.tsx index ae2158bc1a..3be80853bf 100644 --- a/src/packages/frontend/project/new/new-file-page.tsx +++ b/src/packages/frontend/project/new/new-file-page.tsx @@ -6,13 +6,14 @@ import { Button, Input, Modal, Space } from "antd"; import { useEffect, useRef, useState } from "react"; import { defineMessage, FormattedMessage, useIntl } from "react-intl"; + import { default_filename } from "@cocalc/frontend/account"; import { Alert, Col, Row } from "@cocalc/frontend/antd-bootstrap"; import { filenameIcon } from "@cocalc/frontend/file-associations"; import { ProjectActions, useActions, - useRedux, + // useRedux, useTypedRedux, } from "@cocalc/frontend/app-framework"; import { @@ -29,13 +30,13 @@ import ComputeServer from "@cocalc/frontend/compute/inline"; import { FileUpload, UploadLink } from "@cocalc/frontend/file-upload"; import { labels } from "@cocalc/frontend/i18n"; import { special_filenames_with_no_extension } from "@cocalc/frontend/project-file"; -import { ProjectMap } from "@cocalc/frontend/todo-types"; +// import { ProjectMap } from "@cocalc/frontend/todo-types"; import { filename_extension, is_only_downloadable } from "@cocalc/util/misc"; import { COLORS } from "@cocalc/util/theme"; import { PathNavigator } from "../explorer/path-navigator"; import { useAvailableFeatures } from "../use-available-features"; import { FileTypeSelector } from "./file-type-selector"; -import { NewFileButton } from "./new-file-button"; +// import { NewFileButton } from "./new-file-button"; import { NewFileDropdown } from "./new-file-dropdown"; const CREATE_MSG = defineMessage({ @@ -84,15 +85,15 @@ export default function NewFilePage(props: Props) { { project_id }, "file_creation_error", ); - const downloading_file = useTypedRedux({ project_id }, "downloading_file"); - const project_map: ProjectMap | undefined = useRedux([ - "projects", - "project_map", - ]); - const get_total_project_quotas = useRedux([ - "projects", - "get_total_project_quotas", - ]); + // const downloading_file = useTypedRedux({ project_id }, "downloading_file"); + // const project_map: ProjectMap | undefined = useRedux([ + // "projects", + // "project_map", + // ]); + // const get_total_project_quotas = useRedux([ + // "projects", + // "get_total_project_quotas", + // ]); if (actions == null) { return ; @@ -164,16 +165,16 @@ export default function NewFilePage(props: Props) { ); } - function blocked() { - if (project_map == null) { - return ""; - } - if (get_total_project_quotas(project_id)?.network) { - return ""; - } else { - return " (access blocked -- see project settings)"; - } - } + // function blocked() { + // if (project_map == null) { + // return ""; + // } + // if (get_total_project_quotas(project_id)?.network) { + // return ""; + // } else { + // return " (access blocked -- see project settings)"; + // } + // } function createFolder() { getActions().create_folder({ @@ -506,8 +507,8 @@ export default function NewFilePage(props: Props) { availableFeatures={availableFeatures} filename={filename} filenameChanged={filenameChanged} - > - + {/* createFile()} loading={downloading_file} /> - - + */} {renderUpload()} diff --git a/src/packages/frontend/project/page/home-page/ai-generate-document.tsx b/src/packages/frontend/project/page/home-page/ai-generate-document.tsx index 74a0743500..b7a92a857c 100644 --- a/src/packages/frontend/project/page/home-page/ai-generate-document.tsx +++ b/src/packages/frontend/project/page/home-page/ai-generate-document.tsx @@ -62,6 +62,7 @@ import getKernelSpec from "@cocalc/frontend/jupyter/kernelspecs"; import { splitCells } from "@cocalc/frontend/jupyter/llm/split-cells"; import NBViewer from "@cocalc/frontend/jupyter/nbviewer/nbviewer"; import { LLMCostEstimation } from "@cocalc/frontend/misc/llm-cost-estimation"; +import { useProjectContext } from "@cocalc/frontend/project/context"; import { LLMEvent } from "@cocalc/frontend/project/history/types"; import { DELAY_SHOW_MS } from "@cocalc/frontend/project/new/consts"; import { STYLE as NEW_FILE_STYLE } from "@cocalc/frontend/project/new/new-file-button"; @@ -1102,18 +1103,17 @@ export function AIGenerateDocumentModal({ } export function AIGenerateDocumentButton({ - project_id, style, mode = "full", ext, filename, }: { - project_id: string; style?: CSS; mode?: "full" | "flyout"; ext: Props["ext"]; filename?: string; }) { + const { project_id } = useProjectContext(); const intl = useIntl(); const [show, setShow] = useState(false); @@ -1122,10 +1122,11 @@ export function AIGenerateDocumentButton({ } const btnStyle: CSS = { - width: "100%", overflowX: "hidden", overflow: "hidden", whiteSpace: "nowrap", + width: "100%", + textOverflow: "ellipsis", ...(mode === "flyout" ? { ...NEW_FILE_STYLE, marginRight: "0", marginBottom: "0" } : {}), @@ -1159,7 +1160,13 @@ export function AIGenerateDocumentButton({ style={btnStyle} size={mode === "flyout" ? "small" : undefined} > - + {mode === "full" ? ` ${intl.formatMessage(labels.ai_generate_label)}`