Skip to content

Commit 4f5652f

Browse files
committed
Add app server developer settings
1 parent 5af6976 commit 4f5652f

File tree

8 files changed

+424
-54
lines changed

8 files changed

+424
-54
lines changed

npm-packages/shared-utils/src/types/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ export type AppConfig = {
139139
displayName?: string;
140140
description?: string;
141141
materialIcon?: string;
142+
license?: string;
143+
repository?: string;
142144
appType?: AppTypeEnum;
143145
fileTypes?: string[];
144146
thumbnail?: string;

web/components/interface/navigation/nav.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import packageJson from "../../../../package.json";
2323
import readme from "../../../../README.md";
2424

2525
const appInfo: AppInfoModalContent = {
26+
id: "pulse-editor",
2627
name: "Pulse Editor",
2728
version: packageJson.version,
2829
author: "ClayPulse",

web/components/marketplace/app/app-preview-card.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PlatformEnum } from "@/lib/enums";
2+
import { useAppInfo } from "@/lib/hooks/use-app-info";
23
import useExtensionManager from "@/lib/hooks/use-extension-manager";
34
import { getRemoteClientBaseURL } from "@/lib/module-federation/remote";
45
import {
@@ -50,6 +51,14 @@ export default function AppPreviewCard({
5051
attributes?: DraggableAttributes;
5152
listeners?: SyntheticListenerMap;
5253
}) {
54+
const {
55+
disableExtension,
56+
enableExtension,
57+
uninstallExtension,
58+
installExtension,
59+
} = useExtensionManager();
60+
const { openAppInfoModal } = useAppInfo();
61+
5362
const [isEnabled, setIsEnabled] = useState(false);
5463
const [isLoaded, setIsLoaded] = useState(false);
5564
const [isInstalled, setIsInstalled] = useState(false);
@@ -59,12 +68,6 @@ export default function AppPreviewCard({
5968
y: 0,
6069
isOpen: false,
6170
});
62-
const {
63-
disableExtension,
64-
enableExtension,
65-
uninstallExtension,
66-
installExtension,
67-
} = useExtensionManager();
6871

6972
const editorContext = useContext(EditorContext);
7073

@@ -332,7 +335,27 @@ export default function AppPreviewCard({
332335
Use
333336
</Button>
334337
)}
335-
<Button color="secondary" size="sm">
338+
<Button
339+
color="secondary"
340+
size="sm"
341+
onPress={() => {
342+
openAppInfoModal({
343+
id: extension.config.id,
344+
name: extension.config.displayName ?? extension.config.id,
345+
version: extension.config.version,
346+
author: extension.config.author,
347+
license: extension.config.license,
348+
url: extension.config.repository,
349+
readme: extension.config.repository
350+
? extension.config.repository + "/README.md"
351+
: undefined,
352+
});
353+
editorContext?.setEditorStates((prev) => ({
354+
...prev,
355+
isMarketplaceOpen: false,
356+
}));
357+
}}
358+
>
336359
Details
337360
</Button>
338361
{!isInstalled ? (

web/components/misc/env-input.tsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { fetchAPI } from "@/lib/pulse-editor-website/backend";
2+
import { addToast, Button, Input } from "@heroui/react";
3+
import { useState } from "react";
4+
import Icon from "./icon";
5+
6+
export default function EnvInput({
7+
appId,
8+
env,
9+
onUpdated,
10+
}: {
11+
appId: string;
12+
env: { key: string; value: string; isSecret: boolean };
13+
onUpdated: () => void;
14+
}) {
15+
const [isEditing, setIsEditing] = useState<boolean>(false);
16+
const [editedValue, setEditedValue] = useState<string>(env.value);
17+
const [isSecret, setIsSecret] = useState<boolean>(env.isSecret);
18+
19+
return (
20+
<div className="flex items-center gap-x-1" key={env.key}>
21+
<div className="w-1/3 font-mono text-sm break-all">{env.key}</div>
22+
<div className="w-2/3 font-mono text-sm break-all">
23+
{isEditing ? (
24+
<Input
25+
size="sm"
26+
value={editedValue}
27+
onValueChange={setEditedValue}
28+
type={isSecret ? "password" : "text"}
29+
/>
30+
) : (
31+
<p>{env.value}</p>
32+
)}
33+
</div>
34+
35+
{isEditing ? (
36+
<Button
37+
isIconOnly
38+
variant="light"
39+
onPress={async () => {
40+
addToast({
41+
title: "Updating Variable",
42+
description: `Updating environment variable ${env.key}.`,
43+
});
44+
45+
await fetchAPI("/api/app/settings/env/set", {
46+
method: "POST",
47+
body: JSON.stringify({
48+
appId: appId,
49+
key: env.key,
50+
value: editedValue,
51+
isSecret: isSecret,
52+
}),
53+
headers: {
54+
"Content-Type": "application/json",
55+
},
56+
});
57+
58+
onUpdated();
59+
setIsEditing((prev) => false);
60+
61+
addToast({
62+
title: "Variable Updated",
63+
description: `Environment variable ${env.key} updated successfully.`,
64+
color: "success",
65+
});
66+
}}
67+
>
68+
<Icon name="check" />
69+
</Button>
70+
) : (
71+
<Button
72+
isIconOnly
73+
variant="light"
74+
onPress={() => setIsEditing((prev) => true)}
75+
>
76+
<Icon name="edit" />
77+
</Button>
78+
)}
79+
80+
{isEditing ? (
81+
<Button
82+
isIconOnly
83+
variant="light"
84+
onPress={() => setIsSecret((prev) => !prev)}
85+
>
86+
{isSecret ? <Icon name="lock" /> : <Icon name="lock_open" />}
87+
</Button>
88+
) : (
89+
<Button
90+
isIconOnly
91+
variant="light"
92+
onPress={async () => {
93+
addToast({
94+
title: "Deleting Variable",
95+
description: `Deleting environment variable ${env.key}.`,
96+
});
97+
await fetchAPI("/api/app/settings/env/delete", {
98+
method: "DELETE",
99+
body: JSON.stringify({
100+
appId: appId,
101+
key: env.key,
102+
}),
103+
headers: {
104+
"Content-Type": "application/json",
105+
},
106+
});
107+
onUpdated();
108+
109+
addToast({
110+
title: "Variable Deleted",
111+
description: `Environment variable ${env.key} deleted successfully.`,
112+
color: "success",
113+
});
114+
}}
115+
>
116+
<Icon name="delete" className="text-danger!" />
117+
</Button>
118+
)}
119+
</div>
120+
);
121+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Markdown from "react-markdown";
2+
import rehypeRaw from "rehype-raw";
3+
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
4+
import remarkGfm from "remark-gfm";
5+
import remarkAlert from "remark-github-blockquote-alert";
6+
import "remark-github-blockquote-alert/alert.css";
7+
8+
export default function MarkdownRender({ content }: { content: string }) {
9+
return (
10+
<div className="markdown-styles">
11+
<Markdown
12+
remarkPlugins={[remarkGfm, remarkAlert]}
13+
rehypePlugins={[rehypeRaw, () => rehypeSanitize(customSchema)]}
14+
remarkRehypeOptions={{}}
15+
>
16+
{content}
17+
</Markdown>
18+
</div>
19+
);
20+
}
21+
22+
const customSchema = {
23+
...defaultSchema,
24+
tagNames: [
25+
...(defaultSchema.tagNames ?? []),
26+
"div",
27+
"blockquote",
28+
"svg",
29+
"path",
30+
"img",
31+
],
32+
attributes: {
33+
...defaultSchema.attributes,
34+
div: ["className", "dir", "align"],
35+
blockquote: ["className", "dir"],
36+
svg: ["className", "viewBox", "width", "height", "aria-hidden"],
37+
p: ["className"],
38+
path: ["d"],
39+
img: ["src", "alt"],
40+
},
41+
};

0 commit comments

Comments
 (0)