syncs TGS, tgui, and build tools to latest tgstation (#3039)
lol said the crab, lmao
6
.gitattributes
vendored
@@ -3,7 +3,7 @@
|
||||
## Enforce text mode and LF line breaks
|
||||
*.bat text eol=lf
|
||||
*.css text eol=lf
|
||||
*.css text eol=lf
|
||||
*.cjs text eol=lf
|
||||
*.dm text eol=lf
|
||||
*.dme text eol=lf
|
||||
*.dmf text eol=lf
|
||||
@@ -14,6 +14,7 @@
|
||||
*.json text eol=lf
|
||||
*.jsx text eol=lf
|
||||
*.md text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.py text eol=lf
|
||||
*.scss text eol=lf
|
||||
*.sh text eol=lf
|
||||
@@ -41,3 +42,6 @@
|
||||
|
||||
## Force changelog merging to use union
|
||||
html/changelog.html text eol=lf merge=union
|
||||
|
||||
## Force tab indents on dm files
|
||||
*dm whitespace=indent-with-non-tab
|
||||
|
||||
108
.github/AUTODOC_GUIDE.md
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
# dmdoc
|
||||
[DOCUMENTATION]: http://codedocs.tgstation13.org
|
||||
|
||||
[BYOND]: https://secure.byond.com/
|
||||
|
||||
[DMDOC]: https://github.com/SpaceManiac/SpacemanDMM/tree/master/src/dmdoc
|
||||
|
||||
[DMDOC] is a documentation generator for DreamMaker, the scripting language
|
||||
of the [BYOND] game engine. It produces simple static HTML files based on
|
||||
documented files, macros, types, procs, and vars.
|
||||
|
||||
We use **dmdoc** to generate [DOCUMENTATION] for our code, and that documentation
|
||||
is automatically generated and built on every new commit to the master branch
|
||||
|
||||
This gives new developers a clickable reference [DOCUMENTATION] they can browse to better help
|
||||
gain understanding of the /tg/station codebase structure and api reference.
|
||||
|
||||
## Documenting code on /tg/station
|
||||
We use block comments to document procs and classes, and we use `///` line comments
|
||||
when documenting individual variables.
|
||||
|
||||
It is required that all new code be covered with DMdoc code, according to the [Requirements](#Required)
|
||||
|
||||
We also require that when you touch older code, you must document the functions that you
|
||||
have touched in the process of updating that code
|
||||
|
||||
### Required
|
||||
A class *must* always be autodocumented, and all public functions *must* be documented
|
||||
|
||||
All class level defined variables *must* be documented
|
||||
|
||||
Internal functions *should* be documented, but may not be
|
||||
|
||||
A public function is any function that a developer might reasonably call while using
|
||||
or interacting with your object. Internal functions are helper functions that your
|
||||
public functions rely on to implement logic
|
||||
|
||||
|
||||
### Documenting a proc
|
||||
When documenting a proc, we give a short one line description (as this is shown
|
||||
next to the proc definition in the list of all procs for a type or global
|
||||
namespace), then a longer paragraph which will be shown when the user clicks on
|
||||
the proc to jump to it's definition
|
||||
```
|
||||
/**
|
||||
* Short description of the proc
|
||||
*
|
||||
* Longer detailed paragraph about the proc
|
||||
* including any relevant detail
|
||||
* Arguments:
|
||||
* * arg1 - Relevance of this argument
|
||||
* * arg2 - Relevance of this argument
|
||||
*/
|
||||
```
|
||||
|
||||
### Documenting a class
|
||||
We first give the name of the class as a header, this can be omitted if the name is
|
||||
just going to be the typepath of the class, as dmdoc uses that by default
|
||||
|
||||
Then we give a short oneline description of the class
|
||||
|
||||
Finally we give a longer multi paragraph description of the class and it's details
|
||||
```
|
||||
/**
|
||||
* # Classname (Can be omitted if it's just going to be the typepath)
|
||||
*
|
||||
* The short overview
|
||||
*
|
||||
* A longer
|
||||
* paragraph of functionality about the class
|
||||
* including any assumptions/special cases
|
||||
*
|
||||
*/
|
||||
```
|
||||
|
||||
### Documenting a variable/define
|
||||
Give a short explanation of what the variable, in the context of the class, or define is.
|
||||
```
|
||||
/// Type path of item to go in suit slot
|
||||
var/suit = null
|
||||
```
|
||||
|
||||
## Module level description of code
|
||||
Modules are the best way to describe the structure/intent of a package of code
|
||||
where you don't want to be tied to the formal layout of the class structure.
|
||||
|
||||
On /tg/station we do this by adding markdown files inside the `code` directory
|
||||
that will also be rendered and added to the modules tree. The structure for
|
||||
these is deliberately not defined, so you can be as freeform and as wheeling as
|
||||
you would like.
|
||||
|
||||
[Here is a representative example of what you might write](http://codedocs.tgstation13.org/code/modules/keybindings/readme.html)
|
||||
|
||||
## Special variables
|
||||
You can use certain special template variables in DM DOC comments and they will be expanded
|
||||
```
|
||||
[DEFINE_NAME] - Expands to a link to the define definition if documented
|
||||
[/mob] - Expands to a link to the docs for the /mob class
|
||||
[/mob/proc/Dizzy] - Expands to a link that will take you to the /mob class and anchor you to the dizzy proc docs
|
||||
[/mob/var/stat] - Expands to a link that will take you to the /mob class and anchor you to the stat var docs
|
||||
```
|
||||
|
||||
You can customise the link name by using `[link name][link shorthand].`
|
||||
|
||||
eg. `[see more about dizzy here] [/mob/proc/Dizzy]`
|
||||
|
||||
This is very useful to quickly link to other parts of the autodoc code to expand
|
||||
upon a comment made, or reasoning about code
|
||||
16
.github/workflows/ci_suite.yml
vendored
@@ -58,8 +58,9 @@ jobs:
|
||||
bash tools/ci/install_byond.sh
|
||||
source $HOME/BYOND/byond/bin/byondsetup
|
||||
python3 tools/ci/template_dm_generator.py
|
||||
tgui/bin/tgui --build
|
||||
bash tools/ci/dm.sh -DCIBUILDING -DCITESTING -DALL_MAPS vorestation.dme
|
||||
tools/build/build
|
||||
env:
|
||||
CBT_BUILD_MODE : ALL_MAPS
|
||||
|
||||
run_all_tests:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
@@ -85,8 +86,8 @@ jobs:
|
||||
sudo systemctl start mysql
|
||||
mysql -u root -proot -e 'CREATE DATABASE tg_ci;'
|
||||
mysql -u root -proot tg_ci < SQL/tgstation_schema.sql
|
||||
# mysql -u root -proot -e 'CREATE DATABASE tg_ci_prefixed;'
|
||||
# mysql -u root -proot tg_ci_prefixed < SQL/tgstation_schema_prefixed.sql
|
||||
# mysql -u root -proot -e 'CREATE DATABASE tg_ci_prefixed;'
|
||||
# mysql -u root -proot tg_ci_prefixed < SQL/tgstation_schema_prefixed.sql
|
||||
- name: Install rust-g
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
@@ -97,9 +98,10 @@ jobs:
|
||||
run: |
|
||||
bash tools/ci/install_byond.sh
|
||||
source $HOME/BYOND/byond/bin/byondsetup
|
||||
tgui/bin/tgui --build
|
||||
bash tools/ci/dm.sh -DCIBUILDING vorestation.dme
|
||||
tools/build/build
|
||||
bash tools/ci/run_server.sh
|
||||
env:
|
||||
CBT_BUILD_MODE: TEST_RUN
|
||||
|
||||
test_windows:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
@@ -109,6 +111,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Compile
|
||||
run: pwsh tools/ci/build.ps1
|
||||
env:
|
||||
DM_EXE: "C:\\byond\\bin\\dm.exe"
|
||||
- name: Create artifact
|
||||
run: |
|
||||
md deploy
|
||||
|
||||
1
.github/workflows/compile_changelogs.yml
vendored
@@ -47,6 +47,7 @@ jobs:
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "Changelogs"
|
||||
git pull origin master
|
||||
git add html/changelogs
|
||||
git commit -m "Automatic changelog compile [ci skip]" -a || true
|
||||
- name: "Push"
|
||||
if: steps.value_holder.outputs.CL_ENABLED
|
||||
|
||||
13
.github/workflows/conflicts.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: 'Check for merge conflicts'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: mschilde/auto-label-merge-conflicts@2e8fcc76c6430272ec8bb64fb74ec1592156aa6a
|
||||
with:
|
||||
CONFLICT_LABEL_NAME: 'Merge Conflict'
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
1
.github/workflows/docker_publish.yml
vendored
@@ -20,4 +20,3 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
dockerfile: Dockerfile
|
||||
tags: "latest"
|
||||
cache: true
|
||||
|
||||
68
.gitignore
vendored
@@ -7,6 +7,9 @@
|
||||
#Ignore byond config folder.
|
||||
/cfg/**/*
|
||||
|
||||
# Ignore compiled linux libs in the root folder, e.g. librust_g.so
|
||||
/*.so
|
||||
|
||||
#Ignore compiled files and other files generated during compilation.
|
||||
*.mdme
|
||||
*.dmb
|
||||
@@ -49,27 +52,6 @@ __pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
#*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
@@ -77,8 +59,7 @@ var/
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
pip-*.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
@@ -88,7 +69,6 @@ htmlcov/
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
@@ -97,10 +77,6 @@ coverage.xml
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask instance folder
|
||||
instance/
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
@@ -108,9 +84,6 @@ instance/
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# IPython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
@@ -123,10 +96,6 @@ celerybeat-schedule
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# IntelliJ IDEA / PyCharm (with plugin)
|
||||
.idea
|
||||
|
||||
@@ -149,12 +118,6 @@ Desktop.ini
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
#*.cab
|
||||
#*.msi
|
||||
#*.msm
|
||||
#*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
@@ -195,11 +158,10 @@ Temporary Items
|
||||
|
||||
#Visual studio stuff
|
||||
*.vscode/*
|
||||
!/.vscode/extensions.json
|
||||
tools/MapAtmosFixer/MapAtmosFixer/obj/*
|
||||
tools/MapAtmosFixer/MapAtmosFixer/bin/*
|
||||
tools/CreditsTool/bin/*
|
||||
tools/CreditsTool/obj/*
|
||||
/tools/MapAtmosFixer/MapAtmosFixer/obj/*
|
||||
/tools/MapAtmosFixer/MapAtmosFixer/bin/*
|
||||
/tools/CreditsTool/bin/*
|
||||
/tools/CreditsTool/obj/*
|
||||
|
||||
#GitHub Atom
|
||||
.atom-build.json
|
||||
@@ -224,10 +186,10 @@ tools/CreditsTool/obj/*
|
||||
!/config/title_screens/images/exclude
|
||||
|
||||
#Linux docker
|
||||
tools/LinuxOneShot/SetupProgram/obj/*
|
||||
tools/LinuxOneShot/SetupProgram/bin/*
|
||||
tools/LinuxOneShot/SetupProgram/.vs
|
||||
tools/LinuxOneShot/Database
|
||||
tools/LinuxOneShot/TGS_Config
|
||||
tools/LinuxOneShot/TGS_Instances
|
||||
tools/LinuxOneShot/TGS_Logs
|
||||
/tools/LinuxOneShot/SetupProgram/obj/*
|
||||
/tools/LinuxOneShot/SetupProgram/bin/*
|
||||
/tools/LinuxOneShot/SetupProgram/.vs
|
||||
/tools/LinuxOneShot/Database
|
||||
/tools/LinuxOneShot/TGS_Config
|
||||
/tools/LinuxOneShot/TGS_Instances
|
||||
/tools/LinuxOneShot/TGS_Logs
|
||||
|
||||
15
.vscode/extensions.json
vendored
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"gbasood.byond-dm-language-support",
|
||||
"platymuus.dm-langclient",
|
||||
"EditorConfig.EditorConfig",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
"recommendations": [
|
||||
"gbasood.byond-dm-language-support",
|
||||
"platymuus.dm-langclient",
|
||||
"EditorConfig.EditorConfig",
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"stylemistake.auto-comment-blocks",
|
||||
"Donkie.vscode-tgstation-test-adapter"
|
||||
]
|
||||
}
|
||||
|
||||
12
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "byond",
|
||||
"request": "launch",
|
||||
"name": "Launch DreamSeeker",
|
||||
"preLaunchTask": "Build All",
|
||||
"dmb": "${workspaceFolder}/${command:CurrentDMB}"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.vscode/settings.json
vendored
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"eslint.nodePath": "tgui/.yarn/sdks",
|
||||
"eslint.nodePath": "./tgui/.yarn/sdks",
|
||||
"eslint.workingDirectories": [
|
||||
"./tgui"
|
||||
],
|
||||
"typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"search.exclude": {
|
||||
"tgui/.yarn": true,
|
||||
"tgui/.pnp.*": true
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"workbench.editorAssociations": [
|
||||
{
|
||||
@@ -14,5 +19,15 @@
|
||||
}
|
||||
],
|
||||
"files.eol": "\n",
|
||||
"gitlens.advanced.blame.customArguments": ["-w"]
|
||||
"gitlens.advanced.blame.customArguments": ["-w"],
|
||||
"tgstationTestExplorer.project.resultsType": "json",
|
||||
"[javascript]": {
|
||||
"editor.rulers": [80]
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.rulers": [80]
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.rulers": [80]
|
||||
}
|
||||
}
|
||||
|
||||
55
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "process",
|
||||
"command": "tools/build/build",
|
||||
"windows": {
|
||||
"command": ".\\tools\\build\\build.bat"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"DM_EXE": "${config:dreammaker.byondPath}"
|
||||
}
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$dreammaker",
|
||||
"$tsc",
|
||||
"$eslint-stylish"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"dependsOn": "dm: reparse",
|
||||
"label": "Build All"
|
||||
},
|
||||
{
|
||||
"type": "dreammaker",
|
||||
"dme": "vorestation.dme",
|
||||
"problemMatcher": [
|
||||
"$dreammaker"
|
||||
],
|
||||
"group": "build",
|
||||
"label": "dm: build - vorestation.dme"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "tgui/bin/tgui",
|
||||
"windows": {
|
||||
"command": ".\\tgui\\bin\\tgui.bat"
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$tsc",
|
||||
"$eslint-stylish"
|
||||
],
|
||||
"group": "build",
|
||||
"label": "tgui: build"
|
||||
},
|
||||
{
|
||||
"command": "${command:dreammaker.reparse}",
|
||||
"group": "build",
|
||||
"label": "dm: reparse"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// tgstation-server DMAPI
|
||||
|
||||
#define TGS_DMAPI_VERSION "5.2.7"
|
||||
#define TGS_DMAPI_VERSION "6.0.3"
|
||||
|
||||
// All functions and datums outside this document are subject to change with any version and should not be relied on.
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
#define TGS_EVENT_REPO_CHECKOUT 1
|
||||
/// When the repository performs a fetch operation. No parameters
|
||||
#define TGS_EVENT_REPO_FETCH 2
|
||||
/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
|
||||
/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
|
||||
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
|
||||
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path
|
||||
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
|
||||
@@ -95,6 +95,13 @@
|
||||
#define TGS_EVENT_WATCHDOG_SHUTDOWN 15
|
||||
/// Before the watchdog detaches for a TGS update/restart. No parameters.
|
||||
#define TGS_EVENT_WATCHDOG_DETACH 16
|
||||
// We don't actually implement these 4 events as the DMAPI can never receive them.
|
||||
// #define TGS_EVENT_WATCHDOG_LAUNCH 17
|
||||
// #define TGS_EVENT_WATCHDOG_CRASH 18
|
||||
// #define TGS_EVENT_WORLD_END_PROCESS 19
|
||||
// #define TGS_EVENT_WORLD_REBOOT 20
|
||||
/// Watchdog event when TgsInitializationComplete() is called. No parameters.
|
||||
#define TGS_EVENT_WORLD_PRIME 21
|
||||
|
||||
// OTHER ENUMS
|
||||
|
||||
@@ -115,21 +122,21 @@
|
||||
//REQUIRED HOOKS
|
||||
|
||||
/**
|
||||
* Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
|
||||
*
|
||||
* * event_handler - Optional user defined [/datum/tgs_event_handler].
|
||||
* * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
|
||||
*/
|
||||
* Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
|
||||
*
|
||||
* * event_handler - Optional user defined [/datum/tgs_event_handler].
|
||||
* * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
|
||||
*/
|
||||
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
|
||||
return
|
||||
|
||||
/**
|
||||
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
|
||||
*
|
||||
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
|
||||
* Before this point, note that any static files or directories may be in use by another server. Your code should account for this.
|
||||
* This function should not be called before ..() in [/world/proc/New].
|
||||
*/
|
||||
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
|
||||
*
|
||||
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
|
||||
* Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
|
||||
* This function should not be called before ..() in [/world/proc/New].
|
||||
*/
|
||||
/world/proc/TgsInitializationComplete()
|
||||
return
|
||||
|
||||
@@ -137,8 +144,8 @@
|
||||
#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
|
||||
|
||||
/**
|
||||
* Call this at the beginning of [world/proc/Reboot].
|
||||
*/
|
||||
* Call this as late as possible in [world/proc/Reboot].
|
||||
*/
|
||||
/world/proc/TgsReboot()
|
||||
return
|
||||
|
||||
@@ -149,6 +156,8 @@
|
||||
/datum/tgs_revision_information
|
||||
/// Full SHA of the commit.
|
||||
var/commit
|
||||
/// ISO 8601 timestamp of when the commit was created
|
||||
var/timestamp
|
||||
/// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch.
|
||||
var/origin_commit
|
||||
|
||||
@@ -172,36 +181,34 @@
|
||||
var/deprefixed_parameter
|
||||
|
||||
/**
|
||||
* Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
|
||||
*/
|
||||
* Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
|
||||
*/
|
||||
/datum/tgs_version/proc/Wildcard()
|
||||
return
|
||||
|
||||
/**
|
||||
* Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version.
|
||||
*
|
||||
* other_version - The [/datum/tgs_version] to compare against.
|
||||
*/
|
||||
* Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version.
|
||||
*
|
||||
* other_version - The [/datum/tgs_version] to compare against.
|
||||
*/
|
||||
/datum/tgs_version/proc/Equals(datum/tgs_version/other_version)
|
||||
return
|
||||
|
||||
/// Represents a merge of a GitHub pull request.
|
||||
/datum/tgs_revision_information/test_merge
|
||||
/// The pull request number.
|
||||
/// The test merge number.
|
||||
var/number
|
||||
/// The pull request title when it was merged.
|
||||
/// The test merge source's title when it was merged.
|
||||
var/title
|
||||
/// The pull request body when it was merged.
|
||||
/// The test merge source's body when it was merged.
|
||||
var/body
|
||||
/// The GitHub username of the pull request's author.
|
||||
/// The Username of the test merge source's author.
|
||||
var/author
|
||||
/// An http URL to the pull request.
|
||||
/// An http URL to the test merge source.
|
||||
var/url
|
||||
/// The SHA of the pull request when that was merged.
|
||||
var/pull_request_commit
|
||||
/// ISO 8601 timestamp of when the pull request was merged.
|
||||
var/time_merged
|
||||
/// (Nullable) Comment left by the TGS user who initiated the merge..
|
||||
/// The SHA of the test merge when that was merged.
|
||||
var/head_commit
|
||||
/// Optional comment left by the TGS user who initiated the merge.
|
||||
var/comment
|
||||
|
||||
/// Represents a connected chat channel.
|
||||
@@ -231,10 +238,10 @@
|
||||
var/datum/tgs_chat_channel/channel
|
||||
|
||||
/**
|
||||
* User definable callback for handling TGS events.
|
||||
*
|
||||
* event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each
|
||||
*/
|
||||
* User definable callback for handling TGS events.
|
||||
*
|
||||
* event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each
|
||||
*/
|
||||
/datum/tgs_event_handler/proc/HandleEvent(event_code, ...)
|
||||
set waitfor = FALSE
|
||||
return
|
||||
@@ -249,67 +256,67 @@
|
||||
var/admin_only = FALSE
|
||||
|
||||
/**
|
||||
* Process command activation. Should return a string to respond to the issuer with.
|
||||
*
|
||||
* sender - The [/datum/tgs_chat_user] who issued the command.
|
||||
* params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
|
||||
*/
|
||||
* Process command activation. Should return a string to respond to the issuer with.
|
||||
*
|
||||
* sender - The [/datum/tgs_chat_user] who issued the command.
|
||||
* params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
|
||||
*/
|
||||
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
|
||||
CRASH("[type] has no implementation for Run()")
|
||||
|
||||
// API FUNCTIONS
|
||||
|
||||
/// Returns the maximum supported [/datum/tgs_version] of the DMAPI.
|
||||
/world/proc/TgsMaximumAPIVersion()
|
||||
/world/proc/TgsMaximumApiVersion()
|
||||
return
|
||||
|
||||
/// Returns the minimum supported [/datum/tgs_version] of the DMAPI.
|
||||
/world/proc/TgsMinimumAPIVersion()
|
||||
/world/proc/TgsMinimumApiVersion()
|
||||
return
|
||||
|
||||
/**
|
||||
* Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
|
||||
*/
|
||||
* Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
|
||||
*/
|
||||
/world/proc/TgsAvailable()
|
||||
return
|
||||
|
||||
// No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called.
|
||||
|
||||
/**
|
||||
* Forces a hard reboot of DreamDaemon by ending the process.
|
||||
*
|
||||
* Unlike del(world) clients will try to reconnect.
|
||||
* If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again
|
||||
*/
|
||||
* Forces a hard reboot of DreamDaemon by ending the process.
|
||||
*
|
||||
* Unlike del(world) clients will try to reconnect.
|
||||
* If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again
|
||||
*/
|
||||
/world/proc/TgsEndProcess()
|
||||
return
|
||||
|
||||
/**
|
||||
* Send a message to connected chats.
|
||||
*
|
||||
* message - The string to send.
|
||||
* admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
|
||||
*/
|
||||
* Send a message to connected chats.
|
||||
*
|
||||
* message - The string to send.
|
||||
* admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
|
||||
*/
|
||||
/world/proc/TgsTargetedChatBroadcast(message, admin_only = FALSE)
|
||||
return
|
||||
|
||||
/**
|
||||
* Send a private message to a specific user.
|
||||
*
|
||||
* message - The string to send.
|
||||
* user: The [/datum/tgs_chat_user] to PM.
|
||||
*/
|
||||
* Send a private message to a specific user.
|
||||
*
|
||||
* message - The string to send.
|
||||
* user: The [/datum/tgs_chat_user] to PM.
|
||||
*/
|
||||
/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user)
|
||||
return
|
||||
|
||||
// The following functions will sleep if a call to TgsNew() is sleeping
|
||||
|
||||
/**
|
||||
* Send a message to connected chats that are flagged as game-related in TGS.
|
||||
*
|
||||
* message - The string to send.
|
||||
* channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
|
||||
*/
|
||||
* Send a message to connected chats that are flagged as game-related in TGS.
|
||||
*
|
||||
* message - The string to send.
|
||||
* channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
|
||||
*/
|
||||
/world/proc/TgsChatBroadcast(message, list/channels = null)
|
||||
return
|
||||
|
||||
|
||||
2
code/controllers/configuration/entries/logging.dm
Normal file
@@ -0,0 +1,2 @@
|
||||
/datum/config_entry/flag/emergency_tgui_logging
|
||||
config_entry_value = FALSE
|
||||
@@ -16,7 +16,7 @@
|
||||
if(revinfo)
|
||||
commit = revinfo.commit
|
||||
originmastercommit = revinfo.origin_commit
|
||||
date = rustg_git_commit_date(commit)
|
||||
date = revinfo.timestamp || rustg_git_commit_date(commit)
|
||||
|
||||
// goes to DD log and config_error.txt
|
||||
log_world(get_log_message())
|
||||
@@ -29,7 +29,8 @@
|
||||
|
||||
for(var/line in testmerge)
|
||||
var/datum/tgs_revision_information/test_merge/tm = line
|
||||
msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]"
|
||||
msg += "Test merge active of PR #[tm.number] commit [tm.head_commit]"
|
||||
// SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.head_commit]", "title" = "[tm.title]", "author" = "[tm.author]"))
|
||||
|
||||
if(commit && commit != originmastercommit)
|
||||
msg += "HEAD: [commit]"
|
||||
@@ -44,7 +45,7 @@
|
||||
. = header ? "The following pull requests are currently test merged:<br>" : ""
|
||||
for(var/line in testmerge)
|
||||
var/datum/tgs_revision_information/test_merge/tm = line
|
||||
var/cm = tm.pull_request_commit
|
||||
var/cm = tm.head_commit
|
||||
var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11))
|
||||
if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder))
|
||||
continue
|
||||
@@ -78,7 +79,9 @@
|
||||
msg += "No commit information"
|
||||
if(world.TgsAvailable())
|
||||
var/datum/tgs_version/version = world.TgsVersion()
|
||||
msg += "Server tools version: [version.raw_parameter]"
|
||||
msg += "TGS version: [version.raw_parameter]"
|
||||
var/datum/tgs_version/api_version = world.TgsApiVersion()
|
||||
msg += "DMAPI version: [api_version.raw_parameter]"
|
||||
|
||||
/*
|
||||
// Game mode odds
|
||||
@@ -123,5 +126,5 @@
|
||||
if(probabilities[ctag] > 0)
|
||||
var/percentage = round(probabilities[ctag] / sum * 100, 0.1)
|
||||
msg += "[ctag] [percentage]%"
|
||||
*/
|
||||
to_chat(src, msg.Join("<br>"))
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@ var/global/floorIsLava = 0
|
||||
|
||||
////////////////////////////////
|
||||
/proc/message_admins(var/msg)
|
||||
msg = "<span class='log_message'><span class='prefix'>ADMIN LOG:</span> <span class='message'>[msg]</span></span>"
|
||||
msg = "<span class='admin log_message'><span class='prefix'>ADMIN LOG:</span> <span class='message'>[msg]</span></span>"
|
||||
//log_adminwarn(msg) //log_and_message_admins is for this
|
||||
|
||||
for(var/client/C in admins)
|
||||
@@ -13,7 +13,7 @@ var/global/floorIsLava = 0
|
||||
to_chat(C, msg)
|
||||
|
||||
/proc/msg_admin_attack(var/text) //Toggleable Attack Messages
|
||||
var/rendered = "<span class='log_message><span class='prefix'>ATTACK:</span> <span class='message'>[text]</span></span>"
|
||||
var/rendered = "<span class='admin log_message><span class='prefix'>ATTACK:</span> <span class='message'>[text]</span></span>"
|
||||
for(var/client/C in admins)
|
||||
if((R_ADMIN|R_MOD) & C.holder.rights)
|
||||
if(C.is_preference_enabled(/datum/client_preference/mod/show_attack_logs))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/datum/asset/simple/tgui_common
|
||||
keep_local_name = TRUE
|
||||
assets = list(
|
||||
"tgui-common.chunk.js" = 'tgui/public/tgui-common.chunk.js',
|
||||
"tgui-common.chunk.js" = 'tgui/public/tgui-common.bundle.js',
|
||||
)
|
||||
|
||||
/datum/asset/simple/tgui
|
||||
@@ -203,6 +203,13 @@
|
||||
)
|
||||
parents = list("font-awesome.css" = 'html/font-awesome/css/all.min.css')
|
||||
|
||||
/datum/asset/simple/namespaced/tgfont
|
||||
assets = list(
|
||||
"tgfont.eot" = 'tgui/packages/tgfont/dist/tgfont.eot',
|
||||
"tgfont.woff2" = 'tgui/packages/tgfont/dist/tgfont.woff2',
|
||||
)
|
||||
parents = list("tgfont.css" = 'tgui/packages/tgfont/dist/tgfont.css')
|
||||
|
||||
/datum/asset/spritesheet/chat
|
||||
name = "chat"
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
topiclimiter[ADMINSWARNED_AT] = minute
|
||||
msg += " Administrators have been informed."
|
||||
log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
|
||||
message_admins("[ADMIN_LOOKUPFLW(src)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
|
||||
message_admins("[ADMIN_LOOKUPFLW(usr)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
|
||||
to_chat(src, "<span class='danger'>[msg]</span>")
|
||||
return
|
||||
|
||||
@@ -77,13 +77,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
to_chat(src, "<span class='danger'>Your previous action was ignored because you've done too many in a second</span>")
|
||||
return
|
||||
|
||||
// Tgui Topic middleware.
|
||||
|
||||
// Tgui Topic middleware
|
||||
if(tgui_Topic(href_list))
|
||||
if(CONFIG_GET(flag/emergency_tgui_logging))
|
||||
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
|
||||
return
|
||||
|
||||
//Logs all hrefs, except chat pings
|
||||
if(!(href_list["_src_"] == "chat" && href_list["proc"] == "ping" && LAZYLEN(href_list) == 2))
|
||||
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
|
||||
//Logs all hrefs
|
||||
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
|
||||
|
||||
//byond bug ID:2256651
|
||||
if (asset_cache_job && (asset_cache_job in completed_asset_jobs))
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
if(5)
|
||||
api_datum = /datum/tgs_api/v5
|
||||
|
||||
var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion();
|
||||
var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
|
||||
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
|
||||
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
|
||||
api_datum = /datum/tgs_api/latest
|
||||
@@ -64,10 +64,10 @@
|
||||
TGS_WRITE_GLOBAL(tgs, null)
|
||||
TGS_ERROR_LOG("Failed to activate API!")
|
||||
|
||||
/world/TgsMaximumAPIVersion()
|
||||
/world/TgsMaximumApiVersion()
|
||||
return new /datum/tgs_version("5.x.x")
|
||||
|
||||
/world/TgsMinimumAPIVersion()
|
||||
/world/TgsMinimumApiVersion()
|
||||
return new /datum/tgs_version("3.2.x")
|
||||
|
||||
/world/TgsInitializationComplete()
|
||||
|
||||
@@ -50,7 +50,7 @@ TGS_PROTECT_DATUM(/datum/tgs_api)
|
||||
/datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only)
|
||||
return TGS_UNIMPLEMENTED
|
||||
|
||||
/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only)
|
||||
/datum/tgs_api/proc/ChatPrivateMessage(message, datum/tgs_chat_user/user)
|
||||
return TGS_UNIMPLEMENTED
|
||||
|
||||
/datum/tgs_api/proc/SecurityLevel()
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
#include "v5\_defines.dm"
|
||||
#include "v5\api.dm"
|
||||
#include "v5\commands.dm"
|
||||
#include "v5\undef.dm"
|
||||
#include "v5\undefs.dm"
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
comms_key = world.params[SERVICE_WORLD_PARAM]
|
||||
instance_name = world.params[SERVICE_INSTANCE_PARAM]
|
||||
if(!instance_name)
|
||||
instance_name = "TG Station Server" //maybe just upgraded
|
||||
instance_name = "TG Station Server" //maybe just upgraded
|
||||
|
||||
var/list/logs = file2list(".git/logs/HEAD")
|
||||
if(logs.len)
|
||||
@@ -92,14 +92,14 @@
|
||||
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
|
||||
TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
|
||||
return
|
||||
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
|
||||
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
|
||||
return TRUE
|
||||
|
||||
/datum/tgs_api/v3210/OnTopic(T)
|
||||
var/list/params = params2list(T)
|
||||
var/their_sCK = params[SERVICE_CMD_PARAM_KEY]
|
||||
if(!their_sCK)
|
||||
return FALSE //continue world/Topic
|
||||
return FALSE //continue world/Topic
|
||||
|
||||
if(their_sCK != comms_key)
|
||||
return "Invalid comms key!";
|
||||
@@ -160,7 +160,7 @@
|
||||
var/datum/tgs_revision_information/test_merge/tm = new
|
||||
tm.number = text2num(I)
|
||||
var/list/entry = json[I]
|
||||
tm.pull_request_commit = entry["commit"]
|
||||
tm.head_commit = entry["commit"]
|
||||
tm.author = entry["author"]
|
||||
tm.title = entry["title"]
|
||||
. += tm
|
||||
@@ -176,11 +176,11 @@
|
||||
return ri
|
||||
|
||||
/datum/tgs_api/v3210/EndProcess()
|
||||
sleep(world.tick_lag) //flush the buffers
|
||||
sleep(world.tick_lag) //flush the buffers
|
||||
ExportService(SERVICE_REQUEST_KILL_PROCESS)
|
||||
|
||||
/datum/tgs_api/v3210/ChatChannelInfo()
|
||||
return list()
|
||||
return list() // :omegalul:
|
||||
|
||||
/datum/tgs_api/v3210/ChatBroadcast(message, list/channels)
|
||||
if(channels)
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
var/list/json = cached_json["testMerges"]
|
||||
for(var/entry in json)
|
||||
var/datum/tgs_revision_information/test_merge/tm = new
|
||||
tm.time_merged = text2num(entry["timeMerged"])
|
||||
tm.timestamp = text2num(entry["timeMerged"])
|
||||
|
||||
var/list/revInfo = entry["revision"]
|
||||
if(revInfo)
|
||||
@@ -104,7 +104,7 @@
|
||||
tm.url = entry["url"]
|
||||
tm.author = entry["author"]
|
||||
tm.number = entry["number"]
|
||||
tm.pull_request_commit = entry["pullRequestRevision"]
|
||||
tm.head_commit = entry["pullRequestRevision"]
|
||||
tm.comment = entry["comment"]
|
||||
|
||||
cached_test_merges += tm
|
||||
@@ -114,18 +114,11 @@
|
||||
/datum/tgs_api/v4/OnInitializationComplete()
|
||||
Export(TGS4_COMM_SERVER_PRIMED)
|
||||
|
||||
var/tgs4_secret_sleep_offline_sauce = 24051994
|
||||
var/old_sleep_offline = world.sleep_offline
|
||||
world.sleep_offline = tgs4_secret_sleep_offline_sauce
|
||||
sleep(1)
|
||||
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
|
||||
world.sleep_offline = old_sleep_offline
|
||||
|
||||
/datum/tgs_api/v4/OnTopic(T)
|
||||
var/list/params = params2list(T)
|
||||
var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER]
|
||||
if(!their_sCK)
|
||||
return FALSE //continue world/Topic
|
||||
return FALSE //continue world/Topic
|
||||
|
||||
if(their_sCK != access_identifier)
|
||||
return "Invalid comms key!";
|
||||
@@ -199,7 +192,7 @@
|
||||
|
||||
//request a new port
|
||||
export_lock = FALSE
|
||||
var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose
|
||||
var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose
|
||||
|
||||
if(!new_port_json)
|
||||
TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]")
|
||||
@@ -242,7 +235,7 @@
|
||||
|
||||
var/port = result[TGS4_PARAMETER_DATA]
|
||||
if(!isnum(port))
|
||||
return //this is valid, server may just want use to reboot
|
||||
return //this is valid, server may just want use to reboot
|
||||
|
||||
if(port == 0)
|
||||
//to byond 0 means any port and "none" means close vOv
|
||||
@@ -255,7 +248,7 @@
|
||||
return instance_name
|
||||
|
||||
/datum/tgs_api/v4/TestMerges()
|
||||
return cached_test_merges
|
||||
return cached_test_merges.Copy()
|
||||
|
||||
/datum/tgs_api/v4/EndProcess()
|
||||
Export(TGS4_COMM_END_PROCESS)
|
||||
|
||||
8
code/modules/tgs/v5/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# DMAPI V5
|
||||
|
||||
This DMAPI implements bridge requests using HTTP GET requests to TGS. It has no security restrictions.
|
||||
|
||||
- [_defines.dm](./_defines.dm) contains constant definitions.
|
||||
- [api.dm](./api.dm) contains the bulk of the API code.
|
||||
- [commands.dm](./commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
|
||||
- [undefs.dm](./undefs.dm) Undoes the work of `_defines.dm`.
|
||||
@@ -79,6 +79,7 @@
|
||||
#define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses"
|
||||
|
||||
#define DMAPI5_REVISION_INFORMATION_COMMIT_SHA "commitSha"
|
||||
#define DMAPI5_REVISION_INFORMATION_TIMESTAMP "timestamp"
|
||||
#define DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA "originCommitSha"
|
||||
|
||||
#define DMAPI5_CHAT_USER_ID "id"
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
var/datum/tgs_revision_information/revision
|
||||
var/list/chat_channels
|
||||
|
||||
var/initialized = FALSE
|
||||
|
||||
/datum/tgs_api/v5/ApiVersion()
|
||||
return new /datum/tgs_version(TGS_DMAPI_VERSION)
|
||||
return new /datum/tgs_version(
|
||||
#include "interop_version.dm"
|
||||
)
|
||||
|
||||
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
|
||||
server_port = world.params[DMAPI5_PARAM_SERVER_PORT]
|
||||
@@ -46,6 +50,7 @@
|
||||
if(istype(revisionData))
|
||||
revision = new
|
||||
revision.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
|
||||
revision.timestamp = revisionData[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
|
||||
revision.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
|
||||
else
|
||||
TGS_ERROR_LOG("Failed to decode [DMAPI5_RUNTIME_INFORMATION_REVISION] from runtime information!")
|
||||
@@ -61,15 +66,18 @@
|
||||
if(revInfo)
|
||||
tm.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
|
||||
tm.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
|
||||
tm.timestamp = entry[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
|
||||
else
|
||||
TGS_WARNING_LOG("Failed to decode [DMAPI5_TEST_MERGE_REVISION] from test merge #[tm.number]!")
|
||||
|
||||
tm.time_merged = text2num(entry[DMAPI5_TEST_MERGE_TIME_MERGED])
|
||||
if(!tm.timestamp)
|
||||
tm.timestamp = entry[DMAPI5_TEST_MERGE_TIME_MERGED]
|
||||
|
||||
tm.title = entry[DMAPI5_TEST_MERGE_TITLE_AT_MERGE]
|
||||
tm.body = entry[DMAPI5_TEST_MERGE_BODY_AT_MERGE]
|
||||
tm.url = entry[DMAPI5_TEST_MERGE_URL]
|
||||
tm.author = entry[DMAPI5_TEST_MERGE_AUTHOR]
|
||||
tm.pull_request_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION]
|
||||
tm.head_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION]
|
||||
tm.comment = entry[DMAPI5_TEST_MERGE_COMMENT]
|
||||
|
||||
test_merges += tm
|
||||
@@ -79,6 +87,7 @@
|
||||
chat_channels = list()
|
||||
DecodeChannels(runtime_information)
|
||||
|
||||
initialized = TRUE
|
||||
return TRUE
|
||||
|
||||
/datum/tgs_api/v5/proc/RequireInitialBridgeResponse()
|
||||
@@ -88,13 +97,6 @@
|
||||
/datum/tgs_api/v5/OnInitializationComplete()
|
||||
Bridge(DMAPI5_BRIDGE_COMMAND_PRIME)
|
||||
|
||||
var/tgs4_secret_sleep_offline_sauce = 29051994
|
||||
var/old_sleep_offline = world.sleep_offline
|
||||
world.sleep_offline = tgs4_secret_sleep_offline_sauce
|
||||
sleep(1)
|
||||
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
|
||||
world.sleep_offline = old_sleep_offline
|
||||
|
||||
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
|
||||
var/list/response = list()
|
||||
response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message
|
||||
@@ -105,12 +107,16 @@
|
||||
var/list/params = params2list(T)
|
||||
var/json = params[DMAPI5_TOPIC_DATA]
|
||||
if(!json)
|
||||
return FALSE //continue world/Topic
|
||||
return FALSE // continue to /world/Topic
|
||||
|
||||
var/list/topic_parameters = json_decode(json)
|
||||
if(!topic_parameters)
|
||||
return TopicResponse("Invalid topic parameters json!");
|
||||
|
||||
if(!initialized)
|
||||
TGS_WARNING_LOG("Missed topic due to not being initialized: [T]")
|
||||
return TRUE // too early to handle, but it's still our responsibility
|
||||
|
||||
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
|
||||
if(their_sCK != access_identifier)
|
||||
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!");
|
||||
@@ -267,7 +273,7 @@
|
||||
|
||||
var/port = result[DMAPI5_BRIDGE_RESPONSE_NEW_PORT]
|
||||
if(!isnum(port))
|
||||
return //this is valid, server may just want use to reboot
|
||||
return //this is valid, server may just want use to reboot
|
||||
|
||||
if(port == 0)
|
||||
//to byond 0 means any port and "none" means close vOv
|
||||
@@ -282,7 +288,7 @@
|
||||
|
||||
/datum/tgs_api/v5/TestMerges()
|
||||
RequireInitialBridgeResponse()
|
||||
return test_merges
|
||||
return test_merges.Copy()
|
||||
|
||||
/datum/tgs_api/v5/EndProcess()
|
||||
Bridge(DMAPI5_BRIDGE_COMMAND_KILL)
|
||||
@@ -327,7 +333,7 @@
|
||||
|
||||
/datum/tgs_api/v5/ChatChannelInfo()
|
||||
RequireInitialBridgeResponse()
|
||||
return chat_channels
|
||||
return chat_channels.Copy()
|
||||
|
||||
/datum/tgs_api/v5/proc/DecodeChannels(chat_update_json)
|
||||
var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS]
|
||||
|
||||
1
code/modules/tgs/v5/interop_version.dm
Normal file
@@ -0,0 +1 @@
|
||||
"5.3.0"
|
||||
99
code/modules/tgs/v5/undefs.dm
Normal file
@@ -0,0 +1,99 @@
|
||||
#undef DMAPI5_PARAM_SERVER_PORT
|
||||
#undef DMAPI5_PARAM_ACCESS_IDENTIFIER
|
||||
|
||||
#undef DMAPI5_BRIDGE_DATA
|
||||
#undef DMAPI5_TOPIC_DATA
|
||||
|
||||
#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
|
||||
#undef DMAPI5_BRIDGE_COMMAND_STARTUP
|
||||
#undef DMAPI5_BRIDGE_COMMAND_PRIME
|
||||
#undef DMAPI5_BRIDGE_COMMAND_REBOOT
|
||||
#undef DMAPI5_BRIDGE_COMMAND_KILL
|
||||
#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
|
||||
|
||||
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
|
||||
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
|
||||
|
||||
#undef DMAPI5_RESPONSE_ERROR_MESSAGE
|
||||
|
||||
#undef DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE
|
||||
#undef DMAPI5_BRIDGE_PARAMETER_CURRENT_PORT
|
||||
#undef DMAPI5_BRIDGE_PARAMETER_VERSION
|
||||
#undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE
|
||||
#undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL
|
||||
|
||||
#undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT
|
||||
#undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION
|
||||
|
||||
#undef DMAPI5_CHAT_MESSAGE_TEXT
|
||||
#undef DMAPI5_CHAT_MESSAGE_CHANNEL_IDS
|
||||
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_ACCESS_IDENTIFIER
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_SERVER_PORT
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_REVISION
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES
|
||||
#undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL
|
||||
|
||||
#undef DMAPI5_CHAT_UPDATE_CHANNELS
|
||||
|
||||
#undef DMAPI5_TEST_MERGE_TIME_MERGED
|
||||
#undef DMAPI5_TEST_MERGE_REVISION
|
||||
#undef DMAPI5_TEST_MERGE_TITLE_AT_MERGE
|
||||
#undef DMAPI5_TEST_MERGE_BODY_AT_MERGE
|
||||
#undef DMAPI5_TEST_MERGE_URL
|
||||
#undef DMAPI5_TEST_MERGE_AUTHOR
|
||||
#undef DMAPI5_TEST_MERGE_NUMBER
|
||||
#undef DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION
|
||||
#undef DMAPI5_TEST_MERGE_COMMENT
|
||||
|
||||
#undef DMAPI5_CHAT_COMMAND_NAME
|
||||
#undef DMAPI5_CHAT_COMMAND_PARAMS
|
||||
#undef DMAPI5_CHAT_COMMAND_USER
|
||||
|
||||
#undef DMAPI5_EVENT_NOTIFICATION_TYPE
|
||||
#undef DMAPI5_EVENT_NOTIFICATION_PARAMETERS
|
||||
|
||||
#undef DMAPI5_TOPIC_COMMAND_CHAT_COMMAND
|
||||
#undef DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION
|
||||
#undef DMAPI5_TOPIC_COMMAND_CHANGE_PORT
|
||||
#undef DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE
|
||||
#undef DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED
|
||||
#undef DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE
|
||||
#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
|
||||
#undef DMAPI5_TOPIC_COMMAND_HEARTBEAT
|
||||
#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
|
||||
|
||||
#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
|
||||
#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
|
||||
#undef DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION
|
||||
#undef DMAPI5_TOPIC_PARAMETER_NEW_PORT
|
||||
#undef DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE
|
||||
#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
|
||||
#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
|
||||
#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
|
||||
|
||||
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
|
||||
#undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES
|
||||
|
||||
#undef DMAPI5_REVISION_INFORMATION_COMMIT_SHA
|
||||
#undef DMAPI5_REVISION_INFORMATION_TIMESTAMP
|
||||
#undef DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA
|
||||
|
||||
#undef DMAPI5_CHAT_USER_ID
|
||||
#undef DMAPI5_CHAT_USER_FRIENDLY_NAME
|
||||
#undef DMAPI5_CHAT_USER_MENTION
|
||||
#undef DMAPI5_CHAT_USER_CHANNEL
|
||||
|
||||
#undef DMAPI5_CHAT_CHANNEL_ID
|
||||
#undef DMAPI5_CHAT_CHANNEL_FRIENDLY_NAME
|
||||
#undef DMAPI5_CHAT_CHANNEL_CONNECTION_NAME
|
||||
#undef DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL
|
||||
#undef DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL
|
||||
#undef DMAPI5_CHAT_CHANNEL_TAG
|
||||
|
||||
#undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME
|
||||
#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
|
||||
#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY
|
||||
15
code/modules/tgui/states/never.dm
Normal file
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2021 Arm A. Hammer
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: never_state
|
||||
*
|
||||
* Always closes the UI, no matter what. See the ui_state in religious_tool.dm to see an example
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(never_state, /datum/ui_state/never_state, new)
|
||||
|
||||
/datum/ui_state/never_state/can_use_topic(src_object, mob/user)
|
||||
return UI_CLOSE
|
||||
@@ -96,6 +96,8 @@
|
||||
window.send_message("ping")
|
||||
var/flush_queue = window.send_asset(get_asset_datum(
|
||||
/datum/asset/simple/namespaced/fontawesome))
|
||||
flush_queue |= window.send_asset(get_asset_datum(
|
||||
/datum/asset/simple/namespaced/tgfont))
|
||||
for(var/datum/asset/asset in src_object.ui_assets(user))
|
||||
flush_queue |= window.send_asset(asset)
|
||||
if (flush_queue)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
@@ -37,6 +37,9 @@
|
||||
* Initializes tgui panel.
|
||||
*/
|
||||
/datum/tgui_panel/proc/initialize(force = FALSE)
|
||||
set waitfor = FALSE
|
||||
// Minimal sleep to defer initialization to after client constructor
|
||||
sleep(1)
|
||||
initialized_at = world.time
|
||||
// Perform a clean initialization
|
||||
window.initialize(inline_assets = list(
|
||||
@@ -44,9 +47,11 @@
|
||||
get_asset_datum(/datum/asset/simple/tgui_panel),
|
||||
))
|
||||
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
|
||||
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
|
||||
window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
|
||||
// Other setup
|
||||
request_telemetry()
|
||||
addtimer(CALLBACK(src, .proc/on_initialize_timed_out), 2 SECONDS)
|
||||
addtimer(CALLBACK(src, .proc/on_initialize_timed_out), 5 SECONDS)
|
||||
|
||||
/**
|
||||
* private
|
||||
|
||||
@@ -6,6 +6,7 @@ $include game_options.txt
|
||||
#$include antag_rep.txt
|
||||
$include resources.txt
|
||||
# Cit-specific imports
|
||||
$include logging.txt
|
||||
$include entries/urls.txt
|
||||
$include entries/fail2topic.txt
|
||||
$include entries/lobby.txt
|
||||
|
||||
2
config/logging.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
# Forces tgui hrefs to be logged. Debug only.
|
||||
# EMERGENCY_TGUI_LOGGING
|
||||
0
dependencies.sh
Normal file → Executable file
@@ -1,15 +1,43 @@
|
||||
rules:
|
||||
## Enforce a maximum cyclomatic complexity allowed in a program
|
||||
complexity: [error, { max: 25 }]
|
||||
# complexity: [warn, { max: 25 }]
|
||||
## Enforce consistent brace style for blocks
|
||||
brace-style: [error, stroustrup, { allowSingleLine: false }]
|
||||
# brace-style: [warn, stroustrup, { allowSingleLine: false }]
|
||||
## Enforce the consistent use of either backticks, double, or single quotes
|
||||
quotes: [error, single, {
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true,
|
||||
}]
|
||||
react/jsx-closing-bracket-location: [error, {
|
||||
selfClosing: after-props,
|
||||
nonEmpty: after-props,
|
||||
}]
|
||||
react/display-name: error
|
||||
# quotes: [warn, single, {
|
||||
# avoidEscape: true,
|
||||
# allowTemplateLiterals: true,
|
||||
# }]
|
||||
# react/jsx-closing-bracket-location: [warn, {
|
||||
# selfClosing: after-props,
|
||||
# nonEmpty: after-props,
|
||||
# }]
|
||||
# react/display-name: warn
|
||||
|
||||
## Radar
|
||||
## ------------------------------------------------------
|
||||
# radar/cognitive-complexity: warn
|
||||
radar/max-switch-cases: warn
|
||||
radar/no-all-duplicated-branches: warn
|
||||
radar/no-collapsible-if: warn
|
||||
radar/no-collection-size-mischeck: warn
|
||||
radar/no-duplicate-string: warn
|
||||
radar/no-duplicated-branches: warn
|
||||
radar/no-element-overwrite: warn
|
||||
radar/no-extra-arguments: warn
|
||||
radar/no-identical-conditions: warn
|
||||
radar/no-identical-expressions: warn
|
||||
radar/no-identical-functions: warn
|
||||
radar/no-inverted-boolean-check: warn
|
||||
radar/no-one-iteration-loop: warn
|
||||
radar/no-redundant-boolean: warn
|
||||
radar/no-redundant-jump: warn
|
||||
radar/no-same-line-conditional: warn
|
||||
radar/no-small-switch: warn
|
||||
radar/no-unused-collection: warn
|
||||
radar/no-use-of-empty-return-value: warn
|
||||
radar/no-useless-catch: warn
|
||||
radar/prefer-immediate-return: warn
|
||||
radar/prefer-object-literal: warn
|
||||
radar/prefer-single-boolean-return: warn
|
||||
radar/prefer-while: warn
|
||||
|
||||
@@ -9,9 +9,8 @@ env:
|
||||
es6: true
|
||||
browser: true
|
||||
node: true
|
||||
globals:
|
||||
Byond: readonly
|
||||
plugins:
|
||||
- radar
|
||||
- react
|
||||
settings:
|
||||
react:
|
||||
@@ -20,7 +19,6 @@ rules:
|
||||
|
||||
## Possible Errors
|
||||
## ----------------------------------------
|
||||
|
||||
## Enforce “for” loop update clause moving the counter in the right
|
||||
## direction.
|
||||
# for-direction: error
|
||||
@@ -509,7 +507,7 @@ rules:
|
||||
## Require braces around arrow function bodies
|
||||
# arrow-body-style: error
|
||||
## Require parentheses around arrow function arguments
|
||||
arrow-parens: [error, as-needed]
|
||||
# arrow-parens: [error, as-needed]
|
||||
## Enforce consistent spacing before and after the arrow in arrow functions
|
||||
arrow-spacing: [error, { before: true, after: true }]
|
||||
## Require super() calls in constructors
|
||||
|
||||
8
tgui/.gitignore
vendored
@@ -13,10 +13,10 @@ package-lock.json
|
||||
!/.yarn/lock.yml
|
||||
|
||||
## Build artifacts
|
||||
/public/.tmp/**/*
|
||||
/public/**/*
|
||||
!/public/*.html
|
||||
# /public/.tmp/**/*
|
||||
# /public/**/*
|
||||
# !/public/*.html
|
||||
|
||||
## Previously ignored locations that are kept to avoid confusing git
|
||||
## while transitioning to a new project structure.
|
||||
/packages/tgui/public/**
|
||||
# /packages/tgui/public/**
|
||||
|
||||
12
tgui/.prettierrc.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
arrowParens: always
|
||||
bracketSpacing: true
|
||||
endOfLine: lf
|
||||
jsxBracketSameLine: true
|
||||
jsxSingleQuote: false
|
||||
printWidth: 80
|
||||
proseWrap: preserve
|
||||
quoteProps: preserve
|
||||
semi: true
|
||||
singleQuote: true
|
||||
tabWidth: 2
|
||||
trailingComma: es5
|
||||
86
tgui/.yarn/releases/yarn-berry.js
vendored
20
tgui/.yarn/sdks/eslint/lib/api.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint/lib/api.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint/lib/api.js your application uses
|
||||
module.exports = absRequire(`eslint/lib/api.js`);
|
||||
2
tgui/.yarn/sdks/eslint/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint",
|
||||
"version": "7.19.0-pnpify",
|
||||
"version": "7.21.0-pnpify",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
20
tgui/.yarn/sdks/typescript/lib/tsc.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsc.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/tsc.js`);
|
||||
111
tgui/.yarn/sdks/typescript/lib/tsserver.js
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes is much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = pnpApi.resolveVirtual(str);
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = str.replace(/\\/g, `/`)
|
||||
str = str.replace(/^\/?/, `/`);
|
||||
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
if (str.match(/\.zip\//)) {
|
||||
str = `${isVSCode ? `^` : ``}zip:${str}`;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?zip:\//, ``)
|
||||
: str.replace(/^\^?zip:/, ``);
|
||||
}
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let isVSCode = false;
|
||||
|
||||
return Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string} */ message) {
|
||||
const parsedMessage = JSON.parse(message)
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
parsedMessage.arguments.hostInfo === `vscode`
|
||||
) {
|
||||
isVSCode = true;
|
||||
}
|
||||
|
||||
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === `string` ? fromEditorPath(value) : value;
|
||||
}));
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
||||
20
tgui/.yarn/sdks/typescript/lib/typescript.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/typescript.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/typescript.js`);
|
||||
2
tgui/.yarn/sdks/typescript/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "typescript",
|
||||
"version": "4.1.5-pnpify",
|
||||
"version": "4.2.3-pnpify",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
enableScripts: false
|
||||
|
||||
logFilters:
|
||||
## DISABLED_BUILD_SCRIPTS
|
||||
- code: YN0004
|
||||
level: discard
|
||||
## INCOMPATIBLE_OS - fsevents junk
|
||||
- code: YN0062
|
||||
level: discard
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
|
||||
preferAggregateCacheInfo: true
|
||||
|
||||
preferInteractive: true
|
||||
|
||||
yarnPath: .yarn/releases/yarn-2.4.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-2.4.1.cjs
|
||||
|
||||
@@ -8,13 +8,13 @@ const createBabelConfig = options => {
|
||||
const { mode, presets = [], plugins = [] } = options;
|
||||
return {
|
||||
presets: [
|
||||
['@babel/preset-typescript', {
|
||||
[require.resolve('@babel/preset-typescript'), {
|
||||
allowDeclareFields: true,
|
||||
}],
|
||||
['@babel/preset-env', {
|
||||
[require.resolve('@babel/preset-env'), {
|
||||
modules: 'commonjs',
|
||||
useBuiltIns: 'entry',
|
||||
corejs: '3.8',
|
||||
corejs: '3.10',
|
||||
spec: false,
|
||||
loose: true,
|
||||
targets: [],
|
||||
@@ -22,13 +22,13 @@ const createBabelConfig = options => {
|
||||
...presets,
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-class-properties', {
|
||||
[require.resolve('@babel/plugin-proposal-class-properties'), {
|
||||
loose: true,
|
||||
}],
|
||||
'@babel/plugin-transform-jscript',
|
||||
'babel-plugin-inferno',
|
||||
'babel-plugin-transform-remove-console',
|
||||
'common/string.babel-plugin.cjs',
|
||||
require.resolve('@babel/plugin-transform-jscript'),
|
||||
require.resolve('babel-plugin-inferno'),
|
||||
require.resolve('babel-plugin-transform-remove-console'),
|
||||
require.resolve('common/string.babel-plugin.cjs'),
|
||||
...plugins,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ task-lint() {
|
||||
cd "${base_dir}"
|
||||
yarn run tsc
|
||||
echo "tgui: type check passed"
|
||||
yarn run eslint packages --ext .js,.jsx,.ts,.tsx,.cjs,.mjs "${@}"
|
||||
yarn run eslint packages --ext .js,.cjs,.ts,.tsx "${@}"
|
||||
echo "tgui: eslint check passed"
|
||||
}
|
||||
|
||||
@@ -89,8 +89,9 @@ task-clean() {
|
||||
rm -rf .yarn/cache
|
||||
rm -rf .yarn/unplugged
|
||||
rm -rf .yarn/webpack
|
||||
rm -rf .yarn/build-state.yml
|
||||
rm -rf .yarn/install-state.gz
|
||||
rm -f .yarn/build-state.yml
|
||||
rm -f .yarn/install-state.gz
|
||||
rm -f .yarn/install-target
|
||||
rm -f .pnp.js
|
||||
## NPM artifacts
|
||||
rm -rf **/node_modules
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
## Copyright (c) 2020 Aleksej Komarov
|
||||
## SPDX-License-Identifier: MIT
|
||||
|
||||
## Initial set-up
|
||||
## --------------------------------------------------------
|
||||
|
||||
## Normalize current directory
|
||||
$basedir = Split-Path $MyInvocation.MyCommand.Path
|
||||
$basedir = Resolve-Path "$($basedir)\.."
|
||||
Set-Location $basedir
|
||||
[Environment]::CurrentDirectory = $basedir
|
||||
|
||||
|
||||
## Functions
|
||||
## --------------------------------------------------------
|
||||
|
||||
function yarn {
|
||||
node.exe ".yarn\releases\yarn-berry.js" @Args
|
||||
}
|
||||
|
||||
function Remove-Quiet {
|
||||
Remove-Item -ErrorAction SilentlyContinue @Args
|
||||
}
|
||||
|
||||
function task-install {
|
||||
yarn install
|
||||
}
|
||||
|
||||
## Runs webpack
|
||||
function task-webpack {
|
||||
yarn run webpack-cli @Args
|
||||
}
|
||||
|
||||
## Runs a development server
|
||||
function task-dev-server {
|
||||
yarn node "packages/tgui-dev-server/index.esm.js" @Args
|
||||
}
|
||||
|
||||
## Run a linter through all packages
|
||||
function task-eslint {
|
||||
yarn run eslint packages @Args
|
||||
Write-Output "tgui: eslint check passed"
|
||||
}
|
||||
|
||||
## Mr. Proper
|
||||
function task-clean {
|
||||
## Build artifacts
|
||||
Remove-Quiet -Recurse -Force "public\.tmp"
|
||||
Remove-Quiet -Force "public\*.map"
|
||||
Remove-Quiet -Force "public\*.hot-update.*"
|
||||
## Yarn artifacts
|
||||
Remove-Quiet -Recurse -Force ".yarn\cache"
|
||||
Remove-Quiet -Recurse -Force ".yarn\unplugged"
|
||||
Remove-Quiet -Recurse -Force ".yarn\build-state.yml"
|
||||
Remove-Quiet -Recurse -Force ".yarn\install-state.gz"
|
||||
Remove-Quiet -Force ".pnp.js"
|
||||
## NPM artifacts
|
||||
Get-ChildItem -Path "." -Include "node_modules" -Recurse -File:$false | Remove-Item -Recurse -Force
|
||||
Remove-Quiet -Force "package-lock.json"
|
||||
}
|
||||
|
||||
|
||||
## Main
|
||||
## --------------------------------------------------------
|
||||
|
||||
if ($Args[0] -eq "--clean") {
|
||||
task-clean
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Args[0] -eq "--dev") {
|
||||
$Rest = $Args | Select-Object -Skip 1
|
||||
task-install
|
||||
task-dev-server @Rest
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Args[0] -eq "--lint") {
|
||||
$Rest = $Args | Select-Object -Skip 1
|
||||
task-install
|
||||
task-eslint @Rest
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Args[0] -eq "--lint-harder") {
|
||||
$Rest = $Args | Select-Object -Skip 1
|
||||
task-install
|
||||
task-eslint -c ".eslintrc-harder.yml" @Rest
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Args[0] -eq "--fix") {
|
||||
$Rest = $Args | Select-Object -Skip 1
|
||||
task-install
|
||||
task-eslint --fix @Rest
|
||||
exit 0
|
||||
}
|
||||
|
||||
## Analyze the bundle
|
||||
if ($Args[0] -eq "--analyze") {
|
||||
task-install
|
||||
task-webpack --mode=production --analyze
|
||||
exit 0
|
||||
}
|
||||
|
||||
## Make a production webpack build
|
||||
if ($Args.Length -eq 0) {
|
||||
task-install
|
||||
task-eslint
|
||||
task-webpack --mode=production
|
||||
exit 0
|
||||
}
|
||||
|
||||
## Run webpack with custom flags
|
||||
task-install
|
||||
task-webpack @Args
|
||||
@@ -54,7 +54,7 @@ function task-dev-server {
|
||||
function task-lint {
|
||||
yarn run tsc
|
||||
Write-Output "tgui: type check passed"
|
||||
yarn run eslint packages --ext .js,.jsx,.ts,.tsx,.cjs,.mjs @Args
|
||||
yarn run eslint packages --ext ".js,.cjs,.ts,.tsx" @Args
|
||||
Write-Output "tgui: eslint check passed"
|
||||
}
|
||||
|
||||
@@ -72,8 +72,9 @@ function task-clean {
|
||||
Remove-Quiet -Recurse -Force ".yarn\cache"
|
||||
Remove-Quiet -Recurse -Force ".yarn\unplugged"
|
||||
Remove-Quiet -Recurse -Force ".yarn\webpack"
|
||||
Remove-Quiet -Recurse -Force ".yarn\build-state.yml"
|
||||
Remove-Quiet -Recurse -Force ".yarn\install-state.gz"
|
||||
Remove-Quiet -Force ".yarn\build-state.yml"
|
||||
Remove-Quiet -Force ".yarn\install-state.gz"
|
||||
Remove-Quiet -Force ".yarn\install-target"
|
||||
Remove-Quiet -Force ".pnp.js"
|
||||
## NPM artifacts
|
||||
Get-ChildItem -Path "." -Include "node_modules" -Recurse -File:$false | Remove-Item -Recurse -Force
|
||||
|
||||
17
tgui/global.d.ts
vendored
@@ -5,6 +5,23 @@
|
||||
*/
|
||||
|
||||
declare global {
|
||||
// Webpack asset modules.
|
||||
// Should match extensions used in webpack config.
|
||||
declare module '*.png' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
type ByondType = {
|
||||
/**
|
||||
* True if javascript is running in BYOND.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
module.exports = {
|
||||
roots: ['<rootDir>/packages'],
|
||||
testMatch: [
|
||||
'<rootDir>/packages/**/__tests__/*.{js,jsx,ts,tsx}',
|
||||
'<rootDir>/packages/**/*.{spec,test}.{js,jsx,ts,tsx}',
|
||||
'<rootDir>/packages/**/__tests__/*.{js,ts,tsx}',
|
||||
'<rootDir>/packages/**/*.{spec,test}.{js,ts,tsx}',
|
||||
],
|
||||
testEnvironment: 'jsdom',
|
||||
testRunner: require.resolve('jest-circus/runner'),
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx|cjs|mjs)$': require.resolve('babel-jest'),
|
||||
'^.+\\.(js|cjs|ts|tsx)$': require.resolve('babel-jest'),
|
||||
},
|
||||
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],
|
||||
moduleFileExtensions: ['js', 'cjs', 'ts', 'tsx', 'json'],
|
||||
resetMocks: true,
|
||||
};
|
||||
|
||||
@@ -6,38 +6,39 @@
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.12.17",
|
||||
"@babel/eslint-parser": "^7.12.17",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.13",
|
||||
"@babel/core": "^7.13.15",
|
||||
"@babel/eslint-parser": "^7.13.14",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-transform-jscript": "^7.12.13",
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/jsdom": "^16.2.6",
|
||||
"@types/node": "^14.14.31",
|
||||
"@typescript-eslint/parser": "^4.15.1",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/jsdom": "^16.2.10",
|
||||
"@types/node": "^14.14.41",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-inferno": "^6.1.1",
|
||||
"babel-plugin-inferno": "^6.2.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"common": "workspace:*",
|
||||
"css-loader": "^5.0.2",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"css-loader": "^5.2.2",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-plugin-radar": "^0.2.1",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"inferno": "^7.4.8",
|
||||
"jest": "^26.6.3",
|
||||
"jest-circus": "^26.6.3",
|
||||
"jsdom": "^16.4.0",
|
||||
"mini-css-extract-plugin": "^1.3.8",
|
||||
"jsdom": "^16.5.3",
|
||||
"mini-css-extract-plugin": "^1.4.1",
|
||||
"sass": "^1.32.8",
|
||||
"sass-loader": "^11.0.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"typescript": "^4.1.5",
|
||||
"typescript": "^4.2.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.23.0",
|
||||
"webpack-bundle-analyzer": "^4.4.0",
|
||||
"webpack-cli": "^4.5.0"
|
||||
"webpack": "^5.33.2",
|
||||
"webpack-bundle-analyzer": "^4.4.1",
|
||||
"webpack-cli": "^4.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
20
tgui/packages/common/collections.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { range, zip } from "./collections";
|
||||
|
||||
// Type assertions, these will lint if the types are wrong.
|
||||
const _zip1: [string, number] = zip(["a"], [1])[0];
|
||||
|
||||
describe("range", () => {
|
||||
test("range(0, 5)", () => {
|
||||
expect(range(0, 5)).toEqual([0, 1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("zip", () => {
|
||||
test("zip(['a', 'b', 'c'], [1, 2, 3, 4])", () => {
|
||||
expect(zip(["a", "b", "c"], [1, 2, 3, 4])).toEqual([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -171,6 +171,15 @@ export const sortBy = (...iterateeFns) => array => {
|
||||
return mappedArray;
|
||||
};
|
||||
|
||||
export const sort = sortBy();
|
||||
|
||||
/**
|
||||
* Returns a range of numbers from start to end, exclusively.
|
||||
* For example, range(0, 5) will return [0, 1, 2, 3, 4].
|
||||
*/
|
||||
export const range = (start: number, end: number): number[] =>
|
||||
new Array(end - start).fill(null).map((_, index) => index + start);
|
||||
|
||||
/**
|
||||
* A fast implementation of reduce.
|
||||
*/
|
||||
@@ -203,14 +212,17 @@ export const reduce = (reducerFn, initialValue) => array => {
|
||||
* is determined by the order they occur in the array. The iteratee is
|
||||
* invoked with one argument: value.
|
||||
*/
|
||||
export const uniqBy = iterateeFn => array => {
|
||||
/* eslint-disable indent */
|
||||
export const uniqBy = <T extends unknown>(
|
||||
iterateeFn?: (value: T) => unknown
|
||||
) => (array: T[]) => {
|
||||
const { length } = array;
|
||||
const result = [];
|
||||
const seen = iterateeFn ? [] : result;
|
||||
let index = -1;
|
||||
outer:
|
||||
while (++index < length) {
|
||||
let value = array[index];
|
||||
let value: T | 0 = array[index];
|
||||
const computed = iterateeFn ? iterateeFn(value) : value;
|
||||
value = value !== 0 ? value : 0;
|
||||
if (computed === computed) {
|
||||
@@ -234,15 +246,20 @@ export const uniqBy = iterateeFn => array => {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
/* eslint-enable indent */
|
||||
|
||||
export const uniq = uniqBy();
|
||||
|
||||
type Zip<T extends unknown[][]> = {
|
||||
[I in keyof T]: T[I] extends (infer U)[] ? U : never;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* Creates an array of grouped elements, the first of which contains
|
||||
* the first elements of the given arrays, the second of which contains
|
||||
* the second elements of the given arrays, and so on.
|
||||
*
|
||||
* @returns {any[]}
|
||||
*/
|
||||
export const zip = (...arrays) => {
|
||||
export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
|
||||
if (arrays.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export const round = (value, precision) => {
|
||||
m = Math.pow(10, precision);
|
||||
value *= m;
|
||||
// sign of the number
|
||||
sgn = (value > 0) | -(value < 0);
|
||||
sgn = +(value > 0) | -(value < 0);
|
||||
// isHalf = value % 1 === 0.5 * sgn;
|
||||
isHalf = Math.abs(value % 1) >= 0.4999999999854481;
|
||||
f = Math.floor(value);
|
||||
73
tgui/packages/common/react.js
vendored
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper for conditionally adding/removing classes in React
|
||||
*
|
||||
* @param {any[]} classNames
|
||||
* @return {string}
|
||||
*/
|
||||
export const classes = classNames => {
|
||||
let className = '';
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
const part = classNames[i];
|
||||
if (typeof part === 'string') {
|
||||
className += part + ' ';
|
||||
}
|
||||
}
|
||||
return className;
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes children prop, so that it is always an array of VDom
|
||||
* elements.
|
||||
*/
|
||||
export const normalizeChildren = children => {
|
||||
if (Array.isArray(children)) {
|
||||
return children.flat().filter(value => value);
|
||||
}
|
||||
if (typeof children === 'object') {
|
||||
return [children];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Shallowly checks if two objects are different.
|
||||
* Credit: https://github.com/developit/preact-compat
|
||||
*/
|
||||
export const shallowDiffers = (a, b) => {
|
||||
let i;
|
||||
for (i in a) {
|
||||
if (!(i in b)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (i in b) {
|
||||
if (a[i] !== b[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default inferno hooks for pure components.
|
||||
*/
|
||||
export const pureComponentHooks = {
|
||||
onComponentShouldUpdate: (lastProps, nextProps) => {
|
||||
return shallowDiffers(lastProps, nextProps);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper to determine whether the object is renderable by React.
|
||||
*/
|
||||
export const canRender = value => {
|
||||
return value !== undefined
|
||||
&& value !== null
|
||||
&& typeof value !== 'boolean';
|
||||
};
|
||||
@@ -11,7 +11,7 @@ export const IMPL_LOCAL_STORAGE = 1;
|
||||
export const IMPL_INDEXED_DB = 2;
|
||||
|
||||
const INDEXED_DB_VERSION = 1;
|
||||
const INDEXED_DB_NAME = 'tgui-citadel-main';
|
||||
const INDEXED_DB_NAME = 'tgui-citadel-rp';
|
||||
const INDEXED_DB_STORE_NAME = 'storage-v1';
|
||||
|
||||
const READ_ONLY = 'readonly';
|
||||
|
||||
1
tgui/packages/tgfont/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/dist
|
||||
14
tgui/packages/tgfont/config.cjs
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2021 AnturK https://github.com/AnturK
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
name: 'tgfont',
|
||||
inputDir: './icons',
|
||||
outputDir: './dist',
|
||||
fontTypes: ['woff2', 'eot'],
|
||||
assetTypes: ['css'],
|
||||
prefix: 'tg',
|
||||
};
|
||||
1
tgui/packages/tgfont/icons/air-tank-slash.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 437.4 434.4" style="enable-background:new 0 0 437.4 434.4" xml:space="preserve"><style>.st0{fill:none;stroke:#000;stroke-width:.57;stroke-miterlimit:10}</style><g id="Layer_1"><path class="st0" d="m41.18 306.18 84.43 84.43-19.53 20.8c-11.26 11.99-26.1 17.98-40.93 17.98-14.84 0-29.68-5.99-40.93-17.98-22.52-23.97-22.52-63.2 0-87.18l16.96-18.05zm310.48-156.3L139.65 375.65l-84.43-84.43L269.8 62.7c22.51-23.98 59.34-23.98 81.86 0 22.51 23.97 22.51 63.2 0 87.18z"/><path d="M426.11 88.46c-24.57-26.71-49.15-53.42-73.72-80.13a93.834 93.834 0 0 1-30.93-4.39c-6.96 7.41-6.96 19.53 0 26.94l79.36 84.52c6.96 7.41 18.34 7.41 25.29 0 6.96-7.41 6.96-19.53 0-26.94zM248.07 35.83c13.66 16.01 4.9 48.19-19.57 71.87-24.47 23.68-33.21 29.27-46.88 13.25-13.66-16.01-2.33-26.15 22.14-49.83 24.47-23.67 30.65-51.3 44.31-35.29z" style="stroke-width:.5;fill:none;stroke:#000;stroke-miterlimit:10"/><path class="st0" d="m139.93 375.93-.28-.28m-13.72 15.28-.32-.32"/><path transform="rotate(-45.001 211.427 209.932)" style="stroke:#000;stroke-width:18;stroke-miterlimit:10" d="M191.43-6.09h40v432.04h-40z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
tgui/packages/tgfont/icons/air-tank.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 437.4 434.4" style="enable-background:new 0 0 437.4 434.4" xml:space="preserve"><style>.st0{fill:none;stroke:#000;stroke-width:.57;stroke-miterlimit:10}</style><g id="Layer_1"><path class="st0" d="m41.18 306.18 84.43 84.43-19.53 20.8c-11.26 11.99-26.1 17.98-40.93 17.98-14.84 0-29.68-5.99-40.93-17.98-22.52-23.97-22.52-63.2 0-87.18l16.96-18.05zm310.48-156.3L139.65 375.65l-84.43-84.43L269.8 62.7c22.51-23.98 59.34-23.98 81.86 0 22.51 23.97 22.51 63.2 0 87.18z"/><path d="M426.11 88.46c-24.57-26.71-49.15-53.42-73.72-80.13a93.834 93.834 0 0 1-30.93-4.39c-6.96 7.41-6.96 19.53 0 26.94l79.36 84.52c6.96 7.41 18.34 7.41 25.29 0 6.96-7.41 6.96-19.53 0-26.94zM248.07 35.83c13.66 16.01 4.9 48.19-19.57 71.87-24.47 23.68-33.21 29.27-46.88 13.25-13.66-16.01-2.33-26.15 22.14-49.83 24.47-23.67 30.65-51.3 44.31-35.29z" style="stroke-width:.5;fill:none;stroke:#000;stroke-miterlimit:10"/><path class="st0" d="m139.93 375.93-.28-.28m-13.72 15.28-.32-.32"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1007 B |
1
tgui/packages/tgfont/icons/image-minus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="670" height="512"><path d="M655.714 228H484.286c-7.857 0-14.286 6.412-14.286 14.25v28.5c0 7.838 6.429 14.25 14.286 14.25h171.428c7.857 0 14.286-6.413 14.286-14.25v-28.5c0-7.838-6.429-14.25-14.286-14.25z" style="stroke-width:.89174"/><path d="M384 121.941V128H256V0h6.059a24 24 0 0 1 16.97 7.029l97.941 97.941a24.002 24.002 0 0 1 7.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160zm-135.46 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.54 328c4.686-4.686 11.799-4.201 16.485.485L160.54 368l103.52-103.52c4.686-4.686 12.284-4.686 16.971 0l39.514 39.515z"/></svg>
|
||||
|
After Width: | Height: | Size: 746 B |
1
tgui/packages/tgfont/icons/image-plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="670" height="512"><path d="M655.714 228.429h-57.143v-57.143c0-7.857-6.428-14.286-14.285-14.286h-28.572c-7.857 0-14.285 6.429-14.285 14.286v57.143h-57.143c-7.857 0-14.286 6.428-14.286 14.285v28.572c0 7.857 6.429 14.285 14.286 14.285h57.143v57.143c0 7.857 6.428 14.286 14.285 14.286h28.572c7.857 0 14.285-6.429 14.285-14.286v-57.143h57.143c7.857 0 14.286-6.428 14.286-14.285v-28.572c0-7.857-6.429-14.285-14.286-14.285z" style="stroke-width:.892857"/><path d="M384 121.941V128H256V0h6.059a24 24 0 0 1 16.97 7.029l97.941 97.941a24.002 24.002 0 0 1 7.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160zm-135.46 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.54 328c4.686-4.686 11.799-4.201 16.485.485L160.54 368l103.52-103.52c4.686-4.686 12.284-4.686 16.971 0l39.514 39.515z"/></svg>
|
||||
|
After Width: | Height: | Size: 961 B |
3
tgui/packages/tgfont/icons/nanotrasen-logo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 425 200" opacity=".33"><path d="M178.004.039H106.8a6.761 6.026 0 0 0-6.761 6.025v187.872a6.761 6.026 0 0 0 6.761 6.025h53.107a6.761 6.026 0 0 0 6.762-6.025V92.392l72.216 104.7a6.761 6.026 0 0 0 5.76 2.87H318.2a6.761 6.026 0 0 0 6.761-6.026V6.064A6.761 6.026 0 0 0 318.2.04h-54.717a6.761 6.026 0 0 0-6.76 6.025v102.62L183.763 2.909a6.761 6.026 0 0 0-5.76-2.87zM4.845 22.109A13.412 12.502 0 0 1 13.478.039h66.118A5.365 5 0 0 1 84.96 5.04v79.88zm415.31 155.782a13.412 12.502 0 0 1-8.633 22.07h-66.118a5.365 5 0 0 1-5.365-5.001v-79.88z"/></svg>
|
||||
<!-- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -->
|
||||
<!-- http://creativecommons.org/licenses/by-sa/4.0/ -->
|
||||
|
After Width: | Height: | Size: 770 B |
1
tgui/packages/tgfont/icons/sound-minus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="542" height="512"><path d="M215.03 71.05 126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97z"/><path d="M527.714 228H356.286c-7.857 0-14.286 6.412-14.286 14.25v28.5c0 7.838 6.429 14.25 14.286 14.25h171.428c7.857 0 14.286-6.413 14.286-14.25v-28.5c0-7.838-6.429-14.25-14.286-14.25z" style="stroke-width:.89174"/></svg>
|
||||
|
After Width: | Height: | Size: 469 B |
1
tgui/packages/tgfont/icons/sound-plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="542" height="512"><path d="M527.714 227.929h-57.143v-57.143c0-7.857-6.428-14.286-14.285-14.286h-28.572c-7.857 0-14.285 6.429-14.285 14.286v57.143h-57.143c-7.857 0-14.286 6.428-14.286 14.285v28.572c0 7.857 6.429 14.285 14.286 14.285h57.143v57.143c0 7.857 6.428 14.286 14.285 14.286h28.572c7.857 0 14.285-6.429 14.285-14.286v-57.143h57.143c7.857 0 14.286-6.428 14.286-14.285v-28.572c0-7.857-6.429-14.285-14.286-14.285z" style="stroke-width:.892857"/><path d="M215.03 71.05 126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97z"/></svg>
|
||||
|
After Width: | Height: | Size: 684 B |
3
tgui/packages/tgfont/icons/syndicate-logo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 289.742" opacity=".33"><path d="M93.538 0c-18.113 0-34.22 3.112-48.324 9.334-13.965 6.222-24.612 15.072-31.94 26.547C6.084 47.22 2.972 60.631 2.972 76.116c0 10.647 2.725 20.465 8.175 29.453 5.616 8.987 14.039 17.352 25.27 25.094 11.23 7.606 26.507 15.419 45.83 23.438 19.984 8.296 34.849 15.555 44.593 21.776 9.744 6.223 16.761 12.859 21.055 19.91 4.295 7.052 6.442 15.764 6.442 26.134 0 16.178-5.202 28.483-15.606 36.917-10.24 8.435-25.022 12.653-44.345 12.653-14.039 0-25.516-1.66-34.434-4.978-8.918-3.457-16.186-8.711-21.8-15.763-5.616-7.052-10.076-16.661-13.379-28.829H0v56.827c33.857 7.328 63.749 10.994 89.678 10.994 16.02 0 30.72-1.383 44.098-4.148 13.542-2.904 25.104-7.467 34.683-13.69 9.744-6.359 17.34-14.519 22.79-24.474 5.45-10.093 8.175-22.4 8.175-36.917 0-12.997-3.302-24.335-9.908-34.014-6.44-9.818-15.525-18.527-27.251-26.132-11.561-7.604-27.911-15.831-49.051-24.68-17.506-7.19-30.72-13.69-39.638-19.497S54.969 93.756 49.479 87.316c-5.426-6.366-9.658-15.07-9.658-24.887 0-9.264 2.075-17.214 6.223-23.85C57.142 24.18 87.331 36.782 91.12 62.925c4.84 6.775 8.85 16.247 12.03 28.415h20.532v-56c-4.479-5.924-9.955-10.631-15.909-14.373 1.64.479 3.19 1.023 4.639 1.64 6.498 2.626 12.168 7.327 17.007 14.103 4.84 6.775 8.85 16.246 12.03 28.414 0 0 8.48-.129 8.49-.002.417 6.415-1.754 9.453-4.124 12.561-2.417 3.17-5.145 6.79-4.003 13.003 1.508 8.203 10.184 10.597 14.622 9.312-3.318-.5-5.318-1.75-5.318-1.75s1.876.999 5.65-1.36c-3.276.956-10.704-.797-11.8-6.763-.958-5.208.946-7.295 3.4-10.514 2.455-3.22 5.285-6.959 4.685-14.489l.003.002h8.927v-56c-15.072-3.871-27.653-6.36-37.747-7.465C114.279.552 104.046 0 93.537 0zm70.321 17.309.238 40.305c1.318 1.226 2.44 2.278 3.341 3.106 4.84 6.775 8.85 16.246 12.03 28.414H200v-56c-6.677-4.594-19.836-10.473-36.14-15.825zm-28.12 5.605 8.565 17.717c-11.97-6.467-13.847-9.717-8.565-17.717zm22.797 0c2.771 8 1.787 11.25-4.494 17.717l4.494-17.717zm15.222 24.009 8.565 17.716c-11.97-6.466-13.847-9.717-8.565-17.716zm22.797 0c2.771 8 1.787 11.25-4.494 17.716l4.494-17.716zM97.44 49.13l8.565 17.716c-11.97-6.467-13.847-9.717-8.565-17.716zm22.795 0c2.772 7.999 1.788 11.25-4.493 17.716l4.493-17.716z"/></svg>
|
||||
<!-- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -->
|
||||
<!-- http://creativecommons.org/licenses/by-sa/4.0/ -->
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
14
tgui/packages/tgfont/mkdist.cjs
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2021 AnturK https://github.com/AnturK
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// Change working directory to project root
|
||||
process.chdir(__dirname);
|
||||
|
||||
// Silently make a dist folder
|
||||
try {
|
||||
require('fs').mkdirSync('dist');
|
||||
}
|
||||
catch (err) {}
|
||||
11
tgui/packages/tgfont/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "tgfont",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"fantasticon": "^1.1.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node mkdist.cjs && fantasticon --config config.cjs"
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ DreamSeeker.getInstancesByPids = async pids => {
|
||||
}
|
||||
if (pidsToResolve.length > 0) {
|
||||
try {
|
||||
const command = 'netstat -ano | findstr LISTENING';
|
||||
const command = 'netstat -ano | findstr TCP | findstr 0.0.0.0:0';
|
||||
const { stdout } = await promisify(exec)(command, {
|
||||
// Max buffer of 1MB (default is 200KB)
|
||||
maxBuffer: 1024 * 1024,
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"glob": "^7.1.6",
|
||||
"source-map": "^0.7.3",
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"ws": "^7.4.3"
|
||||
"ws": "^7.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { createLogger } from 'common/logging.js';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { basename } from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { resolveGlob, resolvePath } from './util.js';
|
||||
import { regQuery } from './winreg.js';
|
||||
import { DreamSeeker } from './dreamseeker.js';
|
||||
@@ -68,7 +67,7 @@ export const findCacheRoot = async () => {
|
||||
|
||||
const onCacheRootFound = cacheRoot => {
|
||||
logger.log(`found cache at '${cacheRoot}'`);
|
||||
// Plant dummy
|
||||
// Plant a dummy
|
||||
fs.closeSync(fs.openSync(cacheRoot + '/dummy', 'w'));
|
||||
};
|
||||
|
||||
@@ -93,15 +92,21 @@ export const reloadByondCache = async bundleDir => {
|
||||
for (let cacheDir of cacheDirs) {
|
||||
// Clear garbage
|
||||
const garbage = await resolveGlob(cacheDir, './*.+(bundle|chunk|hot-update).*');
|
||||
for (let file of garbage) {
|
||||
await promisify(fs.unlink)(file);
|
||||
try {
|
||||
for (let file of garbage) {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
// Copy assets
|
||||
for (let asset of assets) {
|
||||
const destination = resolvePath(cacheDir, basename(asset));
|
||||
fs.writeFileSync(destination, fs.readFileSync(asset));
|
||||
}
|
||||
logger.log(`copied ${assets.length} files to '${cacheDir}'`);
|
||||
}
|
||||
// Copy assets
|
||||
for (let asset of assets) {
|
||||
const destination = resolvePath(cacheDir, basename(asset));
|
||||
await promisify(fs.copyFile)(asset, destination);
|
||||
catch (err) {
|
||||
logger.error(`failed copying to '${cacheDir}'`);
|
||||
logger.error(err);
|
||||
}
|
||||
logger.log(`copied ${assets.length} files to '${cacheDir}'`);
|
||||
}
|
||||
// Notify dreamseeker
|
||||
const dss = await dssPromise;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "4.3.0",
|
||||
"dependencies": {
|
||||
"common": "workspace:*",
|
||||
"dompurify": "^2.2.6",
|
||||
"dompurify": "^2.2.7",
|
||||
"inferno": "^7.4.8",
|
||||
"tgui": "workspace:*",
|
||||
"tgui-dev-server": "workspace:*",
|
||||
|
||||
@@ -73,19 +73,19 @@ export const setClientTheme = name => {
|
||||
'output.text-color': COLOR_WHITE_TEXT,
|
||||
'statwindow.background-color': COLOR_WHITE_DARKBG,
|
||||
'statwindow.text-color': COLOR_WHITE_TEXT,
|
||||
'stat.background-color': COLOR_WHITE_BG,
|
||||
'stat.tab-background-color': COLOR_WHITE_DARKBG,
|
||||
'stat.text-color': COLOR_WHITE_TEXT,
|
||||
'stat.tab-text-color': COLOR_WHITE_TEXT,
|
||||
'stat.prefix-color': COLOR_WHITE_TEXT,
|
||||
'stat.suffix-color': COLOR_WHITE_TEXT,
|
||||
'statbrowser.background-color': COLOR_WHITE_BG,
|
||||
'statbrowser.tab-background-color': COLOR_WHITE_DARKBG,
|
||||
'statbrowser.text-color': COLOR_WHITE_TEXT,
|
||||
'statbrowser.tab-text-color': COLOR_WHITE_TEXT,
|
||||
'statbrowser.prefix-color': COLOR_WHITE_TEXT,
|
||||
'statbrowser.suffix-color': COLOR_WHITE_TEXT,
|
||||
// Say, OOC, me Buttons etc.
|
||||
'saybutton.background-color': COLOR_WHITE_DARKBG,
|
||||
'saybutton.text-color': COLOR_WHITE_TEXT,
|
||||
// 'oocbutton.background-color': COLOR_WHITE_DARKBG,
|
||||
// 'oocbutton.text-color': COLOR_WHITE_TEXT,
|
||||
// 'mebutton.background-color': COLOR_WHITE_DARKBG,
|
||||
// 'mebutton.text-color': COLOR_WHITE_TEXT,
|
||||
'oocbutton.background-color': COLOR_WHITE_DARKBG,
|
||||
'oocbutton.text-color': COLOR_WHITE_TEXT,
|
||||
'mebutton.background-color': COLOR_WHITE_DARKBG,
|
||||
'mebutton.text-color': COLOR_WHITE_TEXT,
|
||||
'asset_cache_browser.background-color': COLOR_WHITE_DARKBG,
|
||||
'asset_cache_browser.text-color': COLOR_WHITE_TEXT,
|
||||
'tooltip.background-color': COLOR_WHITE_BG,
|
||||
@@ -123,19 +123,19 @@ export const setClientTheme = name => {
|
||||
'output.text-color': COLOR_DARK_TEXT,
|
||||
'statwindow.background-color': COLOR_DARK_DARKBG,
|
||||
'statwindow.text-color': COLOR_DARK_TEXT,
|
||||
'stat.background-color': COLOR_DARK_DARKBG,
|
||||
'stat.tab-background-color': COLOR_DARK_BG,
|
||||
'stat.text-color': COLOR_DARK_TEXT,
|
||||
'stat.tab-text-color': COLOR_DARK_TEXT,
|
||||
'stat.prefix-color': COLOR_DARK_TEXT,
|
||||
'stat.suffix-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.background-color': COLOR_DARK_DARKBG,
|
||||
'statbrowser.tab-background-color': COLOR_DARK_BG,
|
||||
'statbrowser.text-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.tab-text-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.prefix-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.suffix-color': COLOR_DARK_TEXT,
|
||||
// Say, OOC, me Buttons etc.
|
||||
'saybutton.background-color': COLOR_DARK_BG,
|
||||
'saybutton.text-color': COLOR_DARK_TEXT,
|
||||
// 'oocbutton.background-color': COLOR_DARK_BG,
|
||||
// 'oocbutton.text-color': COLOR_DARK_TEXT,
|
||||
// 'mebutton.background-color': COLOR_DARK_BG,
|
||||
// 'mebutton.text-color': COLOR_DARK_TEXT,
|
||||
'oocbutton.background-color': COLOR_DARK_BG,
|
||||
'oocbutton.text-color': COLOR_DARK_TEXT,
|
||||
'mebutton.background-color': COLOR_DARK_BG,
|
||||
'mebutton.text-color': COLOR_DARK_TEXT,
|
||||
'asset_cache_browser.background-color': COLOR_DARK_BG,
|
||||
'asset_cache_browser.text-color': COLOR_DARK_TEXT,
|
||||
'tooltip.background-color': COLOR_DARK_BG,
|
||||
@@ -173,19 +173,19 @@ export const setClientTheme = name => {
|
||||
'output.text-color': COLOR_DARK_TEXT,
|
||||
'statwindow.background-color': COLOR_DARK_DARKBG,
|
||||
'statwindow.text-color': COLOR_DARK_TEXT,
|
||||
'stat.background-color': COLOR_DARK_DARKBG,
|
||||
'stat.tab-background-color': COLOR_DARK_BG,
|
||||
'stat.text-color': COLOR_DARK_TEXT,
|
||||
'stat.tab-text-color': COLOR_DARK_TEXT,
|
||||
'stat.prefix-color': COLOR_DARK_TEXT,
|
||||
'stat.suffix-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.background-color': COLOR_DARK_DARKBG,
|
||||
'statbrowser.tab-background-color': COLOR_DARK_BG,
|
||||
'statbrowser.text-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.tab-text-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.prefix-color': COLOR_DARK_TEXT,
|
||||
'statbrowser.suffix-color': COLOR_DARK_TEXT,
|
||||
// Say, OOC, me Buttons etc.
|
||||
'saybutton.background-color': COLOR_DARK_BG,
|
||||
'saybutton.text-color': COLOR_DARK_TEXT,
|
||||
// 'oocbutton.background-color': COLOR_DARK_BG,
|
||||
// 'oocbutton.text-color': COLOR_DARK_TEXT,
|
||||
// 'mebutton.background-color': COLOR_DARK_BG,
|
||||
// 'mebutton.text-color': COLOR_DARK_TEXT,
|
||||
'oocbutton.background-color': COLOR_DARK_BG,
|
||||
'oocbutton.text-color': COLOR_DARK_TEXT,
|
||||
'mebutton.background-color': COLOR_DARK_BG,
|
||||
'mebutton.text-color': COLOR_DARK_TEXT,
|
||||
'asset_cache_browser.background-color': COLOR_DARK_BG,
|
||||
'asset_cache_browser.text-color': COLOR_DARK_TEXT,
|
||||
'tooltip.background-color': COLOR_DARK_BG,
|
||||
|
||||
@@ -14,8 +14,4 @@ import './ie8';
|
||||
import './dom4';
|
||||
import './css-om';
|
||||
import './inferno';
|
||||
|
||||
// Fetch is required for Webpack HMR
|
||||
if (module.hot) {
|
||||
require('whatwg-fetch');
|
||||
}
|
||||
import 'unfetch/polyfill';
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"name": "tgui-polyfill",
|
||||
"version": "4.3.0",
|
||||
"dependencies": {
|
||||
"core-js": "^3.9.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"whatwg-fetch": "^3.6.1"
|
||||
"core-js": "^3.10.1",
|
||||
"regenerator-runtime": "^0.13.8",
|
||||
"unfetch": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="user-secret" class="svg-inline--fa fa-user-secret fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" opacity=".33">
|
||||
<path fill="currentColor" d="M383.9 308.3l23.9-62.6c4-10.5-3.7-21.7-15-21.7h-58.5c11-18.9 17.8-40.6 17.8-64v-.3c39.2-7.8 64-19.1 64-31.7 0-13.3-27.3-25.1-70.1-33-9.2-32.8-27-65.8-40.6-82.8-9.5-11.9-25.9-15.6-39.5-8.8l-27.6 13.8c-9 4.5-19.6 4.5-28.6 0L182.1 3.4c-13.6-6.8-30-3.1-39.5 8.8-13.5 17-31.4 50-40.6 82.8-42.7 7.9-70 19.7-70 33 0 12.6 24.8 23.9 64 31.7v.3c0 23.4 6.8 45.1 17.8 64H56.3c-11.5 0-19.2 11.7-14.7 22.3l25.8 60.2C27.3 329.8 0 372.7 0 422.4v44.8C0 491.9 20.1 512 44.8 512h358.4c24.7 0 44.8-20.1 44.8-44.8v-44.8c0-48.4-25.8-90.4-64.1-114.1zM176 480l-41.6-192 49.6 32 24 40-32 120zm96 0l-32-120 24-40 49.6-32L272 480zm41.7-298.5c-3.9 11.9-7 24.6-16.5 33.4-10.1 9.3-48 22.4-64-25-2.8-8.4-15.4-8.4-18.3 0-17 50.2-56 32.4-64 25-9.5-8.8-12.7-21.5-16.5-33.4-.8-2.5-6.3-5.7-6.3-5.8v-10.8c28.3 3.6 61 5.8 96 5.8s67.7-2.1 96-5.8v10.8c-.1.1-5.6 3.2-6.4 5.8z"></path>
|
||||
</svg>
|
||||
</svg>
|
||||
<!-- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -->
|
||||
<!-- http://creativecommons.org/licenses/by-sa/4.0/ -->
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
3
tgui/packages/tgui/assets/bg-wizard.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="meteor" class="svg-inline--fa fa-meteor fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" opacity=".33"><path fill="currentColor" d="M511.328,20.8027c-11.60759,38.70264-34.30724,111.70173-61.30311,187.70077,6.99893,2.09372,13.4042,4,18.60653,5.59368a16.06158,16.06158,0,0,1,9.49854,22.906c-22.106,42.29635-82.69047,152.795-142.47819,214.40356-.99984,1.09373-1.99969,2.5-2.99954,3.49995A194.83046,194.83046,0,1,1,57.085,179.41009c.99985-1,2.40588-2,3.49947-3,61.59994-59.90549,171.97367-120.40473,214.37343-142.4982a16.058,16.058,0,0,1,22.90274,9.49988c1.59351,5.09368,3.49947,11.5936,5.5929,18.59351C379.34818,35.00565,452.43074,12.30281,491.12794.70921A16.18325,16.18325,0,0,1,511.328,20.8027ZM319.951,320.00207A127.98041,127.98041,0,1,0,191.97061,448.00046,127.97573,127.97573,0,0,0,319.951,320.00207Zm-127.98041-31.9996a31.9951,31.9951,0,1,1-31.9951-31.9996A31.959,31.959,0,0,1,191.97061,288.00247Zm31.9951,79.999a15.99755,15.99755,0,1,1-15.99755-15.9998A16.04975,16.04975,0,0,1,223.96571,368.00147Z"></path></svg>
|
||||
<!-- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -->
|
||||
<!-- http://creativecommons.org/licenses/by-sa/4.0/ -->
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,383 +0,0 @@
|
||||
/**
|
||||
* This file provides a clear separation layer between backend updates
|
||||
* and what state our React app sees.
|
||||
*
|
||||
* Sometimes backend can response without a "data" field, but our final
|
||||
* state will still contain previous "data" because we are merging
|
||||
* the response with already existing state.
|
||||
*
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perf } from 'common/perf';
|
||||
import { setupDrag } from './drag';
|
||||
import { focusMap } from './focus';
|
||||
import { createLogger } from './logging';
|
||||
import { resumeRenderer, suspendRenderer } from './renderer';
|
||||
|
||||
const logger = createLogger('backend');
|
||||
|
||||
export const backendUpdate = state => ({
|
||||
type: 'backend/update',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const backendSetSharedState = (key, nextState) => ({
|
||||
type: 'backend/setSharedState',
|
||||
payload: { key, nextState },
|
||||
});
|
||||
|
||||
export const backendSuspendStart = () => ({
|
||||
type: 'backend/suspendStart',
|
||||
});
|
||||
|
||||
export const backendSuspendSuccess = () => ({
|
||||
type: 'backend/suspendSuccess',
|
||||
payload: {
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
config: {},
|
||||
data: {},
|
||||
shared: {},
|
||||
// Start as suspended
|
||||
suspended: Date.now(),
|
||||
suspending: false,
|
||||
};
|
||||
|
||||
export const backendReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
if (type === 'backend/update') {
|
||||
// Merge config
|
||||
const config = {
|
||||
...state.config,
|
||||
...payload.config,
|
||||
};
|
||||
// Merge data
|
||||
const data = {
|
||||
...state.data,
|
||||
...payload.static_data,
|
||||
...payload.data,
|
||||
};
|
||||
// Merge shared states
|
||||
const shared = { ...state.shared };
|
||||
if (payload.shared) {
|
||||
for (let key of Object.keys(payload.shared)) {
|
||||
const value = payload.shared[key];
|
||||
if (value === '') {
|
||||
shared[key] = undefined;
|
||||
}
|
||||
else {
|
||||
shared[key] = JSON.parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return new state
|
||||
return {
|
||||
...state,
|
||||
config,
|
||||
data,
|
||||
shared,
|
||||
suspended: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'backend/setSharedState') {
|
||||
const { key, nextState } = payload;
|
||||
return {
|
||||
...state,
|
||||
shared: {
|
||||
...state.shared,
|
||||
[key]: nextState,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendStart') {
|
||||
return {
|
||||
...state,
|
||||
suspending: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendSuccess') {
|
||||
const { timestamp } = payload;
|
||||
return {
|
||||
...state,
|
||||
data: {},
|
||||
shared: {},
|
||||
config: {
|
||||
...state.config,
|
||||
title: '',
|
||||
status: 1,
|
||||
},
|
||||
suspending: false,
|
||||
suspended: timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export const backendMiddleware = store => {
|
||||
let fancyState;
|
||||
let suspendInterval;
|
||||
|
||||
return next => action => {
|
||||
const { suspended } = selectBackend(store.getState());
|
||||
const { type, payload } = action;
|
||||
|
||||
if (type === 'update') {
|
||||
store.dispatch(backendUpdate(payload));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'suspend') {
|
||||
store.dispatch(backendSuspendSuccess());
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'ping') {
|
||||
sendMessage({
|
||||
type: 'pingReply',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendStart' && !suspendInterval) {
|
||||
logger.log(`suspending (${window.__windowId__})`);
|
||||
// Keep sending suspend messages until it succeeds.
|
||||
// It may fail multiple times due to topic rate limiting.
|
||||
const suspendFn = () => sendMessage({
|
||||
type: 'suspend',
|
||||
});
|
||||
suspendFn();
|
||||
suspendInterval = setInterval(suspendFn, 2000);
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendSuccess') {
|
||||
suspendRenderer();
|
||||
clearInterval(suspendInterval);
|
||||
suspendInterval = undefined;
|
||||
Byond.winset(window.__windowId__, {
|
||||
'is-visible': false,
|
||||
});
|
||||
setImmediate(() => focusMap());
|
||||
}
|
||||
|
||||
if (type === 'backend/update') {
|
||||
const fancy = payload.config?.window?.fancy;
|
||||
// Initialize fancy state
|
||||
if (fancyState === undefined) {
|
||||
fancyState = fancy;
|
||||
}
|
||||
// React to changes in fancy
|
||||
else if (fancyState !== fancy) {
|
||||
logger.log('changing fancy mode to', fancy);
|
||||
fancyState = fancy;
|
||||
Byond.winset(window.__windowId__, {
|
||||
titlebar: !fancy,
|
||||
'can-resize': !fancy,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Resume on incoming update
|
||||
if (type === 'backend/update' && suspended) {
|
||||
// Show the payload
|
||||
logger.log('backend/update', payload);
|
||||
// Signal renderer that we have resumed
|
||||
resumeRenderer();
|
||||
// Setup drag
|
||||
setupDrag();
|
||||
// We schedule this for the next tick here because resizing and unhiding
|
||||
// during the same tick will flash with a white background.
|
||||
setImmediate(() => {
|
||||
perf.mark('resume/start');
|
||||
// Doublecheck if we are not re-suspended.
|
||||
const { suspended } = selectBackend(store.getState());
|
||||
if (suspended) {
|
||||
return;
|
||||
}
|
||||
Byond.winset(window.__windowId__, {
|
||||
'is-visible': true,
|
||||
});
|
||||
perf.mark('resume/finish');
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.log('visible in',
|
||||
perf.measure('render/finish', 'resume/finish'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return next(action);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to /datum/tgui_window.
|
||||
*/
|
||||
export const sendMessage = (message = {}) => {
|
||||
const { payload, ...rest } = message;
|
||||
const data = {
|
||||
// Message identifying header
|
||||
tgui: 1,
|
||||
window_id: window.__windowId__,
|
||||
// Message body
|
||||
...rest,
|
||||
};
|
||||
// JSON-encode the payload
|
||||
if (payload !== null && payload !== undefined) {
|
||||
data.payload = JSON.stringify(payload);
|
||||
}
|
||||
Byond.topic(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends an action to `ui_act` on `src_object` that this tgui window
|
||||
* is associated with.
|
||||
*/
|
||||
export const sendAct = (action, payload = {}) => {
|
||||
// Validate that payload is an object
|
||||
const isObject = typeof payload === 'object'
|
||||
&& payload !== null
|
||||
&& !Array.isArray(payload);
|
||||
if (!isObject) {
|
||||
logger.error(`Payload for act() must be an object, got this:`, payload);
|
||||
return;
|
||||
}
|
||||
sendMessage({
|
||||
type: 'act/' + action,
|
||||
payload,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef BackendState
|
||||
* @type {{
|
||||
* config: {
|
||||
* title: string,
|
||||
* status: number,
|
||||
* interface: string,
|
||||
* window: {
|
||||
* key: string,
|
||||
* size: [number, number],
|
||||
* fancy: boolean,
|
||||
* locked: boolean,
|
||||
* },
|
||||
* client: {
|
||||
* ckey: string,
|
||||
* address: string,
|
||||
* computer_id: string,
|
||||
* },
|
||||
* user: {
|
||||
* name: string,
|
||||
* observer: number,
|
||||
* },
|
||||
* },
|
||||
* data: any,
|
||||
* shared: any,
|
||||
* suspending: boolean,
|
||||
* suspended: boolean,
|
||||
* }}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Selects a backend-related slice of Redux state
|
||||
*
|
||||
* @return {BackendState}
|
||||
*/
|
||||
export const selectBackend = state => state.backend || {};
|
||||
|
||||
/**
|
||||
* A React hook (sort of) for getting tgui state and related functions.
|
||||
*
|
||||
* This is supposed to be replaced with a real React Hook, which can only
|
||||
* be used in functional components.
|
||||
*
|
||||
* @return {BackendState & {
|
||||
* act: sendAct,
|
||||
* }}
|
||||
*/
|
||||
export const useBackend = context => {
|
||||
const { store } = context;
|
||||
const state = selectBackend(store.getState());
|
||||
return {
|
||||
...state,
|
||||
act: sendAct,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates state on Redux store without sharing it with other clients.
|
||||
*
|
||||
* Use it when you want to have a stateful variable in your component
|
||||
* that persists between renders, but will be forgotten after you close
|
||||
* the UI.
|
||||
*
|
||||
* It is a lot more performant than `setSharedState`.
|
||||
*
|
||||
* @param {any} context React context.
|
||||
* @param {string} key Key which uniquely identifies this state in Redux store.
|
||||
* @param {any} initialState Initializes your global variable with this value.
|
||||
*/
|
||||
export const useLocalState = (context, key, initialState) => {
|
||||
const { store } = context;
|
||||
const state = selectBackend(store.getState());
|
||||
const sharedStates = state.shared ?? {};
|
||||
const sharedState = (key in sharedStates)
|
||||
? sharedStates[key]
|
||||
: initialState;
|
||||
return [
|
||||
sharedState,
|
||||
nextState => {
|
||||
store.dispatch(backendSetSharedState(key, (
|
||||
typeof nextState === 'function'
|
||||
? nextState(sharedState)
|
||||
: nextState
|
||||
)));
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates state on Redux store, and **shares** it with other clients
|
||||
* in the game.
|
||||
*
|
||||
* Use it when you want to have a stateful variable in your component
|
||||
* that persists not only between renders, but also gets pushed to other
|
||||
* clients that observe this UI.
|
||||
*
|
||||
* This makes creation of observable s
|
||||
*
|
||||
* @param {any} context React context.
|
||||
* @param {string} key Key which uniquely identifies this state in Redux store.
|
||||
* @param {any} initialState Initializes your global variable with this value.
|
||||
*/
|
||||
export const useSharedState = (context, key, initialState) => {
|
||||
const { store } = context;
|
||||
const state = selectBackend(store.getState());
|
||||
const sharedStates = state.shared ?? {};
|
||||
const sharedState = (key in sharedStates)
|
||||
? sharedStates[key]
|
||||
: initialState;
|
||||
return [
|
||||
sharedState,
|
||||
nextState => {
|
||||
sendMessage({
|
||||
type: 'setSharedState',
|
||||
key,
|
||||
value: JSON.stringify(
|
||||
typeof nextState === 'function'
|
||||
? nextState(sharedState)
|
||||
: nextState
|
||||
) || '',
|
||||
});
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -63,7 +63,7 @@ export class AnimatedNumber extends Component {
|
||||
if (!isSafeNumber(targetValue)) {
|
||||
return targetValue || null;
|
||||
}
|
||||
let formattedValue = currentValue;
|
||||
let formattedValue;
|
||||
// Use custom formatter
|
||||
if (format) {
|
||||
formattedValue = format(currentValue);
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { createVNode } from 'inferno';
|
||||
import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags';
|
||||
import { CSS_COLORS } from '../constants';
|
||||
|
||||
/**
|
||||
* Coverts our rem-like spacing unit into a CSS unit.
|
||||
*/
|
||||
export const unit = value => {
|
||||
if (typeof value === 'string') {
|
||||
// Transparently convert pixels into rem units
|
||||
if (value.endsWith('px') && !Byond.IS_LTE_IE8) {
|
||||
return parseFloat(value) / 12 + 'rem';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
return value * 12 + 'px';
|
||||
}
|
||||
return value + 'rem';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as `unit`, but half the size for integers numbers.
|
||||
*/
|
||||
export const halfUnit = value => {
|
||||
if (typeof value === 'string') {
|
||||
return unit(value);
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return unit(value * 0.5);
|
||||
}
|
||||
};
|
||||
|
||||
const isColorCode = str => !isColorClass(str);
|
||||
|
||||
const isColorClass = str => typeof str === 'string'
|
||||
&& CSS_COLORS.includes(str);
|
||||
|
||||
const mapRawPropTo = attrName => (style, value) => {
|
||||
if (typeof value === 'number' || typeof value === 'string') {
|
||||
style[attrName] = value;
|
||||
}
|
||||
};
|
||||
|
||||
const mapUnitPropTo = (attrName, unit) => (style, value) => {
|
||||
if (typeof value === 'number' || typeof value === 'string') {
|
||||
style[attrName] = unit(value);
|
||||
}
|
||||
};
|
||||
|
||||
const mapBooleanPropTo = (attrName, attrValue) => (style, value) => {
|
||||
if (value) {
|
||||
style[attrName] = attrValue;
|
||||
}
|
||||
};
|
||||
|
||||
const mapDirectionalUnitPropTo = (attrName, unit, dirs) => (style, value) => {
|
||||
if (typeof value === 'number' || typeof value === 'string') {
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
style[attrName + '-' + dirs[i]] = unit(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mapColorPropTo = attrName => (style, value) => {
|
||||
if (isColorCode(value)) {
|
||||
style[attrName] = value;
|
||||
}
|
||||
};
|
||||
|
||||
const styleMapperByPropName = {
|
||||
// Direct mapping
|
||||
position: mapRawPropTo('position'),
|
||||
overflow: mapRawPropTo('overflow'),
|
||||
overflowX: mapRawPropTo('overflow-x'),
|
||||
overflowY: mapRawPropTo('overflow-y'),
|
||||
top: mapUnitPropTo('top', unit),
|
||||
bottom: mapUnitPropTo('bottom', unit),
|
||||
left: mapUnitPropTo('left', unit),
|
||||
right: mapUnitPropTo('right', unit),
|
||||
width: mapUnitPropTo('width', unit),
|
||||
minWidth: mapUnitPropTo('min-width', unit),
|
||||
maxWidth: mapUnitPropTo('max-width', unit),
|
||||
height: mapUnitPropTo('height', unit),
|
||||
minHeight: mapUnitPropTo('min-height', unit),
|
||||
maxHeight: mapUnitPropTo('max-height', unit),
|
||||
fontSize: mapUnitPropTo('font-size', unit),
|
||||
fontFamily: mapRawPropTo('font-family'),
|
||||
lineHeight: (style, value) => {
|
||||
if (typeof value === 'number') {
|
||||
style['line-height'] = value;
|
||||
}
|
||||
else if (typeof value === 'string') {
|
||||
style['line-height'] = unit(value);
|
||||
}
|
||||
},
|
||||
opacity: mapRawPropTo('opacity'),
|
||||
textAlign: mapRawPropTo('text-align'),
|
||||
verticalAlign: mapRawPropTo('vertical-align'),
|
||||
// Boolean props
|
||||
inline: mapBooleanPropTo('display', 'inline-block'),
|
||||
bold: mapBooleanPropTo('font-weight', 'bold'),
|
||||
italic: mapBooleanPropTo('font-style', 'italic'),
|
||||
nowrap: mapBooleanPropTo('white-space', 'nowrap'),
|
||||
// Margins
|
||||
m: mapDirectionalUnitPropTo('margin', halfUnit, [
|
||||
'top', 'bottom', 'left', 'right',
|
||||
]),
|
||||
mx: mapDirectionalUnitPropTo('margin', halfUnit, [
|
||||
'left', 'right',
|
||||
]),
|
||||
my: mapDirectionalUnitPropTo('margin', halfUnit, [
|
||||
'top', 'bottom',
|
||||
]),
|
||||
mt: mapUnitPropTo('margin-top', halfUnit),
|
||||
mb: mapUnitPropTo('margin-bottom', halfUnit),
|
||||
ml: mapUnitPropTo('margin-left', halfUnit),
|
||||
mr: mapUnitPropTo('margin-right', halfUnit),
|
||||
// Margins
|
||||
p: mapDirectionalUnitPropTo('padding', halfUnit, [
|
||||
'top', 'bottom', 'left', 'right',
|
||||
]),
|
||||
px: mapDirectionalUnitPropTo('padding', halfUnit, [
|
||||
'left', 'right',
|
||||
]),
|
||||
py: mapDirectionalUnitPropTo('padding', halfUnit, [
|
||||
'top', 'bottom',
|
||||
]),
|
||||
pt: mapUnitPropTo('padding-top', halfUnit),
|
||||
pb: mapUnitPropTo('padding-bottom', halfUnit),
|
||||
pl: mapUnitPropTo('padding-left', halfUnit),
|
||||
pr: mapUnitPropTo('padding-right', halfUnit),
|
||||
// Color props
|
||||
color: mapColorPropTo('color'),
|
||||
textColor: mapColorPropTo('color'),
|
||||
backgroundColor: mapColorPropTo('background-color'),
|
||||
// Utility props
|
||||
fillPositionedParent: (style, value) => {
|
||||
if (value) {
|
||||
style['position'] = 'absolute';
|
||||
style['top'] = 0;
|
||||
style['bottom'] = 0;
|
||||
style['left'] = 0;
|
||||
style['right'] = 0;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const computeBoxProps = props => {
|
||||
const computedProps = {};
|
||||
const computedStyles = {};
|
||||
// Compute props
|
||||
for (let propName of Object.keys(props)) {
|
||||
if (propName === 'style') {
|
||||
continue;
|
||||
}
|
||||
// IE8: onclick workaround
|
||||
if (Byond.IS_LTE_IE8 && propName === 'onClick') {
|
||||
computedProps.onclick = props[propName];
|
||||
continue;
|
||||
}
|
||||
const propValue = props[propName];
|
||||
const mapPropToStyle = styleMapperByPropName[propName];
|
||||
if (mapPropToStyle) {
|
||||
mapPropToStyle(computedStyles, propValue);
|
||||
}
|
||||
else {
|
||||
computedProps[propName] = propValue;
|
||||
}
|
||||
}
|
||||
// Concatenate styles
|
||||
let style = '';
|
||||
for (let attrName of Object.keys(computedStyles)) {
|
||||
const attrValue = computedStyles[attrName];
|
||||
style += attrName + ':' + attrValue + ';';
|
||||
}
|
||||
if (props.style) {
|
||||
for (let attrName of Object.keys(props.style)) {
|
||||
const attrValue = props.style[attrName];
|
||||
style += attrName + ':' + attrValue + ';';
|
||||
}
|
||||
}
|
||||
if (style.length > 0) {
|
||||
computedProps.style = style;
|
||||
}
|
||||
return computedProps;
|
||||
};
|
||||
|
||||
export const computeBoxClassName = props => {
|
||||
const color = props.textColor || props.color;
|
||||
const backgroundColor = props.backgroundColor;
|
||||
return classes([
|
||||
isColorClass(color) && 'color-' + color,
|
||||
isColorClass(backgroundColor) && 'color-bg-' + backgroundColor,
|
||||
]);
|
||||
};
|
||||
|
||||
export const Box = props => {
|
||||
const {
|
||||
as = 'div',
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
// Render props
|
||||
if (typeof children === 'function') {
|
||||
return children(computeBoxProps(props));
|
||||
}
|
||||
const computedClassName = typeof className === 'string'
|
||||
? className + ' ' + computeBoxClassName(rest)
|
||||
: computeBoxClassName(rest);
|
||||
const computedProps = computeBoxProps(rest);
|
||||
// Render a wrapper element
|
||||
return createVNode(
|
||||
VNodeFlags.HtmlElement,
|
||||
as,
|
||||
computedClassName,
|
||||
children,
|
||||
ChildFlags.UnknownChildren,
|
||||
computedProps);
|
||||
};
|
||||
|
||||
Box.defaultHooks = pureComponentHooks;
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { Box, unit } from './Box';
|
||||
|
||||
export const computeFlexProps = props => {
|
||||
const {
|
||||
className,
|
||||
direction,
|
||||
wrap,
|
||||
align,
|
||||
justify,
|
||||
inline,
|
||||
spacing = 0,
|
||||
...rest
|
||||
} = props;
|
||||
return {
|
||||
className: classes([
|
||||
'Flex',
|
||||
Byond.IS_LTE_IE10 && (
|
||||
direction === 'column'
|
||||
? 'Flex--iefix--column'
|
||||
: 'Flex--iefix'
|
||||
),
|
||||
inline && 'Flex--inline',
|
||||
spacing > 0 && 'Flex--spacing--' + spacing,
|
||||
className,
|
||||
]),
|
||||
style: {
|
||||
...rest.style,
|
||||
'flex-direction': direction,
|
||||
'flex-wrap': wrap,
|
||||
'align-items': align,
|
||||
'justify-content': justify,
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
|
||||
export const Flex = props => (
|
||||
<Box {...computeFlexProps(props)} />
|
||||
);
|
||||
|
||||
Flex.defaultHooks = pureComponentHooks;
|
||||
|
||||
export const computeFlexItemProps = props => {
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
grow,
|
||||
order,
|
||||
shrink,
|
||||
// IE11: Always set basis to specified width, which fixes certain
|
||||
// bugs when rendering tables inside the flex.
|
||||
basis = props.width,
|
||||
align,
|
||||
...rest
|
||||
} = props;
|
||||
return {
|
||||
className: classes([
|
||||
'Flex__item',
|
||||
Byond.IS_LTE_IE10 && 'Flex__item--iefix',
|
||||
Byond.IS_LTE_IE10 && grow > 0 && 'Flex__item--iefix--grow',
|
||||
className,
|
||||
]),
|
||||
style: {
|
||||
...style,
|
||||
'flex-grow': grow,
|
||||
'flex-shrink': shrink,
|
||||
'flex-basis': unit(basis),
|
||||
'order': order,
|
||||
'align-self': align,
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
|
||||
export const FlexItem = props => (
|
||||
<Box {...computeFlexItemProps(props)} />
|
||||
);
|
||||
|
||||
FlexItem.defaultHooks = pureComponentHooks;
|
||||
|
||||
Flex.Item = FlexItem;
|
||||
@@ -8,11 +8,11 @@ import { BooleanLike, classes, pureComponentHooks } from 'common/react';
|
||||
import { Box, BoxProps, unit } from './Box';
|
||||
|
||||
export interface FlexProps extends BoxProps {
|
||||
direction: string | BooleanLike;
|
||||
wrap: string | BooleanLike;
|
||||
align: string | BooleanLike;
|
||||
justify: string | BooleanLike;
|
||||
inline: BooleanLike;
|
||||
direction?: string | BooleanLike;
|
||||
wrap?: string | BooleanLike;
|
||||
align?: string | BooleanLike;
|
||||
justify?: string | BooleanLike;
|
||||
inline?: BooleanLike;
|
||||
}
|
||||
|
||||
export const computeFlexProps = (props: FlexProps) => {
|
||||
|
||||
@@ -28,17 +28,23 @@ export const Icon = props => {
|
||||
if (typeof rotation === 'number') {
|
||||
style['transform'] = `rotate(${rotation}deg)`;
|
||||
}
|
||||
const faRegular = FA_OUTLINE_REGEX.test(name);
|
||||
const faName = name.replace(FA_OUTLINE_REGEX, '');
|
||||
let iconClass = "";
|
||||
if (name.startsWith("tg-")) {
|
||||
// tgfont icon
|
||||
iconClass = name;
|
||||
} else {
|
||||
// font awesome icon
|
||||
const faRegular = FA_OUTLINE_REGEX.test(name);
|
||||
const faName = name.replace(FA_OUTLINE_REGEX, '');
|
||||
iconClass = (faRegular ? 'far ' : 'fas ') + 'fa-'+ faName + (spin ? " fa-spin" : "");
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
as="i"
|
||||
className={classes([
|
||||
'Icon',
|
||||
className,
|
||||
faRegular ? 'far' : 'fas',
|
||||
'fa-' + faName,
|
||||
spin && 'fa-spin',
|
||||
iconClass,
|
||||
])}
|
||||
style={style}
|
||||
{...rest} />
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { Box, unit } from './Box';
|
||||
import { Divider } from './Divider';
|
||||
|
||||
export const LabeledList = props => {
|
||||
const { children } = props;
|
||||
return (
|
||||
<table className="LabeledList">
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
LabeledList.defaultHooks = pureComponentHooks;
|
||||
|
||||
export const LabeledListItem = props => {
|
||||
const {
|
||||
className,
|
||||
label,
|
||||
labelColor = 'label',
|
||||
color,
|
||||
textAlign,
|
||||
buttons,
|
||||
content,
|
||||
children,
|
||||
} = props;
|
||||
return (
|
||||
<tr
|
||||
className={classes([
|
||||
'LabeledList__row',
|
||||
className,
|
||||
])}>
|
||||
<Box
|
||||
as="td"
|
||||
color={labelColor}
|
||||
className={classes([
|
||||
'LabeledList__cell',
|
||||
'LabeledList__label',
|
||||
])}>
|
||||
{label ? label + ':' : null}
|
||||
</Box>
|
||||
<Box
|
||||
as="td"
|
||||
color={color}
|
||||
textAlign={textAlign}
|
||||
className={classes([
|
||||
'LabeledList__cell',
|
||||
'LabeledList__content',
|
||||
])}
|
||||
colSpan={buttons ? undefined : 2}>
|
||||
{content}
|
||||
{children}
|
||||
</Box>
|
||||
{buttons && (
|
||||
<td className="LabeledList__cell LabeledList__buttons">
|
||||
{buttons}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
LabeledListItem.defaultHooks = pureComponentHooks;
|
||||
|
||||
export const LabeledListDivider = props => {
|
||||
const padding = props.size
|
||||
? unit(Math.max(0, props.size - 1))
|
||||
: 0;
|
||||
return (
|
||||
<tr className="LabeledList__row">
|
||||
<td
|
||||
colSpan={3}
|
||||
style={{
|
||||
'padding-top': padding,
|
||||
'padding-bottom': padding,
|
||||
}}>
|
||||
<Divider />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
LabeledListDivider.defaultHooks = pureComponentHooks;
|
||||
|
||||
LabeledList.Item = LabeledListItem;
|
||||
LabeledList.Divider = LabeledListDivider;
|
||||
@@ -10,7 +10,7 @@ import { Box, unit } from './Box';
|
||||
import { Divider } from './Divider';
|
||||
|
||||
type LabeledListProps = {
|
||||
children: InfernoNode;
|
||||
children?: any;
|
||||
};
|
||||
|
||||
export const LabeledList = (props: LabeledListProps) => {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { canRender, classes } from 'common/react';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { addScrollableNode, removeScrollableNode } from '../events';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
export class Section extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.ref = createRef();
|
||||
this.scrollable = props.scrollable;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.scrollable) {
|
||||
addScrollableNode(this.ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.scrollable) {
|
||||
removeScrollableNode(this.ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
title,
|
||||
level = 1,
|
||||
buttons,
|
||||
fill,
|
||||
fitted,
|
||||
scrollable,
|
||||
children,
|
||||
...rest
|
||||
} = this.props;
|
||||
const hasTitle = canRender(title) || canRender(buttons);
|
||||
const hasContent = canRender(children);
|
||||
return (
|
||||
<div
|
||||
ref={this.ref}
|
||||
className={classes([
|
||||
'Section',
|
||||
'Section--level--' + level,
|
||||
Byond.IS_LTE_IE8 && 'Section--iefix',
|
||||
fill && 'Section--fill',
|
||||
fitted && 'Section--fitted',
|
||||
scrollable && 'Section--scrollable',
|
||||
className,
|
||||
...computeBoxClassName(rest),
|
||||
])}
|
||||
{...computeBoxProps(rest)}>
|
||||
{hasTitle && (
|
||||
<div className="Section__title">
|
||||
<span className="Section__titleText">
|
||||
{title}
|
||||
</span>
|
||||
<div className="Section__buttons">
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{fitted && children
|
||||
|| hasContent && (
|
||||
<div className="Section__content">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { formatTime } from '../format';
|
||||
import { Component } from 'inferno';
|
||||
|
||||
// AnimatedNumber Copypaste
|
||||
@@ -57,13 +57,7 @@ export class TimeDisplay extends Component {
|
||||
if (!isSafeNumber(val)) {
|
||||
return this.state.value || null;
|
||||
}
|
||||
// THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER
|
||||
// HH:MM:SS
|
||||
// 00:02:13
|
||||
const seconds = toFixed(Math.floor((val/10) % 60)).padStart(2, "0");
|
||||
const minutes = toFixed(Math.floor((val/(10*60)) % 60)).padStart(2, "0");
|
||||
const hours = toFixed(Math.floor((val/(10*60*60)) % 24)).padStart(2, "0");
|
||||
const formattedValue = `${hours}:${minutes}:${seconds}`;
|
||||
return formattedValue;
|
||||
|
||||
return formatTime(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ window.addEventListener('beforeunload', e => {
|
||||
|
||||
const keyHeldByCode = {};
|
||||
|
||||
class KeyEvent {
|
||||
export class KeyEvent {
|
||||
constructor(e, type, repeat) {
|
||||
this.event = e;
|
||||
this.type = type;
|
||||
|
||||
@@ -105,7 +105,7 @@ export const formatMoney = (value, precision = 0) => {
|
||||
*/
|
||||
export const formatDb = value => {
|
||||
const db = 20 * Math.log(value) / Math.log(10);
|
||||
const sign = db >= 0 ? '+' : db < 0 ? '–' : '';
|
||||
const sign = db >= 0 ? '+' : '–';
|
||||
let formatted = Math.abs(db);
|
||||
if (formatted === Infinity) {
|
||||
formatted = 'Inf';
|
||||
@@ -169,3 +169,30 @@ export const formatSiBaseTenUnit = (
|
||||
);
|
||||
return finalString.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats decisecond count into HH::MM::SS display by default
|
||||
* "short" format does not pad and adds hms suffixes
|
||||
*/
|
||||
export const formatTime = (val, formatType) => {
|
||||
// THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER
|
||||
// HH:MM:SS
|
||||
// 00:02:13
|
||||
const seconds = toFixed(Math.floor((val/10) % 60));
|
||||
const minutes = toFixed(Math.floor((val/(10*60)) % 60));
|
||||
const hours = toFixed(Math.floor((val/(10*60*60)) % 24));
|
||||
switch (formatType) {
|
||||
case "short": {
|
||||
const hours_truncated = hours > 0 ? `${hours}h` : "";
|
||||
const minutes_truncated = minutes > 0 ? `${minutes}m` : "";
|
||||
const seconds_truncated = seconds > 0 ? `${seconds}s` : "";
|
||||
return `${hours_truncated}${minutes_truncated}${seconds_truncated}`;
|
||||
}
|
||||
default: {
|
||||
const seconds_padded = seconds.padStart(2, "0");
|
||||
const minutes_padded = minutes.padStart(2, "0");
|
||||
const hours_padded = hours.padStart(2, "0");
|
||||
return `${hours_padded}:${minutes_padded}:${seconds_padded}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,34 +4,37 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { KEY_CTRL, KEY_ENTER, KEY_ESCAPE, KEY_F, KEY_F5, KEY_R, KEY_SHIFT, KEY_SPACE, KEY_TAB } from 'common/keycodes';
|
||||
import { globalEvents } from './events';
|
||||
import * as keycodes from 'common/keycodes';
|
||||
import { globalEvents, KeyEvent } from './events';
|
||||
import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('hotkeys');
|
||||
|
||||
// BYOND macros, in `key: command` format.
|
||||
const byondMacros = {};
|
||||
const byondMacros: Record<string, string> = {};
|
||||
|
||||
// Array of acquired keys, which will not be sent to BYOND.
|
||||
// Default set of acquired keys, which will not be sent to BYOND.
|
||||
const hotKeysAcquired = [
|
||||
// Default set of acquired keys
|
||||
KEY_ESCAPE,
|
||||
KEY_ENTER,
|
||||
KEY_SPACE,
|
||||
KEY_TAB,
|
||||
KEY_CTRL,
|
||||
KEY_SHIFT,
|
||||
KEY_F5,
|
||||
keycodes.KEY_ESCAPE,
|
||||
keycodes.KEY_ENTER,
|
||||
keycodes.KEY_SPACE,
|
||||
keycodes.KEY_TAB,
|
||||
keycodes.KEY_CTRL,
|
||||
keycodes.KEY_SHIFT,
|
||||
keycodes.KEY_UP,
|
||||
keycodes.KEY_DOWN,
|
||||
keycodes.KEY_LEFT,
|
||||
keycodes.KEY_RIGHT,
|
||||
keycodes.KEY_F5,
|
||||
];
|
||||
|
||||
// State of passed-through keys.
|
||||
const keyState = {};
|
||||
const keyState: Record<string, boolean> = {};
|
||||
|
||||
/**
|
||||
* Converts a browser keycode to BYOND keycode.
|
||||
*/
|
||||
const keyCodeToByond = keyCode => {
|
||||
const keyCodeToByond = (keyCode: number) => {
|
||||
if (keyCode === 16) return 'Shift';
|
||||
if (keyCode === 17) return 'Ctrl';
|
||||
if (keyCode === 18) return 'Alt';
|
||||
@@ -63,14 +66,15 @@ const keyCodeToByond = keyCode => {
|
||||
* Keyboard passthrough logic. This allows you to keep doing things
|
||||
* in game while the browser window is focused.
|
||||
*/
|
||||
const handlePassthrough = key => {
|
||||
const handlePassthrough = (key: KeyEvent) => {
|
||||
const keyString = String(key);
|
||||
// In addition to F5, support reloading with Ctrl+R and Ctrl+F5
|
||||
if (key.ctrl && (key.code === KEY_F5 || key.code === KEY_R)) {
|
||||
if (keyString === 'Ctrl+F5' || keyString === 'Ctrl+R') {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
// Prevent passthrough on Ctrl+F
|
||||
if (key.ctrl && key.code === KEY_F) {
|
||||
if (keyString === 'Ctrl+F') {
|
||||
return;
|
||||
}
|
||||
// NOTE: Alt modifier is pretty bad and sticky in IE11.
|
||||
@@ -109,14 +113,14 @@ const handlePassthrough = key => {
|
||||
* Acquires a lock on the hotkey, which prevents it from being
|
||||
* passed through to BYOND.
|
||||
*/
|
||||
export const acquireHotKey = keyCode => {
|
||||
export const acquireHotKey = (keyCode: number) => {
|
||||
hotKeysAcquired.push(keyCode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes the hotkey available to BYOND again.
|
||||
*/
|
||||
export const releaseHotKey = keyCode => {
|
||||
export const releaseHotKey = (keyCode: number) => {
|
||||
const index = hotKeysAcquired.indexOf(keyCode);
|
||||
if (index >= 0) {
|
||||
hotKeysAcquired.splice(index, 1);
|
||||
@@ -133,25 +137,33 @@ export const releaseHeldKeys = () => {
|
||||
}
|
||||
};
|
||||
|
||||
type ByondSkinMacro = {
|
||||
command: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const setupHotKeys = () => {
|
||||
// Read macros
|
||||
Byond.winget('default.*').then(data => {
|
||||
Byond.winget('default.*').then((data: Record<string, string>) => {
|
||||
// Group each macro by ref
|
||||
const groupedByRef = {};
|
||||
const groupedByRef: Record<string, ByondSkinMacro> = {};
|
||||
for (let key of Object.keys(data)) {
|
||||
const keyPath = key.split('.');
|
||||
const ref = keyPath[1];
|
||||
const prop = keyPath[2];
|
||||
if (ref && prop) {
|
||||
// This piece of code imperatively adds each property to a
|
||||
// ByondSkinMacro object in the order we meet it, which is hard
|
||||
// to express safely in typescript.
|
||||
if (!groupedByRef[ref]) {
|
||||
groupedByRef[ref] = {};
|
||||
groupedByRef[ref] = {} as any;
|
||||
}
|
||||
groupedByRef[ref][prop] = data[key];
|
||||
}
|
||||
}
|
||||
// Insert macros
|
||||
const escapedQuotRegex = /\\"/g;
|
||||
const unescape = str => str
|
||||
const unescape = (str: string) => str
|
||||
.substring(1, str.length - 1)
|
||||
.replace(escapedQuotRegex, '"');
|
||||
for (let ref of Object.keys(groupedByRef)) {
|
||||
@@ -165,7 +177,7 @@ export const setupHotKeys = () => {
|
||||
globalEvents.on('window-blur', () => {
|
||||
releaseHeldKeys();
|
||||
});
|
||||
globalEvents.on('key', key => {
|
||||
globalEvents.on('key', (key: KeyEvent) => {
|
||||
handlePassthrough(key);
|
||||
});
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import './styles/themes/ntos.scss';
|
||||
import './styles/themes/paper.scss';
|
||||
import './styles/themes/retro.scss';
|
||||
import './styles/themes/syndicate.scss';
|
||||
import './styles/themes/wizard.scss';
|
||||
|
||||
import { perf } from 'common/perf';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client';
|
||||
|
||||
@@ -13,17 +13,17 @@ export const captureExternalLinks = () => {
|
||||
/** @type {HTMLElement} */
|
||||
let target = e.target;
|
||||
// Recurse down the tree to find a valid link
|
||||
while (target && target !== document.body) {
|
||||
while (true) {
|
||||
// Reached the end, bail.
|
||||
if (!target || target === document.body) {
|
||||
return;
|
||||
}
|
||||
const tagName = String(target.tagName).toLowerCase();
|
||||
if (tagName === 'a') {
|
||||
break;
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
// Not a link, do nothing.
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const hrefAttr = target.getAttribute('href') || '';
|
||||
// Leave BYOND links alone
|
||||
const isByondLink = hrefAttr.charAt(0) === '?'
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
"version": "4.3.0",
|
||||
"dependencies": {
|
||||
"common": "workspace:*",
|
||||
"dompurify": "^2.2.6",
|
||||
"dateformat": "^4.5.1",
|
||||
"dompurify": "^2.2.7",
|
||||
"inferno": "^7.4.8",
|
||||
"inferno-vnode-flags": "^7.4.8",
|
||||
"marked": "^2.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"marked": "^2.0.3",
|
||||
"tgui-dev-server": "workspace:*",
|
||||
"tgui-polyfill": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { selectBackend } from './backend';
|
||||
import { selectDebug } from './debug/selectors';
|
||||
import { Window } from './layouts';
|
||||
|
||||
const requireInterface = require.context('./interfaces', false, /\.js$/);
|
||||
const requireInterface = require.context('./interfaces');
|
||||
|
||||
const routingError = (type, name) => () => {
|
||||
return (
|
||||
@@ -47,15 +47,27 @@ export const getRoutedComponent = store => {
|
||||
}
|
||||
}
|
||||
const name = config?.interface;
|
||||
const interfacePathBuilders = [
|
||||
name => `./${name}.tsx`,
|
||||
name => `./${name}.js`,
|
||||
name => `./${name}/index.tsx`,
|
||||
name => `./${name}/index.js`,
|
||||
];
|
||||
let esModule;
|
||||
try {
|
||||
esModule = requireInterface(`./${name}.js`);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
return routingError('notFound', name);
|
||||
while (!esModule && interfacePathBuilders.length > 0) {
|
||||
const interfacePathBuilder = interfacePathBuilders.shift();
|
||||
const interfacePath = interfacePathBuilder(name);
|
||||
try {
|
||||
esModule = requireInterface(interfacePath);
|
||||
}
|
||||
throw err;
|
||||
catch (err) {
|
||||
if (err.code !== 'MODULE_NOT_FOUND') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!esModule) {
|
||||
return routingError('notFound', name);
|
||||
}
|
||||
const Component = esModule[name];
|
||||
if (!Component) {
|
||||
|
||||
10
tgui/packages/tgui/styles/atomic/links.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
@use '../colors.scss';
|
||||
|
||||
a {
|
||||
&:link, &:visited {
|
||||
color: colors.$blue;
|
||||
}
|
||||
&:hover, &:active {
|
||||
color: colors.$primary;
|
||||
}
|
||||
}
|
||||