diff --git a/code/modules/nano/nanomanager.dm b/code/modules/nano/nanomanager.dm index 45bb25f115e..7a09e7ffd3b 100644 --- a/code/modules/nano/nanomanager.dm +++ b/code/modules/nano/nanomanager.dm @@ -1,12 +1,27 @@ // This is the window/UI manager for Nano UI // There should only ever be one (global) instance of nanomanger /datum/nanomanager + // the list of current open /nanoui UIs var/open_uis[0] var/list/processing_uis = list() + /** + * Create a new nanomanager instance. + * + * @return /nanomanager new nanomanager object + */ /datum/nanomanager/New() return + /** + * Get an open /nanoui ui for the current user, src_object and ui_key + * + * @param user /mob The mob who opened/owns the ui + * @param src_object /obj|/mob The obj or mob which the ui belongs to + * @param ui_key string A string key used for the ui + * + * @return /nanoui Returns the found ui, for null if none exists + */ /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)) @@ -19,7 +34,14 @@ return ui return null - + + /** + * Update all /nanoui uis attached to src_object + * + * @param src_object /obj|/mob The obj or mob which the uis belong to + * + * @return int The number of uis updated + */ /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)) @@ -33,6 +55,14 @@ update_count++ return update_count + /** + * Add a /nanoui ui to the list of open uis + * This is called by the /nanoui open() proc + * + * @param ui /nanoui The ui to add + * + * @return nothing + */ /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)) @@ -45,6 +75,14 @@ uis.Add(ui) processing_uis.Add(ui) + /** + * Remove a /nanoui ui from the list of open uis + * This is called by the /nanoui close() proc + * + * @param ui /nanoui The ui to remove + * + * @return int 0 if no ui was removed, 1 if removed successfully + */ /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)) @@ -56,8 +94,17 @@ ui.user.open_uis.Remove(ui) var/list/uis = open_uis[src_object_key][ui.ui_key] return uis.Remove(ui) + + /** + * This is called on user logout + * Closes/clears all uis attached to the user's /mob + * + * @param user /mob The user's mob + * + * @return nothing + */ -// 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 diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm index 56bb7b8aa0e..4261a617c11 100644 --- a/code/modules/nano/nanoui.dm +++ b/code/modules/nano/nanoui.dm @@ -1,40 +1,77 @@ +/********************************************************** +NANO UI FRAMEWORK + +nanoui class (or whatever Byond calls classes) + +nanoui is used to open and update nano browser uis +**********************************************************/ + + #define STATUS_INTERACTIVE 2 // GREEN Visability #define STATUS_UPDATE 1 // ORANGE Visability #define STATUS_DISABLED 0 // RED Visability /datum/nanoui + // the user who opened this ui var/mob/user + // the object this ui "belongs" to var/atom/movable/src_object + // the title of this ui var/title + // the key of this ui, this is to allow multiple (different) uis for each src_object var/ui_key - var/window_id // window_id is used as the window name for browse and onclose + // window_id is used as the window name/identifier for browse and onclose + var/window_id + // the browser window width var/width = 0 + // the browser window height var/height = 0 - var/atom/ref = null + // whether to use extra logic when window closes var/on_close_logic = 1 + // the ref to use when the window is closed (if on_close_logic is 1), usually null + var/atom/ref = null + // options for modifying window behaviour 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 + // the list of stylesheets to apply to this ui var/list/stylesheets = list() + // the list of javascript scripts to use for this ui var/list/scripts = list() + // the list of templates to use with this ui (usually just one) var/templates[0] - var/title_image - var/head_elements - var/body_elements - var/head_content = "" - var/content = "
" // the #mainTemplate div will contain the compiled "main" template html + // the body content for this ui, do not change unless you know what you're doing + // the #mainTemplate div will contain the compiled "main" template html + var/content = "
" + // initial data, containing the full data structure, must be sent to the ui (the data structure cannot be extended later on) var/list/initial_data[0] + // set to 1 to update the ui automatically every master_controller tick var/is_auto_updating = 0 + // the current status/visibility of the ui var/status = STATUS_INTERACTIVE - + // Only allow users with a certain user.stat to get updates. Defaults to 0 (concious) var/allowed_user_stat = 0 // -1 = ignore, 0 = alive, 1 = unconcious or alive, 2 = dead concious or alive - + /** + * Create a new nanoui instance. + * + * @param nuser /mob The mob who has opened/owns this ui + * @param nsrc_object /obj|/mob The obj or mob which this ui belongs to + * @param nui_key string A string key to use for this ui. Allows for multiple unique uis on one src_oject + * @param ntemplate string The name of the template file from /nano/templates (e.g. "my_template.tmpl") + * @param ntitle string The title of this ui + * @param nwidth int the width of the ui window + * @param nheight int the height of the ui window + * @param nref /atom A custom ref to use if "on_close_logic" is set to 1 + * + * @return /nanoui new nanoui object + */ /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 the passed template as the 'main' template, this is required add_template("main", ntemplate) if (ntitle) @@ -46,8 +83,13 @@ if (nref) ref = nref - add_common_assets() + add_common_assets() + /** + * Use this proc to add assets which are common to all nano uis + * + * @return nothing + */ /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. @@ -56,6 +98,14 @@ add_stylesheet("shared.css") // this CSS sheet is common to all UIs add_stylesheet("icons.css") // this CSS sheet is common to all UIs + /** + * Set the current status (also known as visibility) of this ui. + * + * @param state int The status to set, see the defines at the top of this file + * @param push_update int (bool) Push an update to the ui to update it's status (an update is always sent if the status has changed to red (0)) + * + * @return nothing + */ /datum/nanoui/proc/set_status(state, push_update) if (state != status) status = state @@ -64,6 +114,13 @@ else status = state + /** + * Update the status (visibility) of this ui based on the user's status + * + * @param push_update int (bool) Push an update to the ui to update it's status. This is set to 0/false if an update is going to be pushed anyway (to avoid unnessary updates) + * + * @return nothing + */ /datum/nanoui/proc/update_status(push_update = 0) if (istype(user, /mob/living/silicon/ai)) set_status(STATUS_INTERACTIVE, push_update) // interactive (green visibility) @@ -74,17 +131,17 @@ set_status(STATUS_DISABLED, push_update) // no updates, completely disabled (red visibility) else var/dist = get_dist(src_object, user) - + if (dist > 4) close() return - + if ((allowed_user_stat > -1) && (user.stat > allowed_user_stat)) set_status(STATUS_DISABLED, push_update) // no updates, completely disabled (red visibility) else if (user.restrained() || user.lying) set_status(STATUS_UPDATE, push_update) // update only (orange visibility) else if (!(src_object in view(4, user))) // If the src object is not in visable, set status to 0 - set_status(STATUS_DISABLED, push_update) // interactive (green visibility) + set_status(STATUS_DISABLED, push_update) // interactive (green visibility) else if (dist <= 1) set_status(STATUS_INTERACTIVE, push_update) // interactive (green visibility) else if (dist <= 2) @@ -92,59 +149,129 @@ else if (dist <= 4) set_status(STATUS_DISABLED, push_update) // no updates, completely disabled (red visibility) + /** + * Set the ui to auto update (every master_controller tick) + * + * @param state int (bool) Set auto update to 1 or 0 (true/false) + * + * @return nothing + */ /datum/nanoui/proc/set_auto_update(state = 1) is_auto_updating = state -/datum/nanoui/proc/set_initial_data(data) - initial_data = modify_data(data) + /** + * Set the initial data for the ui. This is vital as the data structure set here cannot be changed when pushing new updates. + * + * @param data /list The list of data for this ui + * + * @return nothing + */ +/datum/nanoui/proc/set_initial_data(list/data) + initial_data = add_default_data(data) -/datum/nanoui/proc/add_head_content(nhead_content) - head_content = nhead_content + /** + * Add default data to the data being sent to the ui. + * + * @param data /list The list of data to be modified + * + * @return /list modified data + */ +/datum/nanoui/proc/add_default_data(list/data) + data["ui"] = list( + "status" = status, + "user" = list("name" = user.name) + ) + //user << list2json(data) + return data + /** + * Set the browser window options for this ui + * + * @param nwindow_options string The new window options + * + * @return nothing + */ /datum/nanoui/proc/set_window_options(nwindow_options) window_options = nwindow_options -/datum/nanoui/proc/set_title_image(ntitle_image) - //title_image = ntitle_image - + /** + * Add a CSS stylesheet to this UI + * + * @param file string The name of the CSS file from /nano/css (e.g. "my_style.css") + * + * @return nothing + */ /datum/nanoui/proc/add_stylesheet(file) stylesheets.Add(file) + /** + * Add a JavsScript script to this UI + * + * @param file string The name of the JavaScript file from /nano/js (e.g. "my_script.js") + * + * @return nothing + */ /datum/nanoui/proc/add_script(file) scripts.Add(file) -/datum/nanoui/proc/add_template(name, file) - templates[name] = file + /** + * Add a template to this UI + * Templates are combined with the data sent to the UI to create the rendered view + * Each template needs a div in ui.content to contain the rendered content. + * The div format is '
' where is replaced with the templater's key. + * All UIs are set up by default to use a 'main' template, so only use this proc if you want to add advanced functionality. + * + * @param key string The key name for this template, used to identify the div to render this template into ('
') + * @param file string The name of the template file from /nano/templates (e.g. "my_template.tmpl") + * + * @return nothing + */ +/datum/nanoui/proc/add_template(key, file) + templates[key] = file + /** + * Set the HTML content of the UI + * This should only really be used to add more template divs (see the add_template() proc) + * + * @param ncontent string The new HTML content for this UI + * + * @return nothing + */ /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 + /** + * Set whether or not to use the "old" on close logic (custom refs and unset_machine()) + * + * @param state int (bool) Set on_close_logic to 1 or 0 (true/false) + * + * @return nothing + */ +/datum/nanoui/proc/use_on_close_logic(state) + on_close_logic = state + /** + * Return the HTML header content for this UI + * + * @return string HTML header content + */ /datum/nanoui/proc/get_header() + var/head_content = "" for (var/filename in stylesheets) head_content += "" - 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) + 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) - + //user << initial_data_json var/url_parameters_json = list2json(list("src" = "\ref[src]")) @@ -180,19 +307,15 @@ }
- [title ? "
[title]
" : ""] + [title ? "
[title]
" : ""]
"} - -/datum/nanoui/Topic(href, href_list) - update_status(0) // update the status - if (status != STATUS_INTERACTIVE || user != usr) // If UI is not interactive or usr calling Topic is not the UI user - //usr << "Not interaction or wrong usr" - return - - if (src_object.Topic(href, href_list)) - nanomanager.update_uis(src_object) // update all UIs attached to src_object + /** + * Return the HTML footer content for this UI + * + * @return string HTML footer content + */ /datum/nanoui/proc/get_footer() var/scriptsContent = "" @@ -206,28 +329,49 @@ "} -/datum/nanoui/proc/get_content() + /** + * Return the HTML for this UI + * + * @return string HTML for the UI + */ +/datum/nanoui/proc/get_html() return {" [get_header()] [content] [get_footer()] "} + /** + * Open this UI + * + * @return nothing + */ /datum/nanoui/proc/open() var/window_size = "" if (width && height) window_size = "size=[width]x[height];" update_status(0) - user << browse(get_content(), "window=[window_id];[window_size][window_options]") + user << browse(get_html(), "window=[window_id];[window_size][window_options]") on_close_winset() //onclose(user, window_id) nanomanager.ui_opened(src) + /** + * Close this UI + * + * @return nothing + */ /datum/nanoui/proc/close() is_auto_updating = 0 nanomanager.ui_closed(src) user << browse(null, "window=[window_id]") + /** + * Set the UI window to call the nanoclose verb when the window is closed + * This allows Nano to handle closed windows + * + * @return nothing + */ /datum/nanoui/proc/on_close_winset() if(!user.client) world << "ERROR: No user.client!?" @@ -236,30 +380,12 @@ winset(user, window_id, "on-close=\"nanoclose [params]\"") -/datum/nanoui/proc/process(update = 0) - if (status && (update || is_auto_updating)) - src_object.ui_interact(user, ui_key) // Update the UI (update_status() is called whenever a UI is updated) - else - update_status(1) // Not updating UI, so lets check here if status has changed - -/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) - update_status(0) - if (status == STATUS_DISABLED && !force_push) - return // Cannot update UI, no visibility - - data = modify_data(data) - //user << list2json(data) - user << output(list2params(list(list2json(data))),"[window_id].browser:receiveUpdateData") - on_close_winset() - + /** + * Called when a Nano UI window is closed + * This is how Nano handles closed windows + * + * @return nothing + */ /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 @@ -286,4 +412,49 @@ src.mob.unset_machine() else world << "[src] UI not found" - return \ No newline at end of file + + /** + * Push data to an already open UI window + * + * @return nothing + */ +/datum/nanoui/proc/push_data(data, force_push = 0) + update_status(0) + if (status == STATUS_DISABLED && !force_push) + return // Cannot update UI, no visibility + + data = add_default_data(data) + //user << list2json(data) + user << output(list2params(list(list2json(data))),"[window_id].browser:receiveUpdateData") + //on_close_winset() + + /** + * This Topic() proc is called whenever a user clicks on a link within a Nano UI + * If the UI status is currently STATUS_INTERACTIVE then call the src_object Topic() + * If the src_object Topic() returns 1 (true) then update all UIs attached to src_object + * + * @return nothing + */ +/datum/nanoui/Topic(href, href_list) + update_status(0) // update the status + if (status != STATUS_INTERACTIVE || user != usr) // If UI is not interactive or usr calling Topic is not the UI user + //usr << "Not interaction or wrong usr" + return + + if (src_object.Topic(href, href_list)) + nanomanager.update_uis(src_object) // update all UIs attached to src_object + + /** + * Process this UI, updating the entire UI or just the status (aka visibility) + * This process proc is called by the master_controller + * + * @param update string For this UI to update + * + * @return nothing + */ +/datum/nanoui/proc/process(update = 0) + if (status && (update || is_auto_updating)) + src_object.ui_interact(user, ui_key) // Update the UI (update_status() is called whenever a UI is updated) + else + update_status(1) // Not updating UI, so lets check here if status has changed + diff --git a/nano/js/nano_base_helpers.js b/nano/js/nano_base_helpers.js index 0272faf3e48..721795c09e4 100644 --- a/nano/js/nano_base_helpers.js +++ b/nano/js/nano_base_helpers.js @@ -1,6 +1,7 @@ +// NanoBaseHelpers is where the base template helpers (common to all templates) are stored NanoBaseHelpers = function () { - var _urlParameters = {}; // This is populated with the base url parameters, which is probaby just the "src" parameter + var _urlParameters = {}; // This is populated with the base url parameters (used by all links), which is probaby just the "src" parameter var init = function () { @@ -117,6 +118,7 @@ NanoBaseHelpers = function () }); } + // generate a Byond href, combines _urlParameters with parameters var generateHref = function (parameters) { var queryString = '?'; diff --git a/nano/js/nano_config.js b/nano/js/nano_config.js index 2fe30a9d72c..7766b64a4b5 100644 --- a/nano/js/nano_config.js +++ b/nano/js/nano_config.js @@ -1,3 +1,4 @@ +// NanoConfig is the place to store utility functions var NanoConfig = function () { return { diff --git a/nano/js/nano_update.js b/nano/js/nano_update.js index 035d71862aa..47f1ee6ee52 100644 --- a/nano/js/nano_update.js +++ b/nano/js/nano_update.js @@ -1,18 +1,30 @@ +// NanoUpdate handles data from the server and uses it to render templates NanoUpdate = function () { + // _isInitialised is set to true when all of this ui's templates have been processed/rendered var _isInitialised = false; + // the array of template names to use for this ui var _templates = null; + // the data for this ui var _data = null; - var _earlyUpdateData = null; // This is for newer data which has arrived before the template has been rendered + // new data which arrives before _isInitialised is true is stored here for processing later + var _earlyUpdateData = null; + // this is an array of callbacks which are called when new data arrives, before it is processed var _beforeUpdateCallbacks = []; + // this is an array of callbacks which are called when new data arrives, before it is processed var _afterUpdateCallbacks = []; + // _canClick is used to disable clicks for a short period after each click (to avoid mis-clicks) var _canClick = true; + // the init function is called when the ui has loaded + // this function sets up the templates and base functionality var init = function () { + // this callback is triggered after new data is processed + // it updates the status/visibility icon and adds click event handling to buttons/links NanoUpdate.addAfterUpdateCallback(function (updateData) { var uiStatusClass; if (updateData['ui']['status'] == 2) @@ -56,17 +68,17 @@ NanoUpdate = function () }); }); - var body = $('body'); // We store data in the body tag, it's as good a place as any + // We store initialData and templateData in the body tag, it's as good a place as any + var body = $('body'); + var templateData = body.data('templateData'); + _data = body.data('initialData'); - _data = body.data('initialData'); - - if (!_data) + if (!templateData || !_data) { alert('Error: Initial data did not load correctly.'); - } - - var templateData = body.data('templateData'); + } + // we count the number of templates for this ui so that we know when they've all been rendered var templateCount = 0; for (var key in templateData) { @@ -76,6 +88,7 @@ NanoUpdate = function () } } + // load each template file and render it using _data for (var key in templateData) { if (templateData.hasOwnProperty(key)) @@ -126,6 +139,7 @@ NanoUpdate = function () var updateData; try { + // parse the JSON string from the server into a JSON object updateData = jQuery.parseJSON(jsonString); } catch (error) @@ -178,12 +192,15 @@ NanoUpdate = function () } } + // execute all callbacks in the callbacks array/object provided, updateData is passed to them for processing var executeCallbacks = function (callbacks, updateData) { for (var index in callbacks) { callbacks[index].call(this, updateData); } + + return updateData; } return { diff --git a/nano/templates/TemplatesGuide.txt b/nano/templates/TemplatesGuide.txt new file mode 100644 index 00000000000..1d98e1ac9d0 --- /dev/null +++ b/nano/templates/TemplatesGuide.txt @@ -0,0 +1,11 @@ +-------------------------------- +Nano UI Template Guide +-------------------------------- + +Nano UI uses templates, which are comprised of HTML and a markup syntax. The markup allows you +to easily add conditionals (if statements), loops (for loops) and custom formatting (using helpers). + +They are stored in the /nano/templates folder and the file extension is .tmpl. + + + diff --git a/nano/templates/chem_dispenser.tmpl b/nano/templates/chem_dispenser.tmpl index c8371c1ca3a..9d9f6525fd4 100644 --- a/nano/templates/chem_dispenser.tmpl +++ b/nano/templates/chem_dispenser.tmpl @@ -1,3 +1,7 @@ +
Energy: diff --git a/nano/templates/cryo.tmpl b/nano/templates/cryo.tmpl index d55aa448415..56dffac33c4 100644 --- a/nano/templates/cryo.tmpl +++ b/nano/templates/cryo.tmpl @@ -1,3 +1,7 @@ +

Cryo Cell Status

diff --git a/nano/templates/wip/apc.tmpl b/nano/templates/wip/apc.tmpl new file mode 100644 index 00000000000..446ba31a145 --- /dev/null +++ b/nano/templates/wip/apc.tmpl @@ -0,0 +1,141 @@ +
+ {^{if locked}} + Swipe ID card to unlock interface + {{else}} + Swipe ID card to lock interface + {{/if}} +
+ +

Status

+ +
+
+ Main Breaker: +
+
+ {^{if locked}} + {^{if isOperating}} + On + {{else}} + Off + {{/if}} + {{else}} + {^{:~link('On', {'breaker' : 1}, isOperating ? 'selected' : null)}}{^{:~link('Off', {'breaker' : 1}, isOperating ? null : 'selected')}} + {{/if}} +
+
+ +
+
+ External Power: +
+
+ {^{if externalPower == 2}} + Good + {{else externalPower == 1}} + Low + {{else}} + None + {{/if}} +
+
+ +
+
+ Power Cell: +
+
+ {^{if powerCellStatus == null}} + Not connected. + {{else}} + {^{:powerCellStatus}}% + {{/if}} +
+
+ +{^{if powerCellStatus != null}} +
+
+ Charge Mode: +
+
+ {^{if locked}} + {^{if chargeMode}} + Auto + {{else}} + Off + {{/if}} + {{else}} + {^{:~link('Auto', {'cmode' : 1}, chargeMode ? 'selected' : null)}}{^{:~link('Off', {'cmode' : 1}, chargeMode ? null : 'selected')}} + {{/if}} +   + {^{if chargingStatus > 1}} + (Fully Charged) + {{else chargingStatus == 1}} + (Charging) + {{else}} + (Not Charging) + {{/if}} +
+
+{{/if}} + +

Power Channels

+ +{^{for powerChannels}} +
+
+ {^{:title}} +
+
+ {^{:powerLoad}} W +
+
+ {^{if !~root.locked}} + {^{:~link('Auto', topicParams.auto, (status == 1 || status == 3) ? 'selected' : null)}} + {^{:~link('On', topicParams.on, (status == 2) ? 'selected' : null)}} + {^{:~link('Off', topicParams.off, (status == 0) ? 'selected' : null)}} + {{/if}} + {^{if status <= 1}} + Off + {{else status >= 2}} + On + {{/if}} + {^{if ~root.locked}} + {^{if status == 1 || status == 3}} + (Auto) + {{else}} + (Manual) + {{/if}} + {{/if}} +
+
+{{/for}} + +
 
+ +
+
+ Total Load: +
+
+ {{:totalLoad}} W +
+
+ +
+
+ Cover Lock: +
+
+ {^{if locked}} + {^{if coverLocked}} + Engaged + {{else}} + Disengaged + {{/if}} + {{else}} + {^{:~link('Engaged', {'lock' : 1}, coverLocked ? 'selected' : null)}}{^{:~link('Disengaged', {'lock' : 1}, coverLocked ? null : 'selected')}} + {{/if}} +
+
\ No newline at end of file