merge oracle ui from kepler

This commit is contained in:
Alonefromhell
2019-08-27 22:12:00 +02:00
committed by Linzolle
parent f742a34fb0
commit eee1ccbe2c
15 changed files with 1141 additions and 34 deletions

View File

@@ -6,7 +6,17 @@ SUBSYSTEM_DEF(assets)
var/list/preload = list()
/datum/controller/subsystem/assets/Initialize(timeofday)
for(var/type in typesof(/datum/asset))
var/list/priority_assets = list(
/datum/asset/simple/oui_theme_nano,
/datum/asset/simple/goonchat
)
for(var/type in priority_assets)
var/datum/asset/A = new type()
A.register()
for(var/type in typesof(/datum/asset) - (priority_assets | list(/datum/asset, /datum/asset/simple)))
var/datum/asset/A = type
if (type != initial(A._abstract))
get_asset_datum(type)

View File

@@ -31,6 +31,7 @@
var/spam_flag = 0
var/contact_poison // Reagent ID to transfer on contact
var/contact_poison_volume = 0
var/datum/oracle_ui/ui = null
/obj/item/paper/pickup(user)
@@ -40,16 +41,40 @@
if(!istype(G) || G.transfer_prints)
H.reagents.add_reagent(contact_poison,contact_poison_volume)
contact_poison = null
ui.check_view_all()
..()
/obj/item/paper/dropped(mob/user)
ui.check_view(user)
return ..()
/obj/item/paper/Initialize()
. = ..()
pixel_y = rand(-8, 8)
pixel_x = rand(-9, 9)
ui = new /datum/oracle_ui(src, 420, 600, get_asset_datum(/datum/asset/spritesheet/simple/paper))
ui.can_resize = FALSE
update_icon()
updateinfolinks()
/obj/item/paper/oui_getcontent(mob/target)
if(!target.is_literate())
return "<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[stars(info)]<HR>[stamps]</BODY></HTML>"
else if(istype(target.get_active_held_item(), /obj/item/pen) | istype(target.get_active_held_item(), /obj/item/toy/crayon))
return "<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[info_links]<HR>[stamps]</BODY></HTML>"
else
return "<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[info]<HR>[stamps]</BODY></HTML>"
/obj/item/paper/oui_canview(mob/target)
if(check_rights_for(target.client, R_FUN)) //Allows admins to view faxes
return TRUE
if(isAI(target))
var/mob/living/silicon/ai/ai = target
return get_dist(src, ai.current) < 2
if(iscyborg(target))
return get_dist(src, target) < 2
return ..()
/obj/item/paper/update_icon()
@@ -65,20 +90,13 @@
/obj/item/paper/examine(mob/user)
..()
to_chat(user, "<span class='notice'>Alt-click to fold it.</span>")
var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
assets.send(user)
if(in_range(user, src) || isobserver(user))
if(user.is_literate())
user << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[info]<HR>[stamps]</BODY></HTML>", "window=[name]")
onclose(user, "[name]")
else
user << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[stars(info)]<HR>[stamps]</BODY></HTML>", "window=[name]")
onclose(user, "[name]")
if(oui_canview(user))
ui.render(user)
else
to_chat(user, "<span class='warning'>You're too far away to read it!</span>")
/obj/item/paper/proc/show_content(mob/user)
user.examinate(src)
/obj/item/paper/verb/rename()
set name = "Rename paper"
@@ -98,7 +116,7 @@
if((loc == usr && usr.stat == CONSCIOUS))
name = "paper[(n_name ? text("- '[n_name]'") : null)]"
add_fingerprint(usr)
ui.render_all()
/obj/item/paper/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[user] scratches a grid on [user.p_their()] wrist with the paper! It looks like [user.p_theyre()] trying to commit sudoku...</span>")
@@ -108,7 +126,7 @@
spam_flag = FALSE
/obj/item/paper/attack_self(mob/user)
user.examinate(src)
show_content(user)
if(rigged && (SSevents.holidays && SSevents.holidays[APRIL_FOOLS]))
if(!spam_flag)
spam_flag = TRUE
@@ -123,11 +141,9 @@
else //cyborg or AI not seeing through a camera
dist = get_dist(src, user)
if(dist < 2)
usr << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[info]<HR>[stamps]</BODY></HTML>", "window=[name]")
onclose(usr, "[name]")
show_content(user)
else
usr << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[stars(info)]<HR>[stamps]</BODY></HTML>", "window=[name]")
onclose(usr, "[name]")
to_chat(user, "<span class='notice'>You can't quite see it.</span>")
/obj/item/paper/proc/addtofield(id, text, links = 0)
@@ -173,6 +189,7 @@
for(var/i in 1 to min(fields, 15))
addtofield(i, "<font face=\"[PEN_FONT]\"><A href='?src=[REF(src)];write=[i]'>write</A></font>", 1)
info_links = info_links + "<font face=\"[PEN_FONT]\"><A href='?src=[REF(src)];write=end'>write</A></font>"
ui.render_all()
/obj/item/paper/proc/clearpaper()
@@ -289,7 +306,7 @@
if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
if(user.is_literate())
user << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY>[info_links]<HR>[stamps]</BODY><div align='right'style='position:fixed;bottom:0;font-style:bold;'><A href='?src=[REF(src)];help=1'>\[?\]</A></div></HTML>", "window=[name]")
show_content(user)
return
else
to_chat(user, "<span class='notice'>You don't know how to read or write.</span>")
@@ -312,6 +329,7 @@
add_overlay(stampoverlay)
to_chat(user, "<span class='notice'>You stamp the paper with your rubber stamp.</span>")
ui.render_all()
if(P.is_hot())
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10))

