mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
* Modernizes TGUI (#4219) * Ports the first two PRs. CBT works local * tchussi oops * linttobuild * Update Dockerfile for CBT (#56175) Follow-up to fix the failing Docker CI on master. - Reorganize the entire Dockerfile to be more readable - Inline the tgstation/byond Dockerfile into our own, so we can change the base distro at will - Also allows us to trash the dependencies.sh<->Dockerfile hack - Use 32-bit libs on a 64-bit distro so that we can download and run recent 64-bit Node binaries - Call tools/build/build rather than DreamMaker directly * TGUI 43 plus hotfix 1 * https://github.com/tgstation/tgstation/pull/58701 * https://github.com/tgstation/tgstation/pull/56223 * https://github.com/tgstation/tgstation/pull/56229 * https://github.com/tgstation/tgstation/pull/56713 * https://github.com/tgstation/tgstation/pull/56797 * https://github.com/tgstation/tgstation/pull/57081/ * https://github.com/tgstation/tgstation/pull/57154 * https://github.com/tgstation/tgstation/pull/57251 * https://github.com/tgstation/tgstation/pull/56269 * https://github.com/tgstation/tgstation/pull/57277 * https://github.com/tgstation/tgstation/pull/57287 * https://github.com/tgstation/tgstation/pull/57326 * QF * GimmeHopeUpdateExtra_Pr_Labels * Flush queue in preloading part of tgui_panel (#57432) * Flush queue in preloading part of tgui_panel * Update tgui_panel.dm * Compiles * https://github.com/tgstation/tgstation/pull/57461 * tgui: Upgrade to Yarn 2.4.1 and TypeScript 4.2 (#57465) * Fix tgui reloading (#57499) * Add note about rust-g for building on Linux (#57622) This PR adds a minor note about needing to compile rust-g to Linux building instructions. I'm not sure if this is the appropriate place for it, but I don't see a better place to put it. * Fixes build script failing properly on windows. (#57623) * Adds easy to extend custom svg font. (#57717) Just throw in svgs into tgui/packages/tgfont/icons directory and you can use them in tgui with tg- prefix. Co-authored-by: Style Mistake <stylemistake@gmail.com> Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> * Typescript fixups. (#57746) * Improve tgui routing to allow interface subdirectories (#57825) * Improve tgui routing to allow interface subdirectories * Reverse order of interface file resolution, to be consistent with common expectation (top-level .tsx, top-level .js, directory .tsx, directory.js) and the way Webpack checks * Fixes TGS compiles on windows. (#57834) Batch quotes strike again. * tgui: ESLint and VSCode settings improvements (#57905) Added column 80 rulers to all javascript and scss files. Added a "prettier" config to have sensible defaults for those who prefer to use it. Added RadarJS (fork of SonarQube's SonarJS). Launchable only via bin/tgui --lint-harder for now. Disabled ESLint rule for parens around arrow function arguments, because in TypeScript they're pretty much always required, and I don't want to replace it throughout the codebase. Removed unused javascript extensions from tooling (jsx, mjs). * https://github.com/tgstation/tgstation/pull/57931 * https://github.com/tgstation/tgstation/pull/58061 * https://github.com/tgstation/tgstation/pull/58081 * https://github.com/tgstation/tgstation/pull/58212 * https://github.com/tgstation/tgstation/pull/58215 * https://github.com/tgstation/tgstation/pull/58219 * Add "Except DM" build mode to build.js (#58245) * https://github.com/tgstation/tgstation/pull/58484 * FixBaconFabandPodLauncher * haharunlintmanuallyfirstkids * One last fab change then its perf * f5willwork * This May Do it * prune * Fix Docker build failing on a clean checkout (#56190) Follow-up to #56175 which turned out to be accidentally relying on Node already being downloaded. * Huh? Rebuild you better be a fluke * getinthere * Syncretize * Revert "Syncretize" This reverts commit 41749b68868d2af1b82de9ba6de39cf8052dd1ee. * zzzz * quick path change * another quick * Correct Dockerfile, bloats it with GCC lol. * small * back2oldfile I HATE DOCKER I HATE EXTOOLS * firstthingnotdonelol * yuabzn * v * reverttoclassic * Difffffffffff * prec1s * ccc * ok * bitte * jesus im dum * z * Nuke Window resizable and improve drag code (#56727) * comp Co-authored-by: Tad Hardesty <tad@platymuus.com> Co-authored-by: Aleksej Komarov <stylemistake@gmail.com> Co-authored-by: Cyprex <35031555+Cyprex@users.noreply.github.com> Co-authored-by: AnturK <AnturK@users.noreply.github.com> Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Co-authored-by: Mordent <62817778+mordent-goonstation@users.noreply.github.com> Co-authored-by: Jonathan Rubenstein <jrubcop@gmail.com> * thanks yarn! * Update Panel.js * compiles * restores exosuit * chmod * Update travis_config.txt * honk * honk2 * Update turdis.yml * Update ExosuitFabricator.js * Revert "Update turdis.yml" This reverts commit7517df56e5. * Revert "Update ExosuitFabricator.js" This reverts commitac31fa4543. * wtf is this * lol * Update tgui.dm Co-authored-by: Sinestia <40812746+Sinestia@users.noreply.github.com> Co-authored-by: Tad Hardesty <tad@platymuus.com> Co-authored-by: Aleksej Komarov <stylemistake@gmail.com> Co-authored-by: Cyprex <35031555+Cyprex@users.noreply.github.com> Co-authored-by: AnturK <AnturK@users.noreply.github.com> Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Co-authored-by: Mordent <62817778+mordent-goonstation@users.noreply.github.com> Co-authored-by: Jonathan Rubenstein <jrubcop@gmail.com> Co-authored-by: Jamie D <993128+JamieD1@users.noreply.github.com>
355 lines
11 KiB
Markdown
355 lines
11 KiB
Markdown
# Tutorial and Examples
|
|
|
|
## Main concepts
|
|
|
|
Basic tgui backend code consists of the following vars and procs:
|
|
|
|
```
|
|
ui_interact(mob/user, datum/tgui/ui)
|
|
ui_data(mob/user)
|
|
ui_act(action, params)
|
|
ui_state()
|
|
```
|
|
|
|
- `src_object` - The atom, which UI corresponds to in the game world.
|
|
- `ui_interact` - The proc where you will handle a request to open an
|
|
interface. Typically, you would update an existing UI (if it exists),
|
|
or set up a new instance of UI by calling the `SStgui` subsystem.
|
|
- `ui_data` - In this proc you munges whatever complex data your `src_object`
|
|
has into an associative list, which will then be sent to UI as a JSON string.
|
|
- `ui_act` - This proc receives user actions and reacts to them by changing
|
|
the state of the game.
|
|
- `ui_state` - This proc dictates under what conditions a UI may be interacted
|
|
with. This may be the standard checks that check if you are in range and
|
|
conscious, or more.
|
|
|
|
Once backend is complete, you create an new interface component on the
|
|
frontend, which will receive this JSON data and render it on screen.
|
|
|
|
States are easy to write and extend, and what make tgui interactions so
|
|
powerful. Because states can be overridden from other procs, you can build
|
|
powerful interactions for embedded objects or remote access.
|
|
|
|
## Using It
|
|
|
|
### Backend
|
|
|
|
Let's start with a very basic hello world.
|
|
|
|
```dm
|
|
/obj/machinery/my_machine/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "MyMachine")
|
|
ui.open()
|
|
```
|
|
|
|
This is the proc that defines our interface. There's a bit going on here, so
|
|
let's break it down. First, we override the ui_interact proc on our object. This
|
|
will be called by `interact` for you, which is in turn called by `attack_hand`
|
|
(or `attack_self` for items). `ui_interact` is also called to update a UI (hence
|
|
the `try_update_ui`), so we accept an existing UI to update.
|
|
|
|
Inside the `if(!ui)` block (which means we are creating a new UI), we choose our
|
|
template, title, and size; we can also set various options like `style` (for
|
|
themes), or autoupdate. These options will be elaborated on later (as will
|
|
`ui_state`s).
|
|
|
|
After `ui_interact`, we need to define `ui_data`. This just returns a list of
|
|
data for our object to use. Let's imagine our object has a few vars:
|
|
|
|
```dm
|
|
/obj/machinery/my_machine/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["health"] = health
|
|
data["color"] = color
|
|
|
|
return data
|
|
```
|
|
|
|
The `ui_data` proc is what people often find the hardest about tgui, but its
|
|
really quite simple! You just need to represent your object as numbers, strings,
|
|
and lists, instead of atoms and datums.
|
|
|
|
Finally, the `ui_act` proc is called by the interface whenever the user used an
|
|
input. The input's `action` and `params` are passed to the proc.
|
|
|
|
```dm
|
|
/obj/machinery/my_machine/ui_act(action, params)
|
|
if(..())
|
|
return
|
|
if(action == "change_color")
|
|
var/new_color = params["color"]
|
|
if(!(color in allowed_coors))
|
|
return FALSE
|
|
color = new_color
|
|
. = TRUE
|
|
update_icon()
|
|
```
|
|
|
|
The `..()` (parent call) is very important here, as it is how we check that the
|
|
user is allowed to use this interface (to avoid so-called href exploits). It is
|
|
also very important to clamp and sanitize all input here. Always assume the user
|
|
is attempting to exploit the game.
|
|
|
|
Also note the use of `. = TRUE` (or `FALSE`), which is used to notify the UI
|
|
that this input caused an update. This is especially important for UIs that do
|
|
not auto-update, as otherwise the user will never see their change.
|
|
|
|
### Frontend
|
|
|
|
Finally, let's make a React Component for your interface. This is also
|
|
a source of confusion for new developers. If you got some basic javascript
|
|
and HTML knowledge, that should ease the learning process, although we
|
|
recommend getting yourself introduced to
|
|
[React and JSX](https://reactjs.org/docs/introducing-jsx.html).
|
|
|
|
A React component is not a regular HTML template. A component is a
|
|
javascript function, which accepts a `props` object (that contains
|
|
properties passed to a component) and a `context` object (which is
|
|
necessary to access UI data) as arguments, and outputs an HTML-like
|
|
structure.
|
|
|
|
So let's create our first React Component. Create a file with a name
|
|
`SampleInterface.js` (or any other name you want), and copy this code
|
|
snippet (make sure component name matches the file name):
|
|
|
|
```jsx
|
|
import { useBackend } from '../backend';
|
|
import { Button, LabeledList, Section } from '../components';
|
|
import { Window } from '../layouts';
|
|
|
|
export const SampleInterface = (props, context) => {
|
|
const { act, data } = useBackend(context);
|
|
// Extract `health` and `color` variables from the `data` object.
|
|
const {
|
|
health,
|
|
color,
|
|
} = data;
|
|
return (
|
|
<Window>
|
|
<Window.Content scrollable>
|
|
<Section title="Health status">
|
|
<LabeledList>
|
|
<LabeledList.Item label="Health">
|
|
{health}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Color">
|
|
{color}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Button">
|
|
<Button
|
|
content="Dispatch a 'test' action"
|
|
onClick={() => act('test')} />
|
|
</LabeledList.Item>
|
|
</LabeledList>
|
|
</Section>
|
|
</Window.Content>
|
|
</Window>
|
|
);
|
|
};
|
|
```
|
|
|
|
Here are the key variables you get from a `useBackend(context)` function:
|
|
|
|
- `config` is part of core tgui. It contains meta-information about the
|
|
interface and who uses it, BYOND refs to various objects, and so forth.
|
|
You are rarely going to use it, but sometimes it can be used to your
|
|
advantage when doing complex UIs.
|
|
- `data` is the data returned from `ui_data` and `ui_static_data` procs in
|
|
your DM code. Pretty straight forward.
|
|
- Note, that javascript doesn't have associative arrays, so when you
|
|
return an associative list from DM, it will be available in `data` as a
|
|
javascript object instead of an array. You can use it normally
|
|
like so: `object.key`, so it's not a problem if it's representing a
|
|
data structure, but common `Array` methods, such as `array.map(item => ...)`,
|
|
are not available on it. Always prefer returning clean arrays from your
|
|
code, since arrays are easier to work with in javascript!
|
|
- `act(name, params)` is a function, which you can call to dispatch an action
|
|
to your DM code. It will be processed in `ui_act` proc. Action name will be
|
|
available in `params["action"]`, mixed together with the rest of parameters
|
|
you have passed in `params` object.
|
|
|
|
**Let's talk about the syntax.**
|
|
|
|
The syntax you're seeing here is called JSX - a very simple extension of the
|
|
core javascript language. It's basically a pre-processor, that takes
|
|
expressions that look like html, and turns them into function calls.
|
|
|
|
Take a look at this example:
|
|
|
|
```jsx
|
|
<div className={'color-' + status}>
|
|
You are in {status} condition!
|
|
</div>
|
|
```
|
|
|
|
After compiling the code above, this is what it becomes:
|
|
|
|
```js
|
|
createElement('div',
|
|
{ className: 'color-' + status },
|
|
'You are in ', status, ' condition!');
|
|
```
|
|
|
|
It is very important to remember, that JSX is just a javascript expression
|
|
made out of `createElement` function calls. Naturally, this allows doing
|
|
all sorts of stuff on these expressions, just like you would with anything
|
|
else in javascript.
|
|
|
|
Take a look at these examples:
|
|
|
|
**Render an element inside of another element if `showProgress` is true.**
|
|
|
|
This example uses the `&&` operator (the logical AND). It returns
|
|
the first operand if it evaluates to `false`, and returns the second operand
|
|
if it evaluates to `true`.
|
|
|
|
If `showProgress` is `true`, the whole expression evaluates
|
|
to a `<ProgressBar />` element. If `showProgress` is `false`, the whole
|
|
expression evaluates to `false`, and `false` is not rendered by React.
|
|
|
|
```jsx
|
|
<Box>
|
|
{showProgress && (
|
|
<ProgressBar value={progress} />
|
|
)}
|
|
</Box>
|
|
```
|
|
|
|
You can also use the `||` operator (the logical OR), which works the same way,
|
|
except it will return the second operand on `false` instead of `true`.
|
|
|
|
**Loop over the array to map every item to a corresponding React element.**
|
|
|
|
`Array.map()` is a method, that calls a function on every item in the array,
|
|
and builds a new array based on what was returned by that function.
|
|
|
|
```jsx
|
|
<LabeledList>
|
|
{items.map(item => (
|
|
<LabeledList.Item
|
|
key={item.id}
|
|
label={item.label}>
|
|
{item.content}
|
|
</LabeledList.Item>
|
|
))}
|
|
</LabeledList>
|
|
```
|
|
|
|
If you need more examples of what you can do with React, see the
|
|
[interface conversion guide](docs/converting-old-tgui-interfaces.md).
|
|
|
|
#### Splitting UIs into smaller, modular components
|
|
|
|
You interface will eventually get really, really big. The easiest thing
|
|
you can do in this situation, is divide and conquer. Grab a chunk of your
|
|
JSX code, and wrap it into a second, smaller React component:
|
|
|
|
```jsx
|
|
import { useBackend } from '../backend';
|
|
import { Button, LabeledList, Section } from '../components';
|
|
import { Window } from '../layouts';
|
|
|
|
export const SampleInterface = (props, context) => {
|
|
return (
|
|
<Window>
|
|
<Window.Content scrollable>
|
|
<HealthStatus user="Jerry" />
|
|
</Window.Content>
|
|
</Window>
|
|
);
|
|
};
|
|
|
|
const HealthStatus = (props, context) => {
|
|
const { act, data } = useBackend(context);
|
|
const {
|
|
user,
|
|
} = props;
|
|
const {
|
|
health,
|
|
color,
|
|
} = data;
|
|
return (
|
|
<Section title={"Health status of: " + user}>
|
|
<LabeledList>
|
|
<LabeledList.Item label="Health">
|
|
{health}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Color">
|
|
{color}
|
|
</LabeledList.Item>
|
|
</LabeledList>
|
|
</Section>
|
|
);
|
|
};
|
|
```
|
|
|
|
## Copypasta
|
|
|
|
We all do it, even the best of us. If you just want to make a tgui **fast**,
|
|
here's what you need (note that you'll probably be forced to clean your shit up
|
|
upon code review):
|
|
|
|
```dm
|
|
/obj/copypasta/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "copypasta")
|
|
ui.open()
|
|
|
|
/obj/copypasta/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["var"] = var
|
|
return data
|
|
|
|
/obj/copypasta/ui_act(action, params)
|
|
if(..())
|
|
return
|
|
switch(action)
|
|
if("copypasta")
|
|
var/newvar = params["var"]
|
|
// A demo of proper input sanitation.
|
|
var = CLAMP(newvar, min_val, max_val)
|
|
. = TRUE
|
|
update_icon() // Not applicable to all objects.
|
|
```
|
|
|
|
And the template:
|
|
|
|
```jsx
|
|
import { useBackend } from '../backend';
|
|
import { Button, LabeledList, Section } from '../components';
|
|
import { Window } from '../layouts';
|
|
|
|
export const SampleInterface = (props, context) => {
|
|
const { act, data } = useBackend(context);
|
|
// Extract `health` and `color` variables from the `data` object.
|
|
const {
|
|
health,
|
|
color,
|
|
} = data;
|
|
return (
|
|
<Window>
|
|
<Window.Content scrollable>
|
|
<Section title="Health status">
|
|
<LabeledList>
|
|
<LabeledList.Item label="Health">
|
|
{health}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Color">
|
|
{color}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Button">
|
|
<Button
|
|
content="Dispatch a 'test' action"
|
|
onClick={() => act('test')} />
|
|
</LabeledList.Item>
|
|
</LabeledList>
|
|
</Section>
|
|
</Window.Content>
|
|
</Window>
|
|
);
|
|
};
|
|
```
|