tgui: Fixes assets, CDN support (#52321)

* tgui: Better asset code, CDN support

* Rebuild tgui

* µ-fix
This commit is contained in:
Aleksej Komarov
2020-07-19 00:28:00 -07:00
committed by GitHub
parent b057982442
commit 8a12f15f40
19 changed files with 132 additions and 85 deletions
-2
View File
@@ -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]"
+4 -6
View File
@@ -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.
+9 -14
View File
@@ -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
+47 -10
View File
@@ -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"])
+2 -2
View File
@@ -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);
+39 -8
View File
@@ -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);
};
-2
View File
@@ -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,
+3 -9
View File
@@ -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
+2 -1
View File
@@ -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', {
+2 -1
View File
@@ -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>
+7 -6
View File
@@ -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>
+2 -1
View File
@@ -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>
+5 -4
View File
@@ -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
+2 -6
View File
@@ -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>
+5 -4
View File
@@ -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 => {