View File

@@ -264,6 +264,13 @@
name = "disposal unit"
desc = "A pneumatic waste disposal unit."
icon_state = "disposal"
var/datum/oracle_ui/themed/nano/ui
/obj/machinery/disposal/bin/Initialize(mapload, obj/structure/disposalconstruct/make_from)
. = ..()
ui = new /datum/oracle_ui/themed/nano(src, 330, 190, "disposal_bin")
ui.auto_refresh = TRUE
ui.can_resize = FALSE
// attack by item places it in to disposal
/obj/machinery/disposal/bin/attackby(obj/item/I, mob/user, params)
@@ -275,32 +282,43 @@
STR.remove_from_storage(O,src)
T.update_icon()
update_icon()
ui.soft_update_fields()
else
ui.soft_update_fields()
return ..()
// handle machine interaction
/obj/machinery/disposal/bin/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
/obj/machinery/disposal/bin/ui_interact(mob/user, state)
if(stat & BROKEN)
return
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "disposal_unit", name, 300, 200, master_ui, state)
ui.open()
if(user.loc == src)
to_chat(user, "<span class='warning'>You cannot reach the controls from inside!</span>")
return
ui.render(user)
/obj/machinery/disposal/bin/ui_data(mob/user)
/obj/machinery/disposal/bin/oui_canview(mob/user)
if(user.loc == src)
return FALSE
if(stat & BROKEN)
return FALSE
if(Adjacent(user))
return TRUE
return ..()
/obj/machinery/disposal/bin/oui_data(mob/user)
var/list/data = list()
data["flush"] = flush
data["full_pressure"] = full_pressure
data["pressure_charging"] = pressure_charging
data["panel_open"] = panel_open
var/per = CLAMP(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 100)
data["per"] = round(per, 1)
data["flush"] = flush ? ui.act("Disengage", user, "handle-0", class="active") : ui.act("Engage", user, "handle-1")
data["full_pressure"] = full_pressure ? "Ready" : (pressure_charging ? "Pressurizing" : "Off")
data["pressure_charging"] = pressure_charging ? ui.act("Turn Off", user, "pump-0", class="active", disabled=full_pressure) : ui.act("Turn On", user, "pump-1", disabled=full_pressure)
var/per = full_pressure ? 100 : CLAMP(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 99)
data["per"] = "[round(per, 1)]%"
data["contents"] = ui.act("Eject Contents", user, "eject", disabled=contents.len < 1)
data["isai"] = isAI(user)
return data
/obj/machinery/disposal/bin/ui_act(action, params)
/obj/machinery/disposal/bin/oui_act(mob/user, action, list/params)
if(..())
return
@@ -327,6 +345,7 @@
if("eject")
eject()
. = TRUE
ui.soft_update_fields()
/obj/machinery/disposal/bin/hitby(atom/movable/AM)
@@ -346,6 +365,7 @@
full_pressure = FALSE
pressure_charging = TRUE
update_icon()
ui.soft_update_fields()
/obj/machinery/disposal/bin/update_icon()
cut_overlays()
@@ -389,7 +409,7 @@
do_flush()
flush_count = 0
updateDialog()
ui.soft_update_fields()
if(flush && air_contents.return_pressure() >= SEND_PRESSURE) // flush can happen even without power
do_flush()

View File

