Skip to content

Commit a0f0b4f

Browse files
authored
Merge pull request #5 from ConnectAI-E/feature/artifacts-style
Feature/artifacts style
2 parents 5ec0311 + c27ef6f commit a0f0b4f

File tree

6 files changed

+106
-53
lines changed

6 files changed

+106
-53
lines changed

app/components/artifact.module.scss

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.artifact {
2+
display: flex;
3+
width: 100%;
4+
height: 100%;
5+
flex-direction: column;
6+
&-header {
7+
display: flex;
8+
align-items: center;
9+
height: 36px;
10+
padding: 20px;
11+
background: var(--second);
12+
}
13+
&-title {
14+
flex: 1;
15+
text-align: center;
16+
font-weight: bold;
17+
font-size: 24px;
18+
}
19+
&-content {
20+
flex-grow: 1;
21+
padding: 0 20px 20px 20px;
22+
background-color: var(--second);
23+
}
24+
}
25+
26+
.artifact-iframe {
27+
width: 100%;
28+
border: var(--border-in-light);
29+
border-radius: 6px;
30+
}

app/components/artifact.tsx

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import { Modal, showToast } from "./ui-lib";
1313
import { copyToClipboard, downloadAs } from "../utils";
1414
import { Path, ApiPath, REPO_URL } from "@/app/constant";
1515
import { Loading } from "./home";
16+
import styles from "./artifact.module.scss";
1617

1718
export function HTMLPreview(props: {
1819
code: string;
1920
autoHeight?: boolean;
20-
height?: number;
21+
height?: number | string;
2122
onLoad?: (title?: string) => void;
2223
}) {
2324
const ref = useRef<HTMLIFrameElement>(null);
@@ -65,17 +66,22 @@ export function HTMLPreview(props: {
6566
return props.code + script;
6667
}, [props.code]);
6768

69+
const handleOnLoad = () => {
70+
if (props?.onLoad) {
71+
props.onLoad(title);
72+
}
73+
};
74+
6875
return (
6976
<iframe
77+
className={styles["artifact-iframe"]}
7078
id={frameId.current}
7179
ref={ref}
72-
frameBorder={0}
7380
sandbox="allow-forms allow-modals allow-scripts"
74-
style={{ width: "100%", height }}
75-
// src={`data:text/html,${encodeURIComponent(srcDoc)}`}
81+
style={{ height }}
7682
srcDoc={srcDoc}
77-
onLoad={(e) => props?.onLoad && props?.onLoad(title)}
78-
></iframe>
83+
onLoad={handleOnLoad}
84+
/>
7985
);
8086
}
8187

@@ -179,7 +185,6 @@ export function Artifact() {
179185
const [code, setCode] = useState("");
180186
const [loading, setLoading] = useState(true);
181187
const [fileName, setFileName] = useState("");
182-
const { height } = useWindowSize();
183188

184189
useEffect(() => {
185190
if (id) {
@@ -199,40 +204,28 @@ export function Artifact() {
199204
}, [id]);
200205

201206
return (
202-
<div
203-
style={{
204-
display: "block",
205-
width: "100%",
206-
height: "100%",
207-
position: "relative",
208-
}}
209-
>
210-
<div
211-
style={{
212-
height: 36,
213-
display: "flex",
214-
alignItems: "center",
215-
padding: 12,
216-
}}
217-
>
207+
<div className={styles["artifact"]}>
208+
<div className={styles["artifact-header"]}>
218209
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
219210
<IconButton bordered icon={<GithubIcon />} shadow />
220211
</a>
221-
<div style={{ flex: 1, textAlign: "center" }}>NextChat Artifact</div>
212+
<div className={styles["artifact-title"]}>NextChat Artifact</div>
222213
<ArtifactShareButton id={id} getCode={() => code} fileName={fileName} />
223214
</div>
224-
{loading && <Loading />}
225-
{code && (
226-
<HTMLPreview
227-
code={code}
228-
autoHeight={false}
229-
height={height - 36}
230-
onLoad={(title) => {
231-
setFileName(title as string);
232-
setLoading(false);
233-
}}
234-
/>
235-
)}
215+
<div className={styles["artifact-content"]}>
216+
{loading && <Loading />}
217+
{code && (
218+
<HTMLPreview
219+
code={code}
220+
autoHeight={false}
221+
height={"100%"}
222+
onLoad={(title) => {
223+
setFileName(title as string);
224+
setLoading(false);
225+
}}
226+
/>
227+
)}
228+
</div>
236229
</div>
237230
);
238231
}

app/components/chat.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,12 +641,13 @@ export function ChatActions(props: {
641641
]}
642642
onClose={() => setShowPluginSelector(false)}
643643
onSelection={(s) => {
644-
if (s.length === 0) return;
645644
const plugin = s[0];
646645
chatStore.updateCurrentSession((session) => {
647646
session.mask.plugin = s;
648647
});
649-
showToast(plugin);
648+
if (plugin) {
649+
showToast(plugin);
650+
}
650651
}}
651652
/>
652653
)}

