harry
2025-03-26 21:41:18 +00:00
committed by GitHub
parent 1bc5abd623
commit 04bc9fbd13
73 changed files with 777 additions and 312 deletions
+23
View File
@@ -0,0 +1,23 @@
name: Generate web assets
on:
push:
branches:
- master
jobs:
generate_static_assets:
if: ( !contains(github.event.head_commit.message, '[ci skip]') )
runs-on: ubuntu-latest
concurrency: gen-assets
steps:
- uses: actions/checkout@v3
- name: Generate static assets
run: |
mv tgui/public/ assets
- name: Deploy
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
BRANCH: gh-pages
CLEAN: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SINGLE_COMMIT: true
FOLDER: assets
+1
View File
@@ -72,6 +72,7 @@
#include "code\__DEFINES\gradient.dm"
#include "code\__DEFINES\guns.dm"
#include "code\__DEFINES\hallucinations.dm"
#include "code\__DEFINES\html.dm"
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\hydroponics.dm"
#include "code\__DEFINES\icon_layering.dm"
+1 -1
View File
@@ -113,7 +113,7 @@ GLOBAL_DATUM_INIT(vsc, /vs_control, new())
if("[ch]_NAME" in vars) vw_name = vars["[ch]_NAME"]
dat += "<b>[vw_name] = [vw]</b> <A href='byond://?src=[REF(src)];changevar=[ch]'>\[Change\]</A><br>"
dat += "<i>[vw_desc]</i><br><br>"
user << browse(dat,"window=settings")
user << browse(HTML_SKELETON(dat), "window=settings")
/vs_control/Topic(href,href_list)
if("changevar" in href_list)
+5
View File
@@ -0,0 +1,5 @@
#define HTML_SKELETON_INTERNAL(head, body) \
"<!DOCTYPE html><html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><meta http-equiv='X-UA-Compatible' content='IE=edge'>[head]</head><body>[body]</body></html>"
#define HTML_SKELETON_TITLE(title, body) HTML_SKELETON_INTERNAL("<title>[title]</title>", body)
#define HTML_SKELETON(body) HTML_SKELETON_INTERNAL("", body)
+7
View File
@@ -477,6 +477,8 @@ GLOBAL_LIST_EMPTY(gamemode_cache)
var/asset_cdn_webroot = ""
var/asset_cdn_url = null
var/storage_cdn_iframe = "https://aurorastation.github.io/aurora.3/iframe.html"
GENERAL_PROTECT_DATUM(/datum/configuration)
/datum/configuration/New()
@@ -1087,6 +1089,9 @@ GENERAL_PROTECT_DATUM(/datum/configuration)
if("asset_cdn_url")
asset_cdn_url = (value[length(value)] != "/" ? (value + "/") : value)
if("storage_cdn_iframe")
storage_cdn_iframe = value
else
log_config("Unknown setting in configuration: '[name]'")
@@ -1181,6 +1186,8 @@ GENERAL_PROTECT_DATUM(/datum/configuration)
load_away_sites_config()
load_exoplanets_config()
Master.OnConfigLoad()
/datum/configuration/proc/save_logging_config()
rustg_file_write(json_encode(GLOB.config.logsettings), "config/logging.json")
rustg_file_write(json_encode(GLOB.config.logfiles), "config/logging_files.json")
@@ -34,6 +34,24 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
basehtml = replacetextEx(basehtml, "<!-- tgui:inline-polyfill -->", polyfill)
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "NanoTrasen © 2457-[text2num(time2text(world.realtime, "YYYY")) + 442]")
/datum/controller/subsystem/processing/tgui/OnConfigLoad()
var/storage_iframe = GLOB.config.storage_cdn_iframe
if(storage_iframe && storage_iframe != /datum/configuration::storage_cdn_iframe)
basehtml = replacetextEx(basehtml, "tgui:storagecdn", storage_iframe)
return
if(GLOB.config.asset_transport == "webroot")
var/datum/asset_transport/webroot/webroot = SSassets.transport
var/datum/asset_cache_item/item = webroot.register_asset("iframe.html", file("tgui/public/iframe.html"))
basehtml = replacetext(basehtml, "tgui:storagecdn", webroot.get_asset_url("iframe.html", item))
return
if(!storage_iframe)
return
basehtml = replacetextEx(basehtml, "tgui:storagecdn", storage_iframe)
/datum/controller/subsystem/processing/tgui/Shutdown()
close_all_uis()
+1 -1
View File
@@ -187,7 +187,7 @@
out += "None."
out += "<br><a href='byond://?src=[REF(src)];obj_add=1'>\[add\]</a>"
out += "<b>Ambitions:</b> [ambitions ? ambitions : "None"] <a href='byond://?src=[REF(src)];amb_edit=[REF(src)]'>\[edit\]</a></br>"
usr << browse(out, "window=edit_memory[src]")
usr << browse(HTML_SKELETON(out), "window=edit_memory[src]")
/datum/mind/Topic(href, href_list)
if(!check_rights(R_ADMIN)) return
+5 -5
View File
@@ -152,7 +152,7 @@ GLOBAL_LIST_INIT(all_technomancer_assistance, (typesof(/datum/technomancer/assis
dat += "<a href='byond://?src=[REF(src)];spell_choice=[spell.name]'>Purchase</a> ([spell.cost])<br><br>"
else
dat += "<font color='red'><b>Cannot afford!</b></font><br><br>"
user << browse(dat, "window=radio")
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
if(1) //Equipment
var/dat = ""
@@ -173,7 +173,7 @@ GLOBAL_LIST_INIT(all_technomancer_assistance, (typesof(/datum/technomancer/assis
dat += "<a href='byond://?src=[REF(src)];item_choice=[E.name]'>Purchase</a> ([E.cost])<br><br>"
else
dat += "<font color='red'><b>Cannot afford!</b></font><br><br>"
user << browse(dat, "window=radio")
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
if(2) //Consumables
var/dat = ""
@@ -194,7 +194,7 @@ GLOBAL_LIST_INIT(all_technomancer_assistance, (typesof(/datum/technomancer/assis
dat += "<a href='byond://?src=[REF(src)];item_choice=[C.name]'>Purchase</a> ([C.cost])<br><br>"
else
dat += "<font color='red'><b>Cannot afford!</b></font><br><br>"
user << browse(dat, "window=radio")
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
if(3) //Assistance
var/dat = ""
@@ -214,7 +214,7 @@ GLOBAL_LIST_INIT(all_technomancer_assistance, (typesof(/datum/technomancer/assis
dat += "<a href='byond://?src=[REF(src)];item_choice=[A.name]'>Purchase</a> ([A.cost])<br><br>"
else
dat += "<font color='red'><b>Cannot afford!</b></font><br><br>"
user << browse(dat, "window=radio")
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
if(4) //Info
var/dat = ""
@@ -281,7 +281,7 @@ GLOBAL_LIST_INIT(all_technomancer_assistance, (typesof(/datum/technomancer/assis
dat += "When a function refers to 'allies', it means you, your apprentices, currently controlled entities (with the \
Control function), and friendly simple-minded entities that you've summoned with the Scepter of Enhancement.<br>"
dat += "A meter is equal to one 'tile'.<br>"
user << browse(dat, "window=radio")
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
// Proc: Topic()
+1 -1
View File
@@ -217,7 +217,7 @@
dat += "<b><a href='byond://?src=[REF(src)];menu=3'>No</a></b>"
user << browse(dat, "window=cloning")
user << browse(HTML_SKELETON(dat), "window=cloning")
onclose(user, "cloning")
return
+1 -1
View File
@@ -169,7 +169,7 @@
//onclose(user, "suit_storage_unit")
user << browse(dat, "window=suit_storage_unit;size=400x500")
user << browse(HTML_SKELETON(dat), "window=suit_storage_unit;size=400x500")
onclose(user, "suit_storage_unit")
return
+1 -1
View File
@@ -1495,7 +1495,7 @@
/obj/item/book/manual/tcaf_pamphlet/attack_self(var/mob/user as mob)
if(src.dat)
user << browse("<TT><I>Penned by [author].</I></TT> <BR>" + "[dat]", "window=book")
user << browse(HTML_SKELETON("<TT><I>Penned by [author].</I></TT> <BR>" + "[dat]"), "window=book")
user.visible_message("[user] opens a pamphlet titled \"[src.title]\" and begins reading intently.")
playsound(loc, 'sound/bureaucracy/paperfold.ogg', 50, 1)
onclose(user, "book")
@@ -92,7 +92,7 @@ ABSTRACT_TYPE(/obj/item/storage/secure)
message = "*****"
dat += "<HR>\n>[message]<BR>\n<A href='byond://?src=[REF(src)];type=1'>1</A>-<A href='byond://?src=[REF(src)];type=2'>2</A>-<A href='byond://?src=[REF(src)];type=3'>3</A><BR>\n<A href='byond://?src=[REF(src)];type=4'>4</A>-<A href='byond://?src=[REF(src)];type=5'>5</A>-<A href='byond://?src=[REF(src)];type=6'>6</A><BR>\n<A href='byond://?src=[REF(src)];type=7'>7</A>-<A href='byond://?src=[REF(src)];type=8'>8</A>-<A href='byond://?src=[REF(src)];type=9'>9</A><BR>\n<A href='byond://?src=[REF(src)];type=R'>R</A>-<A href='byond://?src=[REF(src)];type=0'>0</A>-<A href='byond://?src=[REF(src)];type=E'>E</A><BR>\n</TT>"
user << browse(dat, "window=caselock;size=300x280")
user << browse(HTML_SKELETON(dat), "window=caselock;size=300x280")
/obj/item/storage/secure/Topic(href, href_list)
..()
@@ -55,7 +55,7 @@ Frequency:
<A href='byond://?src=[REF(src)];freq=10'>+</A><BR>
<A href='byond://?src=[REF(src)];refresh=1'>Refresh</A>"}
user << browse(dat, "window=radio")
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
return
+2 -2
View File
@@ -188,7 +188,7 @@
i = (++i % 2)
output += "</table>"
user << browse(output, "window=banmirrors;size=600x400")
user << browse(HTML_SKELETON(output), "window=banmirrors;size=600x400")
/proc/display_mirrors_ckeys(mob/user, mirror_id)
if (!user || !check_rights(R_MOD|R_ADMIN) || !mirror_id)
@@ -223,7 +223,7 @@
return
output += "<br><br><a href='byond://?_src_=holder;dbbanmirrors=[query.item[2]];'>Back</a>"
user << browse(output, "window=banmirrors")
user << browse(HTML_SKELETON(output), "window=banmirrors")
/proc/toggle_mirror_status(mob/user, mirror_id, inactive = FALSE)
if (!user || !check_rights(R_MOD|R_ADMIN) || !mirror_id)
+1 -1
View File
@@ -189,7 +189,7 @@ var/savefile/Banlist
dat += "</table>"
dat = "<HR><B>Bans:</B> <FONT COLOR=blue>(U) = Unban , (E) = Edit Ban</FONT> - <FONT COLOR=green>([count] Bans)</FONT><HR><table border=1 rules=all frame=void cellspacing=0 cellpadding=3 >[dat]"
usr << browse(dat, "window=unbanp;size=875x400")
usr << browse(HTML_SKELETON(dat), "window=unbanp;size=875x400")
//////////////////////////////////// DEBUG ////////////////////////////////////
+6 -6
View File
@@ -281,7 +281,7 @@ var/global/enabled_spooking = 0
if(index == page)
dat += "</b>"
usr << browse(dat, "window=player_notes;size=400x400")
usr << browse(HTML_SKELETON(dat), "window=player_notes;size=400x400")
/datum/admins/proc/player_has_info(var/key as text)
@@ -598,7 +598,7 @@ var/global/enabled_spooking = 0
dat+="Please report this on GitHub, along with what you did to make this appear."
usr << browse(dat, "window=admincaster_main;size=400x600")
usr << browse(HTML_SKELETON(dat), "window=admincaster_main;size=400x600")
onclose(usr, "admincaster_main")
@@ -618,7 +618,7 @@ var/global/enabled_spooking = 0
dat += "<tr><td>[ckey] - [ban[2]] - (<a href='byond://?src=[REF(src)];jobban_tgt=[ckey];jobban_job=[job];'>unban</a>)</td></tr>"
dat += "</table>"
usr << browse(dat, "window=ban;size=400x400")
usr << browse(HTML_SKELETON(dat), "window=ban;size=400x400")
/datum/admins/proc/Game()
if(!check_rights(0)) return
@@ -640,7 +640,7 @@ var/global/enabled_spooking = 0
<A href='byond://?src=[REF(src)];vsc=default'>Choose a default ZAS setting</A><br>
"}
usr << browse(dat, "window=admin2;size=210x280")
usr << browse(HTML_SKELETON(dat), "window=admin2;size=210x280")
return
/datum/admins/proc/Secrets()
@@ -658,7 +658,7 @@ var/global/enabled_spooking = 0
continue
dat += "<A href='byond://?src=[REF(src)];admin_secrets=[REF(item)]'>[item.name()]</A><BR>"
dat += "<BR>"
usr << browse(dat, "window=secrets")
usr << browse(HTML_SKELETON(dat), "window=secrets")
return
@@ -1139,7 +1139,7 @@ var/global/enabled_spooking = 0
out += " None."
out += " <a href='byond://?src=[REF(SSticker.mode)];add_antag_type=1'>\[+\]</a><br/>"
usr << browse(out, "window=edit_mode[src]")
usr << browse(HTML_SKELETON(out), "window=edit_mode[src]")
feedback_add_details("admin_verb","SGM")
+1
View File
@@ -499,6 +499,7 @@ GLOBAL_LIST_INIT(admin_verbs_dev, list( //will need to be altered - Ryan784
/client/proc/cmd_generate_lag,
/client/proc/create_poll, //Allows to create polls
/client/proc/profiler_start,
/client/proc/allow_browser_inspect,
))
GLOBAL_LIST_INIT(admin_verbs_cciaa, list(
@@ -10,4 +10,4 @@
dat += "<li>[l]</li>"
if(!GLOB.admin_log.len)
dat += "No-one has done anything this round!"
user << browse(dat, "window=admin_log")
user << browse(HTML_SKELETON(dat), "window=admin_log")
@@ -9,4 +9,4 @@
var/dat = "<B>Bombing List</B>"
for(var/l in GLOB.bombers)
dat += "[l]<BR>"
user << browse(dat, "window=bombers")
user << browse(HTML_SKELETON(dat), "window=bombers")
@@ -11,4 +11,4 @@
if(H.dna && H.ckey)
dat += "<tr><td>[H]</td><td>[H.dna.unique_enzymes]</td><td>[H.b_type]</td></tr>"
dat += "</table>"
user << browse(dat, "window=DNA;size=440x410")
user << browse(HTML_SKELETON(dat), "window=DNA;size=440x410")
@@ -16,4 +16,4 @@
else if(!H.dna)
dat += "<tr><td>[H]</td><td>H.dna = null</td></tr>"
dat += "</table>"
user << browse(dat, "window=fingerprints;size=440x410")
user << browse(HTML_SKELETON(dat), "window=fingerprints;size=440x410")
@@ -12,4 +12,4 @@
var/dat = "<B>Showing last [length(GLOB.lawchanges)] law changes.</B><HR>"
for(var/sig in GLOB.lawchanges)
dat += "[sig]<BR>"
user << browse(dat, "window=lawchanges;size=800x500")
user << browse(HTML_SKELETON(dat), "window=lawchanges;size=800x500")
@@ -12,4 +12,4 @@
var/dat = "<B>Showing last [length(GLOB.lastsignalers)] signalers.</B><HR>"
for(var/sig in GLOB.lastsignalers)
dat += "[sig]<BR>"
user << browse(dat, "window=lastsignalers;size=800x500")
user << browse(HTML_SKELETON(dat), "window=lastsignalers;size=800x500")
+1 -1
View File
@@ -369,7 +369,7 @@
[banhtml.Join("")]
</body>
"}
usr << browse(html,"window=stickybans;size=700x400")
usr << browse(HTML_SKELETON(html), "window=stickybans;size=700x400")
/proc/sticky_banned_ckeys()
if (establish_db_connection(GLOB.dbcon) || length(SSstickyban.dbcache))
+3 -3
View File
@@ -416,7 +416,7 @@
dat += {"<A href='byond://?src=[REF(src)];c_mode2=secret'>Secret</A><br>"}
dat += {"<A href='byond://?src=[REF(src)];c_mode2=random'>Random</A><br>"}
dat += {"Now: [GLOB.master_mode]"}
usr << browse(dat, "window=c_mode")
usr << browse(HTML_SKELETON(dat), "window=c_mode")
else if(href_list["f_secret"])
if(!check_rights(R_ADMIN))
@@ -431,7 +431,7 @@
dat += {"<A href='byond://?src=[REF(src)];f_secret2=[mode]'>[GLOB.config.mode_names[mode]]</A><br>"}
dat += {"<A href='byond://?src=[REF(src)];f_secret2=secret'>Random (default)</A><br>"}
dat += {"Now: [GLOB.secret_force_mode]"}
usr << browse(dat, "window=f_secret")
usr << browse(HTML_SKELETON(dat), "window=f_secret")
else if(href_list["c_mode2"])
if(!check_rights(R_ADMIN|R_SERVER))
@@ -981,7 +981,7 @@
var/obj/pageobj = B.pages[page]
data += "<A href='byond://?src=[REF(src)];AdminFaxViewPage=[page];paper_bundle=[REF(B)]'>Page [page] - [pageobj.name]</A><BR>"
usr << browse(data, "window=[B.name]")
usr << browse(HTML_SKELETON(data), "window=[B.name]")
else
to_chat(usr, SPAN_WARNING("The faxed item is not viewable. This is probably a bug, and should be reported on the tracker: [fax.type]"))
+19 -3
View File
@@ -413,7 +413,7 @@
dellog += "</ol>"
usr << browse(dellog.Join(), "window=dellog")
usr << browse(HTML_SKELETON(dellog.Join()), "window=dellog")
/**
* Same as `cmd_display_del_log`, but only shows harddels
@@ -454,14 +454,14 @@
dellog += "</ol>"
usr << browse(dellog.Join(), "window=harddellog")
usr << browse(HTML_SKELETON(dellog.Join()), "window=harddellog")
/client/proc/cmd_display_init_log()
set category = "Debug"
set name = "Display Initialize() Log"
set desc = "Displays a list of things that didn't handle Initialize() properly"
usr << browse(replacetext(SSatoms.InitLog(), "\n", "<br>"), "window=initlog")
usr << browse(HTML_SKELETON(replacetext(SSatoms.InitLog(), "\n", "<br>")), "window=initlog")
/client/proc/reload_nanoui_resources()
set category = "Debug"
@@ -499,3 +499,19 @@
for(var/atom/an_atom in world)
if(world.tick_usage > (100+(tick_offenses+jitter_this_run)))
stoplag()
/client/proc/allow_browser_inspect()
set category = "Debug"
set name = "Allow Browser Inspect"
set desc = "Allow browser debugging via inspect"
if(!check_rights(R_DEBUG) || !isclient(src))
return
if(byond_version < 516)
to_chat(src, SPAN_WARNING("You can only use this on 516!"))
return
to_chat(src, SPAN_INFO("You can now right click to use inspect on browsers."))
winset(src, null, list("browser-options" = "+devtools"))
winset(src, null, list("browser-options" = "+find"))
+2 -2
View File
@@ -39,7 +39,7 @@
Tile Update: [SSair.tiles_to_update.len]<BR>
"}
usr << browse(output,"window=airreport")
usr << browse(HTML_SKELETON(output), "window=airreport")
/client/proc/fix_next_move()
set category = "Debug"
@@ -95,7 +95,7 @@
else
output += "&nbsp;&nbsp;&nbsp;&nbsp;[device]<br>"
usr << browse(output,"window=radioreport")
usr << browse(HTML_SKELETON(output), "window=radioreport")
feedback_add_details("admin_verb","RR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/reload_admins()
+1 -1
View File
@@ -101,7 +101,7 @@ GLOBAL_VAR_INIT(intercom_range_display_status, 0)
if(!window_check)
output += "<li><font color='red'>Camera not connected to wall at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Network: [C1.network]</color></li>"
output += "</ul>"
usr << browse(output,"window=airreport;size=1000x500")
usr << browse(HTML_SKELETON(output), "window=airreport;size=1000x500")
feedback_add_details("admin_verb","mCRP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/intercom_view()
+1 -1
View File
@@ -70,7 +70,7 @@
to_chat(src, "Some accounts did not have proper ages set in their clients. This function requires database to be present")
if(msg != "")
src << browse(msg, "window=Player_age_check")
src << browse(HTML_SKELETON(msg), "window=Player_age_check")
else
to_chat(src, "No matches for that age range found.")
@@ -19,7 +19,7 @@
dat += "<A href='byond://?src=[REF(src)];fireArea=1'>Open Fire - Area</A><BR>"
dat += "<A href='byond://?src=[REF(src)];fireCords=1'>Open Fire - Coordinates</A><BR>"
dat += "Deployment of weapon authorized by <br>[SSatlas.current_map.company_name] Chief Naval Director<br><br>Remember, friendly fire is grounds for termination of your contract and life.<HR>"
user << browse(dat, "window=scroll")
user << browse(HTML_SKELETON(dat), "window=scroll")
onclose(user, "scroll")
return
+1 -1
View File
@@ -178,7 +178,7 @@
browse_data = "[formatted_data]<br><a href='byond://?src=[REF(src)];selection=[cardname]'>Draw Card</a><br><hr>[browse_data]"
user << browse(browse_data, "window=battlemonsters_hand")
user << browse(HTML_SKELETON(browse_data), "window=battlemonsters_hand")
/obj/item/battle_monsters/deck/Topic(href,href_list)
if(..())
+1 -1
View File
@@ -756,7 +756,7 @@ GLOBAL_LIST_INIT(localhost_addresses, list(
dat += "The request is [request["request_age"]] days old.<br>"
dat += "OPTIONS: <a href='byond://?src=[REF(src)];linkingrequest=[request["id"]];linkingaction=accept'>Accept Request</a> | <a href='byond://?src=[REF(src)];linkingrequest=[request["id"]];linkingaction=deny'>Deny Request</a>"
src << browse(dat, "window=LinkingRequests")
src << browse(HTML_SKELETON(dat), "window=LinkingRequests")
return
/client/proc/gather_linking_requests()
@@ -998,7 +998,7 @@ GLOBAL_LIST_INIT(valid_bloodtypes, list(
dat += "</center>"
user << browse(dat.Join(), "window=species;size=700x400")
user << browse(HTML_SKELETON(dat.Join()), "window=species;size=700x400")
/// This proc verifies if a sprite accessory can be put on a robolimb, checking its manufacturer.
/datum/category_item/player_setup_item/general/body/proc/verify_robolimb_appropriate(datum/sprite_accessory/S)
@@ -244,7 +244,7 @@
HTML += "<br>"
HTML += "<hr />"
HTML += "<tt>"
user << browse(HTML, "window=flavor_text;size=430x300")
user << browse(HTML_SKELETON(HTML), "window=flavor_text;size=430x300")
return
/datum/category_item/player_setup_item/general/flavor/proc/SetFlavourTextRobot(mob/user)
@@ -261,5 +261,5 @@
HTML += "<br>"
HTML += "<hr />"
HTML += "<tt>"
user << browse(HTML, "window=flavour_text_robot;size=430x300")
user << browse(HTML_SKELETON(HTML), "window=flavour_text_robot;size=430x300")
return
@@ -495,7 +495,7 @@
dat += "<br><span class='warning'>[faction.get_selection_error(pref, user)]</span>"
dat += "</center>"
user << browse(dat.Join(), "window=factionpreview;size=750x450")
user << browse(HTML_SKELETON(dat.Join()), "window=factionpreview;size=750x450")
/datum/category_item/player_setup_item/occupation/proc/validate_and_set_faction(selected_faction)
var/datum/faction/faction = SSjobs.name_factions[selected_faction]
+1 -1
View File
@@ -108,7 +108,7 @@
dat += "<a href='byond://?src=[REF(src)];choice=change_code'>Change access code</a><br>"
dat += "<a href='byond://?src=[REF(src)];choice=change_id'>Change EFTPOS ID</a><br>"
dat += "Scan card to reset access code <a href='byond://?src=[REF(src)];choice=reset'>\[------\]</a>"
user << browse(dat,"window=eftpos")
user << browse(HTML_SKELETON(dat),"window=eftpos")
else
user << browse(null,"window=eftpos")
+3 -3
View File
@@ -75,13 +75,13 @@ GLOBAL_LIST_EMPTY_TYPED(holodeck_controls, /obj/machinery/computer/holodeck_cont
if(!linkedholodeck)
dat += SPAN_DANGER("Warning: Unable to locate holodeck.<br>")
user << browse(dat, "window=computer;size=400x500")
user << browse(HTML_SKELETON(dat), "window=computer;size=400x500")
onclose(user, "computer")
return
if(!SSatlas.current_map.holodeck_supported_programs.len)
dat += SPAN_DANGER("Warning: No supported holo-programs loaded.<br>")
user << browse(dat, "window=computer;size=400x500")
user << browse(HTML_SKELETON(dat), "window=computer;size=400x500")
onclose(user, "computer")
return
@@ -126,7 +126,7 @@ GLOBAL_LIST_EMPTY_TYPED(holodeck_controls, /obj/machinery/computer/holodeck_cont
dat = "<B>Holodeck Control System</B><BR>"
dat += "Holodeck is <A href='byond://?src=[REF(src)];togglehololock=1'><font color=red>(LOCKED)</font></A><BR>"
user << browse(dat, "window=computer;size=400x500")
user << browse(HTML_SKELETON(dat), "window=computer;size=400x500")
onclose(user, "computer")
return
+1 -1
View File
@@ -226,6 +226,6 @@
if(dat)
last_data = dat
dat += "<br><br>\[<a href='byond://?src=[REF(src)];print=1'>print report</a>\]"
user << browse(dat,"window=plant_analyzer")
user << browse(HTML_SKELETON(dat),"window=plant_analyzer")
return
+2 -2
View File
@@ -218,7 +218,7 @@
to_chat(user, SPAN_NOTICE("The pages of [title] have been cut out!"))
return
if(src.dat)
user << browse("<TT><I>Penned by [author].</I></TT> <BR>" + "[dat]", "window=book")
user << browse(HTML_SKELETON("<TT><I>Penned by [author].</I></TT> <BR>" + "[dat]"), "window=book")
user.visible_message("[user] opens a book titled \"[src.title]\" and begins reading intently.")
playsound(loc, 'sound/bureaucracy/bookopen.ogg', 50, TRUE)
onclose(user, "book")
@@ -314,7 +314,7 @@
if(target_zone == BP_EYES)
user.visible_message(SPAN_NOTICE("You open up the book and show it to [target_mob]. "), \
SPAN_NOTICE(" [user] opens up a book and shows it to [target_mob]. "))
target_mob << browse("<TT><I>Penned by [author].</I></TT> <BR>" + "[dat]", "window=book")
target_mob << browse(HTML_SKELETON("<TT><I>Penned by [author].</I></TT> <BR>" + "[dat]"), "window=book")
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN) //to prevent spam
+6 -11
View File
@@ -35,7 +35,7 @@
/obj/machinery/librarypubliccomp/attack_hand(var/mob/user)
usr.set_machine(src)
var/dat = "<HEAD><TITLE>Library Visitor</TITLE></HEAD><BODY>\n" // <META HTTP-EQUIV='Refresh' CONTENT='10'>
var/dat = ""
switch(screenstate)
if(0)
dat += {"<h2>Search Settings</h2><br>
@@ -63,7 +63,7 @@
dat += "<tr><td>[author]</td><td>[title]</td><td>[category]</td><td>[id]</td></tr>"
dat += "</table><br>"
dat += "<a href='byond://?src=[REF(src)];back=1'>\[Go Back\]</a><br>"
user << browse(dat, "window=publiclibrary")
user << browse(HTML_SKELETON_TITLE("Library Visitor", dat), "window=publiclibrary")
onclose(user, "publiclibrary")
/obj/machinery/librarypubliccomp/Topic(href, href_list)
@@ -133,12 +133,7 @@
/obj/machinery/librarycomp/attack_hand(var/mob/user)
user.set_machine(src)
var/dat
// Public Related Code
if(!is_public)
dat = "<head><title>[SSatlas.current_map.station_name] Library Management</title></head><body>\n"
else
dat = "<head><title>[SSatlas.current_map.station_name] Library</title></head><body>\n"
var/dat = ""
switch(screenstate)
if(0)
// Main Menu
@@ -238,7 +233,7 @@
<a href='byond://?src=[REF(src)];switchscreen=0'>No.</a><br>"}
//dat += "<a href='byond://?src=[REF(user)];mach_close=library'>Close</a><br><br>"
user << browse(dat, "window=library")
user << browse(HTML_SKELETON_TITLE(is_public ? "[SSatlas.current_map.station_name] Library" : "[SSatlas.current_map.station_name] Library Management", dat), "window=library")
onclose(user, "library")
/obj/machinery/librarycomp/emag_act(var/remaining_charges, var/mob/user)
@@ -432,7 +427,7 @@
/obj/machinery/libraryscanner/attack_hand(var/mob/user)
usr.set_machine(src)
var/dat = "<HEAD><TITLE>Scanner Control Interface</TITLE></HEAD><BODY>\n" // <META HTTP-EQUIV='Refresh' CONTENT='10'>
var/dat = ""
if(cache)
dat += "<FONT color=#005500>Data stored in memory.</FONT><br>"
else
@@ -442,7 +437,7 @@
dat += " <a href='byond://?src=[REF(src)];clear=1'>\[Clear Memory\]</a><br><br><a href='byond://?src=[REF(src)];eject=1'>\[Remove Book\]</a>"
else
dat += "<br>"
user << browse(dat, "window=scanner")
user << browse(HTML_SKELETON_TITLE("Scanner Control Interface", dat), "window=scanner")
onclose(user, "scanner")
/obj/machinery/libraryscanner/Topic(href, href_list)
+1 -1
View File
@@ -107,7 +107,7 @@
dat += "<br><br>In total this machine produced <font color='green'><b>[newCoins]</b></font> coins."
dat += "<br><A href='byond://?src=[REF(src)];makeCoins=[1]'>Make coins</A>"
user << browse("[dat]", "window=mint")
user << browse(HTML_SKELETON(dat), "window=mint")
/obj/machinery/mineral/mint/Topic(href, href_list)
if(..())
@@ -18,7 +18,7 @@
<A href='byond://?src=[REF(src)]];create_mob=1'>Create Mob</A><br>
"}
usr << browse(dat, "window=storytellerpanel;size=210x280")
usr << browse(HTML_SKELETON(dat), "window=storytellerpanel;size=210x280")
/mob/abstract/ghost/storyteller/Topic(href, href_list)
. = ..()
+5 -5
View File
@@ -27,7 +27,7 @@
output += "</table>"
src << browse(output,"window=playerpolllist;size=500x300")
src << browse(HTML_SKELETON(output),"window=playerpolllist;size=500x300")
/mob/abstract/new_player/proc/show_poll_link(var/pollid = -1)
if(pollid == -1) return
@@ -129,7 +129,7 @@
output += "</div>"
src << browse(output,"window=playerpoll;size=500x250")
src << browse(HTML_SKELETON(output),"window=playerpoll;size=500x250")
//Polls with a text input
if("TEXT")
@@ -175,7 +175,7 @@
output += "[vote_text]"
src << browse(output,"window=playerpoll;size=500x500")
src << browse(HTML_SKELETON(output),"window=playerpoll;size=500x500")
//Polls with a text input
if("NUMVAL")
@@ -250,7 +250,7 @@
output += "</form>"
src << browse(output,"window=playerpoll;size=500x500")
src << browse(HTML_SKELETON(output),"window=playerpoll;size=500x500")
if("MULTICHOICE")
var/DBQuery/voted_query = GLOB.dbcon.NewQuery("SELECT optionid FROM ss13_poll_vote WHERE pollid = [pollid] AND ckey = '[usr.ckey]'")
@@ -317,7 +317,7 @@
output += "</div>"
src << browse(output,"window=playerpoll;size=500x250")
src << browse(HTML_SKELETON(output),"window=playerpoll;size=500x250")
return
/mob/abstract/new_player/proc/vote_on_poll(var/pollid = -1, var/optionid = -1, var/multichoice = 0)
+2 -2
View File
@@ -390,7 +390,7 @@
if(L.written_style)
dat += "You can write in this language on papers by writing \[lang=[L.key]\]YourTextHere\[/lang\].<br/><br/>"
src << browse(dat, "window=checklanguage")
src << browse(HTML_SKELETON(dat), "window=checklanguage")
return
/mob/living/check_languages()
@@ -410,7 +410,7 @@
if(L.written_style)
dat += "You can write in this language on papers by writing \[lang=[L.key]\]YourTextHere\[/lang\].<br/><br/>"
src << browse(dat, "window=checklanguage")
src << browse(HTML_SKELETON(dat), "window=checklanguage")
/mob/living/Topic(href, href_list)
if(href_list["default_lang"])
@@ -10,7 +10,7 @@
set desc = "Sets an extended description of your character's features."
set category = "IC"
var/HTML = "<body>"
var/HTML = ""
HTML += "<tt><center>"
HTML += "<b>Update Flavour Text</b> <hr />"
HTML += "<br></center>"
@@ -44,4 +44,4 @@
HTML += "<hr />"
HTML +="<a href='byond://?src=[REF(src)];flavor_change=done'>\[Done\]</a>"
HTML += "<tt>"
src << browse(HTML, "window=flavor_changes;size=430x300")
src << browse(HTML_SKELETON(HTML), "window=flavor_changes;size=430x300")
+1 -1
View File
@@ -383,7 +383,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
Recall the channel list at any time by calling <code>Radio-Settings</code> under <em>AI Commands</em>.\
</p>\
"
usr << browse(dat, "window=aihelp,size=520x700")
usr << browse(HTML_SKELETON(dat), "window=aihelp,size=520x700")
/mob/living/silicon/ai/proc/pick_icon()
set category = "AI Commands"
@@ -180,8 +180,7 @@
if(!module)
module = new /obj/item/robot_module/drone(src)
var/dat = "<HEAD><TITLE>Drone modules</TITLE></HEAD><BODY>\n"
dat += {"
var/dat = {"
<B>Activated Modules</B>
<BR>
Module 1: [module_state_1 ? "<A href='byond://?src=[REF(src)];mod=[REF(module_state_1)]>[module_state_1]<A>" : "No Module"]<BR>
@@ -221,4 +220,4 @@
dat += resources
src << browse(dat, "window=robotmod")
src << browse(HTML_SKELETON_TITLE("Drone modules", dat), "window=robotmod")
@@ -485,7 +485,7 @@
to_chat(src, SPAN_WARNING("WARNING: Power too low for self-diagnostic functions."))
return
var/dat = self_diagnosis()
src << browse(dat, "window=robotdiagnosis")
src << browse(HTML_SKELETON(dat), "window=robotdiagnosis")
/mob/living/silicon/robot/verb/toggle_component()
set category = "Robot Commands"
@@ -896,7 +896,7 @@
dat += "[module.malf_AI_module]: <B>Activated</B><BR>"
else
dat += "[module.malf_AI_module]: <A href='byond://?src=[REF(src)];act=[REF(module.malf_AI_module)]>Activate</A><BR>"
src << browse(dat, "window=robotmod")
src << browse(HTML_SKELETON(dat), "window=robotmod")
/mob/living/silicon/robot/Topic(href, href_list)
+1 -1
View File
@@ -231,7 +231,7 @@
var/synth = (L in speech_synthesizer_langs)
dat += "<b>[L.name] ([get_language_prefix()][L.key])</b>[synth ? default_str : null]<br/>Speech Synthesizer: <i>[synth ? "YES" : "NOT SUPPORTED"]</i><br/>[L.desc]<br/><br/>"
src << browse(dat, "window=checklanguage")
src << browse(HTML_SKELETON(dat), "window=checklanguage")
return
/mob/living/silicon/proc/toggle_sensor_mode()
@@ -136,7 +136,7 @@
else
dat += "<br><b>Headset:</b> <a href='byond://?src=[REF(src)];add_inv=ears'>Nothing</a>"
user << browse(dat, "window=mob[name];size=325x500")
user << browse(HTML_SKELETON(dat), "window=mob[name];size=325x500")
onclose(user, "mob[real_name]")
return
+2 -2
View File
@@ -73,7 +73,7 @@
return
/obj/item/clipboard/attack_self(mob/user as mob)
var/dat = "<title>Clipboard</title>"
var/dat = ""
if(haspen)
dat += "<A href='byond://?src=[REF(src)];pen=1'>Remove Pen</A><BR><HR>"
else
@@ -93,7 +93,7 @@
for(var/obj/item/photo/Ph in r_contents)
dat += "<A href='byond://?src=[REF(src)];remove=[REF(Ph)]'>Remove</A> <A href='byond://?src=[REF(src)];rename=[REF(Ph)]'>Rename</A> - <A href='byond://?src=[REF(src)];look=[REF(Ph)]'>[Ph.name]</A><BR>"
user << browse(dat, "window=clipboard")
user << browse(HTML_SKELETON_TITLE("Clipboard", dat), "window=clipboard")
if(!ui_open)
ui_open = TRUE
onclose(user, "clipboard")
+2 -2
View File
@@ -51,7 +51,7 @@
return
/obj/item/folder/attack_self(mob/user as mob)
var/dat = "<title>[name]</title>"
var/dat = ""
for(var/obj/item/paper/P in src)
dat += "[can_write ? "<A href='byond://?src=[REF(src)];write=[REF(P)]'>Write</A> " : ""]<A href='byond://?src=[REF(src)];remove=[REF(P)]'>Remove</A> <A href='byond://?src=[REF(src)];rename=[REF(P)]'>Rename</A> - <A href='byond://?src=[REF(src)];read=[REF(P)]'>[P.name]</A><BR>"
@@ -61,7 +61,7 @@
dat += "<A href='byond://?src=[REF(src)];remove=[REF(Pb)]'>Remove</A> <A href='byond://?src=[REF(src)];rename=[REF(Pb)]'>Rename</A> - <A href='byond://?src=[REF(src)];browse=[REF(Pb)]'>[Pb.name]</A><BR>"
for(var/obj/item/sample/Pf in src)
dat += "<A href='byond://?src=[REF(src)];remove=[REF(Pf)]'>Remove</A> - [Pf.name]<BR>"
user << browse(dat, "window=folder")
user << browse(HTML_SKELETON_TITLE(name, dat), "window=folder")
onclose(user, "folder")
add_fingerprint(usr)
return
@@ -159,7 +159,7 @@
dat += SPAN_WARNING("The dart cartridge is empty!")
dat += " \[<A href='byond://?src=[REF(src)];eject_cart=1'>Eject</A>\]"
user << browse(dat, "window=dartgun")
user << browse(HTML_SKELETON(dat), "window=dartgun")
onclose(user, "dartgun", src)
/obj/item/gun/projectile/dartgun/proc/check_beaker_mixing(var/obj/item/B)
+1 -1
View File
@@ -24,4 +24,4 @@ GLOBAL_LIST_EMPTY(chemical_reaction_logs)
for(var/entry in GLOB.chemical_reaction_logs)
html += "[entry]<br>"
usr << browse(html, "window=chemlogs")
usr << browse(HTML_SKELETON(html), "window=chemlogs")
+1 -1
View File
@@ -270,7 +270,7 @@
dat += "</tr></table><br>Current Selection: [currTag ? currTag : "None"]</tt>"
dat += "<br><a href='byond://?src=[REF(src)];nextTag=CUSTOM'>Enter custom location.</a>"
user << browse(dat, "window=destTagScreen;size=450x375")
user << browse(HTML_SKELETON(dat), "window=destTagScreen;size=450x375")
onclose(user, "destTagScreen")
/obj/item/device/destTagger/attack_self(mob/user)
+1 -1
View File
@@ -348,7 +348,7 @@
for(var/obj/machinery/r_n_d/server/S in servers)
dat += "[S.name] <A href='byond://?src=[REF(src)];send_to=[S.server_id]'> (Transfer)</A><BR>"
dat += "<HR><A href='byond://?src=[REF(src)];main=1'>Main Menu</A>"
user << browse("<TITLE>R&D Server Control</TITLE><HR>[dat]", "window=server_control;size=575x400")
user << browse(HTML_SKELETON_TITLE("R&D Server Control", dat), "window=server_control;size=575x400")
onclose(user, "server_control")
return
@@ -124,7 +124,7 @@
for(var/index=1, index<=construction.len, index++)
dat += "<A href='byond://?src=[REF(src)];activate=[index]'>\[[construction[index]]\]</a><br>"
user << browse(dat, "window=alien_replicator")
user << browse(HTML_SKELETON(dat), "window=alien_replicator")
/obj/machinery/replicator/attackby(obj/item/attacking_item, mob/user)
user.drop_from_inventory(attacking_item, src)
@@ -72,7 +72,7 @@
"}
dat += "<A href='byond://?src=[REF(src)];close=1'>Close</a><br>"
user << browse(dat,"window=locater;size=300x150")
user << browse(HTML_SKELETON(dat),"window=locater;size=300x150")
onclose(user, "locater")
/obj/item/device/beacon_locator/Topic(href, href_list)
@@ -87,7 +87,7 @@
dat += "<p><A href='byond://?src=[REF(src)];reset=1'>Back</A>"
user << browse(dat, "window=keycard_auth;size=500x350")
user << browse(HTML_SKELETON(dat), "window=keycard_auth;size=500x350")
return
+6
View File
@@ -0,0 +1,6 @@
author: harry
delete-after: True
changes:
- rscadd: "compatibility in 516 for browser elements, like the chat and tgui popups"
+14
View File
@@ -1,3 +1,12 @@
.light:root {
--scrollbar-base: #f2f2f2;
--scrollbar-thumb: #a7a7a7;
}
html, body {
scrollbar-color: var(--scrollbar-base) var(--scrollbar-thumb);
}
body {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 12px !important;
@@ -19,6 +28,11 @@ body.dark {
scrollbar-shadow-color: #3b3b3b;
}
.dark:root {
--scrollbar-base: #181818;
--scrollbar-thumb: #363636;
}
#menu {
background-color: #F0F0F0;
position: fixed;
+2
View File
@@ -707,9 +707,11 @@ function draw_verbs(cat) {
function set_theme(which) {
if (which == "light") {
document.body.className = "";
document.documentElement.className = "light";
set_style_sheet("browserOutput_white");
} else if (which == "dark") {
document.body.className = "dark";
document.documentElement.className = "dark";
set_style_sheet("browserOutput");
}
}
+150 -41
View File
@@ -7,8 +7,9 @@
*/
export const IMPL_MEMORY = 0;
export const IMPL_LOCAL_STORAGE = 1;
export const IMPL_HUB_STORAGE = 1;
export const IMPL_INDEXED_DB = 2;
export const IMPL_IFRAME_INDEXED_DB = 3;
const INDEXED_DB_VERSION = 1;
const INDEXED_DB_NAME = 'tgui';
@@ -25,14 +26,11 @@ const testGeneric = (testFn) => () => {
}
};
// Localstorage can sometimes throw an error, even if DOM storage is not
// disabled in IE11 settings.
// See: https://superuser.com/questions/1080011
// prettier-ignore
const testLocalStorage = testGeneric(() => (
window.localStorage && window.localStorage.getItem
));
const testHubStorage = testGeneric(
() => window.hubStorage && window.hubStorage.getItem
);
// TODO: Remove with 516
// prettier-ignore
const testIndexedDb = testGeneric(() => (
(window.indexedDB || window.msIndexedDB)
@@ -45,49 +43,144 @@ class MemoryBackend {
this.store = {};
}
get(key) {
async get(key) {
return this.store[key];
}
set(key, value) {
async set(key, value) {
this.store[key] = value;
}
remove(key) {
async remove(key) {
this.store[key] = undefined;
}
clear() {
async clear() {
this.store = {};
}
}
class LocalStorageBackend {
class HubStorageBackend {
constructor() {
this.impl = IMPL_LOCAL_STORAGE;
this.impl = IMPL_HUB_STORAGE;
}
get(key) {
const value = localStorage.getItem(key);
async get(key) {
const value = await window.hubStorage.getItem('aurora-' + key);
if (typeof value === 'string') {
return JSON.parse(value);
}
}
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
async set(key, value) {
window.hubStorage.setItem('aurora-' + key, JSON.stringify(value));
}
remove(key) {
localStorage.removeItem(key);
async remove(key) {
window.hubStorage.removeItem('aurora-' + key);
}
clear() {
localStorage.clear();
async clear() {
window.hubStorage.clear();
}
}
class IFrameIndexedDbBackend {
constructor() {
this.impl = IMPL_IFRAME_INDEXED_DB;
}
async ready() {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = Byond.storageCdn;
const completePromise = new Promise((resolve) => {
iframe.onload = () => resolve(this);
});
this.documentElement = document.body.appendChild(iframe);
this.iframeWindow = this.documentElement.contentWindow;
return completePromise;
}
async get(key) {
const promise = new Promise((resolve) => {
window.addEventListener('message', (message) => {
if (message.data.key && message.data.key === key) {
resolve(message.data.value);
}
});
});
this.iframeWindow.postMessage({ type: 'get', key: key }, '*');
return promise;
}
async set(key, value) {
this.iframeWindow.postMessage({ type: 'set', key: key, value: value }, '*');
}
async remove(key) {
this.iframeWindow.postMessage({ type: 'remove', key: key }, '*');
}
async clear() {
this.iframeWindow.postMessage({ type: 'clear' }, '*');
}
async ping() {
const promise = new Promise((resolve) => {
window.addEventListener('message', (message) => {
if (message.data === true) {
resolve(true);
}
});
setTimeout(() => resolve(false), 100);
});
this.iframeWindow.postMessage({ type: 'ping' }, '*');
return promise;
}
async processChatMessages(messages) {
this.iframeWindow.postMessage(
{ type: 'processChatMessages', messages: messages },
'*'
);
}
async getChatMessages() {
const promise = new Promise((resolve) => {
window.addEventListener('message', (message) => {
if (message.data.messages) {
resolve(message.data.messages);
}
});
});
this.iframeWindow.postMessage({ type: 'getChatMessages' }, '*');
return promise;
}
async setNumberStored(number) {
this.iframeWindow.postMessage(
{ type: 'setNumberStored', newMax: number },
'*'
);
}
async destroy() {
document.body.removeChild(this.documentElement);
this.documentElement = null;
this.iframeWindow = null;
}
}
class IndexedDbBackend {
// TODO: Remove with 516
constructor() {
this.impl = IMPL_INDEXED_DB;
/** @type {Promise<IDBDatabase>} */
@@ -108,7 +201,7 @@ class IndexedDbBackend {
});
}
getStore(mode) {
async getStore(mode) {
// prettier-ignore
return this.dbPromise.then((db) => db
.transaction(INDEXED_DB_STORE_NAME, mode)
@@ -125,13 +218,6 @@ class IndexedDbBackend {
}
async set(key, value) {
// The reason we don't _save_ null is because IE 10 does
// not support saving the `null` type in IndexedDB. How
// ironic, given the bug below!
// See: https://github.com/mozilla/localForage/issues/161
if (value === null) {
value = undefined;
}
// NOTE: We deliberately make this operation transactionless
const store = await this.getStore(READ_WRITE);
store.put(value, key);
@@ -150,17 +236,40 @@ class IndexedDbBackend {
}
}
// Namespace for keys in storage, so we do not share storage with other servers.
let namespace = 'AURORASTATION_';
/**
* Web Storage Proxy object, which selects the best backend available
* depending on the environment.
* Also applies namespacing to keys.
*/
class StorageProxy {
export class StorageProxy {
constructor() {
this.backendPromise = (async () => {
if (!Byond.TRIDENT) {
if (Byond.storageCdn) {
const iframe = new IFrameIndexedDbBackend();
await iframe.ready();
if ((await iframe.ping()) === true) {
return iframe;
}
iframe.destroy();
}
if (!testHubStorage()) {
Byond.winset(null, 'browser-options', '+byondstorage');
return new Promise((resolve) => {
const listener = () => {
document.removeEventListener('byondstorageupdated', listener);
resolve(new HubStorageBackend());
};
document.addEventListener('byondstorageupdated', listener);
});
}
return new HubStorageBackend();
}
// TODO: Remove with 516
if (testIndexedDb()) {
try {
const backend = new IndexedDbBackend();
@@ -168,26 +277,26 @@ class StorageProxy {
return backend;
} catch {}
}
if (testLocalStorage()) {
return new LocalStorageBackend();
}
console.warn(
'No supported storage backend found. Using in-memory storage.'
);
return new MemoryBackend();
})();
}
async get(key) {
const backend = await this.backendPromise;
return backend.get(namespace + key);
return backend.get(key);
}
async set(key, value) {
const backend = await this.backendPromise;
return backend.set(namespace + key, value);
return backend.set(key, value);
}
async remove(key) {
const backend = await this.backendPromise;
return backend.remove(namespace + key);
return backend.remove(key);
}
async clear() {
-117
View File
@@ -1,117 +0,0 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { createLogger } from 'tgui/logging';
const logger = createLogger('AudioPlayer');
export class AudioPlayer {
constructor() {
// Doesn't support HTMLAudioElement
if (Byond.IS_LTE_IE9) {
return;
}
// Set up the HTMLAudioElement node
this.node = document.createElement('audio');
this.node.style.setProperty('display', 'none');
document.body.appendChild(this.node);
// Set up other properties
this.playing = false;
this.volume = 1;
this.options = {};
this.onPlaySubscribers = [];
this.onStopSubscribers = [];
// Listen for playback start events
this.node.addEventListener('canplaythrough', () => {
logger.log('canplaythrough');
this.playing = true;
this.node.playbackRate = this.options.pitch || 1;
this.node.currentTime = this.options.start || 0;
this.node.volume = this.volume;
this.node.play();
for (let subscriber of this.onPlaySubscribers) {
subscriber();
}
});
// Listen for playback stop events
this.node.addEventListener('ended', () => {
logger.log('ended');
this.stop();
});
// Listen for playback errors
this.node.addEventListener('error', (e) => {
if (this.playing) {
logger.log('playback error', e.error);
this.stop();
}
});
// Check every second to stop the playback at the right time
this.playbackInterval = setInterval(() => {
if (!this.playing) {
return;
}
const shouldStop =
this.options.end > 0 && this.node.currentTime >= this.options.end;
if (shouldStop) {
this.stop();
}
}, 1000);
}
destroy() {
if (!this.node) {
return;
}
this.node.stop();
document.removeChild(this.node);
clearInterval(this.playbackInterval);
}
play(url, options = {}) {
if (!this.node) {
return;
}
logger.log('playing', url, options);
this.options = options;
this.node.src = url;
}
stop() {
if (!this.node) {
return;
}
if (this.playing) {
for (let subscriber of this.onStopSubscribers) {
subscriber();
}
}
logger.log('stopping');
this.playing = false;
this.node.src = '';
}
setVolume(volume) {
if (!this.node) {
return;
}
this.volume = volume;
this.node.volume = volume;
}
onPlay(subscriber) {
if (!this.node) {
return;
}
this.onPlaySubscribers.push(subscriber);
}
onStop(subscriber) {
if (!this.node) {
return;
}
this.onStopSubscribers.push(subscriber);
}
}
+101
View File
@@ -0,0 +1,101 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { createLogger } from 'tgui/logging';
const logger = createLogger('AudioPlayer');
type AudioOptions = {
pitch?: number;
start?: number;
end?: number;
};
export class AudioPlayer {
element: HTMLAudioElement | null;
options: AudioOptions;
volume: number;
onPlaySubscribers: { (): void }[];
onStopSubscribers: { (): void }[];
constructor() {
this.element = null;
this.onPlaySubscribers = [];
this.onStopSubscribers = [];
}
destroy() {
this.element = null;
}
play(url: string, options: AudioOptions = {}) {
if (this.element) {
this.stop();
}
this.options = options;
const audio = (this.element = new Audio(url));
audio.volume = this.volume;
audio.playbackRate = this.options.pitch || 1;
logger.log('playing', url, options);
audio.addEventListener('ended', () => {
logger.log('ended');
this.stop();
});
audio.addEventListener('error', (error) => {
logger.log('playback error', error);
});
if (this.options.end) {
audio.addEventListener('timeupdate', () => {
if (
this.options.end &&
this.options.end > 0 &&
audio.currentTime >= this.options.end
) {
this.stop();
}
});
}
audio.play().catch((error) => logger.log('playback error', error));
this.onPlaySubscribers.forEach((subscriber) => subscriber());
}
stop() {
if (!this.element) return;
logger.log('stopping');
this.element.pause();
this.element = null;
this.onStopSubscribers.forEach((subscriber) => subscriber());
}
setVolume(volume: number): void {
this.volume = volume;
if (!this.element) return;
this.element.volume = volume;
}
onPlay(subscriber: () => void): void {
this.onPlaySubscribers.push(subscriber);
}
onStop(subscriber: () => void): void {
this.onStopSubscribers.push(subscriber);
}
}
+27 -13
View File
@@ -20,22 +20,36 @@ const FORBID_TAGS = ['a', 'iframe', 'link', 'video'];
const saveChatToStorage = async (store) => {
const settings = selectSettings(store.getState());
const state = selectChat(store.getState());
const fromIndex = Math.max(
0,
chatRenderer.messages.length - settings.maxMessages
);
const messages = chatRenderer.messages
.slice(fromIndex)
.map((message) => serializeMessage(message));
if (!window.hubStorage) {
const indexedDbBackend = await storage.backendPromise;
indexedDbBackend.processChatMessages(chatRenderer.storeQueue);
} else {
const fromIndex = Math.max(
0,
chatRenderer.messages.length - settings.maxMessages
);
const messages = chatRenderer.messages
.slice(fromIndex)
.map((message) => serializeMessage(message));
storage.set('chat-messages', messages);
}
chatRenderer.storeQueue = [];
storage.set('chat-state', state);
storage.set('chat-messages', messages);
};
const loadChatFromStorage = async (store) => {
const [state, messages] = await Promise.all([
storage.get('chat-state'),
storage.get('chat-messages'),
]);
const state = await storage.get('chat-state');
let messages;
if (!window.hubStorage) {
messages = await (await storage.backendPromise).getChatMessages();
} else {
messages = await storage.get('chat-messages');
}
// Discard incompatible versions
if (state && state.version <= 4) {
store.dispatch(loadChat());
@@ -43,7 +57,7 @@ const loadChatFromStorage = async (store) => {
}
if (messages) {
for (let message of messages) {
if (message.html) {
if (message?.html) {
message.html = DOMPurify.sanitize(message.html, {
FORBID_TAGS,
});
@@ -110,6 +110,7 @@ class ChatRenderer {
/** @type {HTMLElement} */
this.rootNode = null;
this.queue = [];
this.storeQueue = [];
this.messages = [];
this.visibleMessages = [];
this.page = null;
@@ -326,6 +327,8 @@ class ChatRenderer {
let node;
for (let payload of batch) {
const message = createMessage(payload);
let historical = message.stored;
// Combine messages
const combinable = this.getCombinableMessage(message);
if (combinable) {
@@ -426,6 +429,10 @@ class ChatRenderer {
}
}
}
if (!historical) {
this.storeQueue.push({ ...message, stored: true });
}
// Store the node in the message
message.node = node;
// Query all possible selectors to find out the message type
@@ -14,6 +14,7 @@ import { THEMES } from '../themes';
import { changeSettingsTab, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions';
import { SETTINGS_TABS, FONTS, MAX_HIGHLIGHT_SETTINGS } from './constants';
import { selectActiveTab, selectSettings, selectHighlightSettings, selectHighlightSettingById } from './selectors';
import { IMPL_IFRAME_INDEXED_DB, storage } from 'common/storage';
export const SettingsPanel = (props, context) => {
const activeTab = useSelector(context, selectActiveTab);
@@ -159,13 +160,19 @@ export const SettingsGeneral = (props, context) => {
maxValue={32000}
value={maxMessages}
format={(value) => toFixed(value)}
onChange={(e, value) =>
onChange={(e, value) => {
storage.backendPromise.then((promise) => {
if (promise.impl === IMPL_IFRAME_INDEXED_DB) {
promise.setNumberStored(value);
}
});
dispatch(
updateSettings({
maxMessages: value,
})
)
}
);
}}
/>
</LabeledList.Item>
</LabeledList>
+1
View File
@@ -145,6 +145,7 @@ export class Input extends Component {
<input
ref={this.inputRef}
className="Input__input"
type="search"
placeholder={placeholder}
onInput={this.handleInput}
onFocus={this.handleFocus}
+2
View File
@@ -10,6 +10,8 @@ import { addScrollableNode, removeScrollableNode } from '../events';
export const Layout = (props) => {
const { className, theme = 'scc', children, ...rest } = props;
document.documentElement.className = `theme-${theme}`;
return (
<div className={'theme-' + theme}>
<div
@@ -5,8 +5,24 @@
@use 'sass:color';
@use '../base.scss';
@use '../functions.scss' as *;
$scrollbar-color-multiplier: 1 !default;
$luminance: luminance(base.$color-bg);
$scrollbar-base: color.scale(
base.$color-bg,
$lightness: -25% * $scrollbar-color-multiplier
);
$scrollbar-face: color.scale(
base.$color-bg,
$lightness: if($luminance > 0.05, 30%, 10%) * $scrollbar-color-multiplier
);
// Fancy scrollbar
html,
body {
scrollbar-color: $scrollbar-face $scrollbar-base;
}
.Layout,
.Layout * {
+213
View File
@@ -0,0 +1,213 @@
<!doctype html>
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<meta charset='utf-8' />
<script type='text/javascript'>
const INDEXED_DB_VERSION = 2;
const INDEXED_DB_NAME = 'tgui';
const INDEXED_DB_STORE_NAME = 'storage';
const READ_ONLY = 'readonly';
const READ_WRITE = 'readwrite';
let maxStoredMessages = 2500;
let startFrom = 0;
let writeFrom = 0;
const dbPromise = new Promise((resolve, reject) => {
const indexedDB = window.indexedDB || window.msIndexedDB;
const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
req.onupgradeneeded = (event) => {
try {
if(event.oldVersion < 1) {
req.result.createObjectStore(INDEXED_DB_STORE_NAME);
}
if(event.oldVersion < 2) {
req.result.createObjectStore("ring");
req.result.createObjectStore("messages", { autoIncrement: true }).transaction.oncomplete = async () => {
const trans = req.result.transaction("messages", READ_WRITE).objectStore("messages");
startFrom = await promiseForRequest(trans.add(null)) - 1;
for (let index = 0; index < maxStoredMessages - 1; index++) {
await promiseForRequest(trans.add(null));
}
}
}
} catch (err) {
reject(new Error('Failed to upgrade IDB: ' + req.error));
}
};
req.onsuccess = async () => {
const storage = req.result.transaction(["ring", "messages", INDEXED_DB_STORE_NAME], "readonly")
writeFrom = await promiseForRequest(storage.objectStore("ring").get("value")) || 0;
const panelSettings = await promiseForRequest(storage.objectStore(INDEXED_DB_STORE_NAME).get("panel-settings"))
maxStoredMessages = panelSettings?.maxMessages || 2500;
const messageStore = storage.objectStore("messages");
const messageCount = await promiseForRequest(messageStore.count());
if(messageCount !== maxStoredMessages) {
await alterMessageRing(req.result);
}
let cursorReq = await promiseForRequest(messageStore.openCursor());
let cursor = await(cursorReq);
startFrom = cursor.key - 1;
resolve(req.result);
};
req.onerror = () => {
reject(new Error('Failed to open IDB: ' + req.error));
};
});
const alterMessageRing = async (db) => {
db = db ?? await dbPromise;
const existingMessages = (await getSavedMessages(db)).slice(0, maxStoredMessages);
const trans = db.transaction("messages", READ_WRITE).objectStore("messages");
await promiseForRequest(trans.clear());
startFrom = await promiseForRequest(trans.add(null)) - 1;
for (let index = 0; index < maxStoredMessages - 1; index++) {
await promiseForRequest(trans.add(null));
}
writeFrom = startFrom;
await addMessageBatch(existingMessages, db);
}
window.addEventListener('message', (messageEvent) => {
switch (messageEvent.data.type) {
case 'ping':
messageEvent.source.postMessage(true, "*");
break;
case 'get':
get(messageEvent.data.key).then((value) => {
messageEvent.source.postMessage({key: messageEvent.data.key, value: value}, "*");
});
break;
case 'set':
set(messageEvent.data.key, messageEvent.data.value);
break;
case 'remove':
remove(messageEvent.data.key);
break;
case 'clear':
clear();
break;
case 'setNumberStored':
maxStoredMessages = messageEvent.data.newMax;
alterMessageRing();
break;
case 'processChatMessages':
addMessageBatch(messageEvent.data.messages);
break;
case 'getChatMessages':
getSavedMessages().then((messages) => {
messageEvent.source.postMessage({messages: messages}, "*");
})
default:
break;
}
});
const addMessageBatch = async (messages, db) => {
if(!messages || !messages.length) return;
const database = db ?? await dbPromise;
const trans = database.transaction("messages", READ_WRITE).objectStore("messages");
console.log(writeFrom, startFrom);
let cursorReq = trans.openCursor(IDBKeyRange.bound(writeFrom, writeFrom + messages.length, true));
let cursor = await promiseForRequest(cursorReq);
for (let index = 0; index < messages.length; index++) {
const message = messages[index];
cursor.update(message);
writeFrom = cursor.key;
if(cursor.key === startFrom + maxStoredMessages) {
writeFrom = startFrom;
cursorReq = trans.openCursor(IDBKeyRange.bound(startFrom, startFrom + messages.length - index + 1, true));
cursor = await promiseForRequest(cursorReq);
} else {
cursor.continue();
await promiseForRequest(cursorReq);
}
}
database.transaction("ring", READ_WRITE).objectStore("ring").put(writeFrom, "value");
}
const getSavedMessages = async (db) => {
const database = db ?? await dbPromise;
const trans = database.transaction("messages", READ_ONLY).objectStore("messages");
const messyMessages = await promiseForRequest(trans.getAll());
const upTo = messyMessages.splice(0, writeFrom);
if(messyMessages[0] === null) {
return upTo;
}
const messages = messyMessages.concat(upTo);
return messages;
}
const getStore = async (mode) => {
return dbPromise.then((db) => db
.transaction(INDEXED_DB_STORE_NAME, mode)
.objectStore(INDEXED_DB_STORE_NAME));
};
const get = async (key) => {
const store = await getStore(READ_ONLY);
return new Promise((resolve, reject) => {
const req = store.get(key);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
};
const set = async (key, value) => {
const store = await getStore(READ_WRITE);
store.put(value, key);
};
const remove = async (key) => {
const store = await getStore(READ_WRITE);
store.delete(key);
};
const clear = async () => {
const store = await getStore(READ_WRITE);
store.clear();
};
const promiseForRequest = (request) => {
return new Promise((resolve, reject) => {
request.onsuccess = () => { resolve(request.result); };
request.onerror = () => { reject(request.error); };
});
}
</script>
</head>
</html>
+70 -45
View File
@@ -40,6 +40,8 @@
// Expose inlined metadata
Byond.windowId = parseMetaTag('tgui:windowId');
Byond.storageCdn = "tgui:storagecdn"
// Backwards compatibility
window.__windowId__ = Byond.windowId;
@@ -52,8 +54,15 @@
: null;
})();
// Blink engine version
Byond.BLINK = (function () {
var groups = navigator.userAgent.match(/Chrome\/(\d+)\./);
var majorVersion = groups && groups[1];
return majorVersion ? parseInt(majorVersion, 10) : null;
})();
// Basic checks to detect whether this page runs in BYOND
var isByond = (Byond.TRIDENT !== null || window.cef_to_byond)
var isByond = (Byond.TRIDENT !== null || Byond.BLINK !== null || window.cef_to_byond)
&& location.hostname === '127.0.0.1'
&& location.search !== '?external';
//As of BYOND 515 the path doesn't seem to include tmp dir anymore if you're trying to open tgui in external browser and looking why it doesn't work
@@ -61,10 +70,6 @@
// Version constants
Byond.IS_BYOND = isByond;
Byond.IS_LTE_IE8 = Byond.TRIDENT !== null && Byond.TRIDENT <= 4;
Byond.IS_LTE_IE9 = Byond.TRIDENT !== null && Byond.TRIDENT <= 5;
Byond.IS_LTE_IE10 = Byond.TRIDENT !== null && Byond.TRIDENT <= 6;
Byond.IS_LTE_IE11 = Byond.TRIDENT !== null && Byond.TRIDENT <= 7;
// Strict mode flag
Byond.strictMode = Boolean(Number(parseMetaTag('tgui:strictMode')));
@@ -73,17 +78,12 @@
Byond.__callbacks__ = [];
// Reviver for BYOND JSON
// IE8: No reviver for you!
// See: https://stackoverflow.com/questions/1288962
var byondJsonReviver;
if (!Byond.IS_LTE_IE8) {
byondJsonReviver = function (key, value) {
if (typeof value === 'object' && value !== null && value.__number__) {
return parseFloat(value.__number__);
}
return value;
};
}
var byondJsonReviver = function (key, value) {
if (typeof value === 'object' && value !== null && value.__number__) {
return parseFloat(value.__number__);
}
return value;
};
// Makes a BYOND call.
// See: https://secure.byond.com/docs/ref/skinparams.html
@@ -244,23 +244,10 @@
var loadedAssetByUrl = {};
var isStyleSheetLoaded = function (node, url) {
// Method #1 (works on IE10+)
var styleSheet = node.sheet;
if (styleSheet) {
return styleSheet.rules.length > 0;
}
// Method #2
var styleSheets = document.styleSheets;
var len = styleSheets.length;
for (var i = 0; i < len; i++) {
var styleSheet = styleSheets[i];
if(styleSheet.href === undefined)
continue;
if (styleSheet.href.indexOf(url) !== -1) {
return styleSheet.rules.length > 0;
}
}
// All methods failed
return false;
};
@@ -308,10 +295,7 @@
var node = document.createElement('script');
node.type = 'text/javascript';
node.crossOrigin = 'anonymous';
// IE8: Prefer non-https protocols
node.src = Byond.IS_LTE_IE9
? url.replace('https://', 'http://')
: url;
node.src = url;
if (sync) {
node.defer = true;
}
@@ -332,15 +316,25 @@
var node = document.createElement('link');
node.type = 'text/css';
node.rel = 'stylesheet';
// IE8: Prefer non-https protocols
node.href = Byond.IS_LTE_IE9
? url.replace('https://', 'http://')
: url;
node.crossOrigin = 'anonymous';
node.href = url;
// Temporarily set media to something inapplicable
// to ensure it'll fetch without blocking render
if (!sync) {
node.media = 'only x';
}
var removeNodeAndRetry = function () {
node.parentNode.removeChild(node);
node = null;
retry();
}
// 516: Chromium won't call onload() if there is a 404 error
// Legacy IE doesn't use onerror, so we retain that
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#stylesheet_load_events
node.onerror = function () {
node.onerror = null;
removeNodeAndRetry();
}
node.onload = function () {
node.onload = null;
if (isStyleSheetLoaded(node, url)) {
@@ -348,10 +342,7 @@
node.media = 'all';
return;
}
// Try again
node.parentNode.removeChild(node);
node = null;
retry();
removeNodeAndRetry();
};
injectNode(node);
return;
@@ -365,6 +356,39 @@
Byond.loadCss = function (url, sync) {
loadAsset({ url: url, sync: sync, type: 'css' });
};
Byond.saveBlob = function (blob, filename, ext) {
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename)
} else if (window.showSaveFilePicker) {
var accept = {};
accept[blob.type] = [ext];
var opts = {
suggestedName: filename,
types: [
{
description: "SS13 file",
accept: accept,
}
]
}
try {
window.showSaveFilePicker(opts).then(function (fileHandle) {
fileHandle.createWritable().then(function (writeableFileHandle) {
writeableFileHandle.write(blob).then(function () {
writeableFileHandle.close()
}).catch(function (e) { console.error(e) });
}).catch(function (e) { console.error(e) });
}).catch(function (e) { console.error(e) });
} catch (e) { console.error(e) }
}
};
// Icon cache
Byond.iconRefMap = {};
})();
// Error handling
@@ -395,7 +419,7 @@ window.onerror = function (msg, url, line, col, error) {
else {
window.onerror.__stack__ = stack;
}
var textProp = Byond.IS_LTE_IE8 ? 'innerText' : 'textContent';
var textProp = 'textContent';
errorStack[textProp] = window.onerror.__stack__;
}
// Set window geometry
@@ -510,9 +534,6 @@ window.replaceHtml = function (inline_html) {
+ "<!-- tgui:inline-html-end -->"
);
};
// Signal tgui that we're ready to receive updates
Byond.sendMessage('ready');
</script>
<style>
@@ -657,5 +678,9 @@ Thank you for your cooperation.
</div>
</noscript>
<script>
// Signal tgui that we're ready to receive updates
Byond.sendMessage('ready');
</script>
</body>
</html>