@@ -0,0 +1,233 @@
# `/datum/oracle_ui`
This datum is a replacement for tgui which does not use any Node.js dependencies, and works entirely through raw HTML, JS and CSS. It's designed to be reasonably easy to port something from tgui to oracle_ui.
### How to create a UI
For this example, we're going to port the disposals bin from tgui to oracle_ui.
#### Step 1
In order to create a UI, you will first need to create an instance of `/datum/oracle_ui` or one of its subclasses, in this case `/datum/oracle_ui/themed/nano`.
You need to pass in `src`, the width of the window, the height of the window, and the template to render from. You can optionally set some flags to disallow window resizing and whether to automatically refresh the UI.
`code/modules/recycling/disposal-unit.dm`
```dm
/obj/machinery/disposal/bin/Initialize(mapload, obj/structure/disposalconstruct/make_from)
. = ..()
ui = new /datum/oracle_ui/themed/nano(src, 330, 190, "disposal_bin")
ui.auto_refresh = TRUE
ui.can_resize = FALSE
```
#### Step 2
You will now need to make a template in `modular_kepler/html/oracle_ui/content/{template_name}`.
Values defined as `@{value}` will get replaced at runtime by oracle_ui.
`modular_kepler/html/oracle_ui/content/disposal_bin/index.html`
```html
<div class='display'>
<section>
<span class='label'>State:</span>
<div class='content' id="full_pressure">@{full_pressure}</div>
</section>
<section>
<span class='label'>Pressure:</span>
<div class='content'>
<div class='progressBar' id='per'>
<div class='progressFill' style="width: @{per}"></div>
<div class='progressLabel'>@{per}</div>
</div>
</div>
</section>
<section>
<span class='label'>Handle:</span>
<div class='content' id="flush">@{flush}</div>
</section>
<section>
<span class='label'>Eject:</span>
<div class='content' id="contents">@{contents}</div>
</section>
<section>
<span class='label'>Compressor:</span>
<div class='content' id="pressure_charging">@{pressure_charging}</div>
</section>
</div>
```
#### Step 3
Now you need to implement the methods that provide data to oracle_ui. `oui_data` can be adapted from the `ui_data` proc that tgui uses.
The `act` proc generates a hyperlink that will result in `oui_act` getting called on your object when clicked. The `class` argument defines a css class to be added to the hyperlink, and disabled determines whether the hyperlink will be disabled or not.
Calling `soft_update_fields` will result in the UI being updated on all clients, which is useful when the object changes state.
`code/modules/recycling/disposal-unit.dm`
```dm
/obj/machinery/disposal/bin/oui_data(mob/user)
var/list/data = list()
data["flush"] = flush ? ui.act("Disengage", user, "handle-0", class="active") : ui.act("Engage", user, "handle-1")
data["full_pressure"] = full_pressure ? "Ready" : (pressure_charging ? "Pressurizing" : "Off")
data["pressure_charging"] = pressure_charging ? ui.act("Turn Off", user, "pump-0", class="active", disabled=full_pressure) : ui.act("Turn On", user, "pump-1", disabled=full_pressure)
var/per = full_pressure ? 100 : Clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 99)
data["per"] = "[round(per, 1)]%"
data["contents"] = ui.act("Eject Contents", user, "eject", disabled=contents.len < 1)
data["isai"] = isAI(user)
return data
/obj/machinery/disposal/bin/oui_act(mob/user, action, list/params)
if(..())
return
switch(action)
if("handle-0")
flush = FALSE
update_icon()
. = TRUE
if("handle-1")
if(!panel_open)
flush = TRUE
update_icon()
. = TRUE
if("pump-0")
if(pressure_charging)
pressure_charging = FALSE
update_icon()
. = TRUE
if("pump-1")
if(!pressure_charging)
pressure_charging = TRUE
update_icon()
. = TRUE
if("eject")
eject()
. = TRUE
ui.soft_update_fields()
```
#### Step 4
You now need to hook in and ensure oracle_ui is invoked upon clicking. `render` should be used to open the UI for a user, typically on click.
`code/modules/recycling/disposal-unit.dm`
```dm
/obj/machinery/disposal/bin/ui_interact(mob/user, state)
if(stat & BROKEN)
return
if(user.loc == src)
to_chat(user, "<span class='warning'>You cannot reach the controls from inside!</span>")
return
ui.render(user)
```
#### Done
![gif](https://user-images.githubusercontent.com/202160/37561879-1bb9179e-2a52-11e8-902c-80e6e6df7204.gif)
You should have a functional UI at this point. Some additional odds and ends can be discovered throughout `code/modules/recycling/disposal-unit.dm`. For a full diff of the changes made to it, refer to [the original pull request on GitHub](https://github.com/OracleStation/OracleStation/pull/702/files#diff-4b6c20ec7d37222630e7524d9577e230).
### API Reference
#### `/datum/oracle_ui`
The main datum which handles the UI.
##### `get_content(mob/target)`
Returns the HTML that should be displayed for a specified target mob. Calls `oui_getcontent` on the datasource to get the return value. *This proc is not used in the themed subclass.*
##### `can_view(mob/target)`
Returns whether the specified target mob can view the UI. Calls `oui_canview` on the datasource to get the return value.
##### `test_viewer(mob/target, updating)`
Tests whether the client is valid and can view the UI. If updating is TRUE, checks to see if they still have the UI window open.
##### `render(mob/target, updating = FALSE)`
Opens the UI for a target mob, sending HTML. If updating is TRUE, will only do it to clients which still have the window open.
##### `render_all()`
Does the above, but for all viewers and with updating set to TRUE.
##### `close(mob/target)`
Closes the UI for the specified target mob.
##### `close_all()`
Does the above, but for all viewers.
##### `check_view(mob/target)`
Checks if the specified target mob can view the UI, and if they can't closes their UI
##### `check_view_all()`
Does the above, but for all viewers.
##### `call_js(mob/target, js_func, list/parameters = list())`
Invokes `js_func` in the UI of the specified target mob with the specified parameters.
##### `call_js_all(js_func, list/parameters = list()))`
Does the above, but for all viewers.
##### `steal_focus(mob/target)`
Causes the UI to steal focus for the specified target mob.
##### `steal_focus_all()`
Does the above, but for all viewers.
##### `flash(mob/target, times = -1)`
Causes the UI to flash for the specified target mob the specified number of times, the default keeps the element flashing until focused.
##### `flash_all()`
Does the above, but for all viewers.
##### `href(mob/user, action, list/parameters = list())`
Generates a href for the specified user which will invoke `oui_act` on the datasource with the specified action and parameters.
#### `/datum/oracle_ui/themed`
A subclass which supports templating and theming.
##### `get_file(path)`
Loads a file from disk and returns the contents. Caches files loaded from disk for you.
##### `get_content_file(filename)`
Loads a file from the current content folder and returns the contents.
##### `get_themed_file(filename)`
Loads a file from the current theme folder and returns the contents.
##### `process_template(template, variables)`
Processes a template and populates it with the provided variables.
##### `get_inner_content(mob/target)`
Returns the templated content to be inserted into the main template for the specified target mob.
##### `soft_update_fields()`
For all viewers, updates the fields in the template via the `updateFields` javaScript function.
##### `soft_update_all()`
For all viewers, updates the content body in the template via the `replaceContent` javaScript function.
##### `change_page(var/newpage)`
Changes the template to use to draw the page and forces an update to all viewers
##### `act(label, mob/user, action, list/parameters = list(), class = "", disabled = FALSE`
Returns a fully formatted hyperlink for the specified user. `label` will be the hyperlink label, `action` and `parameters` are what will be passed to `oui_act`, `class` is any CSS classes to apply to the hyperlink and `disabled` will disable the hyperlink.
#### `/datum`
Functions built into all objects to support oracle_ui. There are default implementations for most major superclasses.
##### `oui_canview(mob/user)`
Returns whether the specified user view the UI at this time.
##### `oui_getcontent(mob/user)`
Returns the raw HTML to be sent to the specified user. *This proc is not used in the themed subclass of oracle_ui.*
##### `oui_data(mob/user)`
Returns templating data for the specified user. *This proc is only used in the themed subclass of oracle_ui.*
##### `oui_data_debug(mob/user)`
Returns the above, but JSON-encoded and escaped, for copy pasting into the web IDE. *This proc is only used for debugging purposes.*
##### `oui_act(mob/user, action, list/params)`
Called when a hyperlink is clicked in the UI.

View File

@@ -0,0 +1,9 @@
/datum/asset/simple/oui_theme_nano
assets = list(
// JavaScript
"sui-nano-common.js" = 'modular_kepler/html/oracle_ui/themes/nano/sui-nano-common.js',
"sui-nano-jquery.min.js" = 'modular_kepler/html/oracle_ui/themes/nano/sui-nano-jquery.min.js',
// Stylesheets
"sui-nano-common.css" = 'modular_kepler/html/oracle_ui/themes/nano/sui-nano-common.css',
)

View File

@@ -0,0 +1,44 @@
/datum/proc/oui_canview(mob/user)
return TRUE
/datum/proc/oui_getcontent(mob/user)
return "Default Implementation"
/datum/proc/oui_canuse(mob/user)
if(isobserver(user) && !user.has_unlimited_silicon_privilege)
return FALSE
return oui_canview(user)
/datum/proc/oui_data(mob/user)
return list()
/datum/proc/oui_data_debug(mob/user)
return html_encode(json_encode(oui_data(user)))
/datum/proc/oui_act(mob/user, action, list/params)
// No Implementation
/atom/oui_canview(mob/user)
if(isobserver(user))
return TRUE
if(user.incapacitated())
return FALSE
if(isturf(src.loc) && Adjacent(user))
return TRUE
return FALSE
/obj/item/oui_canview(mob/user)
if(src.loc == user)
return src in user.held_items
return ..()
/obj/machinery/oui_canview(mob/user)
if(user.has_unlimited_silicon_privilege)
return TRUE
if(!can_interact())
return FALSE
if(iscyborg(user))
return can_see(user, src, 7)
if(isAI(user))
return GLOB.cameranet.checkTurfVis(get_turf_pixel(src))
return ..()

View File

@@ -0,0 +1,134 @@
/datum/oracle_ui
var/width = 512
var/height = 512
var/can_close = TRUE
var/can_minimize = FALSE
var/can_resize = TRUE
var/titlebar = TRUE
var/window_id = null
var/viewers[0]
var/auto_check_view = TRUE
var/auto_refresh = FALSE
var/atom/datasource = null
var/datum/asset/assets = null
/datum/oracle_ui/New(atom/n_datasource, n_width = 512, n_height = 512, n_assets = null)
datasource = n_datasource
window_id = REF(src)
width = n_width
height = n_height
/datum/oracle_ui/Destroy()
close_all()
if(src.datum_flags & DF_ISPROCESSING)
STOP_PROCESSING(SSobj, src)
return ..()
/datum/oracle_ui/process()
if(auto_check_view)
check_view_all()
if(auto_refresh)
render_all()
/datum/oracle_ui/proc/get_content(mob/target)
return call(datasource, "oui_getcontent")(target)
/datum/oracle_ui/proc/can_view(mob/target)
return call(datasource, "oui_canview")(target)
/datum/oracle_ui/proc/test_viewer(mob/target, updating)
//If the target is null or does not have a client, remove from viewers and return
if(!target | !target.client | !can_view(target))
viewers -= target
if(viewers.len < 1 && (src.datum_flags & DF_ISPROCESSING))
STOP_PROCESSING(SSobj, src) //No more viewers, stop polling
close(target)
return FALSE
//If this is an update, and they have closed the window, remove from viewers and return
if(updating && winget(target, window_id, "is-visible") != "true")
viewers -= target
if(viewers.len < 1 && (src.datum_flags & DF_ISPROCESSING))
STOP_PROCESSING(SSobj, src) //No more viewers, stop polling
return FALSE
return TRUE
/datum/oracle_ui/proc/render(mob/target, updating = FALSE)
set waitfor = FALSE //Makes this an async call
if(!can_view(target))
return
//Check to see if they have the window open still if updating
if(updating && !test_viewer(target, updating))
return
//Send assets
if(!updating && assets)
assets.send(target)
//Add them to the viewers if they aren't there already
viewers |= target
if(!(src.datum_flags & DF_ISPROCESSING) && (auto_refresh | auto_check_view))
START_PROCESSING(SSobj, src) //Start processing to poll for viewability
//Send the content
if(updating)
target << output(get_content(target), "[window_id].browser")
else
target << browse(get_content(target), "window=[window_id];size=[width]x[height];can_close=[can_close];can_minimize=[can_minimize];can_resize=[can_resize];titlebar=[titlebar];focus=false;")
steal_focus(target)
/datum/oracle_ui/proc/render_all()
for(var/viewer in viewers)
render(viewer, TRUE)
/datum/oracle_ui/proc/close(mob/target)
if(target && target.client)
target << browse(null, "window=[window_id]")
/datum/oracle_ui/proc/close_all()
for(var/viewer in viewers)
close(viewer)
viewers = list()
/datum/oracle_ui/proc/check_view_all()
for(var/viewer in viewers)
check_view(viewer)
/datum/oracle_ui/proc/check_view(mob/target)
set waitfor = FALSE //Makes this an async call
if(!test_viewer(target, TRUE))
close(target)
/datum/oracle_ui/proc/call_js(mob/target, js_func, list/parameters = list())
set waitfor = FALSE //Makes this an async call
if(!test_viewer(target, TRUE))
return
target << output(list2params(parameters),"[window_id].browser:[js_func]")
/datum/oracle_ui/proc/call_js_all(js_func, list/parameters = list())
for(var/viewer in viewers)
call_js(viewer, js_func, parameters)
/datum/oracle_ui/proc/steal_focus(mob/target)
set waitfor = FALSE //Makes this an async call
winset(target, "[window_id]","focus=true")
/datum/oracle_ui/proc/steal_focus_all()
for(var/viewer in viewers)
steal_focus(viewer)
/datum/oracle_ui/proc/flash(mob/target, times = -1)
set waitfor = FALSE //Makes this an async call
winset(target, "[window_id]","flash=[times]")
/datum/oracle_ui/proc/flash_all(times = -1)
for(var/viewer in viewers)
flash(viewer, times)
/datum/oracle_ui/proc/href(mob/user, action, list/parameters = list())
var/params_string = replacetext(list2params(parameters),"&",";")
return "?src=[REF(src)];sui_action=[action];sui_user=[REF(user)];[params_string]"
/datum/oracle_ui/Topic(href, parameters)
var/action = parameters["sui_action"]
var/mob/current_user = locate(parameters["sui_user"])
if(!call(datasource, "oui_canuse")(current_user))
return
if(datasource)
call(datasource, "oui_act")(current_user, action, parameters);

View File

@@ -0,0 +1,82 @@
/datum/oracle_ui/themed
var/theme = ""
var/content_root = ""
var/current_page = "index.html"
var/root_template = ""
/datum/oracle_ui/themed/New(atom/n_datasource, n_width = 512, n_height = 512, n_content_root = "")
root_template = get_themed_file("index.html")
content_root = n_content_root
return ..(n_datasource, n_width, n_height, get_asset_datum(/datum/asset/simple/oui_theme_nano))
/datum/oracle_ui/themed/process()
if(auto_check_view)
check_view_all()
if(auto_refresh)
soft_update_fields()
GLOBAL_LIST_EMPTY(oui_template_variables)
GLOBAL_LIST_EMPTY(oui_file_cache)
/datum/oracle_ui/themed/proc/get_file(path)
if(GLOB.oui_file_cache[path])
return GLOB.oui_file_cache[path]
else if(fexists(path))
var/data = file2text(path)
GLOB.oui_file_cache[path] = data
return data
else
var/errormsg = "MISSING PATH '[path]'"
#ifndef UNIT_TESTS
log_world(errormsg) //Because Travis absolutely hates these procs
#endif
return errormsg
/datum/oracle_ui/themed/proc/get_content_file(filename)
return get_file("./modular_kepler/html/oracle_ui/content/[content_root]/[filename]")
/datum/oracle_ui/themed/proc/get_themed_file(filename)
return get_file("./modular_kepler/html/oracle_ui/themes/[theme]/[filename]")
/datum/oracle_ui/themed/proc/process_template(template, variables)
var/regex/pattern = regex("\\@\\{(\\w+)\\}","gi")
GLOB.oui_template_variables = variables
var/replaced = pattern.Replace(template, /proc/oui_process_template_replace)
GLOB.oui_template_variables = null
return replaced
/proc/oui_process_template_replace(match, group1)
var/value = GLOB.oui_template_variables[group1]
return "[value]"
/datum/oracle_ui/themed/proc/get_inner_content(mob/target)
var/list/data = call(datasource, "oui_data")(target)
return process_template(get_content_file(current_page), data)
/datum/oracle_ui/themed/get_content(mob/target)
var/list/template_data = list("title" = datasource.name, "body" = get_inner_content(target))
return process_template(root_template, template_data)
/datum/oracle_ui/themed/proc/soft_update_fields()
for(var/viewer in viewers)
var/json = json_encode(call(datasource, "oui_data")(viewer))
call_js(viewer, "updateFields", list(json))
/datum/oracle_ui/themed/proc/soft_update_all()
for(var/viewer in viewers)
call_js(viewer, "replaceContent", list(get_inner_content(viewer)))
/datum/oracle_ui/themed/proc/change_page(newpage)
if(newpage == current_page)
return
current_page = newpage
render_all()
/datum/oracle_ui/themed/proc/act(label, mob/user, action, list/parameters = list(), class = "", disabled = FALSE)
if(disabled)
return "<a class=\"disabled\">[label]</a>"
else
return "<a class=\"[class]\" href=\"" + href(user, action, parameters) + "\">[label]</a>"
/datum/oracle_ui/themed/nano
theme = "nano"

View File

@@ -0,0 +1,27 @@
<div class='display'>
<section>
<span class='label'>State:</span>
<div class='content' id="full_pressure">@{full_pressure}</div>
</section>
<section>
<span class='label'>Pressure:</span>
<div class='content'>
<div class='progressBar' id='per'>
<div class='progressFill' style="width: @{per}"></div>
<div class='progressLabel'>@{per}</div>
</div>
</div>
</section>
<section>
<span class='label'>Handle:</span>
<div class='content' id="flush">@{flush}</div>
</section>
<section>
<span class='label'>Eject:</span>
<div class='content' id="contents">@{contents}</div>
</section>
<section>
<span class='label'>Compressor:</span>
<div class='content' id="pressure_charging">@{pressure_charging}</div>
</section>
</div>

View File

@@ -0,0 +1,103 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>OracleUI IDE</title>
<style>
html, body {
margin: 0;
height: 100%;
}
h1 {
margin: 5px;
}
#template_src_container {
position: absolute;
left: 10px;
top: 0px;
width: 50%;
height: 75%;
}
#template_src {
position: absolute;
top: 50px;
left: 0px;
width: 100%;
height: calc(100% - 60px);
}
#data_src_container {
position: absolute;
left: 10px;
top: 75%;
width: 50%;
height: calc(25% - 20px);
}
#data_src {
position: absolute;
top: 50px;
left: 0px;
width: 100%;
height: calc(100% - 50px);
}
#output_container {
position: absolute;
left: calc(50% + 20px);
top: 0%;
width: calc(50% - 50px);
height: calc(100% - 20px);
}
#output_frame {
position: absolute;
top: 50px;
left: 10px;
width: 100%;
height: calc(100% - 50px);
}
</style>
</head>
<body>
<div id="template_src_container">
<h1>Content Template:</h1>
<textarea id="template_src" onchange="updateBody();" onkeyup="updateBody();" onpaste="updateBody();">
<div class='display'>
<section>
<span class='label'>Key:</span>
<div class='content' id="key">@{key}</div>
</section>
</div>
</textarea>
</div>
<div id="data_src_container">
<h1>Data:</h1>
<textarea id="data_src" onchange="updateData();" onkeyup="updateData();" onpaste="updateData();">{"key":"value"}</textarea>
</div>
<div id="output_container">
<h1>Output:</h1>
<iframe name="output_frame" id="output_frame" onload="updateBody();"></iframe>
</div>
</body>
<script type="text/javascript">
function updateBody() {
var body = document.getElementById('template_src').value
var fields = JSON.parse(document.getElementById('data_src').value);
for (var key in fields) {
let value = fields[key];
body = body.replace("@{" + key + "}", value)
}
var targetFrame = document.getElementById('output_frame')
targetFrame.contentWindow.replaceContent(body);
updateData();
}
function updateData() {
var targetFrame = document.getElementById('output_frame');
var json = document.getElementById('data_src').value;
if(JSON.parse(json)) {
targetFrame.contentWindow.updateFields(json);
}
}
var url = document.URL,
shortUrl=url.substring(0,url.lastIndexOf("/"));
window.open(shortUrl + "/themes/nano/index.html", "output_frame");
</script>
</html>

