mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-11 02:34:00 +00:00
[MIRROR] tgui 516 (#10158)
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
021ac2b32d
commit
bb759b294c
@@ -5,13 +5,6 @@
|
|||||||
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
|
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
|
||||||
)
|
)
|
||||||
|
|
||||||
/datum/asset/simple/tgui_edge
|
|
||||||
keep_local_name = TRUE
|
|
||||||
assets = list(
|
|
||||||
"tgui.bundle.edge.js" = file("tgui/public/tgui.bundle.edge.js"),
|
|
||||||
"tgui.bundle.edge.css" = file("tgui/public/tgui.bundle.edge.css"),
|
|
||||||
)
|
|
||||||
|
|
||||||
/datum/asset/simple/tgui_panel
|
/datum/asset/simple/tgui_panel
|
||||||
keep_local_name = TRUE
|
keep_local_name = TRUE
|
||||||
assets = list(
|
assets = list(
|
||||||
|
|||||||
@@ -226,9 +226,7 @@
|
|||||||
//CONNECT//
|
//CONNECT//
|
||||||
///////////
|
///////////
|
||||||
/client/New(TopicData)
|
/client/New(TopicData)
|
||||||
// TODO: Remove version check with 516
|
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS]")
|
||||||
if(byond_version >= 516) // Enable 516 compat browser storage mechanisms
|
|
||||||
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS]")
|
|
||||||
|
|
||||||
TopicData = null //Prevent calls to client.Topic from connect
|
TopicData = null //Prevent calls to client.Topic from connect
|
||||||
|
|
||||||
@@ -360,11 +358,9 @@
|
|||||||
fully_created = TRUE
|
fully_created = TRUE
|
||||||
attempt_auto_fit_viewport()
|
attempt_auto_fit_viewport()
|
||||||
|
|
||||||
// TODO: Remove version check with 516
|
// Now that we're fully initialized, use our prefs
|
||||||
if(byond_version >= 516)
|
if(prefs?.read_preference(/datum/preference/toggle/browser_dev_tools))
|
||||||
// Now that we're fully initialized, use our prefs
|
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS],devtools")
|
||||||
if(prefs?.read_preference(/datum/preference/toggle/browser_dev_tools))
|
|
||||||
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS],devtools")
|
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
//DISCONNECT//
|
//DISCONNECT//
|
||||||
@@ -672,18 +668,6 @@
|
|||||||
src << browse("<html>[message]</html>","window=dropmessage;size=480x360;can_close=1")
|
src << browse("<html>[message]</html>","window=dropmessage;size=480x360;can_close=1")
|
||||||
qdel(src)
|
qdel(src)
|
||||||
|
|
||||||
/// Keydown event in a tgui window this client has open. Has keycode passed to it.
|
|
||||||
/client/verb/TguiKeyDown(keycode as text)
|
|
||||||
set name = "TguiKeyDown"
|
|
||||||
set hidden = TRUE
|
|
||||||
return // stub
|
|
||||||
|
|
||||||
/// Keyup event in a tgui window this client has open. Has keycode passed to it.
|
|
||||||
/client/verb/TguiKeyUp(keycode as text) // Doesn't seem to currently fire?
|
|
||||||
set name = "TguiKeyUp"
|
|
||||||
set hidden = TRUE
|
|
||||||
return // stub
|
|
||||||
|
|
||||||
/client/verb/toggle_fullscreen()
|
/client/verb/toggle_fullscreen()
|
||||||
set name = "Toggle Fullscreen"
|
set name = "Toggle Fullscreen"
|
||||||
set category = "OOC.Client Settings"
|
set category = "OOC.Client Settings"
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
|||||||
var/key = pref.rlimb_data[name]
|
var/key = pref.rlimb_data[name]
|
||||||
if(!istext(key))
|
if(!istext(key))
|
||||||
log_debug("Bad rlimb_data for [key_name(pref.client)], [name] was set to [key]")
|
log_debug("Bad rlimb_data for [key_name(pref.client)], [name] was set to [key]")
|
||||||
to_chat(user, span_warning("Error loading robot limb data for `[name]`, clearing pref."))
|
to_chat(usr, span_warning("Error loading robot limb data for `[name]`, clearing pref."))
|
||||||
pref.rlimb_data -= name
|
pref.rlimb_data -= name
|
||||||
else
|
else
|
||||||
R = LAZYACCESS(all_robolimbs, key)
|
R = LAZYACCESS(all_robolimbs, key)
|
||||||
@@ -552,7 +552,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
|||||||
pref.set_biological_gender(mob_species.genders[1])
|
pref.set_biological_gender(mob_species.genders[1])
|
||||||
pref.custom_species = null
|
pref.custom_species = null
|
||||||
//grab one of the valid hair styles for the newly chosen species
|
//grab one of the valid hair styles for the newly chosen species
|
||||||
var/list/valid_hairstyles = pref.get_valid_hairstyles(user)
|
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||||
|
|
||||||
if(valid_hairstyles.len)
|
if(valid_hairstyles.len)
|
||||||
if(!(pref.h_style in valid_hairstyles))
|
if(!(pref.h_style in valid_hairstyles))
|
||||||
@@ -604,7 +604,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
|||||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||||
|
|
||||||
else if(href_list["hair_style"])
|
else if(href_list["hair_style"])
|
||||||
var/list/valid_hairstyles = pref.get_valid_hairstyles(user)
|
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||||
|
|
||||||
var/new_h_style = tgui_input_list(user, "Choose your character's hair style:", "Character Preference", valid_hairstyles, pref.h_style)
|
var/new_h_style = tgui_input_list(user, "Choose your character's hair style:", "Character Preference", valid_hairstyles, pref.h_style)
|
||||||
if(new_h_style && CanUseTopic(user))
|
if(new_h_style && CanUseTopic(user))
|
||||||
@@ -629,7 +629,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
|||||||
|
|
||||||
else if(href_list["hair_style_left"])
|
else if(href_list["hair_style_left"])
|
||||||
var/H = href_list["hair_style_left"]
|
var/H = href_list["hair_style_left"]
|
||||||
var/list/valid_hairstyles = pref.get_valid_hairstyles(user)
|
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||||
var/start = valid_hairstyles.Find(H)
|
var/start = valid_hairstyles.Find(H)
|
||||||
|
|
||||||
if(start != 1) //If we're not the beginning of the list, become the previous element.
|
if(start != 1) //If we're not the beginning of the list, become the previous element.
|
||||||
@@ -640,7 +640,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
|||||||
|
|
||||||
else if(href_list["hair_style_right"])
|
else if(href_list["hair_style_right"])
|
||||||
var/H = href_list["hair_style_right"]
|
var/H = href_list["hair_style_right"]
|
||||||
var/list/valid_hairstyles = pref.get_valid_hairstyles(user)
|
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||||
var/start = valid_hairstyles.Find(H)
|
var/start = valid_hairstyles.Find(H)
|
||||||
|
|
||||||
if(start != valid_hairstyles.len) //If we're not the end of the list, become the next element.
|
if(start != valid_hairstyles.len) //If we're not the end of the list, become the next element.
|
||||||
@@ -1054,7 +1054,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
|||||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||||
|
|
||||||
else if(href_list["ear_color2"])
|
else if(href_list["ear_color2"])
|
||||||
var/new_earc2 = tgui_color_picker(user, "Choose your character's ear colour:", "Character Preference",
|
var/new_earc2 = tgui_color_picker(user, "Choose your character's secondary ear colour:", "Character Preference",
|
||||||
pref.read_preference(/datum/preference/color/human/ears_color2))
|
pref.read_preference(/datum/preference/color/human/ears_color2))
|
||||||
if(new_earc2)
|
if(new_earc2)
|
||||||
pref.update_preference_by_type(/datum/preference/color/human/ears_color2, new_earc2)
|
pref.update_preference_by_type(/datum/preference/color/human/ears_color2, new_earc2)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
savefile_identifier = PREFERENCE_PLAYER
|
savefile_identifier = PREFERENCE_PLAYER
|
||||||
|
|
||||||
minimum = 1
|
minimum = 1
|
||||||
maximum = 5
|
maximum = 20
|
||||||
step = 1
|
step = 1
|
||||||
|
|
||||||
/datum/preference/numeric/tgui_say_height/create_default_value()
|
/datum/preference/numeric/tgui_say_height/create_default_value()
|
||||||
@@ -90,6 +90,22 @@
|
|||||||
/datum/preference/numeric/tgui_say_height/apply_to_client(client/client, value)
|
/datum/preference/numeric/tgui_say_height/apply_to_client(client/client, value)
|
||||||
client.tgui_say?.load()
|
client.tgui_say?.load()
|
||||||
|
|
||||||
|
|
||||||
|
/datum/preference/numeric/tgui_say_width
|
||||||
|
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||||
|
savefile_key = "tgui_say_width"
|
||||||
|
savefile_identifier = PREFERENCE_PLAYER
|
||||||
|
|
||||||
|
minimum = 360
|
||||||
|
maximum = 800
|
||||||
|
step = 20
|
||||||
|
|
||||||
|
/datum/preference/numeric/tgui_say_width/create_default_value()
|
||||||
|
return 360
|
||||||
|
|
||||||
|
/datum/preference/numeric/tgui_say_width/apply_to_client(client/client, value)
|
||||||
|
client.tgui_say?.load()
|
||||||
|
|
||||||
/datum/preference/text/preset_colors
|
/datum/preference/text/preset_colors
|
||||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
savefile_identifier = PREFERENCE_PLAYER
|
savefile_identifier = PREFERENCE_PLAYER
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ var/global/list/MOVE_KEY_MAPPINGS = list(
|
|||||||
|
|
||||||
// Validate input. Must be one (and only one) of the key codes)
|
// Validate input. Must be one (and only one) of the key codes)
|
||||||
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
|
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
|
||||||
log_debug("Client [ckey] sent an illegal movement key down: [movekeyName] ([movekey])")
|
// og_debug("Client [ckey] sent an illegal movement key down: [movekeyName] ([movekey])") // We forward tgui keys nowadays
|
||||||
return
|
return
|
||||||
|
|
||||||
// Record that we are now holding the key!
|
// Record that we are now holding the key!
|
||||||
@@ -57,7 +57,7 @@ var/global/list/MOVE_KEY_MAPPINGS = list(
|
|||||||
|
|
||||||
// Validate input. Must be one (and only one) of the key codes)
|
// Validate input. Must be one (and only one) of the key codes)
|
||||||
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
|
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
|
||||||
log_debug("Client [ckey] sent an illegal movement key up: [movekeyName] ([movekey])")
|
// log_debug("Client [ckey] sent an illegal movement key up: [movekeyName] ([movekey])") // We forward tgui keys nowadays
|
||||||
return
|
return
|
||||||
|
|
||||||
// Clear bit indicating we were holding the key
|
// Clear bit indicating we were holding the key
|
||||||
|
|||||||
@@ -106,11 +106,8 @@
|
|||||||
strict_mode = TRUE,
|
strict_mode = TRUE,
|
||||||
fancy = user.read_preference(/datum/preference/toggle/tgui_fancy),
|
fancy = user.read_preference(/datum/preference/toggle/tgui_fancy),
|
||||||
assets = list(
|
assets = list(
|
||||||
// FIXME: Delete this when 516 is required!
|
get_asset_datum(/datum/asset/simple/tgui),
|
||||||
user.client.byond_version >= 516 \
|
))
|
||||||
? get_asset_datum(/datum/asset/simple/tgui_edge) \
|
|
||||||
: get_asset_datum(/datum/asset/simple/tgui),
|
|
||||||
))
|
|
||||||
else
|
else
|
||||||
window.send_message("ping")
|
window.send_message("ping")
|
||||||
send_assets()
|
send_assets()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
else
|
else
|
||||||
return
|
return
|
||||||
// Client does NOT have tgui_input on: Returns regular input
|
// Client does NOT have tgui_input on: Returns regular input
|
||||||
if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_mode) || user.client.byond_version < 516) // Todo, remove once virgo is on 516, but currently the TGUI interface is already programmed for edge
|
if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_mode))
|
||||||
return input(user, message, title, default) as color|null
|
return input(user, message, title, default) as color|null
|
||||||
var/datum/tgui_color_picker/picker = new(user, message, title, default, timeout, autofocus)
|
var/datum/tgui_color_picker/picker = new(user, message, title, default, timeout, autofocus)
|
||||||
picker.tgui_interact(user)
|
picker.tgui_interact(user)
|
||||||
|
|||||||
@@ -65,11 +65,13 @@
|
|||||||
window_open = FALSE
|
window_open = FALSE
|
||||||
|
|
||||||
var/minimumHeight = client?.prefs?.read_preference(/datum/preference/numeric/tgui_say_height) || 1
|
var/minimumHeight = client?.prefs?.read_preference(/datum/preference/numeric/tgui_say_height) || 1
|
||||||
winset(client, "tgui_say", "pos=410,400;size=360,[(minimumHeight * 20) + 10];is-visible=0;")
|
var/minimumWidth = client?.prefs?.read_preference(/datum/preference/numeric/tgui_say_width) || 1
|
||||||
|
winset(client, "tgui_say", "pos=410,400;size=360,30;is-visible=0;")
|
||||||
|
|
||||||
window.send_message("props", list(
|
window.send_message("props", list(
|
||||||
lightMode = client?.prefs?.read_preference(/datum/preference/toggle/tgui_say_light),
|
lightMode = client?.prefs?.read_preference(/datum/preference/toggle/tgui_say_light),
|
||||||
minimumHeight = minimumHeight,
|
minimumHeight = minimumHeight,
|
||||||
|
minimumWidth = minimumWidth,
|
||||||
maxLength = max_length,
|
maxLength = max_length,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,9 @@
|
|||||||
tgui_panel.initialize(force = TRUE)
|
tgui_panel.initialize(force = TRUE)
|
||||||
// Force show the panel to see if there are any errors
|
// Force show the panel to see if there are any errors
|
||||||
winset(src, "legacy_output_selector", "left=output_browser")
|
winset(src, "legacy_output_selector", "left=output_browser")
|
||||||
// TODO: Remove version check with 516
|
|
||||||
if(byond_version >= 516)
|
if(prefs?.read_preference(/datum/preference/toggle/browser_dev_tools))
|
||||||
if(prefs?.read_preference(/datum/preference/toggle/browser_dev_tools))
|
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS],devtools")
|
||||||
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS],devtools")
|
|
||||||
else
|
|
||||||
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS]")
|
|
||||||
|
|
||||||
/client/verb/refresh_tgui()
|
/client/verb/refresh_tgui()
|
||||||
set name = "Refresh TGUI"
|
set name = "Refresh TGUI"
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ Notes:
|
|||||||
last_target = null
|
last_target = null
|
||||||
|
|
||||||
/datum/tooltip/proc/do_hide()
|
/datum/tooltip/proc/do_hide()
|
||||||
winshow(owner, control, FALSE)
|
if(owner)
|
||||||
|
winshow(owner, control, FALSE)
|
||||||
|
|
||||||
/datum/tooltip/Destroy(force)
|
/datum/tooltip/Destroy(force)
|
||||||
last_target = null
|
last_target = null
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"tgui:analyze": "webpack --analyze",
|
"tgui:analyze": "webpack --analyze",
|
||||||
"tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js",
|
"tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js",
|
||||||
"tgui:build": "BROWSERSLIST_IGNORE_OLD_DATA=true webpack && webpack --config ./webpack.config.edge.js",
|
"tgui:build": "BROWSERSLIST_IGNORE_OLD_DATA=true webpack",
|
||||||
"tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js",
|
"tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js",
|
||||||
"tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx",
|
"tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx",
|
||||||
"tgui:prettier": "prettier --check .",
|
"tgui:prettier": "prettier --check .",
|
||||||
|
|||||||
68
tgui/packages/common/redux.test.ts
Normal file
68
tgui/packages/common/redux.test.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
Action,
|
||||||
|
applyMiddleware,
|
||||||
|
combineReducers,
|
||||||
|
createAction,
|
||||||
|
createStore,
|
||||||
|
Reducer,
|
||||||
|
} from './redux';
|
||||||
|
|
||||||
|
// Dummy Reducer
|
||||||
|
const counterReducer: Reducer<number, Action<string>> = (state = 0, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'INCREMENT':
|
||||||
|
return state + 1;
|
||||||
|
case 'DECREMENT':
|
||||||
|
return state - 1;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy Middleware
|
||||||
|
const loggingMiddleware = (storeApi) => (next) => (action) => {
|
||||||
|
console.log('Middleware:', action);
|
||||||
|
return next(action);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy Action Creators
|
||||||
|
const increment = createAction('INCREMENT');
|
||||||
|
const decrement = createAction('DECREMENT');
|
||||||
|
|
||||||
|
describe('Redux implementation tests', () => {
|
||||||
|
test('createStore works', () => {
|
||||||
|
const store = createStore(counterReducer);
|
||||||
|
expect(store.getState()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createStore with applyMiddleware works', () => {
|
||||||
|
const store = createStore(
|
||||||
|
counterReducer,
|
||||||
|
applyMiddleware(loggingMiddleware),
|
||||||
|
);
|
||||||
|
expect(store.getState()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispatch works', () => {
|
||||||
|
const store = createStore(counterReducer);
|
||||||
|
store.dispatch(increment());
|
||||||
|
expect(store.getState()).toBe(1);
|
||||||
|
store.dispatch(decrement());
|
||||||
|
expect(store.getState()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('combineReducers works', () => {
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
counter: counterReducer,
|
||||||
|
});
|
||||||
|
const store = createStore(rootReducer);
|
||||||
|
expect(store.getState()).toEqual({ counter: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createAction works', () => {
|
||||||
|
const incrementAction = increment();
|
||||||
|
expect(incrementAction).toEqual({ type: 'INCREMENT' });
|
||||||
|
const decrementAction = decrement();
|
||||||
|
expect(decrementAction).toEqual({ type: 'DECREMENT' });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -29,36 +29,6 @@ const testHubStorage = testGeneric(
|
|||||||
() => window.hubStorage && window.hubStorage.getItem,
|
() => window.hubStorage && window.hubStorage.getItem,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Remove with 516
|
|
||||||
// prettier-ignore
|
|
||||||
const testIndexedDb = testGeneric(() => (
|
|
||||||
(window.indexedDB || window.msIndexedDB)
|
|
||||||
&& (window.IDBTransaction || window.msIDBTransaction)
|
|
||||||
));
|
|
||||||
|
|
||||||
class MemoryBackend {
|
|
||||||
constructor() {
|
|
||||||
this.impl = IMPL_MEMORY;
|
|
||||||
this.store = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(key) {
|
|
||||||
return this.store[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(key, value) {
|
|
||||||
this.store[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(key) {
|
|
||||||
this.store[key] = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async clear() {
|
|
||||||
this.store = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HubStorageBackend {
|
class HubStorageBackend {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.impl = IMPL_HUB_STORAGE;
|
this.impl = IMPL_HUB_STORAGE;
|
||||||
@@ -84,63 +54,6 @@ class HubStorageBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IndexedDbBackend {
|
|
||||||
// TODO: Remove with 516
|
|
||||||
constructor() {
|
|
||||||
this.impl = IMPL_INDEXED_DB;
|
|
||||||
/** @type {Promise<IDBDatabase>} */
|
|
||||||
this.dbPromise = new Promise((resolve, reject) => {
|
|
||||||
const indexedDB = window.indexedDB || window.msIndexedDB;
|
|
||||||
const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
|
|
||||||
req.onupgradeneeded = () => {
|
|
||||||
try {
|
|
||||||
req.result.createObjectStore(INDEXED_DB_STORE_NAME);
|
|
||||||
} catch (err) {
|
|
||||||
reject(new Error('Failed to upgrade IDB: ' + req.error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
req.onsuccess = () => resolve(req.result);
|
|
||||||
req.onerror = () => {
|
|
||||||
reject(new Error('Failed to open IDB: ' + req.error));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getStore(mode) {
|
|
||||||
// prettier-ignore
|
|
||||||
return this.dbPromise.then((db) => db
|
|
||||||
.transaction(INDEXED_DB_STORE_NAME, mode)
|
|
||||||
.objectStore(INDEXED_DB_STORE_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(key) {
|
|
||||||
const store = await this.getStore(READ_ONLY);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const req = store.get(key);
|
|
||||||
req.onsuccess = () => resolve(req.result);
|
|
||||||
req.onerror = () => reject(req.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(key, value) {
|
|
||||||
// NOTE: We deliberately make this operation transactionless
|
|
||||||
const store = await this.getStore(READ_WRITE);
|
|
||||||
store.put(value, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(key) {
|
|
||||||
// NOTE: We deliberately make this operation transactionless
|
|
||||||
const store = await this.getStore(READ_WRITE);
|
|
||||||
store.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async clear() {
|
|
||||||
// NOTE: We deliberately make this operation transactionless
|
|
||||||
const store = await this.getStore(READ_WRITE);
|
|
||||||
store.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web Storage Proxy object, which selects the best backend available
|
* Web Storage Proxy object, which selects the best backend available
|
||||||
* depending on the environment.
|
* depending on the environment.
|
||||||
@@ -151,18 +64,6 @@ class StorageProxy {
|
|||||||
if (!Byond.TRIDENT && testHubStorage()) {
|
if (!Byond.TRIDENT && testHubStorage()) {
|
||||||
return new HubStorageBackend();
|
return new HubStorageBackend();
|
||||||
}
|
}
|
||||||
// TODO: Remove with 516
|
|
||||||
if (testIndexedDb()) {
|
|
||||||
try {
|
|
||||||
const backend = new IndexedDbBackend();
|
|
||||||
await backend.dbPromise;
|
|
||||||
return backend;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
console.warn(
|
|
||||||
'No supported storage backend found. Using in-memory storage.',
|
|
||||||
);
|
|
||||||
return new MemoryBackend();
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* N-dimensional vector manipulation functions.
|
|
||||||
*
|
|
||||||
* Vectors are plain number arrays, i.e. [x, y, z].
|
|
||||||
*
|
|
||||||
* @file
|
|
||||||
* @copyright 2020 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { map, reduce, zip } from './collections';
|
|
||||||
|
|
||||||
const ADD = (a: number, b: number): number => a + b;
|
|
||||||
const SUB = (a: number, b: number): number => a - b;
|
|
||||||
const MUL = (a: number, b: number): number => a * b;
|
|
||||||
const DIV = (a: number, b: number): number => a / b;
|
|
||||||
|
|
||||||
export type Vector = number[];
|
|
||||||
|
|
||||||
export const vecAdd = (...vecs: Vector[]): Vector => {
|
|
||||||
return map(zip(...vecs), (x) => reduce(x, ADD));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecSubtract = (...vecs: Vector[]): Vector => {
|
|
||||||
return map(zip(...vecs), (x) => reduce(x, SUB));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecMultiply = (...vecs: Vector[]): Vector => {
|
|
||||||
return map(zip(...vecs), (x) => reduce(x, MUL));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecDivide = (...vecs: Vector[]): Vector => {
|
|
||||||
return map(zip(...vecs), (x) => reduce(x, DIV));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecScale = (vec: Vector, n: number): Vector => {
|
|
||||||
return map(vec, (x) => x * n);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecInverse = (vec: Vector): Vector => {
|
|
||||||
return map(vec, (x) => -x);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecLength = (vec: Vector): number => {
|
|
||||||
return Math.sqrt(reduce(vecMultiply(vec, vec), ADD));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vecNormalize = (vec: Vector): Vector => {
|
|
||||||
const length = vecLength(vec);
|
|
||||||
return map(vec, (c) => c / length);
|
|
||||||
};
|
|
||||||
@@ -31,7 +31,7 @@ class WebpackCompiler {
|
|||||||
// and retrieve all necessary dependencies.
|
// and retrieve all necessary dependencies.
|
||||||
const requireFromRoot = createRequire(dirname(import.meta.url) + '/../..');
|
const requireFromRoot = createRequire(dirname(import.meta.url) + '/../..');
|
||||||
const webpack = await requireFromRoot('webpack');
|
const webpack = await requireFromRoot('webpack');
|
||||||
const createConfig = await requireFromRoot('./webpack.config.edge.js');
|
const createConfig = await requireFromRoot('./webpack.config.js');
|
||||||
const config = createConfig({}, options);
|
const config = createConfig({}, options);
|
||||||
// Inject the HMR plugin into the config if we're using it
|
// Inject the HMR plugin into the config if we're using it
|
||||||
if (options.hot) {
|
if (options.hot) {
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ export const chatMiddleware = (store) => {
|
|||||||
chatRenderer.processBatch([payload_obj.content], {
|
chatRenderer.processBatch([payload_obj.content], {
|
||||||
doArchive: true,
|
doArchive: true,
|
||||||
});
|
});
|
||||||
|
sequences.push(sequence);
|
||||||
if (game.roundId !== settings.lastId) {
|
if (game.roundId !== settings.lastId) {
|
||||||
storedRounds.push(game.roundId);
|
storedRounds.push(game.roundId);
|
||||||
storedLines.push(settings.totalStoredMessages - 1);
|
storedLines.push(settings.totalStoredMessages - 1);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
typeIsImportant,
|
typeIsImportant,
|
||||||
} from './model';
|
} from './model';
|
||||||
import { highlightNode, linkifyNode } from './replaceInTextNode';
|
import { highlightNode, linkifyNode } from './replaceInTextNode';
|
||||||
import { message } from './types';
|
import type { message } from './types';
|
||||||
|
|
||||||
const logger = createLogger('chatRenderer');
|
const logger = createLogger('chatRenderer');
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { vecLength, vecSubtract } from 'common/vector';
|
|
||||||
import { focusMap } from 'tgui/focus';
|
import { focusMap } from 'tgui/focus';
|
||||||
import { canStealFocus, globalEvents } from 'tgui-core/events';
|
import { canStealFocus, globalEvents } from 'tgui-core/events';
|
||||||
|
import { vecLength, vecSubtract } from 'tgui-core/vector';
|
||||||
|
|
||||||
// Empyrically determined number for the smallest possible
|
// Empyrically determined number for the smallest possible
|
||||||
// text you can select with the mouse.
|
// text you can select with the mouse.
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ img {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
-ms-interpolation-mode: nearest-neighbor; // TODO: Remove with 516
|
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ img {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
-ms-interpolation-mode: nearest-neighbor; // TODO: Remove with 516
|
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ img {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
-ms-interpolation-mode: nearest-neighbor; // TODO: Remove with 516
|
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ img {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
-ms-interpolation-mode: nearest-neighbor; // TODO: Remove with 516
|
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { Component, createRef, RefObject } from 'react';
|
import './styles/main.scss';
|
||||||
|
|
||||||
|
import { FormEvent, KeyboardEvent, useEffect, useRef, useState } from 'react';
|
||||||
import { dragStartHandler } from 'tgui/drag';
|
import { dragStartHandler } from 'tgui/drag';
|
||||||
import {
|
|
||||||
removeAllSkiplines,
|
|
||||||
sanitizeMultiline,
|
|
||||||
} from 'tgui/interfaces/TextInputModal';
|
|
||||||
import { isEscape, KEY } from 'tgui-core/keys';
|
import { isEscape, KEY } from 'tgui-core/keys';
|
||||||
import { clamp } from 'tgui-core/math';
|
import { clamp } from 'tgui-core/math';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import { type BooleanLike, classes } from 'tgui-core/react';
|
||||||
|
|
||||||
import { Channel, ChannelIterator } from './ChannelIterator';
|
import { Channel, ChannelIterator } from './ChannelIterator';
|
||||||
import { ChatHistory } from './ChatHistory';
|
import { ChatHistory } from './ChatHistory';
|
||||||
import { LINE_LENGTHS, RADIO_PREFIXES, WINDOW_SIZES } from './constants';
|
import { LineLength, RADIO_PREFIXES, WindowSize } from './constants';
|
||||||
import { windowClose, windowOpen, windowSet } from './helpers';
|
import { getPrefix, windowClose, windowOpen, windowSet } from './helpers';
|
||||||
import { byondMessages } from './timers';
|
import { byondMessages } from './timers';
|
||||||
|
|
||||||
type ByondOpen = {
|
type ByondOpen = {
|
||||||
@@ -21,390 +19,330 @@ type ByondOpen = {
|
|||||||
type ByondProps = {
|
type ByondProps = {
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
minimumHeight: number;
|
minimumHeight: number;
|
||||||
|
minimumWidth: number;
|
||||||
lightMode: BooleanLike;
|
lightMode: BooleanLike;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
const ROWS: Record<keyof typeof WindowSize, number> = {
|
||||||
buttonContent: string | number;
|
Small: 1,
|
||||||
size: number;
|
Medium: 2,
|
||||||
};
|
Large: 3,
|
||||||
|
Max: 20,
|
||||||
const CHANNEL_REGEX = /^:\w\s|^,b\s/;
|
Width: 360,
|
||||||
|
MaxWidth: 800,
|
||||||
const ROWS: Record<keyof typeof WINDOW_SIZES, number> = {
|
|
||||||
small: 1,
|
|
||||||
medium: 2,
|
|
||||||
large: 3,
|
|
||||||
max: 6,
|
|
||||||
width: 1, // not used
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export class TguiSay extends Component<{}, State> {
|
export function TguiSay() {
|
||||||
private channelIterator: ChannelIterator;
|
const innerRef = useRef<HTMLTextAreaElement>(null);
|
||||||
private chatHistory: ChatHistory;
|
const channelIterator = useRef(new ChannelIterator());
|
||||||
private currentPrefix: keyof typeof RADIO_PREFIXES | null;
|
const chatHistory = useRef(new ChatHistory());
|
||||||
private innerRef: RefObject<HTMLTextAreaElement>;
|
const messages = useRef(byondMessages);
|
||||||
private lightMode: boolean;
|
|
||||||
private minimumHeight: number;
|
|
||||||
private maxLength: number;
|
|
||||||
private messages: typeof byondMessages;
|
|
||||||
state: State;
|
|
||||||
|
|
||||||
constructor(props: never) {
|
// I initially wanted to make these an object or a reducer, but it's not really worth it.
|
||||||
super(props);
|
// You lose the granulatity and add a lot of boilerplate.
|
||||||
|
const [buttonContent, setButtonContent] = useState('');
|
||||||
|
const [currentPrefix, setCurrentPrefix] = useState<
|
||||||
|
keyof typeof RADIO_PREFIXES | null
|
||||||
|
>(null);
|
||||||
|
const [size, setSize] = useState(WindowSize.Small);
|
||||||
|
const [maxLength, setMaxLength] = useState(1024);
|
||||||
|
const [minimumHeight, setMinimumHeight] = useState(WindowSize.Small);
|
||||||
|
const [minimumWidth, setMinimumWidth] = useState(WindowSize.Width);
|
||||||
|
const [lightMode, setLightMode] = useState(false);
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
this.channelIterator = new ChannelIterator();
|
function handleArrowKeys(direction: KEY.Up | KEY.Down): void {
|
||||||
this.chatHistory = new ChatHistory();
|
const chat = chatHistory.current;
|
||||||
this.currentPrefix = null;
|
const iterator = channelIterator.current;
|
||||||
this.innerRef = createRef();
|
|
||||||
this.lightMode = false;
|
|
||||||
this.minimumHeight = 1;
|
|
||||||
this.maxLength = 1024;
|
|
||||||
this.messages = byondMessages;
|
|
||||||
this.state = {
|
|
||||||
buttonContent: '',
|
|
||||||
size: WINDOW_SIZES.small,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleArrowKeys = this.handleArrowKeys.bind(this);
|
if (direction === KEY.Up) {
|
||||||
this.handleBackspaceDelete = this.handleBackspaceDelete.bind(this);
|
if (chat.isAtLatest() && value) {
|
||||||
this.handleClose = this.handleClose.bind(this);
|
|
||||||
this.handleEnter = this.handleEnter.bind(this);
|
|
||||||
this.handleForceSay = this.handleForceSay.bind(this);
|
|
||||||
this.handleIncrementChannel = this.handleIncrementChannel.bind(this);
|
|
||||||
this.handleInput = this.handleInput.bind(this);
|
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
||||||
this.handleOpen = this.handleOpen.bind(this);
|
|
||||||
this.handleProps = this.handleProps.bind(this);
|
|
||||||
this.reset = this.reset.bind(this);
|
|
||||||
this.setSize = this.setSize.bind(this);
|
|
||||||
this.setValue = this.setValue.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
Byond.subscribeTo('props', this.handleProps);
|
|
||||||
Byond.subscribeTo('force', this.handleForceSay);
|
|
||||||
Byond.subscribeTo('open', this.handleOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleArrowKeys(direction: KEY.PageUp | KEY.PageDown) {
|
|
||||||
const currentValue = this.innerRef.current?.value;
|
|
||||||
|
|
||||||
if (direction === KEY.PageUp) {
|
|
||||||
if (this.chatHistory.isAtLatest() && currentValue) {
|
|
||||||
// Save current message to temp history if at the most recent message
|
// Save current message to temp history if at the most recent message
|
||||||
this.chatHistory.saveTemp(currentValue);
|
chat.saveTemp(value);
|
||||||
}
|
}
|
||||||
// Try to get the previous message, fall back to the current value if none
|
// Try to get the previous message, fall back to the current value if none
|
||||||
const prevMessage = this.chatHistory.getOlderMessage();
|
const prevMessage = chat.getOlderMessage();
|
||||||
|
|
||||||
if (prevMessage) {
|
if (prevMessage) {
|
||||||
this.setState({ buttonContent: this.chatHistory.getIndex() });
|
setButtonContent(chat.getIndex().toString());
|
||||||
this.setSize(prevMessage.length);
|
setValue(prevMessage);
|
||||||
this.setValue(prevMessage);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const nextMessage =
|
const nextMessage = chat.getNewerMessage() || chat.getTemp() || '';
|
||||||
this.chatHistory.getNewerMessage() || this.chatHistory.getTemp() || '';
|
|
||||||
|
|
||||||
const buttonContent = this.chatHistory.isAtLatest()
|
const newContent = chat.isAtLatest()
|
||||||
? this.channelIterator.current()
|
? iterator.current()
|
||||||
: this.chatHistory.getIndex();
|
: chat.getIndex().toString();
|
||||||
|
|
||||||
this.setState({ buttonContent });
|
setButtonContent(newContent);
|
||||||
this.setSize(nextMessage.length);
|
setValue(nextMessage);
|
||||||
this.setValue(nextMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBackspaceDelete() {
|
function handleBackspaceDelete(): void {
|
||||||
const typed = this.innerRef.current?.value;
|
const chat = chatHistory.current;
|
||||||
|
const iterator = channelIterator.current;
|
||||||
|
|
||||||
// User is on a chat history message
|
// User is on a chat history message
|
||||||
if (!this.chatHistory.isAtLatest()) {
|
if (!chat.isAtLatest()) {
|
||||||
this.chatHistory.reset();
|
chat.reset();
|
||||||
this.setState({
|
setButtonContent(currentPrefix ?? iterator.current());
|
||||||
buttonContent: this.currentPrefix ?? this.channelIterator.current(),
|
|
||||||
});
|
|
||||||
// Empty input, resets the channel
|
// Empty input, resets the channel
|
||||||
} else if (
|
} else if (currentPrefix && iterator.isSay() && value?.length === 0) {
|
||||||
!!this.currentPrefix &&
|
setCurrentPrefix(null);
|
||||||
this.channelIterator.isSay() &&
|
setButtonContent(iterator.current());
|
||||||
typed?.length === 0
|
|
||||||
) {
|
|
||||||
this.currentPrefix = null;
|
|
||||||
this.setState({ buttonContent: this.channelIterator.current() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setSize(typed?.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose() {
|
function handleClose(): void {
|
||||||
const current = this.innerRef.current;
|
innerRef.current?.blur();
|
||||||
|
|
||||||
if (current) {
|
|
||||||
current.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
this.chatHistory.reset();
|
|
||||||
this.channelIterator.reset();
|
|
||||||
this.currentPrefix = null;
|
|
||||||
windowClose();
|
windowClose();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
chatHistory.current.reset();
|
||||||
|
channelIterator.current.reset();
|
||||||
|
unloadChat();
|
||||||
|
}, 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEnter() {
|
function handleEnter(): void {
|
||||||
const prefix = this.currentPrefix ?? '';
|
const iterator = channelIterator.current;
|
||||||
const value = this.innerRef.current?.value;
|
const prefix = currentPrefix ?? '';
|
||||||
|
|
||||||
if (value?.length && value.length < this.maxLength) {
|
|
||||||
this.chatHistory.add(value);
|
|
||||||
|
|
||||||
// Everything can be multiline, but only emotes get passed that way to the game
|
|
||||||
const sanitizedValue = this.channelIterator.isMultiline()
|
|
||||||
? sanitizeMultiline(value)
|
|
||||||
: removeAllSkiplines(value);
|
|
||||||
|
|
||||||
|
if (value?.length && value.length < maxLength) {
|
||||||
|
chatHistory.current.add(value);
|
||||||
Byond.sendMessage('entry', {
|
Byond.sendMessage('entry', {
|
||||||
channel: this.channelIterator.current(),
|
channel: iterator.current(),
|
||||||
entry: this.channelIterator.isSay()
|
entry: iterator.isSay() ? prefix + value : value,
|
||||||
? prefix + sanitizedValue
|
|
||||||
: sanitizedValue,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleForceSay() {
|
function handleForceSay(): void {
|
||||||
const currentValue = this.innerRef.current?.value;
|
const iterator = channelIterator.current;
|
||||||
|
|
||||||
// Only force say if we're on a visible channel and have typed something
|
// Only force say if we're on a visible channel and have typed something
|
||||||
if (!currentValue || !this.channelIterator.isVisible()) return;
|
if (!value || iterator.isVisible()) return;
|
||||||
|
|
||||||
const prefix = this.currentPrefix ?? '';
|
const prefix = currentPrefix ?? '';
|
||||||
const grunt = this.channelIterator.isSay()
|
const grunt = iterator.isSay() ? prefix + value : value;
|
||||||
? prefix + currentValue
|
|
||||||
: currentValue;
|
|
||||||
|
|
||||||
this.messages.forceSayMsg(grunt);
|
messages.current.forceSayMsg(grunt, iterator.current());
|
||||||
this.reset();
|
unloadChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIncrementChannel() {
|
function handleIncrementChannel(): void {
|
||||||
this.currentPrefix = null;
|
const iterator = channelIterator.current;
|
||||||
|
|
||||||
this.channelIterator.next();
|
iterator.next();
|
||||||
|
setButtonContent(iterator.current());
|
||||||
// If we've looped onto a quiet channel, tell byond to hide thinking indicators
|
setCurrentPrefix(null);
|
||||||
if (!this.channelIterator.isVisible()) {
|
messages.current.channelIncrementMsg(iterator.isVisible());
|
||||||
this.messages.channelIncrementMsg(false, this.channelIterator.current());
|
|
||||||
} else {
|
|
||||||
this.messages.channelIncrementMsg(true, this.channelIterator.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ buttonContent: this.channelIterator.current() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDecrementChannel() {
|
function handleInput(event: FormEvent<HTMLTextAreaElement>): void {
|
||||||
this.currentPrefix = null;
|
let newValue = event.currentTarget.value;
|
||||||
|
|
||||||
this.channelIterator.prev();
|
let newPrefix = getPrefix(newValue) || currentPrefix;
|
||||||
|
// Handles switching prefixes
|
||||||
|
if (newPrefix && newPrefix !== currentPrefix) {
|
||||||
|
setButtonContent(RADIO_PREFIXES[newPrefix]);
|
||||||
|
setCurrentPrefix(newPrefix);
|
||||||
|
newValue = newValue.slice(3);
|
||||||
|
|
||||||
// If we've looped onto a quiet channel, tell byond to hide thinking indicators
|
if (newPrefix === ',b ') {
|
||||||
if (!this.channelIterator.isVisible()) {
|
Byond.sendMessage('thinking', { visible: false });
|
||||||
this.messages.channelIncrementMsg(false, this.channelIterator.current());
|
}
|
||||||
} else {
|
|
||||||
this.messages.channelIncrementMsg(true, this.channelIterator.current());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ buttonContent: this.channelIterator.current() });
|
// Handles typing indicators
|
||||||
|
if (channelIterator.current.isVisible() && newPrefix !== ',b ') {
|
||||||
|
messages.current.typingMsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInput() {
|
function getMarkupString(
|
||||||
const typed = this.innerRef.current?.value;
|
inputText: string,
|
||||||
|
markupType: string,
|
||||||
// If we're typing, send the message
|
startPosition: number,
|
||||||
if (this.channelIterator.isVisible()) {
|
endPosition: number,
|
||||||
this.messages.typingMsg(this.channelIterator.current());
|
) {
|
||||||
}
|
return `${inputText.substring(0, startPosition)}${markupType}${inputText.substring(startPosition, endPosition)}${markupType}${inputText.substring(endPosition)}`;
|
||||||
|
|
||||||
this.setSize(typed?.length);
|
|
||||||
|
|
||||||
// Is there a value? Is it long enough to be a prefix?
|
|
||||||
if (!typed || typed.length < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CHANNEL_REGEX.test(typed)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it a valid prefix?
|
|
||||||
const prefix = typed
|
|
||||||
.slice(0, 3)
|
|
||||||
?.toLowerCase() as keyof typeof RADIO_PREFIXES;
|
|
||||||
if (!RADIO_PREFIXES[prefix] || prefix === this.currentPrefix) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.channelIterator.set('Say');
|
|
||||||
this.currentPrefix = prefix;
|
|
||||||
this.setState({ buttonContent: RADIO_PREFIXES[prefix] });
|
|
||||||
this.setValue(typed.slice(3));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(event: React.KeyboardEvent<HTMLTextAreaElement>) {
|
function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>): void {
|
||||||
const currentValue = this.innerRef.current?.value;
|
if (event.getModifierState('AltGraph')) return;
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case KEY.PageUp:
|
case 'u': // replace with tgui core 1.8.x
|
||||||
case KEY.PageDown:
|
if (event.ctrlKey || event.metaKey) {
|
||||||
// Allow moving between lines if there are newlines
|
event.preventDefault();
|
||||||
/* if (currentValue?.includes('\n')) {
|
const { value, selectionStart, selectionEnd } = event.currentTarget;
|
||||||
break;
|
event.currentTarget.value = getMarkupString(
|
||||||
} */
|
value,
|
||||||
|
'_',
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
);
|
||||||
|
event.currentTarget.selectionEnd = selectionEnd + 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'i': // replace with tgui core 1.8.x
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
const { value, selectionStart, selectionEnd } = event.currentTarget;
|
||||||
|
event.currentTarget.value = getMarkupString(
|
||||||
|
value,
|
||||||
|
'|',
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
);
|
||||||
|
event.currentTarget.selectionEnd = selectionEnd + 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'b': // replace with tgui core 1.8.x
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
const { value, selectionStart, selectionEnd } = event.currentTarget;
|
||||||
|
event.currentTarget.value = getMarkupString(
|
||||||
|
value,
|
||||||
|
'+',
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
);
|
||||||
|
event.currentTarget.selectionEnd = selectionEnd + 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY.Up:
|
||||||
|
case KEY.Down:
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.handleArrowKeys(event.key);
|
handleArrowKeys(event.key);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KEY.Delete:
|
case KEY.Delete:
|
||||||
case KEY.Backspace:
|
case KEY.Backspace:
|
||||||
this.handleBackspaceDelete();
|
handleBackspaceDelete();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KEY.Enter:
|
case KEY.Enter:
|
||||||
// Allow inputting newlines
|
|
||||||
if (event.shiftKey) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.handleEnter();
|
handleEnter();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KEY.Tab:
|
case KEY.Tab:
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.shiftKey) {
|
handleIncrementChannel();
|
||||||
this.handleDecrementChannel();
|
|
||||||
} else {
|
|
||||||
this.handleIncrementChannel();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (isEscape(event.key)) {
|
if (isEscape(event.key)) {
|
||||||
this.handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpen = (data: ByondOpen) => {
|
function handleOpen(data: ByondOpen): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.innerRef.current?.focus();
|
innerRef.current?.focus();
|
||||||
}, 0);
|
windowSet(WindowSize.Width, WindowSize.Small);
|
||||||
|
setSize(WindowSize.Width);
|
||||||
|
}, 1);
|
||||||
|
|
||||||
const { channel } = data;
|
const { channel } = data;
|
||||||
|
const iterator = channelIterator.current;
|
||||||
// Catches the case where the modal is already open
|
// Catches the case where the modal is already open
|
||||||
if (this.channelIterator.isSay()) {
|
if (iterator.isSay()) {
|
||||||
this.channelIterator.set(channel);
|
iterator.set(channel);
|
||||||
}
|
}
|
||||||
this.setState({ buttonContent: this.channelIterator.current() });
|
|
||||||
|
|
||||||
windowOpen(this.channelIterator.current());
|
setButtonContent(iterator.current());
|
||||||
};
|
windowOpen(iterator.current());
|
||||||
|
|
||||||
handleProps = (data: ByondProps) => {
|
|
||||||
const { maxLength, minimumHeight, lightMode } = data;
|
|
||||||
this.maxLength = maxLength;
|
|
||||||
this.minimumHeight = minimumHeight;
|
|
||||||
this.setSize();
|
|
||||||
this.lightMode = !!lightMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.setValue('');
|
|
||||||
this.setSize();
|
|
||||||
this.setState({
|
|
||||||
buttonContent: this.channelIterator.current(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(length = 0) {
|
function handleProps(data: ByondProps): void {
|
||||||
let newSize: number;
|
setMaxLength(data.maxLength);
|
||||||
|
setMinimumHeight(data.minimumHeight);
|
||||||
const currentValue = this.innerRef.current?.value;
|
const minWidth = clamp(
|
||||||
|
data.minimumWidth,
|
||||||
if (currentValue?.includes('\n')) {
|
WindowSize.Width,
|
||||||
newSize = WINDOW_SIZES.large;
|
WindowSize.MaxWidth,
|
||||||
} else if (length > LINE_LENGTHS.medium) {
|
|
||||||
newSize = WINDOW_SIZES.large;
|
|
||||||
} else if (length <= LINE_LENGTHS.medium && length > LINE_LENGTHS.small) {
|
|
||||||
newSize = WINDOW_SIZES.medium;
|
|
||||||
} else {
|
|
||||||
newSize = WINDOW_SIZES.small;
|
|
||||||
}
|
|
||||||
|
|
||||||
newSize = clamp(newSize, this.minimumHeight * 20 + 10, WINDOW_SIZES.max);
|
|
||||||
console.log(newSize);
|
|
||||||
|
|
||||||
if (this.state.size !== newSize) {
|
|
||||||
this.setState({ size: newSize });
|
|
||||||
windowSet(newSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(value: string) {
|
|
||||||
const textArea = this.innerRef.current;
|
|
||||||
if (textArea) {
|
|
||||||
textArea.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const theme =
|
|
||||||
(this.lightMode && 'lightMode') ||
|
|
||||||
(this.currentPrefix && RADIO_PREFIXES[this.currentPrefix]) ||
|
|
||||||
this.channelIterator.current();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`window window-${theme} window-${this.state.size}`}>
|
|
||||||
<Dragzone position="top" theme={theme} />
|
|
||||||
<div className="center">
|
|
||||||
<Dragzone position="left" theme={theme} />
|
|
||||||
<div className="input">
|
|
||||||
<button
|
|
||||||
className={`button button-${theme}`}
|
|
||||||
onClick={this.handleIncrementChannel}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{this.state.buttonContent}
|
|
||||||
</button>
|
|
||||||
<textarea
|
|
||||||
autoCorrect="off"
|
|
||||||
className={`textarea textarea-${theme}`}
|
|
||||||
maxLength={this.maxLength}
|
|
||||||
onInput={this.handleInput}
|
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
ref={this.innerRef}
|
|
||||||
spellCheck={false} // TODO: make preference
|
|
||||||
rows={ROWS[this.state.size] || 1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Dragzone position="right" theme={theme} />
|
|
||||||
</div>
|
|
||||||
<Dragzone position="bottom" theme={theme} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
setMinimumWidth(minWidth);
|
||||||
|
setLightMode(!!data.lightMode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const Dragzone = ({ theme, position }: { theme: string; position: string }) => {
|
function unloadChat(): void {
|
||||||
// Horizontal or vertical?
|
setCurrentPrefix(null);
|
||||||
const location =
|
setButtonContent(channelIterator.current.current());
|
||||||
position === 'left' || position === 'right' ? 'vertical' : 'horizontal';
|
setValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Subscribe to Byond messages */
|
||||||
|
useEffect(() => {
|
||||||
|
Byond.subscribeTo('props', handleProps);
|
||||||
|
Byond.subscribeTo('force', handleForceSay);
|
||||||
|
Byond.subscribeTo('open', handleOpen);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/** Value has changed, we need to check if the size of the window is ok */
|
||||||
|
useEffect(() => {
|
||||||
|
const len = value?.length || 0;
|
||||||
|
|
||||||
|
let newSize: WindowSize;
|
||||||
|
if (len > LineLength.Medium) {
|
||||||
|
newSize = WindowSize.Large;
|
||||||
|
} else if (len <= LineLength.Medium && len > LineLength.Small) {
|
||||||
|
newSize = WindowSize.Medium;
|
||||||
|
} else {
|
||||||
|
newSize = WindowSize.Small;
|
||||||
|
}
|
||||||
|
newSize = clamp(newSize, minimumHeight * 20 + 10, WindowSize.Max);
|
||||||
|
|
||||||
|
if (size !== newSize) {
|
||||||
|
setSize(newSize);
|
||||||
|
windowSet(minimumWidth, newSize);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const theme =
|
||||||
|
(lightMode && 'lightMode') ||
|
||||||
|
(currentPrefix && RADIO_PREFIXES[currentPrefix]) ||
|
||||||
|
channelIterator.current.current();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`dragzone-${location} dragzone-${position} dragzone-${theme}`}
|
<div
|
||||||
onMouseDown={dragStartHandler}
|
className={`window window-${theme} window-${size}`}
|
||||||
/>
|
onMouseDown={dragStartHandler}
|
||||||
|
>
|
||||||
|
{!lightMode && <div className={`shine shine-${theme}`} />}
|
||||||
|
</div>
|
||||||
|
<div className={classes(['content', lightMode && 'content-lightMode'])}>
|
||||||
|
<button
|
||||||
|
className={`button button-${theme}`}
|
||||||
|
onClick={handleIncrementChannel}
|
||||||
|
onMouseDown={dragStartHandler}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{buttonContent}
|
||||||
|
</button>
|
||||||
|
<textarea
|
||||||
|
autoCorrect="off"
|
||||||
|
className={`textarea textarea-${theme}`}
|
||||||
|
maxLength={maxLength}
|
||||||
|
onInput={handleInput}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
ref={innerRef}
|
||||||
|
spellCheck={false}
|
||||||
|
rows={ROWS[size] || 1}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
/** Window sizes in pixels */
|
/** Window sizes in pixels */
|
||||||
export enum WINDOW_SIZES {
|
export enum WindowSize {
|
||||||
small = 30,
|
Small = 30,
|
||||||
medium = 50,
|
Medium = 50,
|
||||||
large = 70,
|
Large = 70,
|
||||||
max = 130,
|
Max = 410,
|
||||||
width = 360,
|
Width = 360,
|
||||||
|
MaxWidth = 800,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Line lengths for autoexpand */
|
/** Line lengths for autoexpand */
|
||||||
export enum LINE_LENGTHS {
|
export enum LineLength {
|
||||||
small = 22,
|
Small = 22,
|
||||||
medium = 45,
|
Medium = 45,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Binary file not shown.
@@ -1,33 +1,35 @@
|
|||||||
import { Channel } from './ChannelIterator';
|
import { Channel } from './ChannelIterator';
|
||||||
import { WINDOW_SIZES } from './constants';
|
import { RADIO_PREFIXES, WindowSize } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Once byond signals this via keystroke, it
|
* Once byond signals this via keystroke, it
|
||||||
* ensures window size, visibility, and focus.
|
* ensures window size, visibility, and focus.
|
||||||
*/
|
*/
|
||||||
export const windowOpen = (channel: Channel) => {
|
export function windowOpen(channel: Channel): void {
|
||||||
setWindowVisibility(true);
|
setWindowVisibility(true);
|
||||||
Byond.sendMessage('open', { channel });
|
Byond.sendMessage('open', { channel });
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the state of the window and hides it from user view.
|
* Resets the state of the window and hides it from user view.
|
||||||
* Sending "close" logs it server side.
|
* Sending "close" logs it server side.
|
||||||
*/
|
*/
|
||||||
export const windowClose = () => {
|
export function windowClose(): void {
|
||||||
setWindowVisibility(false);
|
setWindowVisibility(false);
|
||||||
Byond.winset('map', {
|
Byond.winset('map', {
|
||||||
focus: true,
|
focus: true,
|
||||||
});
|
});
|
||||||
Byond.sendMessage('close');
|
Byond.sendMessage('close');
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies the window size.
|
* Modifies the window size.
|
||||||
*/
|
*/
|
||||||
export const windowSet = (size: number = WINDOW_SIZES.small) => {
|
export function windowSet(
|
||||||
let sizeStr = `${WINDOW_SIZES.width}x${size}`;
|
width = WindowSize.Width,
|
||||||
console.log(sizeStr);
|
size = WindowSize.Small,
|
||||||
|
): void {
|
||||||
|
let sizeStr = `${width}x${size}`;
|
||||||
|
|
||||||
Byond.winset('tgui_say.browser', {
|
Byond.winset('tgui_say.browser', {
|
||||||
size: sizeStr,
|
size: sizeStr,
|
||||||
@@ -36,11 +38,34 @@ export const windowSet = (size: number = WINDOW_SIZES.small) => {
|
|||||||
Byond.winset('tgui_say', {
|
Byond.winset('tgui_say', {
|
||||||
size: sizeStr,
|
size: sizeStr,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** Helper function to set window size and visibility */
|
/** Helper function to set window size and visibility */
|
||||||
const setWindowVisibility = (visible: boolean) => {
|
function setWindowVisibility(visible: boolean): void {
|
||||||
Byond.winset('tgui_say', {
|
Byond.winset('tgui_say', {
|
||||||
'is-visible': visible,
|
'is-visible': visible,
|
||||||
|
size: `${WindowSize.Width}x${WindowSize.Small}`,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const CHANNEL_REGEX = /^[:.]|,b\w\s/;
|
||||||
|
|
||||||
|
/** Tests for a channel prefix, returning it or none */
|
||||||
|
export function getPrefix(
|
||||||
|
value: string,
|
||||||
|
): keyof typeof RADIO_PREFIXES | undefined {
|
||||||
|
if (!value || value.length < 3 || !CHANNEL_REGEX.test(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjusted = value
|
||||||
|
.slice(0, 3)
|
||||||
|
?.toLowerCase()
|
||||||
|
?.replace('.', ':') as keyof typeof RADIO_PREFIXES;
|
||||||
|
|
||||||
|
if (!RADIO_PREFIXES[adjusted]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjusted;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
@use 'sass:color';
|
|
||||||
@use './colors.scss';
|
|
||||||
|
|
||||||
.button {
|
|
||||||
align-items: center;
|
|
||||||
background-color: colors.$button;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
border: thin solid;
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: bold;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0;
|
|
||||||
width: 2.6rem;
|
|
||||||
&:hover {
|
|
||||||
background-color: color.adjust(
|
|
||||||
colors.$button,
|
|
||||||
$lightness: 10%,
|
|
||||||
$space: hsl
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-lightMode {
|
|
||||||
background-color: colors.$lightBorder;
|
|
||||||
border: none;
|
|
||||||
color: black;
|
|
||||||
&:hover {
|
|
||||||
background-color: colors.$lightHover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
@use 'sass:map';
|
@use 'sass:map';
|
||||||
|
|
||||||
$background: hsl(0, 0%, 7%);
|
$background: hsl(0, 0%, 7.5%);
|
||||||
$button: hsl(0, 0%, 12%);
|
$button: hsl(0, 0%, 12.2%);
|
||||||
$lightMode: hsl(0, 0%, 100%);
|
$lightMode: hsl(0, 0%, 100%);
|
||||||
$lightBorder: hsl(0, 0%, 73%);
|
$lightBorder: hsl(0, 0%, 73.3%);
|
||||||
$lightHover: hsl(0, 0%, 92%);
|
$lightHover: hsl(0, 0%, 91.8%);
|
||||||
$scrollbar-color-multiplier: 1 !default;
|
$scrollbar-color-multiplier: 1 !default;
|
||||||
|
|
||||||
$_channel_map: (
|
$_channel_map: (
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
.center {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove conditionals with 516
|
|
||||||
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
|
|
||||||
.center {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove with 516
|
|
||||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
|
||||||
.center {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
flex: 1 1 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
display: flex;
|
|
||||||
font-family: 'Consolas', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove conditionals with 516
|
|
||||||
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
|
|
||||||
.input {
|
|
||||||
flex-grow: 1;
|
|
||||||
gap: 0.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove with 516
|
|
||||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
|
||||||
.input {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
flex: 1 1 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
@use 'sass:color';
|
|
||||||
@use './colors.scss';
|
|
||||||
|
|
||||||
// Remove conditionals with 516
|
|
||||||
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
|
|
||||||
$dragSize: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove with 516
|
|
||||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
|
||||||
$dragSize: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
$dragSize: 0.3rem;
|
|
||||||
$borderSize: 0.2rem;
|
|
||||||
|
|
||||||
.dragzone-horizontal {
|
|
||||||
border-left: $borderSize solid;
|
|
||||||
border-right: $borderSize solid;
|
|
||||||
color: transparent;
|
|
||||||
width: 100%;
|
|
||||||
height: $dragSize;
|
|
||||||
}
|
|
||||||
.dragzone-left {
|
|
||||||
border-left: $borderSize solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragzone-right {
|
|
||||||
border-right: $borderSize solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragzone-vertical {
|
|
||||||
color: transparent;
|
|
||||||
height: 100%;
|
|
||||||
width: $dragSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragzone-top {
|
|
||||||
border-top: $borderSize solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragzone-bottom {
|
|
||||||
border-bottom: $borderSize solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Lightmode static theme */
|
|
||||||
.dragzone-lightMode {
|
|
||||||
border-color: colors.$lightBorder;
|
|
||||||
}
|
|
||||||
@@ -3,27 +3,13 @@
|
|||||||
@use './colors.scss';
|
@use './colors.scss';
|
||||||
|
|
||||||
// Core styles
|
// Core styles
|
||||||
@include meta.load-css('~tgui/styles/reset.scss');
|
@include meta.load-css('~tgui/styles/reset');
|
||||||
// Atomic styles
|
// Atomic styles
|
||||||
@include meta.load-css('~tgui/styles/atomic/text.scss');
|
@include meta.load-css('~tgui/styles/atomic/text.scss');
|
||||||
// External styles
|
// External styles
|
||||||
@include meta.load-css('~tgui-core/styles/components/TextArea');
|
@include meta.load-css('~tgui-core/styles/components/TextArea');
|
||||||
// Local styles
|
// Local styles
|
||||||
@include meta.load-css('./button.scss');
|
@include meta.load-css('./styles.scss');
|
||||||
@include meta.load-css('./content.scss');
|
|
||||||
@include meta.load-css('./dragzone.scss');
|
|
||||||
@include meta.load-css('./textarea.scss');
|
|
||||||
@include meta.load-css('./window.scss');
|
|
||||||
|
|
||||||
@keyframes gradient {
|
|
||||||
0% {
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-position: 100% 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@each $channel, $color in colors.$channel-map {
|
@each $channel, $color in colors.$channel-map {
|
||||||
$darkened: color.adjust($color, $lightness: -20%, $space: hsl);
|
$darkened: color.adjust($color, $lightness: -20%, $space: hsl);
|
||||||
@@ -37,34 +23,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dragzone-#{$channel} {
|
|
||||||
border-color: $darkened;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea-#{$channel} {
|
.textarea-#{$channel} {
|
||||||
color: $color;
|
color: $color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.window-#{$channel} {
|
.window-#{$channel} {
|
||||||
&:after {
|
background-color: $color;
|
||||||
animation: gradient 10s linear infinite;
|
}
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
.shine-#{$channel} {
|
||||||
color.adjust($color, $lightness: -35%, $space: hsl),
|
background: radial-gradient(
|
||||||
$color,
|
circle,
|
||||||
color.adjust($color, $lightness: 10%, $space: hsl),
|
color.adjust($color, $lightness: 5%, $space: hsl),
|
||||||
$color,
|
color.adjust($color, $lightness: -15%, $space: hsl),
|
||||||
color.adjust($color, $lightness: -35%, $space: hsl)
|
color.adjust($color, $lightness: 15%, $space: hsl),
|
||||||
);
|
color.adjust($color, $lightness: -15%, $space: hsl),
|
||||||
background-position: 0% 0%;
|
color.adjust($color, $lightness: 5%, $space: hsl)
|
||||||
background-size: 500% auto;
|
);
|
||||||
bottom: 0px;
|
|
||||||
content: '';
|
|
||||||
height: 2px;
|
|
||||||
left: 0px;
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
119
tgui/packages/tgui-say/styles/styles.scss
Normal file
119
tgui/packages/tgui-say/styles/styles.scss
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
@use 'sass:color';
|
||||||
|
@use './colors.scss';
|
||||||
|
|
||||||
|
.window {
|
||||||
|
background-color: black;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shine {
|
||||||
|
animation: shine 15s linear infinite;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-size: 150% 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-lightMode {
|
||||||
|
background-color: colors.$lightMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Window sizes */
|
||||||
|
.window-30 {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-50 {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-70 {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background-color: black;
|
||||||
|
display: grid;
|
||||||
|
font: 'tgfont';
|
||||||
|
grid-template-columns: 3.5rem 1fr;
|
||||||
|
inset: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-lightMode {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: colors.$button;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
border: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
outline: none;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0.1rem 0.2rem;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
&:hover {
|
||||||
|
background-color: color.adjust(
|
||||||
|
colors.$button,
|
||||||
|
$lightness: 10%,
|
||||||
|
$space: hsl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-lightMode {
|
||||||
|
background-color: colors.$lightBorder;
|
||||||
|
border: none;
|
||||||
|
color: black;
|
||||||
|
&:hover {
|
||||||
|
background-color: colors.$lightHover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 0.1rem 0 0 0.4rem;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0.8rem;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: color.scale(
|
||||||
|
colors.$button,
|
||||||
|
$lightness: -25% * colors.$scrollbar-color-multiplier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: color.scale(
|
||||||
|
colors.$button,
|
||||||
|
$lightness: 10% * colors.$scrollbar-color-multiplier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
@use 'sass:color';
|
|
||||||
@use './colors.scss';
|
|
||||||
|
|
||||||
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
|
|
||||||
* {
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: color.scale(
|
|
||||||
colors.$button,
|
|
||||||
$lightness: -25% * colors.$scrollbar-color-multiplier
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: color.scale(
|
|
||||||
colors.$button,
|
|
||||||
$lightness: 10% * colors.$scrollbar-color-multiplier
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove conditionals with 516
|
|
||||||
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
|
|
||||||
.textarea {
|
|
||||||
flex-grow: 8;
|
|
||||||
outline: none;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove with 516
|
|
||||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
|
||||||
.textarea {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 4;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0.1rem 0 0 0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
@use 'sass:color';
|
|
||||||
@use './colors.scss';
|
|
||||||
|
|
||||||
.window {
|
|
||||||
background-color: colors.$background;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 360px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove with 516
|
|
||||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
|
||||||
.window {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-lightMode {
|
|
||||||
background-color: colors.$lightMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Window sizes */
|
|
||||||
.window-30 {
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-50 {
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-70 {
|
|
||||||
height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-90 {
|
|
||||||
height: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-110 {
|
|
||||||
height: 110px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-130 {
|
|
||||||
height: 130px;
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
import { debounce, throttle } from 'tgui-core/timer';
|
import { debounce, throttle } from 'tgui-core/timer';
|
||||||
|
|
||||||
|
import { Channel } from './ChannelIterator';
|
||||||
|
|
||||||
const SECONDS = 1000;
|
const SECONDS = 1000;
|
||||||
|
|
||||||
/** Timers: Prevents overloading the server, throttles messages */
|
/** Timers: Prevents overloading the server, throttles messages */
|
||||||
export const byondMessages = {
|
export const byondMessages = {
|
||||||
// Debounce: Prevents spamming the server
|
// Debounce: Prevents spamming the server
|
||||||
channelIncrementMsg: debounce(
|
channelIncrementMsg: debounce(
|
||||||
(visible: boolean, channel: string) =>
|
(visible: boolean) => Byond.sendMessage('thinking', { visible }),
|
||||||
Byond.sendMessage('thinking', { visible, channel }),
|
|
||||||
0.4 * SECONDS,
|
0.4 * SECONDS,
|
||||||
),
|
),
|
||||||
forceSayMsg: debounce(
|
forceSayMsg: debounce(
|
||||||
(entry: string) => Byond.sendMessage('force', { entry, channel: 'Say' }),
|
(entry: string, channel: Channel) =>
|
||||||
|
Byond.sendMessage('force', { entry, channel }),
|
||||||
1 * SECONDS,
|
1 * SECONDS,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
// Throttle: Prevents spamming the server
|
// Throttle: Prevents spamming the server
|
||||||
typingMsg: throttle(
|
typingMsg: throttle(() => Byond.sendMessage('typing'), 4 * SECONDS),
|
||||||
(channel: string) => Byond.sendMessage('typing', { channel }),
|
|
||||||
4 * SECONDS,
|
|
||||||
),
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -49,9 +49,18 @@ export class NanoMap extends Component<Props, State> {
|
|||||||
handleDragMove: (e: MouseEvent) => void;
|
handleDragMove: (e: MouseEvent) => void;
|
||||||
handleDragEnd: (e: MouseEvent) => void;
|
handleDragEnd: (e: MouseEvent) => void;
|
||||||
handleZoom: (e: Event, v: number) => void;
|
handleZoom: (e: Event, v: number) => void;
|
||||||
|
handleWheel: (e: WheelEvent) => void;
|
||||||
handleKey: (e: KeyEvent) => void;
|
handleKey: (e: KeyEvent) => void;
|
||||||
ref: EventTarget;
|
ref: EventTarget;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
|
||||||
setZoom(zoom: number) {
|
setZoom(zoom: number) {
|
||||||
const newZoom = Math.min(Math.max(zoom, 1), 8);
|
const newZoom = Math.min(Math.max(zoom, 1), 8);
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
@@ -134,6 +143,14 @@ export class NanoMap extends Component<Props, State> {
|
|||||||
pauseEvent(e);
|
pauseEvent(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.handleWheel = (e: WheelEvent) => {
|
||||||
|
if (e.deltaY > 0) {
|
||||||
|
this.setZoom(this.state.zoom + 1);
|
||||||
|
} else if (e.deltaY < 0) {
|
||||||
|
this.setZoom(this.state.zoom - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.handleZoom = (_e: Event, value: number) => {
|
this.handleZoom = (_e: Event, value: number) => {
|
||||||
this.setZoom(value);
|
this.setZoom(value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { storage } from 'common/storage';
|
import { storage } from 'common/storage';
|
||||||
import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'common/vector';
|
import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'tgui-core/vector';
|
||||||
|
|
||||||
import { createLogger } from './logging';
|
import { createLogger } from './logging';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
ProgressBar,
|
ProgressBar,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
ProgressBar,
|
ProgressBar,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { FullscreenNotice } from './common/FullscreenNotice';
|
import { FullscreenNotice } from './common/FullscreenNotice';
|
||||||
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
id_inserted: BooleanLike;
|
id_inserted: BooleanLike;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Button, Section, Table } from 'tgui-core/components';
|
import { Button, Section, Table } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
entries: { name: string; value: string }[];
|
entries: { name: string; value: string }[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Button, LabeledList, Section } from 'tgui-core/components';
|
import { Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
power: {
|
power: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
ProgressBar,
|
ProgressBar,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
AI_present: boolean;
|
AI_present: boolean;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
ProgressBar,
|
ProgressBar,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { FullscreenNotice } from './common/FullscreenNotice';
|
import { FullscreenNotice } from './common/FullscreenNotice';
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { getGasColor, getGasLabel } from 'tgui/constants';
|
|||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
import { toFixed } from 'tgui-core/math';
|
import { toFixed } from 'tgui-core/math';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { Scrubber, Vent } from './common/AtmosControls';
|
import { Scrubber, Vent } from './common/AtmosControls';
|
||||||
import { single_scrubber, single_vent } from './common/CommonTypes';
|
import type { single_scrubber, single_vent } from './common/CommonTypes';
|
||||||
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, ColorBox, LabeledList, Section } from 'tgui-core/components';
|
import { Button, ColorBox, LabeledList, Section } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data, species } from './types';
|
import type { Data, species } from './types';
|
||||||
|
|
||||||
export const AppearanceChangerSpecies = (props) => {
|
export const AppearanceChangerSpecies = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, Section } from 'tgui-core/components';
|
import { Button, Section } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const AppearanceChangerBodyRecords = () => {
|
export const AppearanceChangerBodyRecords = () => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ import {
|
|||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { createSearch } from 'tgui-core/string';
|
import { createSearch } from 'tgui-core/string';
|
||||||
|
|
||||||
import { MARKINGS_PER_PAGE } from './constants';
|
import {
|
||||||
import { bodyStyle, Data, SPRITE_ACCESSORY_COLOR_CHANNEL_NAMES } from './types';
|
MARKINGS_PER_PAGE,
|
||||||
|
SPRITE_ACCESSORY_COLOR_CHANNEL_NAMES,
|
||||||
|
} from './constants';
|
||||||
|
import type { bodyStyle, Data } from './types';
|
||||||
|
|
||||||
export const AppearanceChangerColors = (props) => {
|
export const AppearanceChangerColors = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useBackend } from 'tgui/backend';
|
|||||||
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
import { capitalize } from 'tgui-core/string';
|
import { capitalize } from 'tgui-core/string';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const AppearanceChangerFlavor = (props) => {
|
export const AppearanceChangerFlavor = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, Section } from 'tgui-core/components';
|
import { Button, Section } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const AppearanceChangerHeader = (props) => {
|
export const AppearanceChangerHeader = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useBackend } from 'tgui/backend';
|
|||||||
import { ImageButton, Input, Section, Stack } from 'tgui-core/components';
|
import { ImageButton, Input, Section, Stack } from 'tgui-core/components';
|
||||||
import { createSearch } from 'tgui-core/string';
|
import { createSearch } from 'tgui-core/string';
|
||||||
|
|
||||||
import { bodyStyle, Data, styles } from './types';
|
import type { bodyStyle, Data, styles } from './types';
|
||||||
|
|
||||||
export const AppearanceChangerParts = (props: {
|
export const AppearanceChangerParts = (props: {
|
||||||
sectionNames: string[];
|
sectionNames: string[];
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
export const SPRITE_ACCESSORY_COLOR_CHANNEL_NAMES = [
|
||||||
|
'Primary',
|
||||||
|
'Secondary',
|
||||||
|
'Tertiary',
|
||||||
|
'Quaternary',
|
||||||
|
];
|
||||||
|
|
||||||
export const TAB_RACE = 0;
|
export const TAB_RACE = 0;
|
||||||
export const TAB_FLAVOR = 1;
|
export const TAB_FLAVOR = 1;
|
||||||
export const TAB_GENDER = 2;
|
export const TAB_GENDER = 2;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
TAB_TAIL,
|
TAB_TAIL,
|
||||||
TAB_WINGS,
|
TAB_WINGS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const AppearanceChanger = (props) => {
|
export const AppearanceChanger = (props) => {
|
||||||
const { act, config, data } = useBackend<Data>();
|
const { act, config, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import { BooleanLike } from 'tgui-core/react';
|
import { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
export const SPRITE_ACCESSORY_COLOR_CHANNEL_NAMES = [
|
|
||||||
'Primary',
|
|
||||||
'Secondary',
|
|
||||||
'Tertiary',
|
|
||||||
'Quaternary',
|
|
||||||
];
|
|
||||||
|
|
||||||
export type Data = {
|
export type Data = {
|
||||||
name: string;
|
name: string;
|
||||||
specimen: string;
|
specimen: string;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Stack,
|
Stack,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Box, Button, LabeledList, Section, Stack } from 'tgui-core/components';
|
import { Box, Button, LabeledList, Section, Stack } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
import { toTitleCase } from 'tgui-core/string';
|
import { toTitleCase } from 'tgui-core/string';
|
||||||
|
|
||||||
type scrubber = {
|
type scrubber = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Button, LabeledList, Section } from 'tgui-core/components';
|
import { Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
on: BooleanLike;
|
on: BooleanLike;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { formatTime } from 'tgui-core/format';
|
import { formatTime } from 'tgui-core/format';
|
||||||
import { round } from 'tgui-core/math';
|
import { round } from 'tgui-core/math';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
timing: number;
|
timing: number;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
NumberInput,
|
NumberInput,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
on: BooleanLike;
|
on: BooleanLike;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
NumberInput,
|
NumberInput,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
on: BooleanLike;
|
on: BooleanLike;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
VirtualList,
|
VirtualList,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { formatSiUnit } from 'tgui-core/format';
|
import { formatSiUnit } from 'tgui-core/format';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
import { toTitleCase } from 'tgui-core/string';
|
import { toTitleCase } from 'tgui-core/string';
|
||||||
|
|
||||||
import { Materials } from './ExosuitFabricator/Material';
|
import { Materials } from './ExosuitFabricator/Material';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
import { capitalize } from 'tgui-core/string';
|
import { capitalize } from 'tgui-core/string';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Stack,
|
Stack,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
import { createSearch } from 'tgui-core/string';
|
import { createSearch } from 'tgui-core/string';
|
||||||
|
|
||||||
type sortable = {
|
type sortable = {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { BodyScannerMainOccupant } from './BodyScannerMainOccupant';
|
|||||||
import { BodyScannerMainOrgansExternal } from './BodyScannerMainOrgansExternal';
|
import { BodyScannerMainOrgansExternal } from './BodyScannerMainOrgansExternal';
|
||||||
import { BodyScannerMainOrgansInternal } from './BodyScannerMainOrgansInternal';
|
import { BodyScannerMainOrgansInternal } from './BodyScannerMainOrgansInternal';
|
||||||
import { BodyScannerMainReagents } from './BodyScannerMainReagents';
|
import { BodyScannerMainReagents } from './BodyScannerMainReagents';
|
||||||
import { occupant } from './types';
|
import type { occupant } from './types';
|
||||||
|
|
||||||
export const BodyScannerMain = (props: { occupant: occupant }) => {
|
export const BodyScannerMain = (props: { occupant: occupant }) => {
|
||||||
const { occupant } = props;
|
const { occupant } = props;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Box, Section } from 'tgui-core/components';
|
import { Box, Section } from 'tgui-core/components';
|
||||||
|
|
||||||
import { abnormalities } from './constants';
|
import { abnormalities } from './constants';
|
||||||
import { occupant } from './types';
|
import type { occupant } from './types';
|
||||||
|
|
||||||
export const BodyScannerMainAbnormalities = (props: { occupant: occupant }) => {
|
export const BodyScannerMainAbnormalities = (props: { occupant: occupant }) => {
|
||||||
const { occupant } = props;
|
const { occupant } = props;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { toFixed } from 'tgui-core/math';
|
|||||||
|
|
||||||
import { damageRange, damages } from './constants';
|
import { damageRange, damages } from './constants';
|
||||||
import { mapTwoByTwo } from './functions';
|
import { mapTwoByTwo } from './functions';
|
||||||
import { occupant } from './types';
|
import type { occupant } from './types';
|
||||||
|
|
||||||
export const BodyScannerMainDamage = (props: { occupant: occupant }) => {
|
export const BodyScannerMainDamage = (props: { occupant: occupant }) => {
|
||||||
const { occupant } = props;
|
const { occupant } = props;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { toFixed } from 'tgui-core/math';
|
import { toFixed } from 'tgui-core/math';
|
||||||
|
|
||||||
import { stats } from './constants';
|
import { stats } from './constants';
|
||||||
import { occupant } from './types';
|
import type { occupant } from './types';
|
||||||
|
|
||||||
export const BodyScannerMainOccupant = (props: { occupant: occupant }) => {
|
export const BodyScannerMainOccupant = (props: { occupant: occupant }) => {
|
||||||
const { act } = useBackend();
|
const { act } = useBackend();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { toFixed } from 'tgui-core/math';
|
|||||||
|
|
||||||
import { damageRange } from './constants';
|
import { damageRange } from './constants';
|
||||||
import { germStatus, reduceOrganStatus } from './functions';
|
import { germStatus, reduceOrganStatus } from './functions';
|
||||||
import { externalOrgan } from './types';
|
import type { externalOrgan } from './types';
|
||||||
|
|
||||||
export const BodyScannerMainOrgansExternal = (props: {
|
export const BodyScannerMainOrgansExternal = (props: {
|
||||||
organs: externalOrgan[];
|
organs: externalOrgan[];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { toFixed } from 'tgui-core/math';
|
|||||||
|
|
||||||
import { damageRange } from './constants';
|
import { damageRange } from './constants';
|
||||||
import { germStatus, reduceOrganStatus } from './functions';
|
import { germStatus, reduceOrganStatus } from './functions';
|
||||||
import { internalOrgan } from './types';
|
import type { internalOrgan } from './types';
|
||||||
|
|
||||||
export const BodyScannerMainOrgansInternal = (props: {
|
export const BodyScannerMainOrgansInternal = (props: {
|
||||||
organs: internalOrgan[];
|
organs: internalOrgan[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Box, Section, Table } from 'tgui-core/components';
|
import { Box, Section, Table } from 'tgui-core/components';
|
||||||
|
|
||||||
import { occupant } from './types';
|
import type { occupant } from './types';
|
||||||
|
|
||||||
export const BodyScannerMainReagents = (props: { occupant: occupant }) => {
|
export const BodyScannerMainReagents = (props: { occupant: occupant }) => {
|
||||||
const { occupant } = props;
|
const { occupant } = props;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Box } from 'tgui-core/components';
|
import { Box } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Window } from 'tgui/layouts';
|
|||||||
|
|
||||||
import { BodyScannerEmpty } from './BodyScannerEmpty';
|
import { BodyScannerEmpty } from './BodyScannerEmpty';
|
||||||
import { BodyScannerMain } from './BodyScannerMain';
|
import { BodyScannerMain } from './BodyScannerMain';
|
||||||
import { Data, occupant } from './types';
|
import type { Data, occupant } from './types';
|
||||||
|
|
||||||
export const BodyScanner = (props) => {
|
export const BodyScanner = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Slider,
|
Slider,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
simulating: BooleanLike;
|
simulating: BooleanLike;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
NoticeBox,
|
NoticeBox,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
activity: BooleanLike;
|
activity: BooleanLike;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
NoticeBox,
|
NoticeBox,
|
||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
geneMasks: { tag: string; mask: string }[];
|
geneMasks: { tag: string; mask: string }[];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Window } from 'tgui/layouts';
|
|||||||
import { Button, NumberInput, Section, Stack } from 'tgui-core/components';
|
import { Button, NumberInput, Section, Stack } from 'tgui-core/components';
|
||||||
import { formatTime } from 'tgui-core/format';
|
import { formatTime } from 'tgui-core/format';
|
||||||
import { round } from 'tgui-core/math';
|
import { round } from 'tgui-core/math';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
time_left: number;
|
time_left: number;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Stack,
|
Stack,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike, classes } from 'tgui-core/react';
|
import { type BooleanLike, classes } from 'tgui-core/react';
|
||||||
import { createSearch } from 'tgui-core/string';
|
import { createSearch } from 'tgui-core/string';
|
||||||
|
|
||||||
type activeCamera = { name: string; status: BooleanLike } | null;
|
type activeCamera = { name: string; status: BooleanLike } | null;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { formatSiUnit } from 'tgui-core/format';
|
import { formatSiUnit } from 'tgui-core/format';
|
||||||
import { toFixed } from 'tgui-core/math';
|
import { toFixed } from 'tgui-core/math';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
connected: BooleanLike;
|
connected: BooleanLike;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useBackend } from 'tgui/backend';
|
|||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Box, Button, Stack } from 'tgui-core/components';
|
import { Box, Button, Stack } from 'tgui-core/components';
|
||||||
import { clamp } from 'tgui-core/math';
|
import { clamp } from 'tgui-core/math';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
const PX_PER_UNIT = 24;
|
const PX_PER_UNIT = 24;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Table,
|
Table,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
const getTagColor = (tag: string) => {
|
const getTagColor = (tag: string) => {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Box, Button, Section } from 'tgui-core/components';
|
|||||||
|
|
||||||
import { BeakerContents } from '../common/BeakerContents';
|
import { BeakerContents } from '../common/BeakerContents';
|
||||||
import { removeAmounts } from './constants';
|
import { removeAmounts } from './constants';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemDispenserBeaker = (props) => {
|
export const ChemDispenserBeaker = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, Icon, Section, Stack, Tooltip } from 'tgui-core/components';
|
import { Button, Icon, Section, Stack, Tooltip } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemDispenserChemicals = (props) => {
|
export const ChemDispenserChemicals = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Box, Button, Section, Stack } from 'tgui-core/components';
|
import { Box, Button, Section, Stack } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemDispenserRecipes = (props) => {
|
export const ChemDispenserRecipes = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useBackend } from 'tgui/backend';
|
|||||||
import { Button, LabeledList, Section, Slider } from 'tgui-core/components';
|
import { Button, LabeledList, Section, Slider } from 'tgui-core/components';
|
||||||
|
|
||||||
import { dispenseAmounts } from './constants';
|
import { dispenseAmounts } from './constants';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemDispenserSettings = (props) => {
|
export const ChemDispenserSettings = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ChemDispenserBeaker } from './ChemDispenserBeaker';
|
|||||||
import { ChemDispenserChemicals } from './ChemDispenserChemicals';
|
import { ChemDispenserChemicals } from './ChemDispenserChemicals';
|
||||||
import { ChemDispenserRecipes } from './ChemDispenserRecipes';
|
import { ChemDispenserRecipes } from './ChemDispenserRecipes';
|
||||||
import { ChemDispenserSettings } from './ChemDispenserSettings';
|
import { ChemDispenserSettings } from './ChemDispenserSettings';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemDispenser = (props) => {
|
export const ChemDispenser = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data, modalData } from './types';
|
import type { Data, modalData } from './types';
|
||||||
|
|
||||||
export const analyzeModalBodyOverride = (modal: modalData) => {
|
export const analyzeModalBodyOverride = (modal: modalData) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Box, Button, Section } from 'tgui-core/components';
|
import { Box, Button, Section } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { BeakerContents } from '../common/BeakerContents';
|
import { BeakerContents } from '../common/BeakerContents';
|
||||||
import { modalOpen } from '../common/ComplexModal';
|
import { modalOpen } from '../common/ComplexModal';
|
||||||
import { transferAmounts } from './constants';
|
import { transferAmounts } from './constants';
|
||||||
import { reagent } from './types';
|
import type { reagent } from './types';
|
||||||
|
|
||||||
export const ChemMasterBeaker = (props: {
|
export const ChemMasterBeaker = (props: {
|
||||||
beaker: BooleanLike;
|
beaker: BooleanLike;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Box, Button, Section } from 'tgui-core/components';
|
import { Box, Button, Section } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { BeakerContents } from '../common/BeakerContents';
|
import { BeakerContents } from '../common/BeakerContents';
|
||||||
import { modalOpen } from '../common/ComplexModal';
|
import { modalOpen } from '../common/ComplexModal';
|
||||||
import { transferAmounts } from './constants';
|
import { transferAmounts } from './constants';
|
||||||
import { reagent } from './types';
|
import type { reagent } from './types';
|
||||||
|
|
||||||
export const ChemMasterBuffer = (props: {
|
export const ChemMasterBuffer = (props: {
|
||||||
mode: BooleanLike;
|
mode: BooleanLike;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Box, Button, Section } from 'tgui-core/components';
|
import { Box, Button, Section } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { modalOpen } from '../common/ComplexModal';
|
import { modalOpen } from '../common/ComplexModal';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, Icon, Section, Stack } from 'tgui-core/components';
|
import { Button, Icon, Section, Stack } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
import { ChemMasterProductionChemical } from './ChemMasterProductionChemical';
|
import { ChemMasterProductionChemical } from './ChemMasterProductionChemical';
|
||||||
import { ChemMasterProductionCondiment } from './ChemMasterProductionCondiment';
|
import { ChemMasterProductionCondiment } from './ChemMasterProductionCondiment';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { analyzeModalBodyOverride } from './ChemMasterAnalyzeModalBodyOverride';
|
|||||||
import { ChemMasterBeaker } from './ChemMasterBeaker';
|
import { ChemMasterBeaker } from './ChemMasterBeaker';
|
||||||
import { ChemMasterBuffer } from './ChemMasterBuffer';
|
import { ChemMasterBuffer } from './ChemMasterBuffer';
|
||||||
import { ChemMasterProduction } from './ChemMasterProduction';
|
import { ChemMasterProduction } from './ChemMasterProduction';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemMaster = (props) => {
|
export const ChemMaster = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Window } from 'tgui/layouts';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
import { BooleanLike } from 'tgui-core/react';
|
import type { BooleanLike } from 'tgui-core/react';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
on: BooleanLike;
|
on: BooleanLike;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useBackend } from 'tgui/backend';
|
|||||||
import { COLORS } from 'tgui/constants';
|
import { COLORS } from 'tgui/constants';
|
||||||
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
import { Box, Button, LabeledList, Section } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data, modalData } from './types';
|
import type { Data, modalData } from './types';
|
||||||
|
|
||||||
export const viewRecordModalBodyOverride = (modal: modalData) => {
|
export const viewRecordModalBodyOverride = (modal: modalData) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Tabs } from 'tgui-core/components';
|
import { Tabs } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const CloningConsoleNavigation = (props) => {
|
export const CloningConsoleNavigation = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const CloningConsoleTemp = (props) => {
|
export const CloningConsoleTemp = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { toFixed } from 'tgui-core/math';
|
import { toFixed } from 'tgui-core/math';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const CloningConsoleMain = (props) => {
|
export const CloningConsoleMain = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
CloningConsoleMain,
|
CloningConsoleMain,
|
||||||
CloningConsoleRecords,
|
CloningConsoleRecords,
|
||||||
} from './CloningConsoleTabs';
|
} from './CloningConsoleTabs';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const CloningConsole = (props) => {
|
export const CloningConsole = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useBackend } from 'tgui/backend';
|
|||||||
import { Button, Slider, Table } from 'tgui-core/components';
|
import { Button, Slider, Table } from 'tgui-core/components';
|
||||||
import { toFixed } from 'tgui-core/math';
|
import { toFixed } from 'tgui-core/math';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ColorMateTint = (props) => {
|
export const ColorMateTint = (props) => {
|
||||||
const { act } = useBackend();
|
const { act } = useBackend();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
import { toFixed } from 'tgui-core/math';
|
import { toFixed } from 'tgui-core/math';
|
||||||
|
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ColorMateMatrix = (props) => {
|
export const ColorMateMatrix = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
|
|
||||||
import { ColorMateHSV, ColorMateTint } from './ColorMateColor';
|
import { ColorMateHSV, ColorMateTint } from './ColorMateColor';
|
||||||
import { ColorMateMatrix } from './ColorMateMatrix';
|
import { ColorMateMatrix } from './ColorMateMatrix';
|
||||||
import { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ColorMate = (props) => {
|
export const ColorMate = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user