Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/src/packages/next/components/landing/info.tsx
Views: 923
/*1* This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Col, Row, Space } from "antd";6import { CSSProperties, ReactNode } from "react";78import { Icon, IconName } from "@cocalc/frontend/components/icon";9import { capitalize } from "@cocalc/util/misc";10import { COLORS } from "@cocalc/util/theme";11import { TitleProps } from "antd/es/typography/Title";12import { CSS, Paragraph, Title } from "components/misc";13import A from "components/misc/A";14import { MAX_WIDTH_LANDING } from "lib/config";15import Image, { StaticImageData } from "./image";16import { MediaURL, SHADOW } from "./util";1718interface Props {19alt?: string;20anchor: string;21below?: ReactNode;22belowWide?: boolean;23caption?: ReactNode;24children: ReactNode;25icon?: IconName;26image?: string | StaticImageData;27imageComponent?: ReactNode; // if set, this replaces the image!28level?: TitleProps["level"];29narrow?: boolean; // emphasis on the text part, not the image.30style?: CSSProperties;31swapCols?: boolean; // if true, then put text on left and image on right.32textStyle?: CSSProperties;33textStyleExtra?: CSSProperties;34title: ReactNode;35video?: string | string[];36wide?: boolean; // if given image is wide and could use more space or its very hard to see.37innerRef?;38icons?: { icon: IconName | JSX.Element; title?: string; link?: string }[];39}4041export default function Info({42alt,43anchor,44below,45belowWide = false,46caption,47children,48icon,49image,50imageComponent,51level = 1,52narrow = false,53style,54swapCols,55textStyle,56textStyleExtra,57title,58video,59wide,60innerRef,61icons,62}: Props) {63function renderIcons() {64if (icons == null || icons.length == 0) {65return null;66}67return (68<div style={{ margin: "auto" }}>69<Space size={"large"}>70{icons.map(({ icon, title, link }, idx) => {71const elt = (72<div style={{ textAlign: "center", color: "#333" }}>73{typeof icon === "string" ? (74<Icon name={icon} style={{ fontSize: "28pt" }} key={icon} />75) : (76icon77)}78<br />79{title ?? capitalize(typeof icon === "string" ? icon : "")}80</div>81);82if (link) {83return (84<A key={idx} href={link}>85{elt}86</A>87);88}89return elt;90})}91</Space>92</div>93);94}95function renderBelow() {96if (!below) return;9798if (belowWide) {99return (100<Col101lg={{ span: 20, offset: 2 }}102md={{ span: 22, offset: 1 }}103style={{ paddingTop: "30px" }}104>105{below}106</Col>107);108} else {109return (110<Col111lg={{ span: 16, offset: 4 }}112md={{ span: 18, offset: 3 }}113style={{ paddingTop: "30px" }}114>115{below}116</Col>117);118}119}120121const head = (122<Title123level={level}124id={anchor}125style={{126textAlign: "center",127marginBottom: "30px",128color: COLORS.GRAY_D,129...textStyle,130}}131>132{icon && (133<span style={{ fontSize: "24pt", marginRight: "5px" }}>134<Icon name={icon} />{" "}135</span>136)}137{title}138</Title>139);140141let graphic: ReactNode = null;142143// common for "text" and "text + image" div wrappers144const padding: CSS = {145paddingTop: "45px",146paddingBottom: "45px",147paddingLeft: "15px",148paddingRight: "15px",149};150151if (image != null) {152graphic = <Image shadow src={image} alt={alt ?? ""} />;153} else if (video != null) {154const videoSrcs = typeof video == "string" ? [video] : video;155verifyHasMp4(videoSrcs);156graphic = (157<div style={{ position: "relative", width: "100%" }}>158<video style={{ width: "100%", ...SHADOW }} loop controls>159{sources(videoSrcs)}160</video>161</div>162);163} else if (imageComponent != null) {164graphic = imageComponent;165}166167if (graphic != null && caption != null) {168graphic = (169<div>170{graphic}171<br />172<br />173<Paragraph174style={{175textAlign: "center",176color: COLORS.GRAY_D,177}}178>179{caption}180</Paragraph>181</div>182);183}184185if (!graphic) {186const noGraphicTextStyle: CSSProperties = {187...style,188};189190if (textStyleExtra != null) {191// if textColStyleExtra is given, then merge it into noGraphicTextStyle.192Object.assign(noGraphicTextStyle, textStyleExtra);193}194195const icons = renderIcons();196197return (198<div199style={{200width: "100%",201...padding,202...style,203}}204>205<div style={{ maxWidth: MAX_WIDTH_LANDING, margin: "0 auto" }}>206<div style={noGraphicTextStyle}>207<div style={{ textAlign: "center" }}>{head}</div>208<div209style={{ margin: "auto", maxWidth: wide ? "600px" : undefined }}210>211{children}212</div>213{icons && (214<div style={{ marginTop: "20px", textAlign: "center" }}>215{icons}216</div>217)}218{below && <div style={{ marginTop: "20px" }}>{below}</div>}219</div>220</div>221</div>222);223}224225const textColStyle: CSSProperties = {226padding: "0 20px 0 20px",227marginBottom: "15px",228display: "flex",229justifyContent: "start",230alignContent: "start",231flexDirection: "column",232};233234if (textStyleExtra != null) {235// if textColStyleExtra is given, then merge it into textColStyle.236Object.assign(textColStyle, textStyleExtra);237}238239const widths = wide ? [7, 17] : narrow ? [12, 12] : [9, 15];240241const textCol = (242<Col key="text" lg={widths[0]} style={textColStyle}>243{children}244</Col>245);246const graphicCol = (247<Col248key="graphics"249lg={widths[1]}250style={{ padding: "0 30px 15px 30px", width: "100%" }}251>252{graphic}253</Col>254);255256const cols = swapCols ? [textCol, graphicCol] : [graphicCol, textCol];257258return (259<div260ref={innerRef}261style={{262...padding,263background: "white",264fontSize: "11pt",265...style,266}}267>268<>269{head}270<Row271style={{272maxWidth: MAX_WIDTH_LANDING,273marginLeft: "auto",274marginRight: "auto",275}}276>277{cols}278{renderBelow()}279{renderIcons()}280</Row>281</>282</div>283);284}285286function sources(video: string[]) {287const v: JSX.Element[] = [];288for (const x of video) {289v.push(<source key={x} src={MediaURL(x)} />);290}291return v;292}293294function verifyHasMp4(video: string[]) {295for (const x of video) {296if (x.endsWith(".mp4")) {297return;298}299}300console.warn(301"include mp4 format for the video, so that it is viewable on iOS!!",302video,303);304}305306interface HeadingProps {307children: ReactNode;308description?: ReactNode;309style?: CSSProperties;310textStyle?: CSSProperties;311level?: TitleProps["level"];312}313314Info.Heading = (props: HeadingProps) => {315const { level = 1, children, description, style, textStyle } = props;316return (317<div318style={{319...{320textAlign: "center",321margin: "0",322padding: "20px",323},324...style,325}}326>327<Title328level={level}329style={{330color: COLORS.GRAY_D,331maxWidth: MAX_WIDTH_LANDING,332margin: "0 auto",333...textStyle,334}}335>336{children}337</Title>338{description && (339<Paragraph340style={{341fontSize: "13pt",342color: COLORS.GRAY_D,343...textStyle,344}}345>346{description}347</Paragraph>348)}349</div>350);351};352353354