mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-22 11:37:40 +01:00
BS12 -> Ponies merge
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
json_token
|
||||
var
|
||||
value
|
||||
New(v)
|
||||
src.value = v
|
||||
text
|
||||
number
|
||||
word
|
||||
symbol
|
||||
eof
|
||||
|
||||
json_reader
|
||||
var
|
||||
list
|
||||
string = list("'", "\"")
|
||||
symbols = list("{", "}", "\[", "]", ":", "\"", "'", ",")
|
||||
sequences = list("b" = 8, "t" = 9, "n" = 10, "f" = 12, "r" = 13)
|
||||
tokens
|
||||
json
|
||||
i = 1
|
||||
|
||||
|
||||
proc
|
||||
// scanner
|
||||
ScanJson(json)
|
||||
src.json = json
|
||||
. = new/list()
|
||||
src.i = 1
|
||||
while(src.i <= lentext(json))
|
||||
var/char = get_char()
|
||||
if(is_whitespace(char))
|
||||
i++
|
||||
continue
|
||||
if(string.Find(char))
|
||||
. += read_string(char)
|
||||
else if(symbols.Find(char))
|
||||
. += new/json_token/symbol(char)
|
||||
else if(is_digit(char))
|
||||
. += read_number()
|
||||
else
|
||||
. += read_word()
|
||||
i++
|
||||
. += new/json_token/eof()
|
||||
|
||||
read_word()
|
||||
var/val = ""
|
||||
while(i <= lentext(json))
|
||||
var/char = get_char()
|
||||
if(is_whitespace(char) || symbols.Find(char))
|
||||
i-- // let scanner handle this character
|
||||
return new/json_token/word(val)
|
||||
val += char
|
||||
i++
|
||||
|
||||
read_string(delim)
|
||||
var
|
||||
escape = FALSE
|
||||
val = ""
|
||||
while(++i <= lentext(json))
|
||||
var/char = get_char()
|
||||
if(escape)
|
||||
switch(char)
|
||||
if("\\", "'", "\"", "/", "u")
|
||||
val += char
|
||||
else
|
||||
// TODO: support octal, hex, unicode sequences
|
||||
ASSERT(sequences.Find(char))
|
||||
val += ascii2text(sequences[char])
|
||||
else
|
||||
if(char == delim)
|
||||
return new/json_token/text(val)
|
||||
else if(char == "\\")
|
||||
escape = TRUE
|
||||
else
|
||||
val += char
|
||||
CRASH("Unterminated string.")
|
||||
|
||||
read_number()
|
||||
var/val = ""
|
||||
var/char = get_char()
|
||||
while(is_digit(char) || char == "." || lowertext(char) == "e")
|
||||
val += char
|
||||
i++
|
||||
char = get_char()
|
||||
i-- // allow scanner to read the first non-number character
|
||||
return new/json_token/number(text2num(val))
|
||||
|
||||
check_char()
|
||||
ASSERT(args.Find(get_char()))
|
||||
|
||||
get_char()
|
||||
return copytext(json, i, i+1)
|
||||
|
||||
is_whitespace(char)
|
||||
return char == " " || char == "\t" || char == "\n" || text2ascii(char) == 13
|
||||
|
||||
is_digit(char)
|
||||
var/c = text2ascii(char)
|
||||
return 48 <= c && c <= 57 || char == "+" || char == "-"
|
||||
|
||||
|
||||
// parser
|
||||
ReadObject(list/tokens)
|
||||
src.tokens = tokens
|
||||
. = new/list()
|
||||
i = 1
|
||||
read_token("{", /json_token/symbol)
|
||||
while(i <= tokens.len)
|
||||
var/json_token/K = get_token()
|
||||
check_type(/json_token/word, /json_token/text)
|
||||
next_token()
|
||||
read_token(":", /json_token/symbol)
|
||||
|
||||
.[K.value] = read_value()
|
||||
|
||||
var/json_token/S = get_token()
|
||||
check_type(/json_token/symbol)
|
||||
switch(S.value)
|
||||
if(",")
|
||||
next_token()
|
||||
continue
|
||||
if("}")
|
||||
next_token()
|
||||
return
|
||||
else
|
||||
die()
|
||||
|
||||
get_token()
|
||||
return tokens[i]
|
||||
|
||||
next_token()
|
||||
return tokens[++i]
|
||||
|
||||
read_token(val, type)
|
||||
var/json_token/T = get_token()
|
||||
if(!(T.value == val && istype(T, type)))
|
||||
CRASH("Expected '[val]', found '[T.value]'.")
|
||||
next_token()
|
||||
return T
|
||||
|
||||
check_type(...)
|
||||
var/json_token/T = get_token()
|
||||
for(var/type in args)
|
||||
if(istype(T, type))
|
||||
return
|
||||
CRASH("Bad token type: [T.type].")
|
||||
|
||||
check_value(...)
|
||||
var/json_token/T = get_token()
|
||||
ASSERT(args.Find(T.value))
|
||||
|
||||
read_key()
|
||||
var/char = get_char()
|
||||
if(char == "\"" || char == "'")
|
||||
return read_string(char)
|
||||
|
||||
read_value()
|
||||
var/json_token/T = get_token()
|
||||
switch(T.type)
|
||||
if(/json_token/text, /json_token/number)
|
||||
next_token()
|
||||
return T.value
|
||||
if(/json_token/word)
|
||||
next_token()
|
||||
switch(T.value)
|
||||
if("true")
|
||||
return TRUE
|
||||
if("false")
|
||||
return FALSE
|
||||
if("null")
|
||||
return null
|
||||
if(/json_token/symbol)
|
||||
switch(T.value)
|
||||
if("\[")
|
||||
return read_array()
|
||||
if("{")
|
||||
return ReadObject(tokens.Copy(i))
|
||||
die()
|
||||
|
||||
read_array()
|
||||
read_token("\[", /json_token/symbol)
|
||||
. = new/list()
|
||||
var/list/L = .
|
||||
while(i <= tokens.len)
|
||||
// Avoid using Add() or += in case a list is returned.
|
||||
L.len++
|
||||
L[L.len] = read_value()
|
||||
var/json_token/T = get_token()
|
||||
check_type(/json_token/symbol)
|
||||
switch(T.value)
|
||||
if(",")
|
||||
next_token()
|
||||
continue
|
||||
if("]")
|
||||
next_token()
|
||||
return
|
||||
else
|
||||
die()
|
||||
next_token()
|
||||
CRASH("Unterminated array.")
|
||||
|
||||
|
||||
die(json_token/T)
|
||||
if(!T) T = get_token()
|
||||
CRASH("Unexpected token: [T.value].")
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
json_writer
|
||||
proc
|
||||
WriteObject(list/L)
|
||||
. = "{"
|
||||
var/i = 1
|
||||
for(var/k in L)
|
||||
var/val = L[k]
|
||||
. += {"\"[k]\":[write(val)]"}
|
||||
if(i++ < L.len)
|
||||
. += ","
|
||||
.+= "}"
|
||||
|
||||
write(val)
|
||||
if(isnum(val))
|
||||
return num2text(val, 100)
|
||||
else if(isnull(val))
|
||||
return "null"
|
||||
else if(istype(val, /list))
|
||||
if(is_associative(val))
|
||||
return WriteObject(val)
|
||||
else
|
||||
return write_array(val)
|
||||
else
|
||||
. += write_string("[val]")
|
||||
|
||||
write_array(list/L)
|
||||
. = "\["
|
||||
for(var/i = 1 to L.len)
|
||||
. += write(L[i])
|
||||
if(i < L.len)
|
||||
. += ","
|
||||
. += "]"
|
||||
|
||||
write_string(txt)
|
||||
var/static/list/json_escape = list("\\", "\"", "'", "\n")
|
||||
for(var/targ in json_escape)
|
||||
var/start = 1
|
||||
while(start <= lentext(txt))
|
||||
var/i = findtext(txt, targ, start)
|
||||
if(!i)
|
||||
break
|
||||
if(targ == "\n")
|
||||
txt = copytext(txt, 1, i) + "\\n" + copytext(txt, i+1)
|
||||
else
|
||||
txt = copytext(txt, 1, i) + "\\" + copytext(txt, i)
|
||||
start = i + 2
|
||||
return {""[txt]""}
|
||||
|
||||
is_associative(list/L)
|
||||
for(var/key in L)
|
||||
// if the key is a list that means it's actually an array of lists (stupid Byond...)
|
||||
if(!isnum(key) && !istype(key, /list))
|
||||
return TRUE
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
n_Json v11.3.21
|
||||
*/
|
||||
|
||||
proc
|
||||
json2list(json)
|
||||
var/static/json_reader/_jsonr = new()
|
||||
return _jsonr.ReadObject(_jsonr.ScanJson(json))
|
||||
|
||||
list2json(list/L)
|
||||
var/static/json_writer/_jsonw = new()
|
||||
return _jsonw.WriteObject(L)
|
||||
@@ -0,0 +1,6 @@
|
||||
// All movable things can have a Nano UI, always use ui_interact to open/interact with a Nano UI
|
||||
/atom/movable/proc/ui_interact(mob/user, ui_key = "main")
|
||||
return
|
||||
|
||||
// Used by the Nano UI Manager (/datum/nanomanager) to track UIs opened by this mob
|
||||
/mob/var/list/open_uis = list()
|
||||
@@ -0,0 +1,69 @@
|
||||
// This is the window/UI manager for Nano UI
|
||||
// There should only ever be one (global) instance of nanomanger
|
||||
/datum/nanomanager
|
||||
var/open_uis[0]
|
||||
var/list/processing_uis = list()
|
||||
|
||||
/datum/nanomanager/New()
|
||||
return
|
||||
|
||||
/datum/nanomanager/proc/get_open_ui(var/mob/user, src_object, ui_key)
|
||||
var/src_object_key = "\ref[src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
return null
|
||||
else if (isnull(open_uis[src_object_key][ui_key]) || !istype(open_uis[src_object_key][ui_key], /list))
|
||||
return null
|
||||
|
||||
for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
|
||||
if (ui.user == user)
|
||||
return ui
|
||||
|
||||
return null
|
||||
|
||||
/datum/nanomanager/proc/update_uis(src_object)
|
||||
var/src_object_key = "\ref[src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
return 0
|
||||
|
||||
var/update_count = 0
|
||||
for (var/ui_key in open_uis[src_object_key])
|
||||
for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
|
||||
if(ui && ui.src_object && ui.user)
|
||||
ui.process()
|
||||
update_count++
|
||||
return update_count
|
||||
|
||||
/datum/nanomanager/proc/ui_opened(var/datum/nanoui/ui)
|
||||
var/src_object_key = "\ref[ui.src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
open_uis[src_object_key] = list(ui.ui_key = list())
|
||||
else if (isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
|
||||
open_uis[src_object_key][ui.ui_key] = list();
|
||||
|
||||
ui.user.open_uis.Add(ui)
|
||||
var/list/uis = open_uis[src_object_key][ui.ui_key]
|
||||
uis.Add(ui)
|
||||
processing_uis.Add(ui)
|
||||
|
||||
/datum/nanomanager/proc/ui_closed(var/datum/nanoui/ui)
|
||||
var/src_object_key = "\ref[ui.src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
return 0 // wasn't open
|
||||
else if (isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
|
||||
return 0 // wasn't open
|
||||
|
||||
processing_uis.Remove(ui)
|
||||
ui.user.open_uis.Remove(ui)
|
||||
var/list/uis = open_uis[src_object_key][ui.ui_key]
|
||||
return uis.Remove(ui)
|
||||
|
||||
// user has logged out (or is switching mob) so close/clear all uis
|
||||
/datum/nanomanager/proc/user_logout(var/mob/user)
|
||||
if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
|
||||
return 0 // has no open uis
|
||||
|
||||
for (var/datum/nanoui/ui in user.open_uis)
|
||||
ui.close();
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/datum/nanoui
|
||||
var/mob/user
|
||||
var/atom/movable/src_object
|
||||
var/title
|
||||
var/ui_key
|
||||
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/on_close_logic = 1
|
||||
var/window_options = "focus=0;can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
|
||||
var/list/stylesheets = list()
|
||||
var/list/scripts = list()
|
||||
var/templates[0]
|
||||
var/title_image
|
||||
var/head_elements
|
||||
var/body_elements
|
||||
var/head_content = ""
|
||||
var/content = "<div id='mainTemplate'></div>" // the #mainTemplate div will contain the compiled "main" template html
|
||||
var/list/initial_data[0]
|
||||
var/is_auto_updating = 0
|
||||
var/status = 2
|
||||
|
||||
|
||||
/datum/nanoui/New(nuser, nsrc_object, nui_key, ntemplate, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
|
||||
user = nuser
|
||||
src_object = nsrc_object
|
||||
ui_key = nui_key
|
||||
window_id = "[ui_key]\ref[src_object]"
|
||||
|
||||
add_template("main", ntemplate)
|
||||
|
||||
if (ntitle)
|
||||
title = ntitle
|
||||
if (nwidth)
|
||||
width = nwidth
|
||||
if (nheight)
|
||||
height = nheight
|
||||
if (nref)
|
||||
ref = nref
|
||||
|
||||
add_common_assets()
|
||||
|
||||
/datum/nanoui/proc/add_common_assets()
|
||||
add_script("libraries.min.js") // The jQuery library
|
||||
add_script("nano_update.js") // The NanoUpdate JS, this is used to receive updates and apply them.
|
||||
add_script("nano_config.js") // The NanoUpdate JS, this is used to receive updates and apply them.
|
||||
add_script("nano_base_helpers.js") // The NanoBaseHelpers JS, this is used to set up template helpers which are common to all templates
|
||||
add_stylesheet("shared.css") // this CSS sheet is common to all UIs
|
||||
add_stylesheet("icons.css") // this CSS sheet is common to all UIs
|
||||
|
||||
/datum/nanoui/proc/set_status(state)
|
||||
if (state != status)
|
||||
status = state
|
||||
push_data(list(), 1) // Update the UI
|
||||
else
|
||||
status = state
|
||||
|
||||
/datum/nanoui/proc/set_auto_update(state = 1)
|
||||
is_auto_updating = state
|
||||
|
||||
/datum/nanoui/proc/set_initial_data(data)
|
||||
initial_data = modify_data(data)
|
||||
|
||||
/datum/nanoui/proc/add_head_content(nhead_content)
|
||||
head_content = nhead_content
|
||||
|
||||
/datum/nanoui/proc/set_window_options(nwindow_options)
|
||||
window_options = nwindow_options
|
||||
|
||||
/datum/nanoui/proc/set_title_image(ntitle_image)
|
||||
//title_image = ntitle_image
|
||||
|
||||
/datum/nanoui/proc/add_stylesheet(file)
|
||||
stylesheets.Add(file)
|
||||
|
||||
/datum/nanoui/proc/add_script(file)
|
||||
scripts.Add(file)
|
||||
|
||||
/datum/nanoui/proc/add_template(name, file)
|
||||
templates[name] = file
|
||||
|
||||
/datum/nanoui/proc/set_content(ncontent)
|
||||
content = ncontent
|
||||
|
||||
/datum/nanoui/proc/add_content(ncontent)
|
||||
content += ncontent
|
||||
|
||||
/datum/nanoui/proc/use_on_close_logic(nsetting)
|
||||
on_close_logic = nsetting
|
||||
|
||||
/datum/nanoui/proc/get_header()
|
||||
for (var/filename in stylesheets)
|
||||
head_content += "<link rel='stylesheet' type='text/css' href='[filename]'>"
|
||||
|
||||
var/title_attributes = "id='uiTitle'"
|
||||
if (title_image)
|
||||
title_attributes = "id='uiTitle icon' style='background-image: url([title_image]);'"
|
||||
|
||||
var/templatel_data[0]
|
||||
for (var/key in templates)
|
||||
templatel_data[key] = templates[key];
|
||||
|
||||
var/template_data_json = "{}" // An empty JSON object
|
||||
if (templatel_data.len > 0)
|
||||
template_data_json = list2json(templatel_data)
|
||||
|
||||
var/initial_data_json = "{}" // An empty JSON object
|
||||
if (initial_data.len > 0)
|
||||
initial_data_json = list2json(initial_data)
|
||||
|
||||
var/url_parameters_json = list2json(list("src" = "\ref[src_object]"))
|
||||
|
||||
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=ISO-8859-1">
|
||||
<head>
|
||||
[head_content]
|
||||
</head>
|
||||
<body scroll=auto data-url-parameters='[url_parameters_json]' data-template-data='[template_data_json]' data-initial-data='[initial_data_json]'>
|
||||
<script type='text/javascript'>
|
||||
function receiveUpdateData(jsonString)
|
||||
{
|
||||
// We need both jQuery and NanoUpdate to be able to recieve data
|
||||
if (typeof NanoUpdate != 'undefined' && typeof jQuery != 'undefined')
|
||||
{
|
||||
NanoUpdate.receiveUpdateData(jsonString);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert('receiveUpdateData error: something is not defined!');
|
||||
if (typeof NanoUpdate == 'undefined')
|
||||
{
|
||||
alert('NanoUpdate not defined!');
|
||||
}
|
||||
if (typeof jQuery == 'undefined')
|
||||
{
|
||||
alert('jQuery not defined!');
|
||||
}
|
||||
}
|
||||
// At the moment any data received before those libraries are loaded will be lost
|
||||
}
|
||||
</script>
|
||||
<div id='uiWrapper'>
|
||||
[title ? "<div id='uiTitleWrapper'><div id='uiStatusIcon' class='icon24 uiStatusGood'></div><div [title_attributes]>[title]</div><div id='uiTitleFluff'></div></div>" : ""]
|
||||
<div id='uiContent'>
|
||||
"}
|
||||
|
||||
/datum/nanoui/proc/get_footer()
|
||||
var/scriptsContent = ""
|
||||
|
||||
for (var/filename in scripts)
|
||||
scriptsContent += "<script type='text/javascript' src='[filename]'></script>"
|
||||
|
||||
return {"
|
||||
[scriptsContent]
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"}
|
||||
|
||||
/datum/nanoui/proc/get_content()
|
||||
return {"
|
||||
[get_header()]
|
||||
[content]
|
||||
[get_footer()]
|
||||
"}
|
||||
|
||||
/datum/nanoui/proc/open()
|
||||
var/window_size = ""
|
||||
if (width && height)
|
||||
window_size = "size=[width]x[height];"
|
||||
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
|
||||
on_close_winset()
|
||||
//onclose(user, window_id)
|
||||
nanomanager.ui_opened(src)
|
||||
|
||||
/datum/nanoui/proc/close()
|
||||
is_auto_updating = 0
|
||||
nanomanager.ui_closed(src)
|
||||
user << browse(null, "window=[window_id]")
|
||||
|
||||
/datum/nanoui/proc/on_close_winset()
|
||||
if(!user.client)
|
||||
world << "ERROR: No user.client!?"
|
||||
return
|
||||
var/params = "\ref[src]"
|
||||
|
||||
winset(user, window_id, "on-close=\"nanoclose [params]\"")
|
||||
|
||||
/datum/nanoui/proc/process(update = 0)
|
||||
var/dist = get_dist(src_object, user)
|
||||
if (dist <= 1)
|
||||
set_status(2) // interactive
|
||||
else if (dist <= 2)
|
||||
set_status(1) // update only
|
||||
else if (dist <= 3)
|
||||
set_status(0) // no updates, completely disabled
|
||||
return // don't auto update
|
||||
else
|
||||
close()
|
||||
return
|
||||
|
||||
if (update || is_auto_updating)
|
||||
src_object.ui_interact(user, ui_key)
|
||||
|
||||
/datum/nanoui/proc/modify_data(data)
|
||||
data["ui"] = list(
|
||||
"status" = status,
|
||||
"user" = list("name" = user.name)
|
||||
)
|
||||
//user << list2json(data)
|
||||
return data
|
||||
|
||||
/datum/nanoui/proc/push_data(data, force_push = 0)
|
||||
if (!status && !force_push)
|
||||
user << "Cannot update UI, user out of range (status [status])"
|
||||
return
|
||||
|
||||
data = modify_data(data)
|
||||
|
||||
user << output(list2params(list(list2json(data))),"[window_id].browser:receiveUpdateData")
|
||||
on_close_winset()
|
||||
|
||||
/client/verb/nanoclose(var/uiref as text)
|
||||
set hidden = 1 // hide this verb from the user's panel
|
||||
set name = "nanoclose" // no autocomplete on cmd line
|
||||
|
||||
//world << "world [src] looking for [uiref]"
|
||||
|
||||
var/datum/nanoui/ui = locate(uiref)
|
||||
|
||||
if (ui)
|
||||
//world << "[src] UI found [ui.window_id]"
|
||||
ui.close()
|
||||
|
||||
if (ui.on_close_logic)
|
||||
if(ui.ref)
|
||||
var/href = "close=1"
|
||||
//world << "[src] Topic [href] [ui.ref]"
|
||||
src.Topic(href, params2list(href), ui.ref) // this will direct to the atom's
|
||||
// Topic() proc via client.Topic()
|
||||
else
|
||||
// no atomref specified (or not found)
|
||||
// so just reset the user mob's machine var
|
||||
if(src && src.mob)
|
||||
//world << "[src] was [src.mob.machine], setting to null"
|
||||
src.mob.unset_machine()
|
||||
else
|
||||
world << "[src] UI not found"
|
||||
return
|
||||
Reference in New Issue
Block a user