Skip to content

Commit

Permalink
Improve Plugin docs 📘
Browse files Browse the repository at this point in the history
  • Loading branch information
Kooshaba committed Mar 11, 2024
1 parent 80bf543 commit 018fe4c
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 80 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Sky Strife is broken into several sub-packages. Here are the packages that are e

```sh copy
git clone https://github.com/latticexyz/skystrife-public.git
cd skystrife
cd skystrife-public
pnpm install
cd packages/contracts
pnpm build
Expand Down
160 changes: 160 additions & 0 deletions packages/client/src/app/ui/PluginManager/AddPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useMUD } from "../../../useMUD";
import { Button } from "../Theme/SkyStrife/Button";
import { Input } from "../Theme/SkyStrife/Input";
import { Body } from "../Theme/SkyStrife/Typography";
import { ChainIcon } from "./ChainIcon";
import { PLUGIN_DOCS_URL, convertTsPluginToJs } from "./PluginManager";
import { TerminalIcon } from "./TerminalIcon";
import { PluginData } from "./types";
import { useState } from "react";
import { twMerge } from "tailwind-merge";

function ImportPluginForm({
setPlugin,
setManagerState,
}: {
setPlugin: (pluginKey: string, data: Partial<PluginData>) => void;
setManagerState: (state: "open" | "adding") => void;
}) {
const {
networkLayer: {
utils: { sendAnalyticsEvent },
},
} = useMUD();

const [newPluginName, setNewPluginName] = useState("");
const [pluginUrl, setPluginUrl] = useState("");
const [newPluginError, setNewPluginError] = useState<string | null>(null);

return (
<div className="flex flex-col gap-y-2">
<Input label="Plugin Name" value={newPluginName} setValue={setNewPluginName} />

<Input label="Plugin URL" value={pluginUrl} setValue={setPluginUrl} />

<div className="h-0" />

<Button
buttonType="primary"
className="w-full"
disabled={!newPluginName || !pluginUrl}
onClick={() => {
if (!newPluginName) {
setNewPluginError("Plugin name is required");
return;
}

if (!pluginUrl) {
setNewPluginError("Plugin URL is required");
return;
}

fetch(pluginUrl)
.then((res) => res.text())
.then((code) => {
try {
const jsCode = convertTsPluginToJs(code);
setPlugin(newPluginName, { code: jsCode, source: "remote" });
setNewPluginName("");
setPluginUrl("");
setNewPluginError(null);
setManagerState("open");

sendAnalyticsEvent("plugin-add", {
name: newPluginName,
source: "remote",
});
} catch (e) {
setNewPluginError((e as any).error?.message);
}
})
.catch((e) => {
setNewPluginError(e.message);
});
}}
>
Add
</Button>

{newPluginError && <div className="text-red-600 w-full text-center">{newPluginError}</div>}
</div>
);
}

export function AddPlugin({
setPlugin,
setManagerState,
}: {
setPlugin: (pluginKey: string, data: Partial<PluginData>) => void;
setManagerState: (state: "open" | "adding") => void;
}) {
const [addMethod, setAddMethod] = useState<"dev" | "remote">("remote");
const [chosenAddMethod, setChosenAddMethod] = useState<"dev" | "remote" | null>(null);

const chooseAddMethodForm = (
<>
<div
onClick={() => {
setAddMethod("remote");
}}
className={twMerge(
"flex flex-col grow items-center justify-around w-full h-[128px] rounded-md",
"border border-[#DDDAD0] bg-white hover:border-[#1A6CBC] hover:bg-blue-300/30 cursor-pointer",
addMethod === "remote" && "border-[#1A6CBC] bg-blue-300/30"
)}
>
<div className="flex flex-col text-center items-center p-6">
<div className="flex items-center gap-x-1">
<ChainIcon />
<span>Import a plugin</span>
</div>
<div className="h-2" />
<Body style={{ fontSize: "12px" }}>Import a plugin from an external URL.</Body>
</div>
</div>

<div
onClick={() => {
setAddMethod("dev");
}}
className={twMerge(
"flex flex-col grow items-center justify-around w-full h-[128px] rounded-md",
"border border-[#DDDAD0] bg-white hover:border-[#1A6CBC] hover:bg-blue-300/30 cursor-pointer",
addMethod === "dev" && "border-[#1A6CBC] bg-blue-300/30"
)}
>
<div className="flex flex-col text-center items-center p-6">
<div className="flex items-center gap-x-1">
<TerminalIcon />
<span>Create plugins locally</span>
</div>
<div className="h-2" />
<Body style={{ fontSize: "12px" }}>Run the Sky Strife plugin dev server on your local machine.</Body>
</div>
</div>

<div className="h-0" />

<Button
buttonType="primary"
disabled={!addMethod}
onClick={() => {
if (addMethod === "dev") {
window.open(PLUGIN_DOCS_URL, "_blank");
return;
}

setChosenAddMethod(addMethod);
}}
>
{addMethod === "dev" ? "Visit docs" : "Import"}
</Button>
</>
);

const addRemoteForm = <ImportPluginForm setPlugin={setPlugin} setManagerState={setManagerState} />;
const addDevForm = <div>Not implemented</div>;
const addForm = addMethod === "remote" ? addRemoteForm : addDevForm;

return <div className="flex flex-col gap-y-3 grow pb-4">{chosenAddMethod ? addForm : chooseAddMethodForm}</div>;
}
13 changes: 13 additions & 0 deletions packages/client/src/app/ui/PluginManager/ChainIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function ChainIcon() {
return (
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.29335 5.79202C9.72657 5.99886 10.1038 6.30664 10.3934 6.68951C10.683 7.07238 10.8765 7.51916 10.9576 7.99232C11.0388 8.46548 11.0052 8.9512 10.8596 9.40867C10.7141 9.86614 10.4609 10.282 10.1214 10.6214L7.12135 13.6213C6.55874 14.184 5.79567 14.5 5.00002 14.5C4.20436 14.5 3.4413 14.184 2.87869 13.6213C2.31607 13.0587 2 12.2957 2 11.5C2 10.7044 2.31607 9.9413 2.87869 9.37868L4.05002 8.20735M12.95 7.79268L14.1213 6.62135C14.684 6.05874 15 5.29567 15 4.50002C15 3.70436 14.684 2.9413 14.1213 2.37869C13.5587 1.81607 12.7957 1.5 12 1.5C11.2044 1.5 10.4413 1.81607 9.87868 2.37869L6.87868 5.37868C6.53911 5.71802 6.28593 6.13389 6.14041 6.59137C5.99488 7.04884 5.96127 7.53456 6.0424 8.00772C6.12352 8.48087 6.31701 8.92765 6.60661 9.31053C6.89621 9.6934 7.27347 10.0012 7.70669 10.208"
stroke="#25241D"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
20 changes: 19 additions & 1 deletion packages/client/src/app/ui/PluginManager/PluginItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMUD } from "../../../useMUD";
import { Button } from "../Theme/SkyStrife/Button";
import { OverlineSmall } from "../Theme/SkyStrife/Typography";
import { TrashIcon } from "./TrashIcon";
Expand All @@ -16,6 +17,12 @@ export function PluginItem({
deletePlugin: () => void;
plugins: Plugins;
}) {
const {
networkLayer: {
utils: { sendAnalyticsEvent },
},
} = useMUD();

const { active, code: pluginCode } = pluginData;

return (
Expand All @@ -38,7 +45,13 @@ export function PluginItem({
<div
className="cursor-pointer flex items-center justify-center"
onClick={() => {
confirm(`Are you sure you want to delete plugin: ${pluginKey}?`) && deletePlugin();
const confirmDelete = confirm(`Are you sure you want to delete plugin: ${pluginKey}?`);
if (confirmDelete) {
deletePlugin();
sendAnalyticsEvent("plugin-delete", {
...pluginData,
});
}
}}
>
<TrashIcon />
Expand All @@ -62,6 +75,11 @@ export function PluginItem({
setPlugin(pluginKey, {
active: !active,
});

sendAnalyticsEvent("plugin-toggle", {
...pluginData,
active: !active,
});
}}
>
{active ? "Stop" : "Run"}
Expand Down
109 changes: 33 additions & 76 deletions packages/client/src/app/ui/PluginManager/PluginManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import uiExampleCode from "plugins/dev/uiExample?raw";
import playerDetailsCode from "plugins/dev/playerDetails?raw";
import { DocsIcon } from "./DocsIcon";
import { CrossIcon } from "../Theme/CrossIcon";
import { useMUD } from "../../../useMUD";
import { AddPlugin } from "./AddPlugin";

const PLUGIN_DEV_SERVER_URL = "http://localhost:1993";
export const PLUGIN_DOCS_URL = "https://github.com/latticexyz/skystrife-public/tree/main/packages/plugins/README.md";

function convertTsPluginToJs(tsCode: string) {
export function convertTsPluginToJs(tsCode: string) {
const jsOutput = transpileModule(tsCode.replace(/import.*?;/g, ""), {
compilerOptions: { module: ModuleKind.ES2015, target: ScriptTarget.ES2015 },
});
Expand All @@ -34,9 +37,7 @@ export function PluginManager() {
});
const [showManager, setShowManager] = useState(false);
const [managerState, setManagerState] = useState<"open" | "adding">("open");
const [newPluginName, setNewPluginName] = useState("");
const [pluginUrl, setPluginUrl] = useState("");
const [newPluginError, setNewPluginError] = useState<string | null>(null);

const [devServerConnected, setDevServerConnected] = useState(false);
const [lastRefreshedPlugin, setLastRefreshedPlugin] = useState<{
pluginKey: string;
Expand Down Expand Up @@ -101,26 +102,30 @@ export function PluginManager() {
}, []);

useEffect(() => {
const ws = new WebSocket("ws://localhost:1993");
ws.onopen = function () {
console.log("Plugin Dev Server connected.");
setDevServerConnected(true);
};

ws.onmessage = function (e) {
const message = JSON.parse(e.data);
console.log(`File changed: ${message.path}, Event Type: ${message.eventType}`);
if (message.path) refreshPlugin(message.path);
};

ws.onerror = function (e) {
console.error("WebSocket Error: ", e);
};

ws.onclose = function (e) {
console.log("WebSocket connection closed", e);
setDevServerConnected(false);
};
try {
const ws = new WebSocket("ws://localhost:1993");
ws.onopen = function () {
console.log("Plugin Dev Server connected.");
setDevServerConnected(true);
};

ws.onmessage = function (e) {
const message = JSON.parse(e.data);
console.log(`File changed: ${message.path}, Event Type: ${message.eventType}`);
if (message.path) refreshPlugin(message.path);
};

ws.onerror = function (e) {
console.error("WebSocket Error: ", e);
};

ws.onclose = function (e) {
console.log("WebSocket connection closed", e);
setDevServerConnected(false);
};
} catch (e) {
console.warn("No plugin dev server connected.");
}
}, [refreshPlugin]);

const pluginList = useMemo(() => {
Expand All @@ -135,12 +140,12 @@ export function PluginManager() {
}}
className="text-ss-text-light text-center grow flex flex-col justify-around"
>
<p>
<div>
<span>No plugins found</span>
<div onClick={() => setManagerState("adding")}>
<Link>Add your first plugin</Link>
</div>
</p>
</div>
</div>

<div className="h-6" />
Expand Down Expand Up @@ -209,9 +214,6 @@ export function PluginManager() {
<Button
buttonType="tertiary"
onClick={() => {
setNewPluginName("");
setPluginUrl("");
setNewPluginError(null);
setManagerState("open");
}}
>
Expand Down Expand Up @@ -247,52 +249,7 @@ export function PluginManager() {

<div className="flex flex-col gap-y-2 grow">
{managerState === "open" && pluginList}
{managerState === "adding" && (
<div className="flex flex-col gap-y-2">
<Input label="Plugin Name" value={newPluginName} setValue={setNewPluginName} />

<Input label="Plugin URL" value={pluginUrl} setValue={setPluginUrl} />

<Button
buttonType="primary"
className="w-full"
disabled={!newPluginName || !pluginUrl}
onClick={() => {
if (!newPluginName) {
setNewPluginError("Plugin name is required");
return;
}

if (!pluginUrl) {
setNewPluginError("Plugin URL is required");
return;
}

fetch(pluginUrl)
.then((res) => res.text())
.then((code) => {
try {
const jsCode = convertTsPluginToJs(code);
setPlugin(newPluginName, { code: jsCode, source: "remote" });
setNewPluginName("");
setPluginUrl("");
setNewPluginError(null);
setManagerState("open");
} catch (e) {
setNewPluginError((e as any).error?.message);
}
})
.catch((e) => {
setNewPluginError(e.message);
});
}}
>
Add
</Button>

{newPluginError && <div className="text-red-600 w-full text-center">{newPluginError}</div>}
</div>
)}
{managerState === "adding" && <AddPlugin setPlugin={setPlugin} setManagerState={setManagerState} />}
</div>

<div className="w-full flex gap-x-2">
Expand All @@ -303,7 +260,7 @@ export function PluginManager() {
</div>
</Button>

<a className="grow" href="https://github.com/latticexyz/skystrife-public/tree/main/packages/plugins">
<a className="grow" href={PLUGIN_DOCS_URL}>
<Button buttonType="tertiary" className="p-1 px-2 w-full">
<div className="w-full flex items-center gap-x-1">
<DocsIcon /> <span>Docs</span>
Expand Down
Loading

0 comments on commit 018fe4c

Please sign in to comment.