", "window=[name]")
+ show_content(user)
return
else
to_chat(user, "You don't know how to read or write.")
@@ -312,6 +329,7 @@
add_overlay(stampoverlay)
to_chat(user, "You stamp the paper with your rubber stamp.")
+ ui.render_all()
if(P.is_hot())
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10))
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index 871fd32b16..082b1a7d11 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -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, "You cannot reach the controls from inside!")
+ 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()
diff --git a/modular_kepler/code/modules/oracle_ui/README.md b/modular_kepler/code/modules/oracle_ui/README.md
new file mode 100644
index 0000000000..8aaf73f513
--- /dev/null
+++ b/modular_kepler/code/modules/oracle_ui/README.md
@@ -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
+
+
+ State:
+
@{full_pressure}
+
+
+ Pressure:
+
+
+
+
@{per}
+
+
+
+
+ Handle:
+
@{flush}
+
+
+ Eject:
+
@{contents}
+
+
+ Compressor:
+
@{pressure_charging}
+
+
+```
+
+#### 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, "You cannot reach the controls from inside!")
+ return
+ ui.render(user)
+```
+
+#### Done
+
+
+
+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.
diff --git a/modular_kepler/code/modules/oracle_ui/assets.dm b/modular_kepler/code/modules/oracle_ui/assets.dm
new file mode 100644
index 0000000000..e68fce38a7
--- /dev/null
+++ b/modular_kepler/code/modules/oracle_ui/assets.dm
@@ -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',
+ )
+
\ No newline at end of file
diff --git a/modular_kepler/code/modules/oracle_ui/hookup_procs.dm b/modular_kepler/code/modules/oracle_ui/hookup_procs.dm
new file mode 100644
index 0000000000..e6038744c1
--- /dev/null
+++ b/modular_kepler/code/modules/oracle_ui/hookup_procs.dm
@@ -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 ..()
diff --git a/modular_kepler/code/modules/oracle_ui/oracle_ui.dm b/modular_kepler/code/modules/oracle_ui/oracle_ui.dm
new file mode 100644
index 0000000000..5e8d6b9c7b
--- /dev/null
+++ b/modular_kepler/code/modules/oracle_ui/oracle_ui.dm
@@ -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);
diff --git a/modular_kepler/code/modules/oracle_ui/themed.dm b/modular_kepler/code/modules/oracle_ui/themed.dm
new file mode 100644
index 0000000000..9bf31bd1da
--- /dev/null
+++ b/modular_kepler/code/modules/oracle_ui/themed.dm
@@ -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 "[label]"
+ else
+ return "[label]"
+
+/datum/oracle_ui/themed/nano
+ theme = "nano"
diff --git a/modular_kepler/html/oracle_ui/content/disposal_bin/index.html b/modular_kepler/html/oracle_ui/content/disposal_bin/index.html
new file mode 100644
index 0000000000..8f7713b53c
--- /dev/null
+++ b/modular_kepler/html/oracle_ui/content/disposal_bin/index.html
@@ -0,0 +1,27 @@
+
+
+ State:
+
@{full_pressure}
+
+
+ Pressure:
+
+
+
+
@{per}
+
+
+
+
+ Handle:
+
@{flush}
+
+
+ Eject:
+
@{contents}
+
+
+ Compressor:
+
@{pressure_charging}
+
+
\ No newline at end of file
diff --git a/modular_kepler/html/oracle_ui/editor_tool.html b/modular_kepler/html/oracle_ui/editor_tool.html
new file mode 100644
index 0000000000..e0ce75bb29
--- /dev/null
+++ b/modular_kepler/html/oracle_ui/editor_tool.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+ OracleUI IDE
+
+
+
+