mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
* Cherry picks the unicode part 2 PR from TG * Things * drop 512 support * TAN --> tan * Manually does the things that was in renamed files * CLAMP --> clamp * ismovableatom -> ismovable * bugfixes, tg is bad * Ports sanitize_name * Bumps checks * Fixes new linter errors (#48126) About The Pull Request This fixes the errors raised by the new feature I'm adding to the linter: SpaceManiac/SpacemanDMM#119 * Update SpacemanDMM suite to 1.2 (#48785) * Update SpacemanDMM suite to 1.2 * Fix new lint errors * Removes unreachable code (#48143) About The Pull Request As detected by SpaceManiac/SpacemanDMM#123 * casually fixes 50 bugs * stoopid evil dreamchecker * stoopid evil dreamchecker * stoopid evil dreamchecker * almost the same thing * Makes all UIs UTF-8 * Fixes bugs * Fixes runtimes, some related to 513, some not * Fixes agent ids Co-authored-by: MrPerson <spamtaffic@gmail.com> Co-authored-by: alexkar598 <> Co-authored-by: spookydonut <github@spooksoftware.com>
469 lines
16 KiB
Plaintext
469 lines
16 KiB
Plaintext
/datum/browser
|
|
var/mob/user
|
|
var/title
|
|
var/window_id // window_id is used as the window name for browse and onclose
|
|
var/width = 0
|
|
var/height = 0
|
|
var/atom/ref = null
|
|
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
|
|
var/stylesheets[0]
|
|
var/scripts[0]
|
|
var/title_image
|
|
var/head_elements
|
|
var/body_elements
|
|
var/head_content = ""
|
|
var/content = ""
|
|
|
|
|
|
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
|
|
|
|
user = nuser
|
|
window_id = nwindow_id
|
|
if (ntitle)
|
|
title = format_text(ntitle)
|
|
if (nwidth)
|
|
width = nwidth
|
|
if (nheight)
|
|
height = nheight
|
|
if (nref)
|
|
ref = nref
|
|
add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs
|
|
add_script("common", 'html/browser/common.js')
|
|
|
|
/datum/browser/proc/add_head_content(nhead_content)
|
|
head_content = nhead_content
|
|
|
|
/datum/browser/proc/set_window_options(nwindow_options)
|
|
window_options = nwindow_options
|
|
|
|
/datum/browser/proc/set_title_image(ntitle_image)
|
|
//title_image = ntitle_image
|
|
|
|
/datum/browser/proc/add_stylesheet(name, file)
|
|
if (istype(name, /datum/asset/spritesheet))
|
|
var/datum/asset/spritesheet/sheet = name
|
|
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
|
|
else
|
|
stylesheets["[ckey(name)].css"] = file
|
|
register_asset("[ckey(name)].css", file)
|
|
|
|
/datum/browser/proc/add_script(name, file)
|
|
scripts["[ckey(name)].js"] = file
|
|
register_asset("[ckey(name)].js", file)
|
|
|
|
/datum/browser/proc/set_content(ncontent)
|
|
content = ncontent
|
|
|
|
/datum/browser/proc/add_content(ncontent)
|
|
content += ncontent
|
|
|
|
/datum/browser/proc/get_header()
|
|
var/file
|
|
for (file in stylesheets)
|
|
head_content += "<link rel='stylesheet' type='text/css' href='[file]'>"
|
|
|
|
for (file in scripts)
|
|
head_content += "<script type='text/javascript' src='[file]'></script>"
|
|
|
|
var/title_attributes = "class='uiTitle'"
|
|
if (title_image)
|
|
title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'"
|
|
|
|
return {"<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
<html>
|
|
<meta http-equiv="Content-Type" content="text/html; charset='UTF-8'">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<head>
|
|
[head_content]
|
|
</head>
|
|
<body scroll=auto>
|
|
<div class='uiWrapper'>
|
|
[title ? "<div class='uiTitleWrapper'><div [title_attributes]><tt>[title]</tt></div></div>" : ""]
|
|
<div class='uiContent'>
|
|
"}
|
|
//" This is here because else the rest of the file looks like a string in notepad++.
|
|
/datum/browser/proc/get_footer()
|
|
return {"
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>"}
|
|
|
|
/datum/browser/proc/get_content()
|
|
return {"
|
|
[get_header()]
|
|
[content]
|
|
[get_footer()]
|
|
"}
|
|
|
|
/datum/browser/proc/open(use_onclose = TRUE)
|
|
if(isnull(window_id)) //null check because this can potentially nuke goonchat
|
|
WARNING("Browser [title] tried to open with a null ID")
|
|
to_chat(user, "<span class='userdanger'>The [title] browser you tried to open failed a sanity check! Please report this on github!</span>")
|
|
return
|
|
var/window_size = ""
|
|
if (width && height)
|
|
window_size = "size=[width]x[height];"
|
|
if (stylesheets.len)
|
|
send_asset_list(user, stylesheets, verify=FALSE)
|
|
if (scripts.len)
|
|
send_asset_list(user, scripts, verify=FALSE)
|
|
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
|
|
if (use_onclose)
|
|
setup_onclose()
|
|
|
|
/datum/browser/proc/setup_onclose()
|
|
set waitfor = 0 //winexists sleeps, so we don't need to.
|
|
for (var/i in 1 to 10)
|
|
if (user && winexists(user, window_id))
|
|
onclose(user, window_id, ref)
|
|
break
|
|
|
|
/datum/browser/proc/close()
|
|
if(!isnull(window_id))//null check because this can potentially nuke goonchat
|
|
user << browse(null, "window=[window_id]")
|
|
else
|
|
WARNING("Browser [title] tried to close with a null ID")
|
|
|
|
/datum/browser/modal/alert/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1,Timeout=6000)
|
|
if (!User)
|
|
return
|
|
|
|
var/output = {"<center><b>[Message]</b></center><br />
|
|
<div style="text-align:center">
|
|
<a style="font-size:large;float:[( Button2 ? "left" : "right" )]" href="?src=[REF(src)];button=1">[Button1]</a>"}
|
|
|
|
if (Button2)
|
|
output += {"<a style="font-size:large;[( Button3 ? "" : "float:right" )]" href="?src=[REF(src)];button=2">[Button2]</a>"}
|
|
|
|
if (Button3)
|
|
output += {"<a style="font-size:large;float:right" href="?src=[REF(src)];button=3">[Button3]</a>"}
|
|
|
|
output += {"</div>"}
|
|
|
|
..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, 350, 150, src, StealFocus, Timeout)
|
|
set_content(output)
|
|
|
|
/datum/browser/modal/alert/Topic(href,href_list)
|
|
if (href_list["close"] || !user || !user.client)
|
|
opentime = 0
|
|
return
|
|
if (href_list["button"])
|
|
var/button = text2num(href_list["button"])
|
|
if (button <= 3 && button >= 1)
|
|
selectedbutton = button
|
|
opentime = 0
|
|
close()
|
|
|
|
//designed as a drop in replacement for alert(); functions the same. (outside of needing User specified)
|
|
/proc/tgalert(var/mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
|
|
if (!User)
|
|
User = usr
|
|
switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout))
|
|
if (1)
|
|
return Button1
|
|
if (2)
|
|
return Button2
|
|
if (3)
|
|
return Button3
|
|
|
|
//Same shit, but it returns the button number, could at some point support unlimited button amounts.
|
|
/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
|
|
if (!istype(User))
|
|
if (istype(User, /client/))
|
|
var/client/C = User
|
|
User = C.mob
|
|
else
|
|
return
|
|
var/datum/browser/modal/alert/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)
|
|
A.open()
|
|
A.wait()
|
|
if (A.selectedbutton)
|
|
return A.selectedbutton
|
|
|
|
/datum/browser/modal
|
|
var/opentime = 0
|
|
var/timeout
|
|
var/selectedbutton = 0
|
|
var/stealfocus
|
|
|
|
/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null, StealFocus = 1, Timeout = 6000)
|
|
..()
|
|
stealfocus = StealFocus
|
|
if (!StealFocus)
|
|
window_options += "focus=false;"
|
|
timeout = Timeout
|
|
|
|
|
|
/datum/browser/modal/close()
|
|
.=..()
|
|
opentime = 0
|
|
|
|
/datum/browser/modal/open(use_onclose)
|
|
set waitfor = 0
|
|
opentime = world.time
|
|
|
|
if (stealfocus)
|
|
. = ..(use_onclose = 1)
|
|
else
|
|
var/focusedwindow = winget(user, null, "focus")
|
|
. = ..(use_onclose = 1)
|
|
|
|
//waits for the window to show up client side before attempting to un-focus it
|
|
//winexists sleeps until it gets a reply from the client, so we don't need to bother sleeping
|
|
for (var/i in 1 to 10)
|
|
if (user && winexists(user, window_id))
|
|
if (focusedwindow)
|
|
winset(user, focusedwindow, "focus=true")
|
|
else
|
|
winset(user, "mapwindow", "focus=true")
|
|
break
|
|
if (timeout)
|
|
addtimer(CALLBACK(src, .proc/close), timeout)
|
|
|
|
/datum/browser/modal/proc/wait()
|
|
while (opentime && selectedbutton <= 0 && (!timeout || opentime+timeout > world.time))
|
|
stoplag(1)
|
|
|
|
/datum/browser/modal/listpicker
|
|
var/valueslist = list()
|
|
|
|
/datum/browser/modal/listpicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/values,inputtype="checkbox", width, height, slidecolor)
|
|
if (!User)
|
|
return
|
|
|
|
var/output = {"<form><input type="hidden" name="src" value="[REF(src)]"><ul class="sparse">"}
|
|
if (inputtype == "checkbox" || inputtype == "radio")
|
|
for (var/i in values)
|
|
var/div_slider = slidecolor
|
|
if(!i["allowed_edit"])
|
|
div_slider = "locked"
|
|
output += {"<li>
|
|
<label class="switch">
|
|
<input type="[inputtype]" value="1" name="[i["name"]]"[i["checked"] ? " checked" : ""][i["allowed_edit"] ? "" : " onclick='return false' onkeydown='return false'"]>
|
|
<div class="slider [div_slider ? "[div_slider]" : ""]"></div>
|
|
<span>[i["name"]]</span>
|
|
</label>
|
|
</li>"}
|
|
else
|
|
for (var/i in values)
|
|
output += {"<li><input id="name="[i["name"]]"" style="width: 50px" type="[type]" name="[i["name"]]" value="[i["value"]]">
|
|
<label for="[i["name"]]">[i["name"]]</label></li>"}
|
|
output += {"</ul><div style="text-align:center">
|
|
<button type="submit" name="button" value="1" style="font-size:large;float:[( Button2 ? "left" : "right" )]">[Button1]</button>"}
|
|
|
|
if (Button2)
|
|
output += {"<button type="submit" name="button" value="2" style="font-size:large;[( Button3 ? "" : "float:right" )]">[Button2]</button>"}
|
|
|
|
if (Button3)
|
|
output += {"<button type="submit" name="button" value="3" style="font-size:large;float:right">[Button3]</button>"}
|
|
|
|
output += {"</form></div>"}
|
|
..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout)
|
|
set_content(output)
|
|
|
|
/datum/browser/modal/listpicker/Topic(href,href_list)
|
|
if (href_list["close"] || !user || !user.client)
|
|
opentime = 0
|
|
return
|
|
if (href_list["button"])
|
|
var/button = text2num(href_list["button"])
|
|
if (button <= 3 && button >= 1)
|
|
selectedbutton = button
|
|
for (var/item in href_list)
|
|
switch(item)
|
|
if ("close", "button", "src")
|
|
continue
|
|
else
|
|
valueslist[item] = href_list[item]
|
|
opentime = 0
|
|
close()
|
|
|
|
/proc/presentpicker(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor)
|
|
if (!istype(User))
|
|
if (istype(User, /client/))
|
|
var/client/C = User
|
|
User = C.mob
|
|
else
|
|
return
|
|
var/datum/browser/modal/listpicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, values, inputtype, width, height, slidecolor)
|
|
A.open()
|
|
A.wait()
|
|
if (A.selectedbutton)
|
|
return list("button" = A.selectedbutton, "values" = A.valueslist)
|
|
|
|
/proc/input_bitfield(var/mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null)
|
|
if (!User || !(bitfield in GLOB.bitfields))
|
|
return
|
|
var/list/pickerlist = list()
|
|
for (var/i in GLOB.bitfields[bitfield])
|
|
var/can_edit = 1
|
|
if(!isnull(allowed_edit_list) && !(allowed_edit_list & GLOB.bitfields[bitfield][i]))
|
|
can_edit = 0
|
|
if (current_value & GLOB.bitfields[bitfield][i])
|
|
pickerlist += list(list("checked" = 1, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit))
|
|
else
|
|
pickerlist += list(list("checked" = 0, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit))
|
|
var/list/result = presentpicker(User, "", title, Button1="Save", Button2 = "Cancel", Timeout=FALSE, values = pickerlist, width = nwidth, height = nheight, slidecolor = nslidecolor)
|
|
if (islist(result))
|
|
if (result["button"] == 2) // If the user pressed the cancel button
|
|
return
|
|
. = 0
|
|
for (var/flag in result["values"])
|
|
. |= GLOB.bitfields[bitfield][flag]
|
|
else
|
|
return
|
|
|
|
/datum/browser/modal/preflikepicker
|
|
var/settings = list()
|
|
var/icon/preview_icon = null
|
|
var/datum/callback/preview_update
|
|
|
|
/datum/browser/modal/preflikepicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/settings,inputtype="checkbox", width = 600, height, slidecolor)
|
|
if (!User)
|
|
return
|
|
src.settings = settings
|
|
|
|
..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout)
|
|
set_content(ShowChoices(User))
|
|
|
|
/datum/browser/modal/preflikepicker/proc/ShowChoices(mob/user)
|
|
if (settings["preview_callback"])
|
|
var/datum/callback/callback = settings["preview_callback"]
|
|
preview_icon = callback.Invoke(settings)
|
|
if (preview_icon)
|
|
user << browse_rsc(preview_icon, "previewicon.png")
|
|
var/dat = ""
|
|
|
|
for (var/name in settings["mainsettings"])
|
|
var/setting = settings["mainsettings"][name]
|
|
if (setting["type"] == "datum")
|
|
if (setting["subtypesonly"])
|
|
dat += "<b>[setting["desc"]]:</b> <a href='?src=[REF(src)];setting=[name];task=input;subtypesonly=1;type=datum;path=[setting["path"]]'>[setting["value"]]</a><BR>"
|
|
else
|
|
dat += "<b>[setting["desc"]]:</b> <a href='?src=[REF(src)];setting=[name];task=input;type=datum;path=[setting["path"]]'>[setting["value"]]</a><BR>"
|
|
else
|
|
dat += "<b>[setting["desc"]]:</b> <a href='?src=[REF(src)];setting=[name];task=input;type=[setting["type"]]'>[setting["value"]]</a><BR>"
|
|
|
|
if (preview_icon)
|
|
dat += "<td valign='center'>"
|
|
|
|
dat += "<div class='statusDisplay'><center><img src=previewicon.png width=[preview_icon.Width()] height=[preview_icon.Height()]></center></div>"
|
|
|
|
dat += "</td>"
|
|
|
|
dat += "</tr></table>"
|
|
|
|
dat += "<hr><center><a href='?src=[REF(src)];button=1'>Ok</a> "
|
|
|
|
dat += "</center>"
|
|
|
|
return dat
|
|
|
|
/datum/browser/modal/preflikepicker/Topic(href,href_list)
|
|
if (href_list["close"] || !user || !user.client)
|
|
opentime = 0
|
|
return
|
|
if (href_list["task"] == "input")
|
|
var/setting = href_list["setting"]
|
|
switch (href_list["type"])
|
|
if ("datum")
|
|
var/oldval = settings["mainsettings"][setting]["value"]
|
|
if (href_list["subtypesonly"])
|
|
settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(subtypesof(text2path(href_list["path"]))))
|
|
else
|
|
settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(typesof(text2path(href_list["path"]))))
|
|
if (isnull(settings["mainsettings"][setting]["value"]))
|
|
settings["mainsettings"][setting]["value"] = oldval
|
|
if ("string")
|
|
settings["mainsettings"][setting]["value"] = stripped_input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]")
|
|
if ("number")
|
|
settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num
|
|
if ("boolean")
|
|
settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("Yes","No")
|
|
if ("ckey")
|
|
settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("none") + GLOB.directory
|
|
if (settings["mainsettings"][setting]["callback"])
|
|
var/datum/callback/callback = settings["mainsettings"][setting]["callback"]
|
|
settings = callback.Invoke(settings)
|
|
if (href_list["button"])
|
|
var/button = text2num(href_list["button"])
|
|
if (button <= 3 && button >= 1)
|
|
selectedbutton = button
|
|
if (selectedbutton != 1)
|
|
set_content(ShowChoices(user))
|
|
open()
|
|
return
|
|
for (var/item in href_list)
|
|
switch(item)
|
|
if ("close", "button", "src")
|
|
continue
|
|
opentime = 0
|
|
close()
|
|
|
|
/proc/presentpreflikepicker(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor)
|
|
if (!istype(User))
|
|
if (istype(User, /client/))
|
|
var/client/C = User
|
|
User = C.mob
|
|
else
|
|
return
|
|
var/datum/browser/modal/preflikepicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, settings, width, height, slidecolor)
|
|
A.open()
|
|
A.wait()
|
|
if (A.selectedbutton)
|
|
return list("button" = A.selectedbutton, "settings" = A.settings)
|
|
|
|
// This will allow you to show an icon in the browse window
|
|
// This is added to mob so that it can be used without a reference to the browser object
|
|
// There is probably a better place for this...
|
|
/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1)
|
|
|
|
|
|
// Registers the on-close verb for a browse window (client/verb/.windowclose)
|
|
// this will be called when the close-button of a window is pressed.
|
|
//
|
|
// This is usually only needed for devices that regularly update the browse window,
|
|
// e.g. canisters, timers, etc.
|
|
//
|
|
// windowid should be the specified window name
|
|
// e.g. code is : user << browse(text, "window=fred")
|
|
// then use : onclose(user, "fred")
|
|
//
|
|
// Optionally, specify the "ref" parameter as the controlled atom (usually src)
|
|
// to pass a "close=1" parameter to the atom's Topic() proc for special handling.
|
|
// Otherwise, the user mob's machine var will be reset directly.
|
|
//
|
|
/proc/onclose(mob/user, windowid, atom/ref=null)
|
|
if(!user.client)
|
|
return
|
|
var/param = "null"
|
|
if(ref)
|
|
param = "[REF(ref)]"
|
|
|
|
winset(user, windowid, "on-close=\".windowclose [param]\"")
|
|
|
|
|
|
|
|
// the on-close client verb
|
|
// called when a browser popup window is closed after registering with proc/onclose()
|
|
// if a valid atom reference is supplied, call the atom's Topic() with "close=1"
|
|
// otherwise, just reset the client mob's machine var.
|
|
//
|
|
/client/verb/windowclose(atomref as text)
|
|
set hidden = 1 // hide this verb from the user's panel
|
|
set name = ".windowclose" // no autocomplete on cmd line
|
|
|
|
if(atomref!="null") // if passed a real atomref
|
|
var/hsrc = locate(atomref) // find the reffed atom
|
|
var/href = "close=1"
|
|
if(hsrc)
|
|
usr = src.mob
|
|
src.Topic(href, params2list(href), hsrc) // this will direct to the atom's
|
|
return // Topic() proc via client.Topic()
|
|
|
|
// no atomref specified (or not found)
|
|
// so just reset the user mob's machine var
|
|
if(src && src.mob)
|
|
src.mob.unset_machine()
|