mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
tgui: Fixes assets, CDN support (#52321)
* tgui: Better asset code, CDN support * Rebuild tgui * µ-fix
This commit is contained in:
@@ -21,8 +21,6 @@
|
||||
#define TGUI_WINDOW_LOADING 1
|
||||
/// Window is free and ready to receive data
|
||||
#define TGUI_WINDOW_READY 2
|
||||
/// Window is in use by a tgui datum
|
||||
#define TGUI_WINDOW_ACTIVE 3
|
||||
|
||||
/// Get a window id based on the provided pool index
|
||||
#define TGUI_WINDOW_ID(index) "tgui-window-[index]"
|
||||
|
||||
@@ -45,7 +45,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
|
||||
if (!ACI)
|
||||
continue
|
||||
.[asset_name] = ACI.url
|
||||
|
||||
|
||||
|
||||
// For registering or sending multiple others at once
|
||||
/datum/asset/group
|
||||
@@ -86,7 +86,9 @@ GLOBAL_LIST_EMPTY(asset_datums)
|
||||
if (!name)
|
||||
CRASH("spritesheet [type] cannot register without a name")
|
||||
ensure_stripped()
|
||||
|
||||
for(var/size_id in sizes)
|
||||
var/size = sizes[size_id]
|
||||
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
|
||||
var/res_name = "spritesheet_[name].css"
|
||||
var/fname = "data/spritesheets/[res_name]"
|
||||
fdel(fname)
|
||||
@@ -94,10 +96,6 @@ GLOBAL_LIST_EMPTY(asset_datums)
|
||||
register_asset(res_name, fcopy_rsc(fname))
|
||||
fdel(fname)
|
||||
|
||||
for(var/size_id in sizes)
|
||||
var/size = sizes[size_id]
|
||||
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
|
||||
|
||||
/datum/asset/spritesheet/send(client/C)
|
||||
if (!name)
|
||||
return
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
|
||||
)
|
||||
|
||||
/datum/asset/group/tgui
|
||||
children = list(
|
||||
/datum/asset/simple/tgui,
|
||||
/datum/asset/simple/fontawesome
|
||||
)
|
||||
|
||||
/datum/asset/simple/headers
|
||||
assets = list(
|
||||
"alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if (!ui)
|
||||
ui = new(user, src, "NtosMain")
|
||||
ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
|
||||
ui.set_autoupdate(TRUE)
|
||||
ui.open()
|
||||
ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
|
||||
|
||||
|
||||
/obj/item/modular_computer/ui_data(mob/user)
|
||||
|
||||
@@ -170,8 +170,8 @@
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui && tgui_id)
|
||||
ui = new(user, src, tgui_id, filedesc)
|
||||
ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
|
||||
ui.open()
|
||||
ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
|
||||
|
||||
// CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC:
|
||||
// Topic calls are automagically forwarded from NanoModule this program contains.
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
var/status = UI_INTERACTIVE
|
||||
/// Topic state used to determine status/interactability.
|
||||
var/datum/ui_state/state = null
|
||||
/// Asset data to be sent with every update
|
||||
var/list/asset_data
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -82,11 +80,14 @@
|
||||
opened_at = world.time
|
||||
window.acquire_lock(src)
|
||||
if(!window.is_ready())
|
||||
window.initialize()
|
||||
window.initialize(inline_assets = list(
|
||||
get_asset_datum(/datum/asset/simple/tgui),
|
||||
))
|
||||
else
|
||||
window.send_message("ping")
|
||||
window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome))
|
||||
for(var/datum/asset/asset in src_object.ui_assets(user))
|
||||
send_asset(asset)
|
||||
window.send_asset(asset)
|
||||
window.send_message("update", get_payload(
|
||||
with_data = TRUE,
|
||||
with_static_data = TRUE))
|
||||
@@ -143,14 +144,10 @@
|
||||
*
|
||||
* required asset datum/asset
|
||||
*/
|
||||
/datum/tgui/proc/send_asset(var/datum/asset/asset)
|
||||
if(!user.client)
|
||||
return
|
||||
if(istype(asset, /datum/asset/spritesheet))
|
||||
var/datum/asset/spritesheet/spritesheet = asset
|
||||
LAZYINITLIST(asset_data)
|
||||
LAZYADD(asset_data["styles"], list(spritesheet.css_filename()))
|
||||
asset.send(user)
|
||||
/datum/tgui/proc/send_asset(datum/asset/asset)
|
||||
if(!window)
|
||||
CRASH("send_asset() can only be called after open().")
|
||||
window.send_asset(asset)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -216,8 +213,6 @@
|
||||
var/static_data = with_static_data && src_object.ui_static_data(user)
|
||||
if(static_data)
|
||||
json_data["static_data"] = static_data
|
||||
if(asset_data)
|
||||
json_data["assets"] = asset_data
|
||||
if(src_object.tgui_shared_states)
|
||||
json_data["shared"] = src_object.tgui_shared_states
|
||||
return json_data
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
var/datum/tgui/locked_by
|
||||
var/fatally_errored = FALSE
|
||||
var/message_queue
|
||||
var/sent_assets = list()
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -36,8 +37,10 @@
|
||||
* Initializes the window with a fresh page. Puts window into the "loading"
|
||||
* state. You can begin sending messages right after initializing. Messages
|
||||
* will be put into the queue until the window finishes loading.
|
||||
*
|
||||
* optional inline_assets list List of assets to inline into the html.
|
||||
*/
|
||||
/datum/tgui_window/proc/initialize()
|
||||
/datum/tgui_window/proc/initialize(inline_assets = list())
|
||||
log_tgui(client, "[id]/initialize")
|
||||
if(!client)
|
||||
return
|
||||
@@ -52,13 +55,23 @@
|
||||
else
|
||||
options += "titlebar=1;can_resize=1;"
|
||||
// Generate page html
|
||||
// TODO: Make this static
|
||||
var/html = SStgui.basehtml
|
||||
html = replacetextEx(html, "\[tgui:windowId]", id)
|
||||
// Send required assets
|
||||
var/datum/asset/asset
|
||||
asset = get_asset_datum(/datum/asset/group/tgui)
|
||||
asset.send(client)
|
||||
// Process inline assets
|
||||
var/inline_styles = ""
|
||||
var/inline_scripts = ""
|
||||
for(var/datum/asset/asset in inline_assets)
|
||||
var/mappings = asset.get_url_mappings()
|
||||
for(var/name in mappings)
|
||||
var/url = mappings[name]
|
||||
// Not urlencoding since asset strings are considered safe
|
||||
if(copytext(name, -4) == ".css")
|
||||
inline_styles += "<link rel=\"stylesheet\" type=\"text/css\" href=\"[url]\">\n"
|
||||
else if(copytext(name, -3) == ".js")
|
||||
inline_scripts += "<script type=\"text/javascript\" defer src=\"[url]\"></script>\n"
|
||||
asset.send()
|
||||
html = replacetextEx(html, "<!-- tgui:styles -->\n", inline_styles)
|
||||
html = replacetextEx(html, "<!-- tgui:scripts -->\n", inline_scripts)
|
||||
// Open the window
|
||||
client << browse(html, "window=[id];[options]")
|
||||
// Instruct the client to signal UI when the window is closed.
|
||||
@@ -86,7 +99,7 @@
|
||||
&& pooled \
|
||||
&& pool_index > 0 \
|
||||
&& pool_index <= TGUI_WINDOW_SOFT_LIMIT \
|
||||
&& status >= TGUI_WINDOW_READY
|
||||
&& status == TGUI_WINDOW_READY
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -107,6 +120,9 @@
|
||||
* Release the window lock.
|
||||
*/
|
||||
/datum/tgui_window/proc/release_lock()
|
||||
// Clean up assets sent by tgui datum which requested the lock
|
||||
if(locked)
|
||||
sent_assets = list()
|
||||
locked = FALSE
|
||||
locked_by = null
|
||||
|
||||
@@ -126,8 +142,7 @@
|
||||
send_message("suspend")
|
||||
return
|
||||
log_tgui(client, "[id]/close")
|
||||
locked = FALSE
|
||||
locked_by = null
|
||||
release_lock()
|
||||
status = TGUI_WINDOW_CLOSED
|
||||
message_queue = null
|
||||
// Do not close the window to give user some time
|
||||
@@ -157,13 +172,30 @@
|
||||
// Pack for sending via output()
|
||||
message = url_encode(message)
|
||||
// Place into queue if window is still loading
|
||||
if(!force && status == TGUI_WINDOW_LOADING)
|
||||
if(!force && status != TGUI_WINDOW_READY)
|
||||
if(!message_queue)
|
||||
message_queue = list()
|
||||
message_queue += list(message)
|
||||
return
|
||||
client << output(message, "[id].browser:update")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Makes an asset available to use in tgui.
|
||||
*
|
||||
* required asset datum/asset
|
||||
*/
|
||||
/datum/tgui_window/proc/send_asset(datum/asset/asset)
|
||||
if(!client || !asset)
|
||||
return
|
||||
if(istype(asset, /datum/asset/spritesheet))
|
||||
var/datum/asset/spritesheet/spritesheet = asset
|
||||
send_message("asset/stylesheet", spritesheet.css_filename())
|
||||
send_message("asset/mappings", asset.get_url_mappings())
|
||||
sent_assets += list(asset)
|
||||
asset.send(client)
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
@@ -184,6 +216,11 @@
|
||||
/datum/tgui_window/proc/on_message(type, list/payload, list/href_list)
|
||||
switch(type)
|
||||
if("ready")
|
||||
// Status can be READY if user has refreshed the window.
|
||||
if(status == TGUI_WINDOW_READY)
|
||||
// Resend the assets
|
||||
for(var/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
status = TGUI_WINDOW_READY
|
||||
if("log")
|
||||
if(href_list["fatal"])
|
||||
|
||||
@@ -98,8 +98,8 @@ const sendRawMessage = msg => {
|
||||
socket.send(json);
|
||||
}
|
||||
else {
|
||||
// Keep only 10 latest messages in the queue
|
||||
if (queue.length > 10) {
|
||||
// Keep only 100 latest messages in the queue
|
||||
if (queue.length > 100) {
|
||||
queue.shift();
|
||||
}
|
||||
queue.push(json);
|
||||
|
||||
@@ -9,15 +9,46 @@ import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('assets');
|
||||
|
||||
const loadedAssets = {
|
||||
styles: [],
|
||||
};
|
||||
const EXCLUDED_PATTERNS = [
|
||||
/v4shim/i,
|
||||
];
|
||||
|
||||
export const loadCSS = filename => {
|
||||
if (loadedAssets.styles.includes(filename)) {
|
||||
const loadedStyles = [];
|
||||
const loadedMappings = {};
|
||||
|
||||
export const loadCSS = url => {
|
||||
if (loadedStyles.includes(url)) {
|
||||
return;
|
||||
}
|
||||
loadedAssets.styles.push(filename);
|
||||
logger.log(`loading stylesheet '${filename}'`);
|
||||
fgLoadCSS(filename);
|
||||
loadedStyles.push(url);
|
||||
logger.log(`loading stylesheet '${url}'`);
|
||||
fgLoadCSS(url);
|
||||
};
|
||||
|
||||
export const resolveAsset = name => (
|
||||
loadedMappings[name] || name
|
||||
);
|
||||
|
||||
export const assetMiddleware = store => next => action => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'asset/stylesheet') {
|
||||
loadCSS(payload);
|
||||
return;
|
||||
}
|
||||
if (type === 'asset/mappings') {
|
||||
for (let name of Object.keys(payload)) {
|
||||
// Skip anything that matches excluded patterns
|
||||
if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
|
||||
continue;
|
||||
}
|
||||
const url = payload[name];
|
||||
const ext = name.split('.').pop();
|
||||
loadedMappings[name] = url;
|
||||
if (ext === 'css') {
|
||||
loadCSS(url);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
next(action);
|
||||
};
|
||||
|
||||
@@ -79,7 +79,6 @@ export const backendReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
config,
|
||||
assets: payload.assets || {},
|
||||
data,
|
||||
shared,
|
||||
visible,
|
||||
@@ -253,7 +252,6 @@ export const sendAct = (action, payload = {}) => {
|
||||
* },
|
||||
* },
|
||||
* data: any,
|
||||
* assets: any,
|
||||
* shared: any,
|
||||
* visible: boolean,
|
||||
* interactive: boolean,
|
||||
|
||||
@@ -30,7 +30,6 @@ import './styles/themes/syndicate.scss';
|
||||
import { perf } from 'common/perf';
|
||||
import { render } from 'inferno';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client';
|
||||
import { loadCSS } from './assets';
|
||||
import { backendUpdate, backendSuspendSuccess, selectBackend, sendMessage } from './backend';
|
||||
import { setupDrag } from './drag';
|
||||
import { logger } from './logging';
|
||||
@@ -96,8 +95,6 @@ const renderLayout = () => {
|
||||
if (initialRender) {
|
||||
initialRender = false;
|
||||
}
|
||||
// Load assets
|
||||
assets?.styles?.forEach(filename => loadCSS(filename));
|
||||
};
|
||||
|
||||
// Parse JSON and report all abnormal JSON strings coming from BYOND
|
||||
@@ -143,9 +140,8 @@ const setupApp = () => {
|
||||
logger.debug(`received message '${message?.type}'`);
|
||||
const { type, payload } = message;
|
||||
if (type === 'update') {
|
||||
window.__ref__ = payload.config.ref;
|
||||
if (suspended) {
|
||||
logger.log('reinitializing to:', payload.config.ref);
|
||||
logger.log('resuming');
|
||||
initialRender = 'recycled';
|
||||
}
|
||||
// Backend update dispatches a store action
|
||||
@@ -162,7 +158,8 @@ const setupApp = () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
logger.log('unhandled message', message);
|
||||
// Pass the message directly to the store
|
||||
store.dispatch(message);
|
||||
};
|
||||
|
||||
// Enable hot module reloading
|
||||
@@ -185,9 +182,6 @@ const setupApp = () => {
|
||||
}
|
||||
window.update(stateJson);
|
||||
}
|
||||
|
||||
// Dynamically load font-awesome from browser's cache
|
||||
loadCSS('font-awesome.css');
|
||||
};
|
||||
|
||||
// Setup a fatal error reporter
|
||||
|
||||
@@ -3,6 +3,7 @@ import { flow } from 'common/fp';
|
||||
import { classes } from 'common/react';
|
||||
import { capitalize } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Collapsible, Dimmer, Divider, Dropdown, Flex, Icon, LabeledList, NumberInput, ProgressBar, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -777,7 +778,7 @@ const DnaConsoleSequencer = (props, context) => {
|
||||
{mutations.map(mutation => (
|
||||
<GenomeImage
|
||||
key={mutation.Alias}
|
||||
url={mutation.Image}
|
||||
url={resolveAsset(mutation.Image)}
|
||||
selected={mutation.Alias === sequencerMutation}
|
||||
onClick={() => {
|
||||
act('set_view', {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Grid, LabeledList, ProgressBar, Section } from '../components';
|
||||
import { NtosWindow } from '../layouts';
|
||||
@@ -73,7 +74,7 @@ export const NtosArcade = (props, context) => {
|
||||
inline
|
||||
width="156px"
|
||||
textAlign="center">
|
||||
<img src={data.BossID} />
|
||||
<img src={resolveAsset(data.BossID)} />
|
||||
</Section>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { classes } from 'common/react';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Icon, NoticeBox, Section } from '../components';
|
||||
import { NtosWindow } from '../layouts';
|
||||
@@ -68,7 +69,9 @@ export const NtosRadarContent = (props, context) => {
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
style={{
|
||||
'background-image': 'url("ntosradarbackground.png")',
|
||||
'background-image': 'url("'
|
||||
+ resolveAsset('ntosradarbackground.png')
|
||||
+ '")',
|
||||
'background-position': 'center',
|
||||
'background-repeat': 'no-repeat',
|
||||
'top': '20px',
|
||||
@@ -91,14 +94,13 @@ export const NtosRadarContent = (props, context) => {
|
||||
)
|
||||
: !!target.userot && (
|
||||
<Box as="img"
|
||||
src={target.arrowstyle}
|
||||
src={resolveAsset(target.arrowstyle)}
|
||||
position="absolute"
|
||||
top="20px"
|
||||
left="243px"
|
||||
style={{
|
||||
'transform': `rotate(${target.rot}deg)`,
|
||||
}}
|
||||
/>
|
||||
}} />
|
||||
) || (
|
||||
<Icon
|
||||
name={target.pointer}
|
||||
@@ -106,8 +108,7 @@ export const NtosRadarContent = (props, context) => {
|
||||
size={2}
|
||||
color={target.color}
|
||||
top={((target.locy * 10) + 19) + 'px'}
|
||||
left={((target.locx * 10) + 16) + 'px'}
|
||||
/>
|
||||
left={((target.locx * 10) + 16) + 'px'} />
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSearch } from 'common/string';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Flex, Icon, Input, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -67,7 +68,7 @@ const OrbitedButton = (props, context) => {
|
||||
{"("}{thing.orbiters}{" "}
|
||||
<Box
|
||||
as="img"
|
||||
src="ghost.png"
|
||||
src={resolveAsset('ghost.png')}
|
||||
opacity={0.7} />
|
||||
{")"}
|
||||
</Box>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button } from '../components';
|
||||
import { refocusLayout } from './Layout';
|
||||
@@ -57,14 +58,14 @@ export const NtosWindow = (props, context) => {
|
||||
<Box key={header.icon} inline mr={1}>
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={header.icon} />
|
||||
src={resolveAsset(header.icon)} />
|
||||
</Box>
|
||||
))}
|
||||
<Box inline>
|
||||
{PC_ntneticon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_ntneticon} />
|
||||
src={resolveAsset(PC_ntneticon)} />
|
||||
)}
|
||||
</Box>
|
||||
{!!PC_showbatteryicon && PC_batteryicon && (
|
||||
@@ -72,7 +73,7 @@ export const NtosWindow = (props, context) => {
|
||||
{PC_batteryicon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_batteryicon} />
|
||||
src={resolveAsset(PC_batteryicon)} />
|
||||
)}
|
||||
{PC_batterypercent && (
|
||||
PC_batterypercent
|
||||
@@ -83,7 +84,7 @@ export const NtosWindow = (props, context) => {
|
||||
<Box inline mr={1}>
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_apclinkicon} />
|
||||
src={resolveAsset(PC_apclinkicon)} />
|
||||
</Box>
|
||||
)}
|
||||
{!!PC_showexitprogram && (
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -218,14 +218,10 @@ Byond.topic({
|
||||
</script>
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" type="text/css" href="tgui.bundle.css">
|
||||
|
||||
<!-- This is processed in byond, so interfaces can override the
|
||||
html head if needed, for custom sheets of style etc. -->
|
||||
<!--customheadhtml-->
|
||||
<!-- tgui:styles -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" defer src="tgui.bundle.js"></script>
|
||||
<!-- tgui:scripts -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { backendMiddleware, backendReducer } from './backend';
|
||||
import { debugReducer } from './debug';
|
||||
import { hotKeyMiddleware } from './hotkeys';
|
||||
import { createLogger } from './logging';
|
||||
import { assetMiddleware } from './assets';
|
||||
|
||||
const logger = createLogger('store');
|
||||
|
||||
@@ -24,13 +25,13 @@ export const createStore = () => {
|
||||
}),
|
||||
]);
|
||||
const middleware = [
|
||||
process.env.NODE_ENV !== 'production' && loggingMiddleware,
|
||||
assetMiddleware,
|
||||
hotKeyMiddleware,
|
||||
backendMiddleware,
|
||||
];
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
middleware.push(loggingMiddleware);
|
||||
}
|
||||
return createReduxStore(reducer, applyMiddleware(...middleware));
|
||||
return createReduxStore(reducer,
|
||||
applyMiddleware(...middleware.filter(Boolean)));
|
||||
};
|
||||
|
||||
const loggingMiddleware = store => next => action => {
|
||||
|
||||
Reference in New Issue
Block a user