Rewrites tgui kitchensink into something usable (#94400)

This commit is contained in:
Jeremiah
2025-12-11 18:28:55 +01:00
committed by GitHub
parent 4381d9d0b4
commit a1313809ae
9 changed files with 147 additions and 204 deletions
+18
View File
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "tgui-workspace",
@@ -49,6 +50,7 @@
"marked-smartypants": "^1.1.9",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-json-tree": "^0.20.0",
"tgui-core": "^5.3.1",
"tgui-dev-server": "workspace:*",
},
@@ -221,6 +223,8 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/lodash": ["@types/lodash@4.17.21", "", {}, "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ=="],
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
"@types/node": ["@types/node@24.0.12", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-LtOrbvDf5ndC9Xi+4QZjVL0woFymF/xSTKZKPgrrl7H7XoeDvnD+E2IclKVDyaK9UM756W/3BXqSU+JEHopA9g=="],
@@ -353,10 +357,14 @@
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
@@ -605,6 +613,8 @@
"ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="],
"is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
@@ -655,6 +665,8 @@
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lru-cache": ["lru-cache@11.1.0", "", {}, "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A=="],
@@ -821,8 +833,12 @@
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
"react-base16-styling": ["react-base16-styling@0.10.0", "", { "dependencies": { "@types/lodash": "^4.17.0", "color": "^4.2.3", "csstype": "^3.1.3", "lodash-es": "^4.17.21" } }, "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg=="],
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-json-tree": ["react-json-tree@0.20.0", "", { "dependencies": { "@types/lodash": "^4.17.15", "react-base16-styling": "^0.10.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-h+f9fUNAxzBx1rbrgUF7+zSWKGHDtt2VPYLErIuB0JyKGnWgFMM21ksqQyb3EXwXNnoMW2rdE5kuAaubgGOx2Q=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
@@ -919,6 +935,8 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
"sirv": ["sirv@2.0.4", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
+13 -13
View File
@@ -1,18 +1,6 @@
{
"private": true,
"name": "tgui-workspace",
"version": "6.0.0",
"packageManager": "bun@1.2.16",
"workspaces": [
"packages/*"
],
"scripts": {
"tgui:analyze": "rspack --analyze",
"tgui:build": "rspack build",
"tgui:dev": "bun --smol packages/tgui-dev-server/index.ts",
"tgui:test": "bun test",
"tgui:tsc": "tsc"
},
"devDependencies": {
"@happy-dom/global-registrator": "^17.4.7",
"@rspack/cli": "^1.4.5",
@@ -26,5 +14,17 @@
"sass-embedded": "^1.89.2",
"sass-loader": "^16.0.5",
"typescript": "^5.8.3"
}
},
"packageManager": "bun@1.2.16",
"private": true,
"scripts": {
"tgui:analyze": "rspack --analyze",
"tgui:build": "rspack build",
"tgui:dev": "bun --smol packages/tgui-dev-server/index.ts",
"tgui:test": "bun test",
"tgui:tsc": "tsc"
},
"workspaces": [
"packages/*"
]
}
+31
View File
@@ -0,0 +1,31 @@
/**
* TGUI Base16 Theme
* Based on TGUI color scheme defined in css variables
* @see https://github.com/chriskempson/base16
* @see https://github.com/tgstation/tgui-core/blob/main/styles/vars-colors.scss
*/
export const tgui16 = {
scheme: 'ntos16',
author: 'Aleksej Komarov et al., (https://github.com/tgstation)',
// Backgrounds / UI surfaces
base00: '#1A1A1A',
base01: '#313234',
base02: '#3a3a36',
base03: '#6b6b5b',
// Foreground / text
base04: '#a59f85',
base05: '#f8f8f2', // default text (near white)
base06: '#f5f4f1',
base07: '#f9f8f5',
// Accent colors
base08: '#f92672',
base09: '#fd971f',
base0A: '#f4bf75',
base0B: '#5BA626', // --color-good
base0C: '#a1efe4',
base0D: '#47739E', // blue primary
base0E: '#ae81ff',
base0F: '#cc6633',
} as const;
+84 -39
View File
@@ -4,62 +4,107 @@
* @license MIT
*/
import { useState } from 'react';
import { Section, Stack, Tabs } from 'tgui-core/components';
import { JSONTree } from 'react-json-tree';
import { Divider, NoticeBox, Section, Stack, Tabs } from 'tgui-core/components';
import { useBackend } from '../backend';
import { tgui16 } from '../constants/theme';
import { Pane, Window } from '../layouts';
const r = import.meta.webpackContext('../', {
recursive: false,
include: /\.stories\.tsx$/,
});
type Props = {
panel?: boolean;
};
/**
* @returns {{
* meta: {
* title: string,
* render: () => any,
* },
* }[]}
*/
function getStories() {
return r.keys().map((path) => r(path));
enum Tab {
Config = 'config',
Data = 'data',
Shared = 'shared',
Chunks = 'outgoingPayloadQueues',
Components = 'components',
}
export function KitchenSink(props) {
const tabs = [
{ name: 'Config', value: Tab.Config },
{ name: 'Data', value: Tab.Data },
{ name: 'Shared', value: Tab.Shared },
{ name: 'Chunks', value: Tab.Chunks },
] as const;
export function KitchenSink(props: Props) {
const { panel } = props;
const [pageIndex, setPageIndex] = useState(0);
const stories = getStories();
if (stories.length === 0) {
return <div>Loading stories...</div>;
}
const [activeTab, setActiveTab] = useState(Tab.Config);
const story = stories[pageIndex];
const Layout = panel ? Pane : Window;
return (
<Layout title="Kitchen Sink" width={600} height={500}>
<Layout.Content>
<Stack fill>
<Stack.Item>
<Section fill fitted>
<Tabs vertical>
{stories.map((story, i) => (
<Tabs.Tab
key={i}
color="transparent"
selected={i === pageIndex}
onClick={() => setPageIndex(i)}
>
{story.meta.title}
</Tabs.Tab>
))}
</Tabs>
</Section>
<Stack.Item grow>
<Tabs vertical>
{tabs.map((tab) => (
<Tabs.Tab
key={tab.name}
className="candystripe"
selected={activeTab === tab.value}
onClick={() => setActiveTab(tab.value)}
>
{tab.name}
</Tabs.Tab>
))}
<Divider />
<Tabs.Tab
selected={activeTab === Tab.Components}
onClick={() => setActiveTab(Tab.Components)}
>
Components
</Tabs.Tab>
</Tabs>
</Stack.Item>
<Stack.Item grow={4}>
{activeTab === Tab.Components ? (
<ComponentsPage />
) : (
<TreePage tab={activeTab} />
)}
</Stack.Item>
<Stack.Item grow>{story.meta.render()}</Stack.Item>
</Stack>
</Layout.Content>
</Layout>
);
}
function ComponentsPage() {
return (
<Section fill>
<NoticeBox info>All component stories have been moved.</NoticeBox>
View them here{' '}
<a href="https://tgstation.github.io/tgui-core">
https://tgstation.github.io/tgui-core
</a>
</Section>
);
}
type TreeProps = {
tab: Tab;
};
function TreePage(props: TreeProps) {
const { tab } = props;
const backend = useBackend();
const inView = backend[tab];
return (
<Section
fill
scrollable
title={`${backend.config.interface.name ?? 'TGUI'} data`}
>
<div style={{ border: 'thin solid var(--color-base)' }}>
<JSONTree data={inView} theme={tgui16} />
</div>
</Section>
);
}
+1
View File
@@ -14,6 +14,7 @@
"marked-smartypants": "^1.1.9",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-json-tree": "^0.20.0",
"tgui-core": "^5.3.1",
"tgui-dev-server": "workspace:*"
},
@@ -1,63 +0,0 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { useState } from 'react';
import { Button, ByondUi, Section, TextArea } from 'tgui-core/components';
import { logger } from '../logging';
export const meta = {
title: 'ByondUi',
render: () => <Story />,
};
function Story() {
const [code, setCode] = useState(
`Byond.winset('${Byond.windowId}', {\n 'is-visible': true,\n})`,
);
return (
<>
<Section title="Button">
<ByondUi
params={{
type: 'button',
text: 'Button',
}}
/>
</Section>
<Section
title="Make BYOND calls"
buttons={
<Button
icon="chevron-right"
onClick={() =>
setTimeout(() => {
try {
const result = new Function(`return (${code})`)();
if (result?.then) {
logger.log('Promise');
result.then(logger.log);
} else {
logger.log(result);
}
} catch (err) {
logger.log(err);
}
})
}
>
Evaluate
</Button>
}
>
<TextArea fluid height="10em" onChange={setCode}>
{code}
</TextArea>
</Section>
</>
);
}
@@ -1,42 +0,0 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { storage } from 'common/storage';
import { Button, LabeledList, NoticeBox, Section } from 'tgui-core/components';
export const meta = {
title: 'Storage',
render: () => <Story />,
};
const Story = (props) => {
if (!window.localStorage) {
return <NoticeBox>Local storage is not available.</NoticeBox>;
}
return (
<Section
title="Local Storage"
buttons={
<Button
icon="recycle"
onClick={() => {
localStorage.clear();
storage.clear();
}}
>
Clear
</Button>
}
>
<LabeledList>
<LabeledList.Item label="Keys in use">
{localStorage.length}
</LabeledList.Item>
</LabeledList>
</Section>
);
};
@@ -1,28 +0,0 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { Input, LabeledList, Section } from 'tgui-core/components';
import { useLocalState } from '../backend';
export const meta = {
title: 'Themes',
render: () => <Story />,
};
function Story() {
const [theme, setTheme] = useLocalState('kitchenSinkTheme', '');
return (
<Section>
<LabeledList>
<LabeledList.Item label="Use theme">
<Input placeholder="theme_name" value={theme} onChange={setTheme} />
</LabeledList.Item>
</LabeledList>
</Section>
);
}
-19
View File
@@ -1,19 +0,0 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { Box } from 'tgui-core/components';
export function BoxWithSampleText(props) {
return (
<Box {...props}>
<Box italic>Jackdaws love my big sphinx of quartz.</Box>
<Box mt={1} bold>
The wide electrification of the southern provinces will give a powerful
impetus to the growth of agriculture.
</Box>
</Box>
);
}