View File

@@ -0,0 +1,19 @@
<!doctype html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<head>
<title>@{title}</title>
<script src="sui-nano-jquery.min.js"></script>
<script src="sui-nano-common.js"></script>
<link rel="stylesheet" type="text/css" href="sui-nano-common.css">
</head>
<body scroll=auto>
<div class='uiWrapper'>
<div class='uiTitleWrapper'><div class='uiTitle'><tt>@{title}</tt></div></div>
<div class='uiContent' id='maincontent'>
@{body}
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,353 @@
body
{
padding: 0;
margin: 0;
background-color: #272727;
font-size: 12px;
color: #ffffff;
line-height: 170%;
cursor: default;
-moz-user-select: none;
-ms-user-select: none;
}
hr
{
background-color: #40628a;
height: 1px;
}
a, a:link, a:visited, a:active, .linkOn, .linkOff
{
color: #ffffff;
text-decoration: none;
background: #40628a;
border: 1px solid #161616;
padding: 1px 4px 1px 4px;
margin: 0 2px 0 0;
cursor:default;
}
a:hover
{
color: #40628a;
background: #ffffff;
}
a.white, a.white:link, a.white:visited, a.white:active
{
color: #40628a;
text-decoration: none;
background: #ffffff;
border: 1px solid #161616;
padding: 1px 4px 1px 4px;
margin: 0 2px 0 0;
cursor:default;
}
a.white:hover
{
color: #ffffff;
background: #40628a;
}
.active, a.active:link, a.active:visited, a.active:active, a.active:hover
{
color: #ffffff;
background: #2f943c;
border-color: #24722e;
}
.disabled, a.disabled:link, a.disabled:visited, a.disabled:active, a.disabled:hover
{
color: #ffffff;
background: #999999;
border-color: #666666;
}
a.icon, .linkOn.icon, .linkOff.icon
{
position: relative;
padding: 1px 4px 2px 20px;
}
a.icon img, .linkOn.icon img
{
position: absolute;
top: 0;
left: 0;
width: 18px;
height: 18px;
}
ul
{
padding: 4px 0 0 10px;
margin: 0;
list-style-type: none;
}
li
{
padding: 0 0 2px 0;
}
img, a img
{
border-style:none;
}
h1, h2, h3, h4, h5, h6
{
margin: 0;
padding: 16px 0 8px 0;
color: #517087;
}
h1
{
font-size: 15px;
}
h2
{
font-size: 14px;
}
h3
{
font-size: 13px;
}
h4
{
font-size: 12px;
}
.uiWrapper
{
width: 100%;
height: 100%;
}
.uiTitle
{
clear: both;
padding: 6px 8px 6px 8px;
border-bottom: 2px solid #161616;
background: #383838;
color: #98B0C3;
font-size: 16px;
}
.uiTitle.icon
{
padding: 6px 8px 6px 42px;
background-position: 2px 50%;
background-repeat: no-repeat;
}
.uiContent
{
clear: both;
padding: 8px;
font-family: Verdana, Geneva, sans-serif;
}
.good
{
color: #00ff00;
}
.average
{
color: #d09000;
}
.bad
{
color: #ff0000;
}
.highlight
{
color: #8BA5C4;
}
.dark
{
color: #272727;
}
.notice
{
position: relative;
background: #E9C183;
color: #15345A;
font-size: 10px;
font-style: italic;
padding: 2px 4px 0 4px;
margin: 4px;
}
.notice.icon
{
padding: 2px 4px 0 20px;
}
.notice img
{
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 16px;
}
div.notice
{
clear: both;
}
.statusDisplay
{
background: #000000;
color: #ffffff;
border: 1px solid #40628a;
padding: 4px;
margin: 3px 0;
}
.statusLabel
{
width: 138px;
float: left;
overflow: hidden;
color: #98B0C3;
}
.statusValue
{
float: left;
}
.block
{
padding: 8px;
margin: 10px 4px 4px 4px;
border: 1px solid #40628a;
background-color: #202020;
}
.block h3
{
padding: 0;
}
.progressBar
{
position: relative;
width: 185px;
height: 14px;
border: 1px solid #666666;
float: left;
overflow: hidden;
padding: 1px;
}
.progressLabel
{
top: -2px;
height: 100%;
position: absolute;
right: 4px;
text-align: right;
}
.progressFill
{
width: 100%;
height: 100%;
background: #40628a;
overflow: hidden;
transition: width 2.2s linear;
}
.progressFill.good
{
color: #ffffff;
background: #00ff00;
}
.progressFill.average
{
color: #ffffff;
background: #d09000;
}
.progressFill.bad
{
color: #ffffff;
background: #ff0000;
}
.progressFill.highlight
{
color: #ffffff;
background: #8BA5C4;
}
.clearBoth
{
clear: both;
}
.clearLeft
{
clear: left;
}
.clearRight
{
clear: right;
}
.line
{
width: 100%;
clear: both;
}
section .label, section .content
{
display: table-cell;
margin: 0;
text-align: left;
vertical-align: middle;
padding: 3px 2px
}
section .label
{
width: 1%;
padding-right: 32px;
white-space: nowrap;
color: #8ba5c4;
}
section
{
display: table-row;
width: 100%
}
.display {
width: calc(100% - 8px);
padding: 4px;
background-color: #000;
background-color: rgba(0, 0, 0, .33);
box-shadow: inset 0 0 5px rgba(0, 0, 0, .5);
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000)";
filter: progid: DXImageTransform.Microsoft.gradient(startColorStr=#54000000, endColorStr=#54000000);
}

