diff --git a/code/datums/records.dm b/code/datums/records.dm
index 2be5d60a252..5593c88c01d 100644
--- a/code/datums/records.dm
+++ b/code/datums/records.dm
@@ -165,7 +165,7 @@
var/nid = ""
var/enzymes
var/identity
- var/exploit_record = "No additional information acquired."
+ var/exploit_record
/datum/record/general/locked/New(var/mob/living/carbon/human/H)
..()
diff --git a/code/game/objects/items/devices/uplink.dm b/code/game/objects/items/devices/uplink.dm
index a6ea8bdb878..9c00dea07ca 100644
--- a/code/game/objects/items/devices/uplink.dm
+++ b/code/game/objects/items/devices/uplink.dm
@@ -98,69 +98,55 @@ Then check if it's true, if true return. This will stop the normal menu appearin
return 0
/*
- NANO UI FOR UPLINK WOOP WOOP
+ TGUI FOR UPLINK WOOP WOOP
*/
-/obj/item/device/uplink/hidden/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- var/title = "Remote Uplink"
- var/data[0]
+/obj/item/device/uplink/hidden/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Uplink", capitalize_first_letters(name))
+ ui.open()
+/obj/item/device/uplink/hidden/ui_data(mob/user)
+ var/list/data = list()
data["welcome"] = welcome
data["telecrystals"] = telecrystals
data["bluecrystals"] = bluecrystals
data["menu"] = nanoui_menu
+ update_nano_data()
data += nanoui_data
-
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
- if (!ui) // No auto-refresh
- ui = new(user, src, ui_key, "uplink.tmpl", title, 450, 600, state = inventory_state)
- ui.set_initial_data(data)
- ui.open()
-
+ return data
// Interaction code. Gathers a list of items purchasable from the paren't uplink and displays it. It also adds a lock button.
/obj/item/device/uplink/hidden/interact(mob/user)
- ui_interact(user)
+ ui_interact(user, null)
-/obj/item/device/uplink/hidden/CanUseTopic()
- if(!active)
- return STATUS_CLOSE
- return ..()
-
-// The purchasing code.
-/obj/item/device/uplink/hidden/Topic(href, href_list)
+/obj/item/device/uplink/hidden/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return 1
- var/mob/user = usr
- if(href_list["buy_item"])
- var/datum/uplink_item/UI = (locate(href_list["buy_item"]) in uplink.items)
+ if(action == "buy_item")
+ var/datum/uplink_item/UI = (locate(params["buy_item"]) in uplink.items)
UI.buy(src, usr)
- else if(href_list["lock"])
+ else if(action == "lock")
toggle()
- var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main")
- ui.close()
- else if(href_list["return"])
+ SStgui.close_uis(src)
+ else if(action == "return")
nanoui_menu = round(nanoui_menu/10)
- else if(href_list["menu"])
- nanoui_menu = text2num(href_list["menu"])
- if(href_list["id"])
- exploit_id = href_list["id"]
- if(href_list["category"])
- category = locate(href_list["category"]) in uplink.categories
- // #TODO-MERGE: Check NanoUI on PDAs
- if(href_list["contract_interact"])
- var/list/params = list("location" = "contract_details", "contract" = href_list["contract_interact"])
- usr.client.process_webint_link("interface/login/sso_server", list2params(params))
- if(href_list["contract_page"])
- nanoui_data["contracts_current_page"] = text2num(href_list["contract_page"])
- update_nano_data()
- if(href_list["contract_view"])
- nanoui_data["contracts_view"] = text2num(href_list["contract_view"])
+ else if(action == "menu")
+ nanoui_menu = text2num(params["menu"])
+ if(params["id"])
+ exploit_id = params["id"]
+ if(params["category"])
+ category = locate(params["category"]) in uplink.categories
+ if(action == "contract_interact")
+ var/list/params_webint = list("location" = "contract_details", "contract" = params["contract_interact"])
+ usr.client.process_webint_link("interface/login/sso_server", list2params(params_webint))
+ if(action == "contract_page")
+ nanoui_data["contracts_current_page"] = text2num(params["contract_page"])
+ if(action == "contract_view")
+ nanoui_data["contracts_view"] = text2num(params["contract_view"])
nanoui_data["contracts_current_page"] = 1
- update_nano_data()
- update_nano_data()
return 1
/obj/item/device/uplink/hidden/proc/update_nano_data()
@@ -189,8 +175,8 @@ Then check if it's true, if true return. This will stop the normal menu appearin
nanoui_data["items"] = items
else if(nanoui_menu == 2)
var/permanentData[0]
- for(var/datum/record/general/locked/L in sortRecord(SSrecords.records_locked))
- permanentData[++permanentData.len] = list(Name = L.name,"id" = L.id)
+ for(var/datum/record/general/locked/record in SSrecords.records_locked)
+ permanentData[++permanentData.len] = list("name" = record.name,"id" = record.id, "has_exploitables" = !!record.exploit_record)
nanoui_data["exploit_records"] = permanentData
else if(nanoui_menu == 21)
nanoui_data["exploit_exists"] = 0
diff --git a/html/changelogs/DreamySkrell-uplink-tgui.yml b/html/changelogs/DreamySkrell-uplink-tgui.yml
new file mode 100644
index 00000000000..ec4f2d28544
--- /dev/null
+++ b/html/changelogs/DreamySkrell-uplink-tgui.yml
@@ -0,0 +1,41 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+# balance
+# admin
+# backend
+# security
+# refactor
+#################################
+
+# Your name.
+author: DreamySkrell
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "Uplink rewritten from nanoui to tgui."
diff --git a/nano/templates/uplink.tmpl b/nano/templates/uplink.tmpl
deleted file mode 100644
index 3e5db5563f2..00000000000
--- a/nano/templates/uplink.tmpl
+++ /dev/null
@@ -1,176 +0,0 @@
-
-
-{{:helper.syndicateMode()}}
-
{{:data.welcome}}
-
-
-
- Functions :
-
-
- {{:helper.link('Request Items', 'gear', {'menu' : 0}, null, 'fixedLeftWider')}}
- {{:helper.link('Exploitable Information', 'gear', {'menu' : 2}, null, 'fixedLeftWider')}}
- {{:helper.link('Extranet Contract Database', 'gear', {'menu' : 3}, null, 'fixedLeftWider')}}
- {{:helper.link('Return', 'arrowreturn-1-w', {'return' : 1}, null, 'fixedLeft')}}
- {{:helper.link('Close', 'gear', {'lock' : "1"}, null, 'fixedLeft')}}
-
-
-
-
-
-
- Telecrystals :
-
-
- {{:data.telecrystals}}
-
-
- Bluecrystals :
-
-
- {{:data.bluecrystals}}
-
-
-
-{{if data.menu == 0}}
- Categories:
- {{for data.categories}}
-
- {{:helper.link(value.name, 'gear', {'menu' : 1, 'category' : value.ref})}}
-
- {{/for}}
-{{else data.menu == 1}}
- Request items:
- Each item costs a number of telecrystals or bluecrystals as indicated by the numbers following their name.
- Note that when buying items, bluecrystals are prioritised over telecrystals.
- {{for data.items}}
-
- {{:helper.link(value.name, 'gear', {'buy_item' : value.ref}, value.can_buy ? null : 'disabled')}}
- {{if value.bc_cost}}
- - {{:value.bc_cost}} BC
- {{/if}}
- {{if value.tc_cost}}
- - {{:value.tc_cost}} TC
- {{/if}}
- {{if value.left <= 10}}
- - {{:value.left}} left in the uplink
- {{/if}}
-
-
- {{:value.description}}
-
- {{/for}}
-{{else data.menu == 2}}
- Information Record List:
-
-
- Select a Record
-
-
- {{for data.exploit_records}}
-
- {{:helper.link(value.Name, 'gear', {'menu' : 21, 'id' : value.id}, null, null)}}
-
- {{/for}}
-{{else data.menu == 21}}
- Information Record:
-
-
-
-
- {{if data.exploit_exists == 1}}
- Name: {{:data.exploit.name}}
- Sex: {{:data.exploit.sex}}
- Species: {{:data.exploit.species}}
- Age: {{:data.exploit.age}}
- Rank: {{:data.exploit.rank}}
- Citizenship: {{:data.exploit.citizenship}}
- Faction: {{:data.exploit.faction}}
- Religion: {{:data.exploit.religion}}
- Fingerprint: {{:data.exploit.fingerprint}}
-
- Acquired Information:
- Notes: {{:data.exploit.nanoui_exploit_record}}
- {{else}}
-
- No exploitative information acquired!
-
-
-
- {{/if}}
-
-
-
-
-{{else data.menu == 3}}
- Available Contracts:
-
- {{if data.contracts_found == 1}}
-
-
- ID Contractor Title
- {{if data.contracts_view == 1}}
- Available Contracts
- {{else}}
- Closed Contracts
- {{/if}}
- {{for data.contracts}}
- {{:value.id}} {{:value.contractee}} {{:value.title}} {{:helper.link('View', null, {'menu' : 31, 'id' : value.id}, null, 'fixedLeft')}}
- {{/for}}
-
-
-
- {{for data.contracts_pages}}
- {{:helper.link(value, null, {'contract_page' : value}, null, null)}}
- {{/for}}
-
- {{if data.contracts_view == 1}}
- {{:helper.link('View Expired Contracts', 'gear', {'contract_view' : 2}, null, null)}}
- {{else}}
- {{:helper.link('View Open Contracts', 'gear', {'contract_view' : 1}, null, null)}}
- {{/if}}
-
-
-
-
- {{else}}
-
-
No Contracts Available.
-
- {{/if}}
-
-{{else data.menu == 31}}
- Viewing Contract:
-
-
-
-
- {{if data.contracts_found == 1}}
- ID: #{{:data.contract.id}}
- Contractee: {{:data.contract.contractee}}
- Status:
- {{if data.contract.status == 1}}
- Open
- {{else}}
- Closed
- {{/if}}
- Title: {{:data.contract.title}}
-
- Description: {{:data.contract.description}}
- Reward: {{:data.contract.reward_other}}
- {{else}}
- Failed to retreive contract information!
- {{/if}}
-
-
-
-
-
- {{if data.contracts_found == 1}}
- {{:helper.link('View Reports And Updates', 'extlink', {'contract_interact' : data.contract.id}, null, null)}}
- {{/if}}
-
-{{/if}}
diff --git a/tgui/packages/tgui/interfaces/Uplink.tsx b/tgui/packages/tgui/interfaces/Uplink.tsx
new file mode 100644
index 00000000000..29784712d93
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Uplink.tsx
@@ -0,0 +1,407 @@
+import { BooleanLike } from '../../common/react';
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Section, LabeledList, Table } from '../components';
+import { Window } from '../layouts';
+
+export type UplinkData = {
+ menu: Number;
+ welcome: string;
+ telecrystals: Number;
+ bluecrystals: Number;
+
+ categories: { name: string; ref: string }[];
+ items: ItemData[];
+
+ exploit_records: ExploitData[];
+ exploit: ExploitData;
+ exploit_exists: BooleanLike;
+
+ contracts_found: Number;
+ contracts_view: Number;
+ contracts: ContractData[];
+ contracts_pages: Number[];
+ contract: ContractData;
+};
+
+type ItemData = {
+ name: string;
+ description: string;
+ can_buy: BooleanLike;
+ tc_cost: number;
+ bc_cost: number;
+ left: number;
+ ref: string;
+};
+
+type ExploitData = {
+ id: number;
+ nanoui_exploit_record: string;
+ name: string;
+ sex: string;
+ age: string;
+ species: string;
+ rank: string;
+ citizenship: string;
+ employer: string;
+ religion: string;
+ fingerprint: string;
+ has_exploitables: BooleanLike;
+};
+
+type ContractData = {
+ id: number;
+ contractee: String;
+ title: String;
+ status: BooleanLike;
+ description: String;
+ reward_other: String;
+};
+
+export const Uplink = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+
+ {data.welcome}
+
+
+
+ {[
+ ['shopping-cart', 'Request Gear', () => act('menu', { menu: 0 })],
+ [
+ 'file-lines',
+ 'Exploitable Information',
+ () => act('menu', { menu: 2 }),
+ ],
+ [
+ 'network-wired',
+ 'Extranet Contract Database',
+ () => act('menu', { menu: 3 }),
+ ],
+ ['arrow-left', 'Return', () => act('return')],
+ ['close', 'Close', () => act('lock')],
+ ].map(([icon, text, action]: [string, string, any]) => (
+
+
+
+ ))}
+
+
+
+
+ Telecrystals
+ {data.telecrystals}
+
+
+ Bluecrystals
+ {data.bluecrystals}
+
+
+
+ {data.menu === 0 ? ItemCategoriesSection(act, data) : ''}
+ {data.menu === 1 ? ItemSection(context, act, data) : ''}
+ {data.menu === 2 ? ExploitSection(act, data) : ''}
+ {data.menu === 21 ? ExploitRecordSection(act, data) : ''}
+ {data.menu === 3 ? ContractsSection(act, data) : ''}
+ {data.menu === 31 ? ContractDetailsSection(act, data) : ''}
+
+
+ );
+};
+
+const ItemCategoriesSection = function (act: any, data: UplinkData) {
+ return (
+
+
+ {data.categories?.map((category) => (
+
+ act('menu', { menu: 1, category: category.ref })}
+ />
+
+ ))}
+
+
+ );
+};
+
+const ItemSection = function (context: any, act: any, data: UplinkData) {
+ const [sortDesc, setSortDesc] = useLocalState(
+ context,
+ `sortDesc`,
+ true
+ );
+
+ // sort by item cost first
+ data.items?.sort((a, b) => {
+ const a_cost = Math.max(a.bc_cost, a.tc_cost, 0);
+ const b_cost = Math.max(b.bc_cost, b.tc_cost, 0);
+ if (sortDesc) {
+ return b_cost - a_cost;
+ } else {
+ return a_cost - b_cost;
+ }
+ });
+ // and then sort to put unavailable items at the end
+ data.items?.sort((a, b) => {
+ if (!a.can_buy && b.can_buy) {
+ return +1;
+ } else if (a.can_buy && !b.can_buy) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
+
+ return (
+
+
+
+ Each item costs a number of telecrystals or bluecrystals as indicated
+ by the numbers following their name.
+
+
+
+
+
+ Note that when buying items, bluecrystals are prioritised over
+ telecrystals.
+
+
+
+
+ {
+ setSortDesc(!sortDesc);
+ }}
+ />
+
+
+ {data.items?.map((item: ItemData) => (
+ <>
+
+
+ act('buy_item', { buy_item: item.ref })}
+ />
+
+
+ {item.bc_cost ? item.bc_cost + ' BC' : ''}{' '}
+
+
+ {item.tc_cost ? item.tc_cost + ' TC' : ''}{' '}
+
+
+ {item.left < 42 ? item.left + ' LEFT' : ''}{' '}
+
+
+
+
+
+ {item.description}
+
+
+
+
+
+ >
+ ))}
+
+
+ );
+};
+
+const ExploitSection = function (act: any, data: UplinkData) {
+ const exploit_sort = (a: ExploitData, b: ExploitData) => {
+ return a.name.localeCompare(b.name);
+ };
+
+ return (
+
+ Select a Record
+
+
+
+ {data.exploit_records
+ ?.sort(exploit_sort)
+ .map((exploit: ExploitData) => (
+ <>
+ act('menu', { menu: 21, id: exploit.id })}
+ />
+
+ >
+ ))}
+
+
+ );
+};
+
+const ExploitRecordSection = function (act: any, data: UplinkData) {
+ if (data.exploit_exists && data.exploit) {
+ const exploit = data.exploit;
+ return (
+
+
+ {exploit.name}
+ {exploit.sex}
+ {exploit.species}
+ {exploit.age}
+ {exploit.rank}
+
+ {exploit.citizenship}
+
+
+ {exploit.employer}
+
+
+ {exploit.religion}
+
+
+ {exploit.fingerprint}
+
+
+
+ {exploit.nanoui_exploit_record
+ ? exploit.nanoui_exploit_record
+ : 'No additional information acquired.'}
+
+ );
+ } else {
+ return (
+
+ No exploitative information acquired!
+
+ );
+ }
+};
+
+const ContractsSection = function (act: any, data: UplinkData) {
+ return (
+
+ {!data.contracts_found ? (
+ No Contracts Available.
+ ) : (
+
+
+
+ ID
+ Contractor
+ Title
+
+
+
+
+ {data.contracts_view === 1
+ ? 'Available Contracts'
+ : 'Closed Contracts'}
+
+
+
+ {data.contracts.map((contract: ContractData) => (
+
+ {contract.id}
+ {contract.contractee}
+ {contract.title}
+
+ act('menu', { menu: 31, id: contract.id })}
+ />
+
+
+ ))}
+
+
+ {data.contracts_pages.map((page_n: Number) => (
+ act('contract_page', { contract_page: page_n })}
+ />
+ ))}
+
+
+ )}
+
+ {data.contracts_view === 1 ? (
+ act('contract_view', { contract_view: 2 })}
+ />
+ ) : (
+ act('contract_view', { contract_view: 1 })}
+ />
+ )}
+
+
+ );
+};
+
+const ContractDetailsSection = function (act: any, data: UplinkData) {
+ return (
+
+ {data.contracts_found === 1 ? (
+
+ {data.contract.id}
+
+ {data.contract.contractee}
+
+
+
+ {data.contract.status === true ? 'Open' : 'Closed'}
+
+
+
+ {data.contract.title}
+
+
+ {data.contract.description}
+
+
+ {data.contract.reward_other}
+
+
+ ) : (
+ {'Failed to retrieve contract information!'}
+ )}
+
+ {data.contracts_found === 1 ? (
+
+ act('contract_interact', { contract_interact: data.contract.id })
+ }
+ />
+ ) : (
+ ''
+ )}
+
+ );
+};