app/components/markdown.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import React from "react";
1414
import { useDebouncedCallback } from "use-debounce";
1515
import { showImageModal, FullScreen } from "./ui-lib";
1616
import { ArtifactShareButton, HTMLPreview } from "./artifact";
17-
17+
import { Plugin } from "../constant";
18+
import { useChatStore } from "../store";
1819
export function Mermaid(props: { code: string }) {
1920
const ref = useRef<HTMLDivElement>(null);
2021
const [hasError, setHasError] = useState(false);
@@ -67,6 +68,9 @@ export function PreCode(props: { children: any }) {
6768
const [mermaidCode, setMermaidCode] = useState("");
6869
const [htmlCode, setHtmlCode] = useState("");
6970
const { height } = useWindowSize();
71+
const chatStore = useChatStore();
72+
const session = chatStore.currentSession();
73+
const plugins = session.mask?.plugin;
7074

7175
const renderArtifacts = useDebouncedCallback(() => {
7276
if (!ref.current) return;
@@ -87,6 +91,11 @@ export function PreCode(props: { children: any }) {
8791
// eslint-disable-next-line react-hooks/exhaustive-deps
8892
}, [refText]);
8993

94+
const enableArtifacts = useMemo(
95+
() => plugins?.includes(Plugin.Artifact),
96+
[plugins],
97+
);
98+
9099
return (
91100
<>
92101
<pre ref={ref}>
@@ -104,10 +113,10 @@ export function PreCode(props: { children: any }) {
104113
{mermaidCode.length > 0 && (
105114
<Mermaid code={mermaidCode} key={mermaidCode} />
106115
)}
107-
{htmlCode.length > 0 && (
108-
<FullScreen className="no-dark html" right={60}>
116+
{htmlCode.length > 0 && enableArtifacts && (
117+
<FullScreen className="no-dark html" right={70}>
109118
<ArtifactShareButton
110-
style={{ position: "absolute", right: 10, top: 10 }}
119+
style={{ position: "absolute", right: 20, top: 10 }}
111120
getCode={() => htmlCode}
112121
/>
113122
<HTMLPreview

app/components/ui-lib.module.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@
309309
}
310310

311311
&-content {
312+
min-width: 300px;
312313
.list {
313314
max-height: 90vh;
314315
overflow-x: hidden;

app/components/ui-lib.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function ListItem(props: {
5555
children?: JSX.Element | JSX.Element[];
5656
icon?: JSX.Element;
5757
className?: string;
58-
onClick?: (event: MouseEvent) => void;
58+
onClick?: (e: MouseEvent) => void;
5959
vertical?: boolean;
6060
}) {
6161
return (
@@ -470,15 +470,35 @@ export function Selector<T>(props: {
470470
onClose?: () => void;
471471
multiple?: boolean;
472472
}) {
473+
const [selectedValues, setSelectedValues] = useState<T[]>(
474+
Array.isArray(props.defaultSelectedValue)
475+
? props.defaultSelectedValue
476+
: props.defaultSelectedValue !== undefined
477+
? [props.defaultSelectedValue]
478+
: [],
479+
);
480+
481+
const handleSelection = (e: MouseEvent, value: T) => {
482+
if (props.multiple) {
483+
e.stopPropagation();
484+
const newSelectedValues = selectedValues.includes(value)
485+
? selectedValues.filter((v) => v !== value)
486+
: [...selectedValues, value];
487+
setSelectedValues(newSelectedValues);
488+
props.onSelection?.(newSelectedValues);
489+
} else {
490+
setSelectedValues([value]);
491+
props.onSelection?.([value]);
492+
props.onClose?.();
493+
}
494+
};
495+
473496
return (
474497
<div className={styles["selector"]} onClick={() => props.onClose?.()}>
475498
<div className={styles["selector-content"]}>
476499
<List>
477500
{props.items.map((item, i) => {
478-
const selected = props.multiple
479-
? // @ts-ignore
480-
props.defaultSelectedValue?.includes(item.value)
481-
: props.defaultSelectedValue === item.value;
501+
const selected = selectedValues.includes(item.value);
482502
return (
483503
<ListItem
484504
className={`${styles["selector-item"]} ${
@@ -487,11 +507,11 @@ export function Selector<T>(props: {
487507
key={i}
488508
title={item.title}
489509
subTitle={item.subTitle}
490-
onClick={(event) => {
491-
event.stopPropagation();
492-
if (!item.disable) {
493-
props.onSelection?.([item.value]);
494-
props.onClose?.();
510+
onClick={(e) => {
511+
if (item.disable) {
512+
e.stopPropagation();
513+
} else {
514+
handleSelection(e, item.value);
495515
}
496516
}}
497517
>
@@ -515,7 +535,6 @@ export function Selector<T>(props: {
515535
</div>
516536
);
517537
}
518-
519538
export function FullScreen(props: any) {
520539
const { children, right = 10, top = 10, ...rest } = props;
521540
const ref = useRef<HTMLDivElement>();

0 commit comments

Comments
 (0)