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/next/components/landing/info.tsx
Views: 687
/*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";7import { Icon, IconName } from "@cocalc/frontend/components/icon";8import { COLORS } from "@cocalc/util/theme";9import { TitleProps } from "antd/es/typography/Title";10import { CSS, Paragraph, Title } from "components/misc";11import { MAX_WIDTH_LANDING } from "lib/config";12import Image, { StaticImageData } from "./image";13import { MediaURL } from "./util";14import { capitalize } from "@cocalc/util/misc";15import A from "components/misc/A";1617const showcase: CSSProperties = {18width: "100%",19boxShadow: "2px 2px 4px rgb(0 0 0 / 25%), 0 2px 4px rgb(0 0 0 / 22%)",20borderRadius: "3px",21} as const;2223interface Props {24alt?: string;25anchor: string;26below?: ReactNode;27belowWide?: boolean;28caption?: ReactNode;29children: ReactNode;30icon?: IconName;31image?: string | StaticImageData;32imageComponent?: ReactNode; // if set, this replaces the image!33level?: TitleProps["level"];34narrow?: boolean; // emphasis on the text part, not the image.35style?: CSSProperties;36swapCols?: boolean; // if true, then put text on left and image on right.37textStyle?: CSSProperties;38textStyleExtra?: CSSProperties;39title: ReactNode;40video?: string | string[];41wide?: boolean; // if given image is wide and could use more space or its very hard to see.42innerRef?;43icons?: { icon: string; title?: string; link?: string }[];44}4546export default function Info({47alt,48anchor,49below,50belowWide = false,51caption,52children,53icon,54image,55imageComponent,56level = 1,57narrow = false,58style,59swapCols,60textStyle,61textStyleExtra,62title,63video,64wide,65innerRef,66icons,67}: Props) {68function renderIcons() {69if (icons == null || icons.length == 0) {70return null;71}72return (73<div style={{ margin: "auto" }}>74<Space size={"large"}>75{icons.map(({ icon, title, link }, idx) => {76const elt = (77<div style={{ textAlign: "center", color: "#333" }}>78<Icon name={icon} style={{ fontSize: "28pt" }} key={icon} />79<br />80{title ?? capitalize(icon)}81</div>82);83if (link) {84return (85<A key={idx} href={link}>86{elt}87</A>88);89}90return elt;91})}92</Space>93</div>94);95}96function renderBelow() {97if (!below) return;9899if (belowWide) {100return (101<Col102lg={{ span: 20, offset: 2 }}103md={{ span: 22, offset: 1 }}104style={{ paddingTop: "30px" }}105>106{below}107</Col>108);109} else {110return (111<Col112lg={{ span: 16, offset: 4 }}113md={{ span: 18, offset: 3 }}114style={{ paddingTop: "30px" }}115>116{below}117</Col>118);119}120}121122const head = (123<Title124level={level}125id={anchor}126style={{127textAlign: "center",128marginBottom: "30px",129color: COLORS.GRAY_D,130...textStyle,131}}132>133{icon && (134<span style={{ fontSize: "24pt", marginRight: "5px" }}>135<Icon name={icon} />{" "}136</span>137)}138{title}139</Title>140);141142let graphic: ReactNode = null;143144// common for "text" and "text + image" div wrappers145const padding: CSS = {146paddingTop: "45px",147paddingBottom: "45px",148paddingLeft: "15px",149paddingRight: "15px",150};151152if (image != null) {153graphic = <Image style={showcase} src={image} alt={alt ?? ""} />;154} else if (video != null) {155const videoSrcs = typeof video == "string" ? [video] : video;156verifyHasMp4(videoSrcs);157graphic = (158<div style={{ position: "relative", width: "100%" }}>159<video style={showcase} loop controls>160{sources(videoSrcs)}161</video>162</div>163);164} else if (imageComponent != null) {165graphic = imageComponent;166}167168if (graphic != null && caption != null) {169graphic = (170<div>171{graphic}172<br />173<br />174<Paragraph175style={{176textAlign: "center",177color: COLORS.GRAY_D,178}}179>180{caption}181</Paragraph>182</div>183);184}185186if (!graphic) {187const noGraphicTextStyle: CSSProperties = {188...style,189};190191if (textStyleExtra != null) {192// if textColStyleExtra is given, then merge it into noGraphicTextStyle.193Object.assign(noGraphicTextStyle, textStyleExtra);194}195196return (197<div198style={{199width: "100%",200...padding,201...style,202}}203>204<div style={{ maxWidth: MAX_WIDTH_LANDING, margin: "0 auto" }}>205<div style={noGraphicTextStyle}>206<div style={{ textAlign: "center" }}>{head}</div>207<div208style={{ margin: "auto", maxWidth: wide ? "600px" : undefined }}209>210{children}211</div>212{below && <div style={{ marginTop: "20px" }}>{below}</div>}213</div>214</div>215</div>216);217}218219const textColStyle: CSSProperties = {220padding: "0 20px 0 20px",221marginBottom: "15px",222display: "flex",223justifyContent: "start",224alignContent: "start",225flexDirection: "column",226};227228if (textStyleExtra != null) {229// if textColStyleExtra is given, then merge it into textColStyle.230Object.assign(textColStyle, textStyleExtra);231}232233const widths = wide ? [7, 17] : narrow ? [12, 12] : [9, 15];234235const textCol = (236<Col key="text" lg={widths[0]} style={textColStyle}>237{children}238</Col>239);240const graphicCol = (241<Col242key="graphics"243lg={widths[1]}244style={{ padding: "0 30px 15px 30px", width: "100%" }}245>246{graphic}247</Col>248);249250const cols = swapCols ? [textCol, graphicCol] : [graphicCol, textCol];251252return (253<div254ref={innerRef}255style={{256...padding,257background: "white",258fontSize: "11pt",259...style,260}}261>262<>263{head}264<Row265style={{266maxWidth: MAX_WIDTH_LANDING,267marginLeft: "auto",268marginRight: "auto",269}}270>271{cols}272{renderBelow()}273{renderIcons()}274</Row>275</>276</div>277);278}279280function sources(video: string[]) {281const v: JSX.Element[] = [];282for (const x of video) {283v.push(<source key={x} src={MediaURL(x)} />);284}285return v;286}287288function verifyHasMp4(video: string[]) {289for (const x of video) {290if (x.endsWith(".mp4")) {291return;292}293}294console.warn(295"include mp4 format for the video, so that it is viewable on iOS!!",296video,297);298}299300interface HeadingProps {301children: ReactNode;302description?: ReactNode;303style?: CSSProperties;304textStyle?: CSSProperties;305level?: TitleProps["level"];306}307308Info.Heading = (props: HeadingProps) => {309const { level = 1, children, description, style, textStyle } = props;310return (311<div312style={{313...{314textAlign: "center",315margin: "0",316padding: "20px",317},318...style,319}}320>321<Title322level={level}323style={{324color: COLORS.GRAY_D,325maxWidth: MAX_WIDTH_LANDING,326margin: "0 auto",327...textStyle,328}}329>330{children}331</Title>332{description && (333<Paragraph334style={{335fontSize: "13pt",336color: COLORS.GRAY_D,337...textStyle,338}}339>340{description}341</Paragraph>342)}343</div>344);345};346347348