View File

@@ -0,0 +1,47 @@
function replaceContent(body) {
var maincontent = document.getElementById('maincontent');
if(maincontent) {
maincontent.innerHTML = body;
}
}
function updateProgressLabels() {
var progressBars = document.getElementsByClassName("progressBar");
for(var i = 0; i < progressBars.length; i++) {
var progressBar = progressBars[i];
if(!progressBar)
continue;
var progressFill = progressBar.getElementsByClassName("progressFill")[0];
if(!progressFill)
continue;
var width = parseInt(getComputedStyle(progressFill).width);
var maxWidth = parseInt(getComputedStyle(progressBar).width);
var progressLabel = progressBar.getElementsByClassName("progressLabel")[0];
if(progressLabel)
progressLabel.innerHTML = Math.round((width / maxWidth) * 100) + '%';
}
}
if(getComputedStyle) { setInterval(updateProgressLabels, 50); } //Fallback
function updateFields(json) {
var fields = JSON.parse(json);
for (var key in fields) {
let value = fields[key];
var element = document.getElementById(key);
if(element == null) {
continue;
} else if(element.classList.contains('progressBar')) {
var progressFill = element.getElementsByClassName("progressFill")[0];
if(progressFill)
progressFill.style["width"] = value;
if(!getComputedStyle) { //Fallback
var progressLabel = element.getElementsByClassName("progressLabel")[0];
if(progressLabel)
progressLabel.innerHTML = value;
}
} else {
element.innerHTML = value;
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -3077,4 +3077,8 @@
#include "modular_citadel\code\modules\vore\eating\voreitems.dm"
#include "modular_citadel\code\modules\vore\eating\vorepanel_vr.dm"
#include "modular_citadel\interface\skin.dmf"
#include "modular_kepler\code\modules\oracle_ui\assets.dm"
#include "modular_kepler\code\modules\oracle_ui\hookup_procs.dm"
#include "modular_kepler\code\modules\oracle_ui\oracle_ui.dm"
#include "modular_kepler\code\modules\oracle_ui\themed.dm"
// END_INCLUDE