mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): refactor template version editor layout (#10912)
This commit is contained in:
@@ -259,10 +259,6 @@ export const AppRouter: FC = () => {
|
||||
<Route path="versions">
|
||||
<Route path=":version">
|
||||
<Route index element={<TemplateVersionPage />} />
|
||||
<Route
|
||||
path="edit"
|
||||
element={<TemplateVersionEditorPage />}
|
||||
/>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
@@ -346,13 +342,17 @@ export const AppRouter: FC = () => {
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
{/* Terminal and CLI auth pages don't have the dashboard layout */}
|
||||
{/* Pages that don't have the dashboard layout */}
|
||||
<Route
|
||||
path="/:username/:workspace/terminal"
|
||||
element={<TerminalPage />}
|
||||
/>
|
||||
<Route path="/cli-auth" element={<CliAuthenticationPage />} />
|
||||
<Route path="/icons" element={<IconsPage />} />
|
||||
<Route
|
||||
path="/templates/:template/versions/:version/edit"
|
||||
element={<TemplateVersionEditorPage />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
{/* Using path="*"" means "match anything", so this route
|
||||
|
||||
@@ -24,10 +24,14 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
|
||||
className = "",
|
||||
}) => {
|
||||
return (
|
||||
<div css={styles.root} className={className}>
|
||||
<div css={styles.root} className={`${className} logs-container`}>
|
||||
<div css={{ minWidth: "fit-content" }}>
|
||||
{lines.map((line, idx) => (
|
||||
<div css={styles.line} className={line.level} key={idx}>
|
||||
<div
|
||||
css={[styles.line]}
|
||||
className={`${line.level} logs-line`}
|
||||
key={idx}
|
||||
>
|
||||
{!hideTimestamps && (
|
||||
<>
|
||||
<span css={styles.time}>
|
||||
|
||||
@@ -77,7 +77,10 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({
|
||||
|
||||
return (
|
||||
<Fragment key={stage}>
|
||||
<div css={[styles.header, sticky && styles.sticky]}>
|
||||
<div
|
||||
css={[styles.header, sticky && styles.sticky]}
|
||||
className="logs-header"
|
||||
>
|
||||
<div>{stage}</div>
|
||||
{shouldDisplayDuration && (
|
||||
<div css={styles.duration}>
|
||||
|
||||
-1
@@ -233,7 +233,6 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
<TextField
|
||||
{...getFieldHelpers("deprecation_message")}
|
||||
disabled={isSubmitting || !accessControlEnabled}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label="Deprecation Message"
|
||||
/>
|
||||
|
||||
@@ -62,6 +62,7 @@ export const FileTreeView: FC<{
|
||||
css={(theme) => css`
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
height: 32px;
|
||||
|
||||
&:focus:not(.active) > .MuiTreeItem-content {
|
||||
background: ${theme.palette.action.hover};
|
||||
@@ -92,7 +93,7 @@ export const FileTreeView: FC<{
|
||||
&.active {
|
||||
& > .MuiTreeItem-content {
|
||||
color: ${theme.palette.text.primary};
|
||||
background: ${colors.gray[13]};
|
||||
background: ${colors.gray[14]};
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Editor, { loader } from "@monaco-editor/react";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { FC, useLayoutEffect, useMemo, useState } from "react";
|
||||
import { FC, useMemo } from "react";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
import type { editor } from "monaco-editor";
|
||||
|
||||
loader.config({ monaco });
|
||||
|
||||
@@ -13,22 +12,6 @@ export const MonacoEditor: FC<{
|
||||
onChange?: (value: string) => void;
|
||||
}> = ({ onChange, value, path }) => {
|
||||
const theme = useTheme();
|
||||
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
||||
useLayoutEffect(() => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
const resizeListener = () => {
|
||||
editor.layout({
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
};
|
||||
window.addEventListener("resize", resizeListener);
|
||||
return () => {
|
||||
window.removeEventListener("resize", resizeListener);
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
const language = useMemo(() => {
|
||||
if (path?.endsWith(".tf")) {
|
||||
@@ -56,7 +39,7 @@ export const MonacoEditor: FC<{
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
wordWrap: "on",
|
||||
padding: {
|
||||
top: 16,
|
||||
@@ -81,8 +64,6 @@ export const MonacoEditor: FC<{
|
||||
},
|
||||
);
|
||||
|
||||
setEditor(editor);
|
||||
|
||||
document.fonts.ready
|
||||
.then(() => {
|
||||
// Ensures that all text is measured properly.
|
||||
@@ -124,7 +105,7 @@ export const MonacoEditor: FC<{
|
||||
],
|
||||
colors: {
|
||||
"editor.foreground": theme.palette.text.primary,
|
||||
"editor.background": theme.palette.background.default,
|
||||
"editor.background": theme.palette.background.paper,
|
||||
},
|
||||
});
|
||||
editor.updateOptions({
|
||||
|
||||
@@ -68,44 +68,45 @@ export const PublishTemplateVersionDialog: FC<
|
||||
confirmText="Publish"
|
||||
title="Publish new version"
|
||||
description={
|
||||
<Stack>
|
||||
<p>You are about to publish a new version of this template.</p>
|
||||
<FormFields>
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
label={Language.versionNameLabel}
|
||||
autoFocus
|
||||
disabled={isPublishing}
|
||||
/>
|
||||
<form id="publish-version" onSubmit={form.handleSubmit}>
|
||||
<Stack>
|
||||
<p>You are about to publish a new version of this template.</p>
|
||||
<FormFields>
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
label={Language.versionNameLabel}
|
||||
autoFocus
|
||||
disabled={isPublishing}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...getFieldHelpers("message")}
|
||||
label="Message"
|
||||
placeholder={Language.messagePlaceholder}
|
||||
autoFocus
|
||||
disabled={isPublishing}
|
||||
multiline
|
||||
rows={5}
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("message")}
|
||||
label="Message"
|
||||
placeholder={Language.messagePlaceholder}
|
||||
disabled={isPublishing}
|
||||
multiline
|
||||
rows={5}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
label={Language.defaultCheckboxLabel}
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={form.values.isActiveVersion}
|
||||
onChange={async (e) => {
|
||||
await form.setFieldValue(
|
||||
"isActiveVersion",
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
name="isActiveVersion"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FormFields>
|
||||
</Stack>
|
||||
<FormControlLabel
|
||||
label={Language.defaultCheckboxLabel}
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={form.values.isActiveVersion}
|
||||
onChange={async (e) => {
|
||||
await form.setFieldValue(
|
||||
"isActiveVersion",
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
name="isActiveVersion"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FormFields>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import Button, { ButtonProps } from "@mui/material/Button";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import CreateIcon from "@mui/icons-material/AddOutlined";
|
||||
import BuildIcon from "@mui/icons-material/BuildOutlined";
|
||||
import PreviewIcon from "@mui/icons-material/VisibilityOutlined";
|
||||
import {
|
||||
ProvisionerJobLog,
|
||||
Template,
|
||||
@@ -16,11 +13,11 @@ import {
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { Alert, AlertDetail } from "components/Alert/Alert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable";
|
||||
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs";
|
||||
import { PublishVersionData } from "pages/TemplateVersionEditorPage/types";
|
||||
import { type FC, useCallback, useEffect, useRef, useState } from "react";
|
||||
import PlayArrowOutlined from "@mui/icons-material/PlayArrowOutlined";
|
||||
import {
|
||||
createFile,
|
||||
existsFile,
|
||||
@@ -41,13 +38,13 @@ import { FileTreeView } from "./FileTreeView";
|
||||
import { MissingTemplateVariablesDialog } from "./MissingTemplateVariablesDialog";
|
||||
import { MonacoEditor } from "./MonacoEditor";
|
||||
import { PublishTemplateVersionDialog } from "./PublishTemplateVersionDialog";
|
||||
import {
|
||||
getStatus,
|
||||
TemplateVersionStatusBadge,
|
||||
} from "./TemplateVersionStatusBadge";
|
||||
import { TemplateVersionStatusBadge } from "./TemplateVersionStatusBadge";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
|
||||
import CloseOutlined from "@mui/icons-material/CloseOutlined";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
|
||||
type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab
|
||||
export interface TemplateVersionEditorProps {
|
||||
@@ -74,8 +71,6 @@ export interface TemplateVersionEditorProps {
|
||||
defaultTab?: Tab;
|
||||
}
|
||||
|
||||
const topbarHeight = 80;
|
||||
|
||||
const findInitialFile = (fileTree: FileTree): string | undefined => {
|
||||
let initialFile: string | undefined;
|
||||
|
||||
@@ -162,90 +157,196 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
["running", "pending"].includes(previousVersion.current.job.status) &&
|
||||
templateVersion.job.status === "succeeded"
|
||||
) {
|
||||
setSelectedTab("resources");
|
||||
setDirty(false);
|
||||
}
|
||||
previousVersion.current = templateVersion;
|
||||
}, [templateVersion]);
|
||||
|
||||
const hasIcon = template.icon && template.icon !== "";
|
||||
const showBuildLogs = Boolean(buildLogs);
|
||||
const editorValue = getFileContent(activePath ?? "", fileTree) as string;
|
||||
|
||||
// Auto scroll
|
||||
const buildLogsRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
}, [showBuildLogs]);
|
||||
if (buildLogsRef.current) {
|
||||
buildLogsRef.current.scrollTop = buildLogsRef.current.scrollHeight;
|
||||
}
|
||||
}, [buildLogs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardFullPage
|
||||
css={{
|
||||
background: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<div css={styles.topbar} data-testid="topbar">
|
||||
<div css={styles.topbarSides}>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
underline="none"
|
||||
to={`/templates/${template.name}`}
|
||||
>
|
||||
<AvatarData
|
||||
title={template.display_name || template.name}
|
||||
subtitle={template.description}
|
||||
avatar={
|
||||
hasIcon && (
|
||||
<Avatar src={template.icon} variant="square" fitImage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
<div css={{ height: "100%", display: "flex", flexDirection: "column" }}>
|
||||
<div
|
||||
css={{
|
||||
height: 48,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 2fr 1fr",
|
||||
alignItems: "center",
|
||||
}}
|
||||
data-testid="topbar"
|
||||
>
|
||||
<div>
|
||||
<Tooltip title="Back to the template">
|
||||
<IconButton
|
||||
component={RouterLink}
|
||||
to={`/templates/${template.name}`}
|
||||
size="small"
|
||||
css={{
|
||||
padding: "0 16px",
|
||||
borderRadius: 0,
|
||||
height: 48,
|
||||
}}
|
||||
>
|
||||
<ArrowBackOutlined css={{ width: 20, height: 20 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{publishedVersion && (
|
||||
<Alert
|
||||
severity="success"
|
||||
dismissible
|
||||
actions={
|
||||
<Button variant="text" size="small" onClick={onCreateWorkspace}>
|
||||
Create a workspace
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
Successfully published {publishedVersion.name}!
|
||||
</Alert>
|
||||
)}
|
||||
<div
|
||||
css={{
|
||||
fontSize: 13,
|
||||
display: "flex",
|
||||
gap: 8,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={template.icon}
|
||||
variant="square"
|
||||
fitImage
|
||||
css={{ width: 16, height: 16 }}
|
||||
/>
|
||||
<RouterLink
|
||||
to={`/templates/${template.name}`}
|
||||
css={{
|
||||
color: theme.palette.text.primary,
|
||||
textDecoration: "none",
|
||||
|
||||
<div css={styles.topbarSides}>
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{template.display_name || template.name}
|
||||
</RouterLink>
|
||||
<span css={{ color: theme.palette.divider }}>/</span>
|
||||
<span css={{ color: theme.palette.text.secondary }}>
|
||||
{templateVersion.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
gap: 8,
|
||||
paddingRight: 16,
|
||||
}}
|
||||
>
|
||||
{buildLogs && (
|
||||
<TemplateVersionStatusBadge version={templateVersion} />
|
||||
)}
|
||||
|
||||
<Button
|
||||
<TopbarButton
|
||||
startIcon={
|
||||
<PlayArrowOutlined
|
||||
css={{ color: theme.palette.success.light }}
|
||||
/>
|
||||
}
|
||||
title="Build template (Ctrl + Enter)"
|
||||
disabled={disablePreview}
|
||||
onClick={() => {
|
||||
triggerPreview();
|
||||
}}
|
||||
>
|
||||
Build template
|
||||
</Button>
|
||||
Build
|
||||
</TopbarButton>
|
||||
|
||||
<Button
|
||||
<TopbarButton
|
||||
variant="contained"
|
||||
disabled={dirty || disableUpdate}
|
||||
onClick={onPublish}
|
||||
>
|
||||
Publish version
|
||||
</Button>
|
||||
Publish
|
||||
</TopbarButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div css={styles.sidebarAndEditor}>
|
||||
<div css={styles.sidebar}>
|
||||
<div css={styles.sidebarTitle}>
|
||||
Template files
|
||||
<div css={styles.sidebarActions}>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexBasis: 0,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{publishedVersion && (
|
||||
<div
|
||||
// We need this to reset the dismissable state of the component
|
||||
// when the published version changes
|
||||
key={publishedVersion.id}
|
||||
css={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
padding: 12,
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
severity="success"
|
||||
dismissible
|
||||
actions={
|
||||
<Button
|
||||
variant="text"
|
||||
size="small"
|
||||
onClick={onCreateWorkspace}
|
||||
>
|
||||
Create a workspace
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
Successfully published {publishedVersion.name}!
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
css={{
|
||||
width: 240,
|
||||
borderRight: `1px solid ${theme.palette.divider}`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
height: 42,
|
||||
padding: "0 8px 0 16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
css={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
Files
|
||||
</span>
|
||||
|
||||
<div
|
||||
css={{
|
||||
marginLeft: "auto",
|
||||
"& svg": {
|
||||
fill: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Create File" placement="top">
|
||||
<IconButton
|
||||
aria-label="Create File"
|
||||
@@ -254,7 +355,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
event.currentTarget.blur();
|
||||
}}
|
||||
>
|
||||
<CreateIcon />
|
||||
<CreateIcon css={{ width: 16, height: 16 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -326,14 +427,14 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
|
||||
<div
|
||||
css={{
|
||||
display: "grid",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
gridTemplateColumns: showBuildLogs ? "1fr 1fr" : "1fr 0fr",
|
||||
minHeight: "100%",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div css={styles.editor} data-chromatic="ignore">
|
||||
<div css={{ flex: 1, overflowY: "auto" }} data-chromatic="ignore">
|
||||
{activePath ? (
|
||||
<MonacoEditor
|
||||
value={editorValue}
|
||||
@@ -353,45 +454,84 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div css={styles.panelWrapper}>
|
||||
<div css={styles.tabs}>
|
||||
<button
|
||||
css={styles.tab}
|
||||
className={selectedTab === "logs" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSelectedTab("logs");
|
||||
<div
|
||||
css={{
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderBottom: selectedTab
|
||||
? `1px solid ${theme.palette.divider}`
|
||||
: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
|
||||
"& .MuiTab-root": {
|
||||
padding: 0,
|
||||
fontSize: 14,
|
||||
textTransform: "none",
|
||||
letterSpacing: "unset",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{templateVersion.job.status !== "succeeded" ? (
|
||||
getStatus(templateVersion).icon
|
||||
) : (
|
||||
<BuildIcon />
|
||||
)}
|
||||
Build Log
|
||||
</button>
|
||||
|
||||
{!disableUpdate && (
|
||||
<button
|
||||
disabled={!buildLogs}
|
||||
css={styles.tab}
|
||||
className={selectedTab === "logs" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSelectedTab("logs");
|
||||
}}
|
||||
>
|
||||
Output
|
||||
</button>
|
||||
|
||||
<button
|
||||
disabled={disableUpdate}
|
||||
css={styles.tab}
|
||||
className={selectedTab === "resources" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSelectedTab("resources");
|
||||
}}
|
||||
>
|
||||
<PreviewIcon />
|
||||
Workspace Preview
|
||||
Resources
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{selectedTab && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setSelectedTab(undefined);
|
||||
}}
|
||||
css={{
|
||||
marginLeft: "auto",
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 0,
|
||||
}}
|
||||
>
|
||||
<CloseOutlined css={{ width: 16, height: 16 }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
css={[
|
||||
styles.panel,
|
||||
{
|
||||
display: selectedTab !== "logs" ? "none" : "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
]}
|
||||
ref={buildLogsRef}
|
||||
css={{
|
||||
display: selectedTab !== "logs" ? "none" : "flex",
|
||||
flexDirection: "column",
|
||||
overflowY: "auto",
|
||||
height: selectedTab ? 280 : 0,
|
||||
}}
|
||||
>
|
||||
{templateVersion.job.error && (
|
||||
<div>
|
||||
@@ -412,9 +552,39 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{buildLogs && buildLogs.length === 0 && (
|
||||
<Loader css={{ height: "100%" }} />
|
||||
)}
|
||||
|
||||
{buildLogs && buildLogs.length > 0 && (
|
||||
<WorkspaceBuildLogs
|
||||
sx={{ borderRadius: 0, border: 0 }}
|
||||
css={{
|
||||
borderRadius: 0,
|
||||
border: 0,
|
||||
|
||||
// Hack to update logs header and lines
|
||||
"& .logs-header": {
|
||||
border: 0,
|
||||
padding: "0 16px",
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
|
||||
"&:first-child": {
|
||||
paddingTop: 16,
|
||||
},
|
||||
|
||||
"&:last-child": {
|
||||
paddingBottom: 16,
|
||||
},
|
||||
},
|
||||
|
||||
"& .logs-line": {
|
||||
paddingLeft: 16,
|
||||
},
|
||||
|
||||
"& .logs-container": {
|
||||
border: "0 !important",
|
||||
},
|
||||
}}
|
||||
hideTimestamps
|
||||
logs={buildLogs}
|
||||
/>
|
||||
@@ -422,13 +592,25 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
css={[
|
||||
styles.panel,
|
||||
{
|
||||
paddingBottom: 16,
|
||||
display: selectedTab !== "resources" ? "none" : undefined,
|
||||
css={{
|
||||
display: selectedTab !== "resources" ? "none" : undefined,
|
||||
overflowY: "auto",
|
||||
height: selectedTab ? 280 : 0,
|
||||
|
||||
// Hack to access customize resource-card from here
|
||||
"& .resource-card": {
|
||||
borderLeft: 0,
|
||||
borderRight: 0,
|
||||
|
||||
"&:first-child": {
|
||||
borderTop: 0,
|
||||
},
|
||||
|
||||
"&:last-child": {
|
||||
borderBottom: 0,
|
||||
},
|
||||
},
|
||||
]}
|
||||
}}
|
||||
>
|
||||
{resources && (
|
||||
<TemplateResourcesTable
|
||||
@@ -441,7 +623,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardFullPage>
|
||||
</div>
|
||||
|
||||
<PublishTemplateVersionDialog
|
||||
key={templateVersion.name}
|
||||
@@ -463,84 +645,30 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const TopbarButton = (props: ButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
css={{
|
||||
height: 28,
|
||||
fontSize: 13,
|
||||
borderRadius: 4,
|
||||
padding: "0 12px",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
topbar: (theme) => ({
|
||||
padding: 16,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
height: topbarHeight,
|
||||
}),
|
||||
topbarSides: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
},
|
||||
sidebarAndEditor: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexBasis: 0,
|
||||
overflow: "hidden",
|
||||
},
|
||||
sidebar: (theme) => ({
|
||||
minWidth: 256,
|
||||
borderRight: `1px solid ${theme.palette.divider}`,
|
||||
}),
|
||||
sidebarTitle: (theme) => ({
|
||||
fontSize: 10,
|
||||
textTransform: "uppercase",
|
||||
padding: "8px 16px",
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 500,
|
||||
letterSpacing: "0.5px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}),
|
||||
sidebarActions: (theme) => ({
|
||||
marginLeft: "auto",
|
||||
"& svg": {
|
||||
fill: theme.palette.text.primary,
|
||||
},
|
||||
}),
|
||||
editor: {
|
||||
flex: 1,
|
||||
},
|
||||
panelWrapper: (theme) => ({
|
||||
flex: 1,
|
||||
borderLeft: `1px solid ${theme.palette.divider}`,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}),
|
||||
panel: {
|
||||
overflowY: "auto",
|
||||
height: "100%",
|
||||
|
||||
// Hack to access customize resource-card from here
|
||||
"& .resource-card": {
|
||||
border: 0,
|
||||
},
|
||||
},
|
||||
tabs: (theme) => ({
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
boxShadow: "#000000 0 6px 6px -6px inset",
|
||||
|
||||
"& .MuiTab-root": {
|
||||
padding: 0,
|
||||
fontSize: 14,
|
||||
textTransform: "none",
|
||||
letterSpacing: "unset",
|
||||
},
|
||||
}),
|
||||
tab: (theme) => ({
|
||||
cursor: "pointer",
|
||||
"&:not(:disabled)": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
padding: 12,
|
||||
fontSize: 10,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.5px",
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
background: "transparent",
|
||||
fontFamily: "inherit",
|
||||
border: 0,
|
||||
@@ -564,15 +692,19 @@ const styles = {
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: 1,
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
bottom: -1,
|
||||
position: "absolute",
|
||||
},
|
||||
},
|
||||
|
||||
"&:hover": {
|
||||
"&:not(:disabled):hover": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
"&:disabled": {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
}),
|
||||
tabBar: (theme) => ({
|
||||
padding: "8px 16px",
|
||||
|
||||
@@ -57,7 +57,7 @@ test("Use custom name, message and set it as active when publishing", async () =
|
||||
return jest.fn() as never;
|
||||
});
|
||||
const buildButton = within(topbar).getByRole("button", {
|
||||
name: "Build template",
|
||||
name: "Build",
|
||||
});
|
||||
await user.click(buildButton);
|
||||
|
||||
@@ -70,7 +70,7 @@ test("Use custom name, message and set it as active when publishing", async () =
|
||||
.mockResolvedValue({ message: "" });
|
||||
await within(topbar).findByText("Success");
|
||||
const publishButton = within(topbar).getByRole("button", {
|
||||
name: "Publish version",
|
||||
name: "Publish",
|
||||
});
|
||||
await user.click(publishButton);
|
||||
const publishDialog = await screen.findByTestId("dialog");
|
||||
@@ -120,7 +120,7 @@ test("Do not mark as active if promote is not checked", async () => {
|
||||
return jest.fn() as never;
|
||||
});
|
||||
const buildButton = within(topbar).getByRole("button", {
|
||||
name: "Build template",
|
||||
name: "Build",
|
||||
});
|
||||
await user.click(buildButton);
|
||||
|
||||
@@ -133,7 +133,7 @@ test("Do not mark as active if promote is not checked", async () => {
|
||||
.mockResolvedValue({ message: "" });
|
||||
await within(topbar).findByText("Success");
|
||||
const publishButton = within(topbar).getByRole("button", {
|
||||
name: "Publish version",
|
||||
name: "Publish",
|
||||
});
|
||||
await user.click(publishButton);
|
||||
const publishDialog = await screen.findByTestId("dialog");
|
||||
@@ -185,7 +185,7 @@ test("Patch request is not send when there are no changes", async () => {
|
||||
return jest.fn() as never;
|
||||
});
|
||||
const buildButton = within(topbar).getByRole("button", {
|
||||
name: "Build template",
|
||||
name: "Build",
|
||||
});
|
||||
await user.click(buildButton);
|
||||
|
||||
@@ -195,11 +195,11 @@ test("Patch request is not send when there are no changes", async () => {
|
||||
.mockResolvedValue(MockTemplateVersionWithEmptyMessage);
|
||||
await within(topbar).findByText("Success");
|
||||
const publishButton = within(topbar).getByRole("button", {
|
||||
name: "Publish version",
|
||||
name: "Publish",
|
||||
});
|
||||
await user.click(publishButton);
|
||||
const publishDialog = await screen.findByTestId("dialog");
|
||||
// It is using the name from the template version
|
||||
// It is using the name from the template
|
||||
const nameField = within(publishDialog).getByLabelText("Version name");
|
||||
expect(nameField).toHaveValue(MockTemplateVersionWithEmptyMessage.name);
|
||||
// Publish
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
TemplateVersion,
|
||||
} from "api/typesGenerated";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
|
||||
|
||||
type Params = {
|
||||
version: string;
|
||||
@@ -107,7 +108,7 @@ export const TemplateVersionEditorPage: FC = () => {
|
||||
<title>{pageTitle(`${templateName} · Template Editor`)}</title>
|
||||
</Helmet>
|
||||
|
||||
{templateQuery.data && templateVersionQuery.data && fileTree && (
|
||||
{templateQuery.data && templateVersionQuery.data && fileTree ? (
|
||||
<TemplateVersionEditor
|
||||
template={templateQuery.data}
|
||||
templateVersion={templateVersionQuery.data}
|
||||
@@ -144,8 +145,21 @@ export const TemplateVersionEditorPage: FC = () => {
|
||||
data,
|
||||
version: templateVersionQuery.data,
|
||||
});
|
||||
const publishedVersion = {
|
||||
...templateVersionQuery.data,
|
||||
...data,
|
||||
};
|
||||
setCurrentVersionName(publishedVersion.name);
|
||||
setIsPublishingDialogOpen(false);
|
||||
setLastSuccessfulPublishedVersion(templateVersionQuery.data);
|
||||
setLastSuccessfulPublishedVersion(publishedVersion);
|
||||
queryClient.setQueryData(
|
||||
templateVersionOptions.queryKey,
|
||||
publishedVersion,
|
||||
);
|
||||
navigate(
|
||||
`/templates/${templateName}/versions/${publishedVersion.name}/edit`,
|
||||
{ replace: true },
|
||||
);
|
||||
}}
|
||||
isAskingPublishParameters={isPublishingDialogOpen}
|
||||
isPublishing={publishVersionMutation.isLoading}
|
||||
@@ -197,6 +211,8 @@ export const TemplateVersionEditorPage: FC = () => {
|
||||
setIsMissingVariablesDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FullScreenLoader />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user