CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/landing/content.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Col, Row, Space } from "antd";
7
import { ReactNode } from "react";
8
import StaticMarkdown from "@cocalc/frontend/editors/slate/static-markdown";
9
import { COLORS } from "@cocalc/util/theme";
10
import Path from "components/app/path";
11
import { CSS, Paragraph, Title } from "components/misc";
12
import SanitizedMarkdown from "components/misc/sanitized-markdown";
13
import { MAX_WIDTH_LANDING } from "lib/config";
14
import useCustomize from "lib/use-customize";
15
import Image from "./image";
16
import SignIn from "./sign-in";
17
import LiveDemo from "components/landing/live-demo";
18
19
// See https://github.com/vercel/next.js/issues/29788 for why we have to define this for now (it's to work around a bug).
20
interface StaticImageData {
21
src: string;
22
height: number;
23
width: number;
24
blurDataURL?: string;
25
}
26
27
interface Props {
28
aboveImage?: ReactNode;
29
alignItems?: "center" | "flex-start";
30
alt?: string;
31
caption?;
32
description?: ReactNode;
33
image?: string | StaticImageData;
34
imageAlternative?: JSX.Element | string; // string as markdown, replaces the image
35
landing?: boolean;
36
body?: ReactNode | string | StaticImageData;
37
startup?: ReactNode;
38
style?: React.CSSProperties;
39
subtitle?: ReactNode;
40
subtitleBelow?: boolean;
41
title: ReactNode;
42
}
43
44
const SUBTITLE_STYLE: CSS = {
45
color: COLORS.GRAY_D,
46
textAlign: "center",
47
};
48
49
function Logo({ logo, title }) {
50
if (!logo) return null;
51
if (typeof logo === "string" || logo.src != null) {
52
return <Image src={logo} style={{ width: "40%" }} alt={`${title} logo`} />;
53
} else {
54
return logo;
55
}
56
}
57
58
export default function Content(props: Props) {
59
const {
60
aboveImage,
61
alignItems = "center",
62
alt,
63
caption,
64
description,
65
image,
66
imageAlternative,
67
landing = false, // for all pages on /landing/* – makes the splash content background at the top blue-ish
68
body,
69
startup,
70
style,
71
subtitle,
72
subtitleBelow = false,
73
title,
74
} = props;
75
76
const { sandboxProjectId } = useCustomize();
77
78
function renderIndexInfo() {
79
if (!imageAlternative) return;
80
if (typeof imageAlternative === "string") {
81
return (
82
<Col xs={24}>
83
<SanitizedMarkdown
84
value={imageAlternative}
85
style={{ padding: "20xp" }}
86
/>
87
</Col>
88
);
89
} else {
90
return <Col xs={24}>{imageAlternative}</Col>;
91
}
92
}
93
94
function renderTitle() {
95
if (title)
96
return (
97
<Title level={2} style={{ color: COLORS.GRAY_DD }}>
98
{title}
99
</Title>
100
);
101
}
102
103
function renderSubtitleTop() {
104
if (subtitleBelow) return;
105
return (
106
<Title level={4} style={SUBTITLE_STYLE}>
107
{typeof subtitle === "string" ? (
108
<StaticMarkdown value={subtitle} />
109
) : (
110
subtitle
111
)}
112
</Title>
113
);
114
}
115
116
function renderSubtitleBelow() {
117
if (!subtitleBelow) return;
118
return (
119
<>
120
<Col xs={0} sm={4}></Col>
121
<Col xs={24} sm={16}>
122
<Title level={4} style={SUBTITLE_STYLE}>
123
{subtitle}
124
</Title>
125
</Col>
126
</>
127
);
128
}
129
130
function renderImage() {
131
// if the index info is anything more than an empty string, we render this here instead
132
if (!!imageAlternative) return renderIndexInfo();
133
if (!image) return;
134
return (
135
<>
136
<Image
137
src={image}
138
priority={true}
139
style={{ paddingRight: "15px", paddingLeft: "15px" }}
140
alt={alt ?? `Image illustrating ${title}`}
141
/>
142
<Paragraph
143
style={{
144
textAlign: "center",
145
color: COLORS.GRAY_DD,
146
fontSize: "12pt",
147
}}
148
>
149
{caption}
150
</Paragraph>
151
</>
152
);
153
}
154
155
function renderAboveImage() {
156
if (aboveImage != null) return aboveImage;
157
}
158
159
function renderBelowImage() {
160
if (aboveImage == null && sandboxProjectId) {
161
return (
162
<div style={{ margin: "15px" }}>
163
<Path
164
style={{ marginBottom: "15px" }}
165
project_id={sandboxProjectId}
166
description="Public Sandbox"
167
/>
168
</div>
169
);
170
}
171
}
172
173
function renderLogo() {
174
if (typeof body === "string" || (body as StaticImageData)?.src != null) {
175
return <Logo logo={body} title={title} />;
176
} else {
177
return <>{body}</>;
178
}
179
}
180
181
return (
182
<div
183
style={{
184
...(landing && { backgroundColor: COLORS.LANDING.TOP_BG }),
185
...style,
186
}}
187
>
188
<Row
189
gutter={[20, 30]}
190
style={{
191
paddingTop: "12px",
192
maxWidth: MAX_WIDTH_LANDING,
193
marginTop: "0",
194
marginBottom: "0",
195
marginLeft: "auto",
196
marginRight: "auto",
197
}}
198
>
199
<Col
200
sm={10}
201
xs={24}
202
style={{
203
display: "flex",
204
alignItems: alignItems,
205
}}
206
>
207
<Space
208
size="large"
209
direction="vertical"
210
style={{ textAlign: "center", width: "100%" }}
211
>
212
{renderLogo()}
213
{renderTitle()}
214
{subtitle && renderSubtitleTop()}
215
{description && (
216
<Title level={4} style={{ color: COLORS.GRAY }}>
217
{description}
218
</Title>
219
)}
220
<div style={{ marginTop: "15px" }}>
221
<LiveDemo
222
context={
223
typeof title == "string" ? title : alt ?? "Feature Page"
224
}
225
/>
226
</div>
227
<SignIn startup={startup ?? title} hideFree={true} />
228
</Space>
229
</Col>
230
<Col sm={14} xs={24}>
231
{renderAboveImage()}
232
{renderImage()}
233
{renderBelowImage()}
234
</Col>
235
{subtitle && renderSubtitleBelow()}
236
</Row>
237
</div>
238
);
239
}
240
241