mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-09 16:07:40 +00:00
Canister and paper updates
should fix papah exploits, though some runtiming is occuring
This commit is contained in:
@@ -118,6 +118,9 @@
|
||||
//reusing the PDA option because I really don't think news comments are worth a config option
|
||||
WRITE_LOG(GLOB.world_pda_log, "COMMENT: [text]")
|
||||
|
||||
/proc/log_paper(text)
|
||||
WRITE_LOG(GLOB.world_paper_log, "PAPER: [text]")
|
||||
|
||||
/proc/log_telecomms(text)
|
||||
if (CONFIG_GET(flag/log_telecomms))
|
||||
WRITE_LOG(GLOB.world_telecomms_log, "TCOMMS: [text]")
|
||||
|
||||
@@ -32,6 +32,8 @@ GLOBAL_VAR(world_asset_log)
|
||||
GLOBAL_PROTECT(world_asset_log)
|
||||
GLOBAL_VAR(world_map_error_log)
|
||||
GLOBAL_PROTECT(world_map_error_log)
|
||||
GLOBAL_VAR(world_paper_log)
|
||||
GLOBAL_PROTECT(world_paper_log)
|
||||
GLOBAL_VAR(subsystem_log)
|
||||
GLOBAL_PROTECT(subsystem_log)
|
||||
GLOBAL_VAR(reagent_log)
|
||||
|
||||
@@ -132,6 +132,7 @@ GLOBAL_LIST(topic_status_cache)
|
||||
GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log"
|
||||
GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log"
|
||||
GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log"
|
||||
GLOB.world_paper_log = "[GLOB.log_directory]/paper.log"
|
||||
GLOB.tgui_log = "[GLOB.log_directory]/tgui.log"
|
||||
GLOB.subsystem_log = "[GLOB.log_directory]/subsystem.log"
|
||||
GLOB.reagent_log = "[GLOB.log_directory]/reagents.log"
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
desc = "A canister for the storage of gas."
|
||||
icon_state = "yellow"
|
||||
density = TRUE
|
||||
ui_x = 300
|
||||
ui_y = 232
|
||||
|
||||
var/valve_open = FALSE
|
||||
var/obj/machinery/atmospherics/components/binary/passive_gate/pump
|
||||
@@ -34,6 +36,7 @@
|
||||
var/restricted = FALSE
|
||||
req_access = list()
|
||||
|
||||
var/update = 0
|
||||
var/static/list/label2types = list(
|
||||
"n2" = /obj/machinery/portable_atmospherics/canister/nitrogen,
|
||||
"o2" = /obj/machinery/portable_atmospherics/canister/oxygen,
|
||||
@@ -159,11 +162,11 @@
|
||||
/obj/machinery/portable_atmospherics/canister/proto
|
||||
name = "prototype canister"
|
||||
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/proto/default
|
||||
name = "prototype canister"
|
||||
desc = "The best way to fix an atmospheric emergency... or the best way to introduce one."
|
||||
icon_state = "proto"
|
||||
icon_state = "proto"
|
||||
volume = 5000
|
||||
max_integrity = 300
|
||||
temperature_resistance = 2000 + T0C
|
||||
@@ -171,6 +174,7 @@
|
||||
can_min_release_pressure = (ONE_ATMOSPHERE / 30)
|
||||
prototype = TRUE
|
||||
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/proto/default/oxygen
|
||||
name = "prototype canister"
|
||||
desc = "A prototype canister for a prototype bike, what could go wrong?"
|
||||
@@ -192,6 +196,7 @@
|
||||
|
||||
update_icon()
|
||||
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/Destroy()
|
||||
qdel(pump)
|
||||
pump = null
|
||||
@@ -215,7 +220,6 @@
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/update_overlays()
|
||||
. = ..()
|
||||
|
||||
if(holding)
|
||||
. += "can-open"
|
||||
if(connected_port)
|
||||
@@ -245,7 +249,8 @@
|
||||
new /obj/item/stack/sheet/metal (loc, 5)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/item/I)
|
||||
obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/item/I)
|
||||
..()
|
||||
if(user.a_intent == INTENT_HARM)
|
||||
return FALSE
|
||||
|
||||
@@ -261,7 +266,8 @@
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/obj_break(damage_flag)
|
||||
if((stat & BROKEN) || (flags_1 & NODECONSTRUCT_1))
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
canister_break()
|
||||
|
||||
@@ -272,10 +278,9 @@
|
||||
T.assume_air(expelled_gas)
|
||||
air_update_turf()
|
||||
|
||||
stat |= BROKEN
|
||||
obj_break()
|
||||
density = FALSE
|
||||
playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
|
||||
update_icon()
|
||||
playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3)
|
||||
investigate_log("was destroyed.", INVESTIGATE_ATMOS)
|
||||
|
||||
if(holding)
|
||||
@@ -318,7 +323,7 @@
|
||||
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "Canister", name, 420, 405, master_ui, state)
|
||||
ui = new(user, src, ui_key, "Canister", name, ui_x, ui_y, master_ui, state)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/portable_atmospherics/canister/ui_data()
|
||||
@@ -353,7 +358,7 @@
|
||||
return
|
||||
switch(action)
|
||||
if("relabel")
|
||||
var/label = input("New canister label:", name) as null|anything in label2types
|
||||
var/label = input("New canister label:", name) as null|anything in sortList(label2types)
|
||||
if(label && !..())
|
||||
var/newtype = label2types[label]
|
||||
if(newtype)
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
*
|
||||
* lipstick wiping is in code/game/objects/items/weapons/cosmetics.dm!
|
||||
*/
|
||||
#define MAX_PAPER_LENGTH 5000
|
||||
#define MAX_PAPER_STAMPS 30 // Too low?
|
||||
#define MAX_PAPER_STAMPS_OVERLAYS 4
|
||||
#define MODE_READING 0
|
||||
#define MODE_WRITING 1
|
||||
#define MODE_STAMPING 2
|
||||
/**
|
||||
** Paper is now using markdown (like in github pull notes) for ALL rendering
|
||||
** so we do loose a bit of functionality but we gain in easy of use of
|
||||
** paper and getting rid of that crashing bug
|
||||
**/
|
||||
/obj/item/paper
|
||||
var/static/regex/sign_regex = regex("%s(?:ign)?(?=\\s|$)", "igm")
|
||||
name = "paper"
|
||||
gender = NEUTER
|
||||
icon = 'icons/obj/bureaucracy.dmi'
|
||||
@@ -27,28 +34,37 @@
|
||||
dog_fashion = /datum/dog_fashion/head
|
||||
color = "white"
|
||||
/// What's actually written on the paper.
|
||||
var/info
|
||||
var/info = ""
|
||||
|
||||
/// The (text for the) stamps on the paper.
|
||||
var/stamps
|
||||
var/list/stamped
|
||||
var/list/stamps /// Positioning for the stamp in tgui
|
||||
var/list/stamped /// Overlay info
|
||||
|
||||
/// This REALLY should be a componenet. Basicly used during, april fools
|
||||
/// to honk at you
|
||||
var/rigged = 0
|
||||
var/spam_flag = 0
|
||||
///
|
||||
|
||||
var/contact_poison // Reagent ID to transfer on contact
|
||||
var/contact_poison_volume = 0
|
||||
|
||||
var/ui_x = 600
|
||||
var/ui_y = 800
|
||||
/// When a piece of paper cannot be edited, this makes it mutable
|
||||
var/finalized = FALSE
|
||||
/// We MAY be edited, mabye we are just looking at it or something.
|
||||
var/readonly = FALSE
|
||||
/// Color of the pin that wrote on this paper
|
||||
/// When the sheet can be "filled out"
|
||||
/// This is an associated list
|
||||
var/list/form_fields = null
|
||||
var/field_counter = 1
|
||||
/// What edit mode we are in and who is
|
||||
/// writing on it right now
|
||||
var/edit_mode = MODE_READING
|
||||
var/mob/living/edit_usr = null
|
||||
/// Setup for writing to a sheet
|
||||
var/pen_color = "black"
|
||||
var/pen_font = ""
|
||||
var/is_crayon = FALSE
|
||||
/// Setup for stamping a sheet
|
||||
var/obj/item/stamp/current_stamp = null
|
||||
var/stamp_class = null
|
||||
|
||||
/**
|
||||
** This proc copies this sheet of paper to a new
|
||||
@@ -58,12 +74,12 @@
|
||||
/obj/item/paper/proc/copy()
|
||||
var/obj/item/paper/N = new(arglist(args))
|
||||
N.info = info
|
||||
N.pen_color = pen_color
|
||||
N.color = color
|
||||
N.finalized = TRUE
|
||||
N.update_icon_state()
|
||||
N.stamps = stamps
|
||||
N.stamped = stamped.Copy()
|
||||
N.form_fields = form_fields.Copy()
|
||||
N.field_counter = field_counter
|
||||
copy_overlays(N, TRUE)
|
||||
return N
|
||||
|
||||
@@ -72,9 +88,10 @@
|
||||
** icons. You can modify the pen_color after if need
|
||||
** be.
|
||||
**/
|
||||
/obj/item/paper/proc/setText(text, read_only = TRUE)
|
||||
readonly = read_only
|
||||
/obj/item/paper/proc/setText(text)
|
||||
info = text
|
||||
form_fields = null
|
||||
field_counter = 0
|
||||
update_icon_state()
|
||||
|
||||
/obj/item/paper/pickup(user)
|
||||
@@ -108,6 +125,7 @@
|
||||
var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
|
||||
. = replacetext(html, "<!--customheadhtml-->", assets.css_tag())
|
||||
|
||||
|
||||
/obj/item/paper/verb/rename()
|
||||
set name = "Rename paper"
|
||||
set category = "Object"
|
||||
@@ -132,13 +150,17 @@
|
||||
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>")
|
||||
return (BRUTELOSS)
|
||||
|
||||
/// ONLY USED FOR APRIL FOOLS
|
||||
|
||||
/// ONLY USED FOR APRIL FOOLS
|
||||
/obj/item/paper/proc/reset_spamflag()
|
||||
spam_flag = FALSE
|
||||
|
||||
|
||||
/obj/item/paper/attack_self(mob/user)
|
||||
readonly = TRUE /// Assume we are just reading it
|
||||
if(edit_usr == user)
|
||||
// we are shifting out of editing mode
|
||||
edit_mode = MODE_READING
|
||||
edit_usr = null
|
||||
if(rigged && (SSevents.holidays && SSevents.holidays[APRIL_FOOLS]))
|
||||
if(!spam_flag)
|
||||
spam_flag = TRUE
|
||||
@@ -148,8 +170,7 @@
|
||||
|
||||
|
||||
/obj/item/paper/proc/clearpaper()
|
||||
finalized = FALSE
|
||||
info = null
|
||||
info = ""
|
||||
stamps = null
|
||||
LAZYCLEARLIST(stamped)
|
||||
cut_overlays()
|
||||
@@ -159,49 +180,56 @@
|
||||
/obj/item/paper/can_interact(mob/user)
|
||||
if(!..())
|
||||
return FALSE
|
||||
if(resistance_flags & ON_FIRE) /// Are we on fire? Hard ot read if so
|
||||
if(resistance_flags & ON_FIRE) // Are we on fire? Hard ot read if so
|
||||
return FALSE
|
||||
if(user.is_blind()) /// Even harder to read if your blind...braile? humm
|
||||
if(user.is_blind()) // Even harder to read if your blind...braile? humm
|
||||
return FALSE
|
||||
return user.can_read(src) // checks if the user can read.
|
||||
|
||||
|
||||
/obj/item/paper/attackby(obj/item/P, mob/living/carbon/human/user, params)
|
||||
readonly = TRUE /// Assume we are just reading it
|
||||
if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
|
||||
if(finalized)
|
||||
to_chat(user, "<span class='warning'>This sheet of paper has already been written too!</span>")
|
||||
if(length(info) >= MAX_PAPER_LENGTH) // Sheet must have less than 1000 charaters
|
||||
to_chat(user, "<span class='warning'>This sheet of paper is full!</span>")
|
||||
return
|
||||
readonly = FALSE /// Nope we are going to write stuff
|
||||
/// should a crayon be in the same subtype as a pen? How about a brush or charcoal?
|
||||
if(istype(P, /obj/item/pen))
|
||||
var/obj/item/pen/PEN = P
|
||||
pen_color = PEN.colour
|
||||
else
|
||||
if(edit_mode != MODE_READING)
|
||||
to_chat(user, "<span class='warning'>[edit_usr] is already working on this sheet!</span>")
|
||||
return
|
||||
|
||||
edit_mode = MODE_WRITING
|
||||
edit_usr = user
|
||||
// should a crayon be in the same subtype as a pen? How about a brush or charcoal?
|
||||
// TODO: Convert all writing stuff to one type, /obj/item/art_tool maybe?
|
||||
is_crayon = istype(P, /obj/item/toy/crayon);
|
||||
if(is_crayon)
|
||||
var/obj/item/toy/crayon/PEN = P
|
||||
pen_color = PEN.crayon_color
|
||||
pen_font = CRAYON_FONT
|
||||
pen_color = PEN.paint_color
|
||||
else
|
||||
var/obj/item/pen/PEN = P
|
||||
pen_font = PEN.font
|
||||
pen_color = PEN.colour
|
||||
|
||||
ui_interact(user)
|
||||
return
|
||||
else if(istype(P, /obj/item/stamp))
|
||||
|
||||
if(!in_range(src, user))
|
||||
if(edit_mode != MODE_READING)
|
||||
to_chat(user, "<span class='warning'>[edit_usr] is already working on this sheet!</span>")
|
||||
return
|
||||
|
||||
edit_mode = MODE_STAMPING // we are read only becausse the sheet is full
|
||||
edit_usr = user
|
||||
current_stamp = P
|
||||
|
||||
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper)
|
||||
if(isnull(stamps))
|
||||
stamps = sheet.css_tag()
|
||||
stamps += sheet.icon_tag(P.icon_state)
|
||||
var/mutable_appearance/stampoverlay = mutable_appearance('icons/obj/bureaucracy.dmi', "paper_[P.icon_state]")
|
||||
stampoverlay.pixel_x = rand(-2, 2)
|
||||
stampoverlay.pixel_y = rand(-3, 2)
|
||||
stamp_class = sheet.icon_class_name(P.icon_state)
|
||||
|
||||
LAZYADD(stamped, P.icon_state)
|
||||
add_overlay(stampoverlay)
|
||||
|
||||
to_chat(user, "<span class='notice'>You stamp the paper with your rubber stamp.</span>")
|
||||
to_chat(user, "<span class='notice'>You ready your stamp over the paper! </span>")
|
||||
|
||||
ui_interact(user)
|
||||
return /// Normaly you just stamp, you don't need to read the thing
|
||||
if(P.get_temperature())
|
||||
else if(P.get_temperature())
|
||||
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10))
|
||||
user.visible_message("<span class='warning'>[user] accidentally ignites [user.p_them()]self!</span>", \
|
||||
"<span class='userdanger'>You miss the paper and accidentally light yourself on fire!</span>")
|
||||
@@ -210,15 +238,19 @@
|
||||
user.IgniteMob()
|
||||
return
|
||||
|
||||
if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper
|
||||
return
|
||||
|
||||
user.dropItemToGround(src)
|
||||
user.visible_message("<span class='danger'>[user] lights [src] ablaze with [P]!</span>", "<span class='danger'>You light [src] on fire!</span>")
|
||||
fire_act()
|
||||
else
|
||||
if(edit_mode != MODE_READING)
|
||||
to_chat(user, "You look at the sheet while [edit_usr] edits it")
|
||||
else
|
||||
edit_mode = MODE_READING
|
||||
ui_interact(user) // The other ui will be created with just read mode outside of this
|
||||
|
||||
. = ..()
|
||||
|
||||
|
||||
/obj/item/paper/fire_act(exposed_temperature, exposed_volume)
|
||||
..()
|
||||
if(!(resistance_flags & FIRE_PROOF))
|
||||
@@ -235,25 +267,58 @@
|
||||
if(!ui)
|
||||
var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
|
||||
assets.send(user)
|
||||
/// The x size is because we double the width for the editor
|
||||
ui = new(user, src, ui_key, "PaperSheet", name, 400, 600, master_ui, state)
|
||||
// The x size is because we double the width for the editor
|
||||
ui = new(user, src, ui_key, "PaperSheet", name, ui_x, ui_y, master_ui, state)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
/obj/item/paper/ui_close(mob/user)
|
||||
/// close the editing window and change the mode
|
||||
if(edit_usr != null && user == edit_usr)
|
||||
edit_mode = MODE_READING
|
||||
edit_usr = null
|
||||
current_stamp = null
|
||||
stamp_class = null
|
||||
|
||||
. = ..()
|
||||
|
||||
|
||||
/obj/item/paper/proc/ui_force_close()
|
||||
var/datum/tgui/ui = SStgui.try_update_ui(usr, src, "main");
|
||||
if(ui)
|
||||
ui.close()
|
||||
|
||||
|
||||
/obj/item/paper/proc/ui_update()
|
||||
var/datum/tgui/ui = SStgui.try_update_ui(usr, src, "main");
|
||||
if(ui)
|
||||
ui.update()
|
||||
|
||||
|
||||
/obj/item/paper/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
// Should all this go in static data and just do a forced update?
|
||||
data["text"] = info
|
||||
data["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling
|
||||
data["pen_color"] = pen_color
|
||||
data["paper_color"] = color || "white" // color might not be set
|
||||
data["edit_sheet"] = readonly || finalized ? FALSE : TRUE
|
||||
/// data["stamps_info"] = list(stamp_info)
|
||||
data["max_length"] = MAX_PAPER_LENGTH
|
||||
data["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling?
|
||||
data["paper_color"] = !color || color == "white" ? "#FFFFFF" : color // color might not be set
|
||||
data["stamps"] = stamps
|
||||
|
||||
if(edit_usr == null || user != edit_usr)
|
||||
data["edit_mode"] = MODE_READING /// Eveyone else is just an observer
|
||||
else
|
||||
data["edit_mode"] = edit_mode
|
||||
|
||||
// pen info for editing
|
||||
data["is_crayon"] = is_crayon
|
||||
data["pen_font"] = pen_font
|
||||
data["pen_color"] = pen_color
|
||||
|
||||
// stamping info for..stamping
|
||||
data["stamp_class"] = stamp_class
|
||||
data["field_counter"] = field_counter
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -261,19 +326,73 @@
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("stamp")
|
||||
var/stamp_x = text2num(params["x"])
|
||||
var/stamp_y = text2num(params["y"])
|
||||
var/stamp_r = text2num(params["r"]) // rotation in degrees
|
||||
|
||||
if (isnull(stamps))
|
||||
stamps = new/list()
|
||||
if(stamps.len < MAX_PAPER_STAMPS)
|
||||
// I hate byond when dealing with freaking lists
|
||||
stamps += list(list(stamp_class, stamp_x, stamp_y,stamp_r)) /// WHHHHY
|
||||
|
||||
/// This does the overlay stuff
|
||||
if (isnull(stamped))
|
||||
stamped = new/list()
|
||||
if(stamped.len < MAX_PAPER_STAMPS_OVERLAYS)
|
||||
var/mutable_appearance/stampoverlay = mutable_appearance('icons/obj/bureaucracy.dmi', "paper_[current_stamp.icon_state]")
|
||||
stampoverlay.pixel_x = rand(-2, 2)
|
||||
stampoverlay.pixel_y = rand(-3, 2)
|
||||
add_overlay(stampoverlay)
|
||||
LAZYADD(stamped, current_stamp.icon_state)
|
||||
|
||||
edit_usr.visible_message("<span class='notice'>[edit_usr] stamps [src] with [current_stamp]!</span>", "<span class='notice'>You stamp [src] with [current_stamp]!</span>")
|
||||
else
|
||||
to_chat(usr, pick("You try to stamp but you miss!", "There is no where else you can stamp!"))
|
||||
|
||||
ui_update()
|
||||
. = TRUE
|
||||
|
||||
if("save")
|
||||
var/in_paper = params["text"]
|
||||
if(length(in_paper) > 0 && length(in_paper) < 1000) // Sheet must have less than 1000 charaters
|
||||
info = in_paper
|
||||
finalized = TRUE // once you have writen to a sheet you cannot write again
|
||||
to_chat(usr, "You have finished your paper masterpiece!");
|
||||
ui_update()
|
||||
else
|
||||
var/paper_len = length(in_paper)
|
||||
var/list/fields = params["form_fields"]
|
||||
field_counter = params["field_counter"] ? text2num(params["field_counter"]) : field_counter
|
||||
|
||||
if(paper_len > MAX_PAPER_LENGTH)
|
||||
// Side note, the only way we should get here is if
|
||||
// the javascript was modified, somehow, outside of
|
||||
// byond.
|
||||
log_paper("[key_name(edit_usr)] writing to paper [name], and overwrote it by [paper_len-MAX_PAPER_LENGTH], aborting")
|
||||
ui_force_close()
|
||||
else if(paper_len == 0)
|
||||
to_chat(usr, pick("Writing block strikes again!", "You forgot to write anthing!"))
|
||||
ui_update()
|
||||
ui_force_close()
|
||||
else
|
||||
// Next find the sign marker and replace it with somones sig
|
||||
// All other processing should of been done in the js module
|
||||
in_paper = sign_regex.Replace(in_paper, "<font face=\"[SIGNFONT]\"><i>[edit_usr]</i></font>")
|
||||
// Do the same with form fields
|
||||
log_paper("[key_name(edit_usr)] writing to paper [name]")
|
||||
if(info != in_paper)
|
||||
to_chat(usr, "You have added to your paper masterpiece!");
|
||||
info = in_paper
|
||||
if(!fields) //Possible runtiming
|
||||
return
|
||||
if(fields && fields.len > 0)
|
||||
for(var/key in fields) // In case somone %sign in a field
|
||||
form_fields[key] = sign_regex.Replace(fields[key], "<font face=\"[SIGNFONT]\"><i>[edit_usr]</i></font>")
|
||||
|
||||
/// Switch ui to reading mode
|
||||
edit_mode = MODE_READING
|
||||
edit_usr = null
|
||||
ui_update()
|
||||
update_icon()
|
||||
|
||||
. = TRUE
|
||||
|
||||
|
||||
/*
|
||||
* Construction paper
|
||||
*/
|
||||
@@ -305,3 +424,10 @@
|
||||
|
||||
/obj/item/paper/crumpled/muddy
|
||||
icon_state = "scrap_mud"
|
||||
|
||||
#undef MAX_PAPER_LENGTH
|
||||
#undef MAX_PAPER_STAMPS
|
||||
#undef MAX_PAPER_STAMPS_OVERLAYS
|
||||
#undef MODE_READING
|
||||
#undef MODE_WRITING
|
||||
#undef MODE_STAMPING
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Flex, Knob, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { AnimatedNumber, Box, Button, Icon, Knob, LabeledControls, LabeledList, Section, Tooltip } from '../components';
|
||||
import { formatSiUnit } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Canister = (props, context) => {
|
||||
@@ -20,82 +23,105 @@ export const Canister = (props, context) => {
|
||||
return (
|
||||
<Window>
|
||||
<Window.Content>
|
||||
<NoticeBox>
|
||||
The regulator {hasHoldingTank ? 'is' : 'is not'} connected
|
||||
to a tank.
|
||||
</NoticeBox>
|
||||
<Section
|
||||
title="Canister"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
content="Relabel"
|
||||
onClick={() => act('relabel')} />
|
||||
<Fragment>
|
||||
{!!isPrototype && (
|
||||
<Button
|
||||
mr={1}
|
||||
icon={restricted ? 'lock' : 'unlock'}
|
||||
color="caution"
|
||||
content={restricted
|
||||
? 'Engineering'
|
||||
: 'Public'}
|
||||
onClick={() => act('restricted')} />
|
||||
)}
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
content="Relabel"
|
||||
onClick={() => act('relabel')} />
|
||||
</Fragment>
|
||||
)}>
|
||||
<Flex mx={-1}>
|
||||
<Flex.Item
|
||||
mx={1}
|
||||
align="center"
|
||||
textAlign="center">
|
||||
<Knob
|
||||
size={2}
|
||||
color={!!valveOpen && 'yellow'}
|
||||
value={releasePressure}
|
||||
unit="kPa"
|
||||
minValue={minReleasePressure}
|
||||
maxValue={maxReleasePressure}
|
||||
step={5}
|
||||
stepPixelSize={1}
|
||||
onDrag={(e, value) => act('pressure', {
|
||||
pressure: value,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
mx={1}
|
||||
my={-0.5}
|
||||
align="center"
|
||||
textAlign="center">
|
||||
<Box my={0.5} color="label">
|
||||
Valve
|
||||
</Box>
|
||||
<Box my={0.5} width="60px">
|
||||
<AnimatedNumber value={releasePressure} /> kPa
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item
|
||||
minWidth="66px"
|
||||
label="Pressure">
|
||||
<AnimatedNumber
|
||||
value={tankPressure}
|
||||
format={value => {
|
||||
if (value < 10000) {
|
||||
return toFixed(value) + ' kPa';
|
||||
}
|
||||
return formatSiUnit(value * 1000, 1, 'Pa');
|
||||
}} />
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Regulator">
|
||||
<Box
|
||||
position="relative"
|
||||
left="-8px">
|
||||
<Knob
|
||||
size={1.25}
|
||||
color={!!valveOpen && 'yellow'}
|
||||
value={releasePressure}
|
||||
unit="kPa"
|
||||
minValue={minReleasePressure}
|
||||
maxValue={maxReleasePressure}
|
||||
step={5}
|
||||
stepPixelSize={1}
|
||||
onDrag={(e, value) => act('pressure', {
|
||||
pressure: value,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="-2px"
|
||||
right="-20px"
|
||||
color="transparent"
|
||||
icon="fast-forward"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: maxReleasePressure,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="16px"
|
||||
right="-20px"
|
||||
color="transparent"
|
||||
icon="undo"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: defaultReleasePressure,
|
||||
})} />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Valve">
|
||||
<Button
|
||||
my={0.5}
|
||||
width="50px"
|
||||
lineHeight={2}
|
||||
fontSize="11px"
|
||||
color={valveOpen
|
||||
? (hasHoldingTank ? 'caution' : 'danger')
|
||||
: null}
|
||||
content={valveOpen ? 'Open' : 'Closed'}
|
||||
onClick={() => act('valve')} />
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
mx={1}
|
||||
grow={1}
|
||||
basis={0}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<AnimatedNumber value={tankPressure} /> kPa
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Port"
|
||||
color={portConnected ? 'good' : 'average'}>
|
||||
{portConnected ? 'Connected' : 'Not Connected'}
|
||||
</LabeledList.Item>
|
||||
{!!isPrototype && (
|
||||
<LabeledList.Item label="Access">
|
||||
<Button
|
||||
icon={restricted ? 'lock' : 'unlock'}
|
||||
color="caution"
|
||||
content={restricted
|
||||
? 'Engineering'
|
||||
: 'Public'}
|
||||
onClick={() => act('restricted')} />
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item
|
||||
mr={1}
|
||||
label="Port">
|
||||
<Box position="relative">
|
||||
<Icon
|
||||
size={1.25}
|
||||
name={portConnected ? 'plug' : 'times'}
|
||||
color={portConnected ? 'good' : 'bad'} />
|
||||
<Tooltip
|
||||
content={portConnected
|
||||
? 'Connected'
|
||||
: 'Disconnected'}
|
||||
position="top" />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
<Section
|
||||
title="Holding Tank"
|
||||
|
||||
@@ -5,33 +5,128 @@
|
||||
* @author Changes stylemistake
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Tabs, Box, Flex, Button, TextArea } from '../components';
|
||||
import { useBackend, useSharedState, useLocalState } from '../backend';
|
||||
import { Component } from 'inferno';
|
||||
import { Tabs, Box, Flex, TextArea } from '../components';
|
||||
import { useBackend } from '../backend';
|
||||
import { Window } from '../layouts';
|
||||
import marked from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { classes, isFalsy } from "common/react";
|
||||
// There is a sanatize option in marked but they say its deprecated.
|
||||
// Might as well use a proper one then
|
||||
|
||||
import { createLogger } from '../logging';
|
||||
import { Fragment } from 'inferno';
|
||||
import { vecCreate, vecAdd, vecSubtract } from 'common/vector';
|
||||
const logger = createLogger('PaperSheet');
|
||||
const MAX_PAPER_LENGTH = 5000; // Question, should we send this with ui_data?
|
||||
|
||||
const walkTokens = token => {
|
||||
switch (token.type) {
|
||||
case 'link':
|
||||
case 'image':
|
||||
token.type = 'text';
|
||||
// Once asset system is up change to some default image
|
||||
// or rewrite for icon images
|
||||
token.href = "";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
const sanatize_text = value => {
|
||||
// This is VERY important to think first if you NEED
|
||||
// the tag you put in here. We are pushing all this
|
||||
// though dangerouslySetInnerHTML and even though
|
||||
// the default DOMPurify kills javascript, it dosn't
|
||||
// kill href links or such
|
||||
return DOMPurify.sanitize(value, {
|
||||
FORBID_ATTR: ['class', 'style'],
|
||||
ALLOWED_TAGS: [
|
||||
'br', 'code', 'li', 'p', 'pre',
|
||||
'span', 'table', 'td', 'tr',
|
||||
'th', 'ul', 'ol', 'menu', 'font', 'b',
|
||||
'center', 'table', 'tr', 'th',
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Hacky, yes, works?...yes
|
||||
const textWidth = (text, font, fontsize) => {
|
||||
// default font height is 12 in tgui
|
||||
font = fontsize + "x " + font;
|
||||
const c = document.createElement('canvas');
|
||||
const ctx = c.getContext("2d");
|
||||
ctx.font = font;
|
||||
const width = ctx.measureText(text).width;
|
||||
return width;
|
||||
};
|
||||
|
||||
|
||||
const setFontinText = (text, font, color, bold=false) => {
|
||||
return "<span style=\""
|
||||
+ "color:'" + color + "';"
|
||||
+ "font-family:'" + font + "';"
|
||||
+ ((bold)
|
||||
? "font-weight: bold;"
|
||||
: "")
|
||||
+ "\">" + text + "</span>";
|
||||
};
|
||||
|
||||
const paperfield_id_headder = "paperfield_";
|
||||
const createIDHeader = index => {
|
||||
return "paperfield_" + index;
|
||||
};
|
||||
// To make a field you do a [_______] or however long the field is
|
||||
// we will then output a TEXT input for it that hopefuly covers
|
||||
// the exact amount of spaces
|
||||
const field_regex = /\[(_+)\]/g;
|
||||
const field_tag_regex = /\[<input\s+(.*?)id="(?<id>paperfield_\d+)"(.*?)\/>\]/gm;
|
||||
const sign_regex = /%s(?:ign)?(?=\\s|$)/igm;
|
||||
|
||||
|
||||
const field_id_regex = /id\s*=\s*'(paperfield_\d+)'/g;
|
||||
const field_maxlength_regex = /maxlength\s*=\s*(\d+)/g;
|
||||
|
||||
const createInputField = (length, width, font,
|
||||
fontsize, color, id) => {
|
||||
return "[<input "
|
||||
+ "type=\"text\" "
|
||||
+ "style=\""
|
||||
+ "font:'" + fontsize + "x " + font + "';"
|
||||
+ "color:'" + color + "';"
|
||||
+ "min-width:" + width + ";"
|
||||
+ "max-width:" + width + ";"
|
||||
+ "\" "
|
||||
+ "id=\"" + id + "\" "
|
||||
+ "maxlength=" + length +" "
|
||||
+ "size=" + length + " "
|
||||
+ "/>]";
|
||||
};
|
||||
|
||||
const createFields = (txt, font, fontsize, color, counter) => {
|
||||
const ret_text = txt.replace(field_regex, (match, p1, offset, string) => {
|
||||
const width = textWidth(match, font, fontsize) + "px";
|
||||
return createInputField(p1.length,
|
||||
width, font, fontsize, color, createIDHeader(counter++));
|
||||
});
|
||||
return { counter: counter, text: ret_text };
|
||||
};
|
||||
|
||||
const signDocument = (txt, color, user) => {
|
||||
return txt.replace(sign_regex, () => {
|
||||
return setFontinText(user, "Times New Roman", color, true);
|
||||
});
|
||||
};
|
||||
const run_marked_default = value => {
|
||||
const clean = DOMPurify.sanitize(value);
|
||||
return marked(clean,
|
||||
|
||||
// Override function, any links and images should
|
||||
// kill any other marked tokens we don't want here
|
||||
const walkTokens = token => {
|
||||
switch (token.type) {
|
||||
case 'url':
|
||||
case 'autolink':
|
||||
case 'reflink':
|
||||
case 'link':
|
||||
case 'image':
|
||||
token.type = 'text';
|
||||
// Once asset system is up change to some default image
|
||||
// or rewrite for icon images
|
||||
token.href = "";
|
||||
break;
|
||||
}
|
||||
};
|
||||
return marked(value,
|
||||
{ breaks: true,
|
||||
smartypants: true,
|
||||
smartLists: true,
|
||||
@@ -40,126 +135,501 @@ const run_marked_default = value => {
|
||||
baseUrl: "thisshouldbreakhttp",
|
||||
});
|
||||
};
|
||||
const fillAllfields = fields => {
|
||||
for (const id in fields) {
|
||||
const dom = document.getElementById(id);
|
||||
if (dom) {
|
||||
|
||||
const PaperSheetView = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
}
|
||||
const dom_text = dom && dom.value ? dom.value : "";
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
** This gets the field, and finds the dom object and sees if
|
||||
** the user has typed something in. If so, it replaces,
|
||||
** the dom object, in txt with the value, spaces so it
|
||||
** fits the [] format and saves the value into a object
|
||||
** There may be ways to optimize this in javascript but
|
||||
** doing this in byond is nightmarish.
|
||||
**
|
||||
** It returns any values that were saved and a corrected
|
||||
** html code or null if nothing was updated
|
||||
*/
|
||||
const checkAllFields = (txt, font, color, user_name, bold=false) => {
|
||||
let matches;
|
||||
let values = {};
|
||||
let replace = [];
|
||||
// I know its tempting to wrap ALL this in a .replace
|
||||
// HOWEVER the user might not of entered anything
|
||||
// if thats the case we are rebuilding the entire string
|
||||
// for nothing, if nothing is entered, txt is just returned
|
||||
while ((matches = field_tag_regex.exec(txt)) !== null) {
|
||||
const full_match = matches[0];
|
||||
const id = matches.groups.id;
|
||||
if (id) {
|
||||
const dom = document.getElementById(id);
|
||||
// make sure we got data, and kill any html that might
|
||||
// be in it
|
||||
const dom_text = dom && dom.value ? dom.value : "";
|
||||
if (dom_text.length === 0) { continue; }
|
||||
const sanitized_text
|
||||
= DOMPurify.sanitize(
|
||||
dom.value.trim(), { ALLOWED_TAGS: [] });
|
||||
if (sanitized_text.length === 0) { continue; }
|
||||
// this is easyer than doing a bunch of text manipulations
|
||||
const target = dom.cloneNode(true);
|
||||
// in case they sign in a field
|
||||
if (sanitized_text.match(sign_regex)) {
|
||||
target.style.fontFamily = "Times New Roman";
|
||||
bold=true;
|
||||
target.defaultValue = user_name;
|
||||
} else {
|
||||
target.style.fontFamily = font;
|
||||
target.defaultValue = sanitized_text;
|
||||
}
|
||||
if (bold) {
|
||||
target.style.fontWeight = "bold";
|
||||
}
|
||||
target.style.color = color;
|
||||
|
||||
target.disabled = true;
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.appendChild(target);
|
||||
|
||||
values[id] = sanitized_text; // save the data
|
||||
|
||||
replace.push({ value: "[" + wrap.innerHTML + "]", raw_text: full_match });
|
||||
}
|
||||
}
|
||||
if (replace.length > 0) {
|
||||
for (const o of replace) {
|
||||
|
||||
txt = txt.replace(o.raw_text, o.value);
|
||||
}
|
||||
}
|
||||
return { text: txt, fields: values };
|
||||
};
|
||||
|
||||
|
||||
const pauseEvent = e => {
|
||||
if (e.stopPropagation) { e.stopPropagation(); }
|
||||
if (e.preventDefault) { e.preventDefault(); }
|
||||
e.cancelBubble=true;
|
||||
e.returnValue=false;
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
const Stamp = (props, context) => {
|
||||
const {
|
||||
paper_color = "white",
|
||||
pen_color = "black",
|
||||
text = '',
|
||||
} = data;
|
||||
const {
|
||||
value = text || '',
|
||||
image,
|
||||
opacity,
|
||||
...rest
|
||||
} = props;
|
||||
// We use this for caching so we don't keep refreshing it each time
|
||||
const [marked_text, setMarkedText] = useLocalState(context, 'marked_text',
|
||||
{ __html: run_marked_default(value) });
|
||||
|
||||
const matrix_trasform = 'rotate(' + image.rotate
|
||||
+ 'deg) translate(' + image.x + 'px,' + image.y + 'px)';
|
||||
const stamp_trasform = {
|
||||
'transform': matrix_trasform,
|
||||
'-ms-transform': matrix_trasform,
|
||||
'-webkit-transform': matrix_trasform,
|
||||
'opacity': opacity || 1.0,
|
||||
'position': 'absolute',
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
opacity={1}
|
||||
backgroundColor={paper_color}
|
||||
color={pen_color}
|
||||
{...rest}
|
||||
dangerouslySetInnerHTML={marked_text} />
|
||||
<div
|
||||
className={classes([
|
||||
'paper121x54',
|
||||
image.sprite,
|
||||
])}
|
||||
style={stamp_trasform}
|
||||
/>
|
||||
);
|
||||
};
|
||||
// If the prop dosn't exist OR its not true
|
||||
const isFalsyProperty = (obj, prop) => {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop) && !isFalsy(obj.prop);
|
||||
};
|
||||
|
||||
const setInputReadonly = (text, readonly) => {
|
||||
return readonly
|
||||
? text.replace(/<input\s[^d]/g, "<input disabled ")
|
||||
: text.replace(/<input\sdisabled\s/g, "<input ");
|
||||
};
|
||||
|
||||
// got to make this a full component if we
|
||||
// want to control updates
|
||||
const PaperSheetView = (props, context) => {
|
||||
const {
|
||||
value,
|
||||
stamps,
|
||||
backgroundColor,
|
||||
readOnly,
|
||||
...rest
|
||||
} = props;
|
||||
const readonly = !isFalsy(readOnly);
|
||||
const stamp_list = stamps || [];
|
||||
const text_html = { __html: "<span class='paper-text'>"
|
||||
+ setInputReadonly(value, readonly) + "</span>" };
|
||||
return (
|
||||
<Box position="relative"
|
||||
backgroundColor={backgroundColor} width="100%" height="100%" >
|
||||
<Box fillPositionedParent={1} width="100%" height="100%"
|
||||
dangerouslySetInnerHTML={text_html} p="10px" />
|
||||
{stamp_list.map((o, i) => (
|
||||
<Stamp key={o[0] + i}
|
||||
image={{ sprite: o[0], x: o[1], y: o[2], rotate: o[3] }} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PaperSheetEdit = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [text, setText] = useLocalState(context, 'text', data.text || '');
|
||||
const [marked_text, setMarkedText] = useLocalState(context, 'marked_text',
|
||||
{ __html: run_marked_default(text) });
|
||||
const [
|
||||
previewSelected,
|
||||
setPreviewSelected,
|
||||
] = useLocalState(context, 'preview', "Preview");
|
||||
const {
|
||||
paper_color = "white",
|
||||
pen_color = "black",
|
||||
} = data;
|
||||
const onInputHandler = (e, value) => {
|
||||
if (value.length < 1000) {
|
||||
setText(value);
|
||||
setMarkedText({ __html: run_marked_default(value) });
|
||||
} else {
|
||||
setText(value.substr(1000));
|
||||
}
|
||||
};
|
||||
// again, need the states for dragging and such
|
||||
class PaperSheetStamper extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotate: 0,
|
||||
};
|
||||
}
|
||||
findStampPosition(e) {
|
||||
const position = {
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column">
|
||||
<Flex.Item>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
key="marked_edit"
|
||||
textColor={'black'}
|
||||
backgroundColor={previewSelected === "Edit" ? "grey" : "white"}
|
||||
selected={previewSelected === "Edit"}
|
||||
onClick={() => setPreviewSelected("Edit")}>
|
||||
Edit
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
key="marked_preview"
|
||||
textColor={'black'}
|
||||
backgroundColor={previewSelected === "Preview" ? "grey" : "white"}
|
||||
selected={previewSelected === "Preview"}
|
||||
onClick={() => setPreviewSelected("Preview")}>
|
||||
Preview
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
key="marked_done"
|
||||
textColor={'black'}
|
||||
backgroundColor={previewSelected === "confirm"
|
||||
? "red"
|
||||
: previewSelected === "save"
|
||||
const offset = {
|
||||
left: e.target.offsetLeft,
|
||||
top: e.target.offsetTop,
|
||||
};
|
||||
|
||||
let reference = e.target.offsetParent;
|
||||
|
||||
while (reference) {
|
||||
offset.left += reference.offsetLeft;
|
||||
offset.top += reference.offsetTop;
|
||||
reference = reference.offsetParent;
|
||||
}
|
||||
|
||||
const pos_x = position.x - offset.left;
|
||||
const pos_y = position.y - offset.top;
|
||||
const pos = vecCreate(pos_x, pos_y);
|
||||
|
||||
const center_offset = vecCreate((121/2), (51/2));
|
||||
const center = vecSubtract(pos, center_offset);
|
||||
return center;
|
||||
}
|
||||
componentDidMount() {
|
||||
document.onwheel = this.handleWheel.bind(this);
|
||||
}
|
||||
handleMouseMove(e) {
|
||||
const pos = this.findStampPosition(e);
|
||||
// center offset of stamp
|
||||
pauseEvent(e);
|
||||
this.setState({ x: pos[0], y: pos[1] });
|
||||
}
|
||||
|
||||
handleMouseClick(e) {
|
||||
const pos = this.findStampPosition(e);
|
||||
const { act, data } = useBackend(this.context);
|
||||
act("stamp", { x: pos[0], y: pos[1], r: this.state.rotate });
|
||||
this.setState({ x: pos[0], y: pos[1] });
|
||||
}
|
||||
|
||||
handleWheel(e) {
|
||||
const rotate_amount = e.deltaY > 0 ? 15 : -15;
|
||||
if (e.deltaY < 0 && this.state.rotate === 0) {
|
||||
this.setState({ rotate: (360+rotate_amount) });
|
||||
} else if (e.deltaY > 0 && this.state.rotate === 360) {
|
||||
this.setState({ rotate: rotate_amount });
|
||||
} else {
|
||||
const rotate = { rotate: rotate_amount + this.state.rotate };
|
||||
this.setState(() => rotate);
|
||||
}
|
||||
pauseEvent(e);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
stamp_class,
|
||||
stamps,
|
||||
...rest
|
||||
} = this.props;
|
||||
const stamp_list = stamps || [];
|
||||
const current_pos = {
|
||||
sprite: stamp_class,
|
||||
x: this.state.x,
|
||||
y: this.state.y,
|
||||
rotate: this.state.rotate,
|
||||
};
|
||||
return (
|
||||
<Box onClick={this.handleMouseClick.bind(this)}
|
||||
onMouseMove={this.handleMouseMove.bind(this)}
|
||||
onwheel={this.handleWheel.bind(this)} {...rest}>
|
||||
<PaperSheetView
|
||||
readOnly={1}
|
||||
value={value}
|
||||
stamps={stamp_list} />
|
||||
<Stamp
|
||||
opacity={0.5} image={current_pos} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ugh. So have to turn this into a full
|
||||
// component too if I want to keep updates
|
||||
// low and keep the wierd flashing down
|
||||
class PaperSheetEdit extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
previewSelected: "Preview",
|
||||
old_text: props.value || "",
|
||||
textarea_text: "",
|
||||
combined_text: props.value || "",
|
||||
};
|
||||
}
|
||||
// This is the main rendering part, this creates the html from marked text
|
||||
// as well as the form fields
|
||||
createPreview(value, do_fields=false) {
|
||||
const { data } = useBackend(this.context);
|
||||
const {
|
||||
text,
|
||||
pen_color,
|
||||
pen_font,
|
||||
is_crayon,
|
||||
field_counter,
|
||||
edit_usr,
|
||||
} = data;
|
||||
const out = { text: text };
|
||||
// check if we are adding to paper, if not
|
||||
// we still have to check if somone entered something
|
||||
// into the fields
|
||||
value = value.trim();
|
||||
if (value.length > 0) {
|
||||
// First lets make sure it ends in a new line
|
||||
value += value[value.length] === "\n" ? " \n" : "\n \n";
|
||||
// Second, we sanatize the text of html
|
||||
const sanatized_text = sanatize_text(value);
|
||||
const signed_text = signDocument(sanatized_text, pen_color, edit_usr);
|
||||
// Third we replace the [__] with fields as markedjs fucks them up
|
||||
const fielded_text = createFields(signed_text
|
||||
, pen_font, 12, pen_color, field_counter);
|
||||
// Fourth, parse the text using markup
|
||||
const formated_text = run_marked_default(fielded_text.text);
|
||||
// Fifth, we wrap the created text in the pin color, and font.
|
||||
// crayon is bold (<b> tags), mabye make fountain pin italic?
|
||||
const fonted_text = setFontinText(formated_text
|
||||
, pen_font, pen_color, is_crayon);
|
||||
out.text += fonted_text;
|
||||
out.field_counter = fielded_text.counter;
|
||||
}
|
||||
if (do_fields) {
|
||||
// finaly we check all the form fields to see
|
||||
// if any data was entered by the user and
|
||||
// if it was return the data and modify
|
||||
// the text
|
||||
const final_processing = checkAllFields(out.text
|
||||
, pen_font, pen_color, edit_usr, is_crayon);
|
||||
out.text = final_processing.text;
|
||||
out.form_fields = final_processing.fields;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
onInputHandler(e, value) {
|
||||
if (value !== this.state.textarea_text) {
|
||||
const combined_length = this.state.old_text.length
|
||||
+ this.state.textarea_text.length;
|
||||
if (combined_length > MAX_PAPER_LENGTH) {
|
||||
if ((combined_length - MAX_PAPER_LENGTH) >= value.length) {
|
||||
value = ''; // basicly we cannot add any more text to the paper
|
||||
} else {
|
||||
value = value.substr(0, value.length
|
||||
- (combined_length - MAX_PAPER_LENGTH));
|
||||
}
|
||||
// we check again to save an update
|
||||
if (value === this.state.textarea_text) { return; }// do nooothing
|
||||
}
|
||||
this.setState(() => {
|
||||
return { textarea_text: value,
|
||||
combined_text: this.createPreview(value) }; });
|
||||
}
|
||||
}
|
||||
// the final update send to byond, final upkeep
|
||||
finalUpdate(new_text) {
|
||||
const { act } = useBackend(this.context);
|
||||
const final_processing = this.createPreview(new_text, true);
|
||||
act('save', final_processing);
|
||||
this.setState(() => { return {
|
||||
textarea_text: "",
|
||||
previewSelected: "save",
|
||||
combined_text: final_processing.text,
|
||||
}; });
|
||||
// byond should switch us to readonly mode from here
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value="",
|
||||
textColor,
|
||||
fontFamily,
|
||||
stamps,
|
||||
backgroundColor,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Flex direction="column" fillPositionedParent={1}>
|
||||
<Flex.Item>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
key="marked_edit"
|
||||
textColor={'black'}
|
||||
backgroundColor={this.state.previewSelected === "Edit"
|
||||
? "grey"
|
||||
: "white"}
|
||||
selected={previewSelected === "confirm"
|
||||
|| previewSelected === "save"}
|
||||
onClick={() => {
|
||||
if (previewSelected === "confirm") {
|
||||
act('save', { text: DOMPurify.sanitize(text) });
|
||||
} else {
|
||||
setPreviewSelected("confirm");
|
||||
}
|
||||
}}>
|
||||
{ previewSelected === "confirm" ? "confirm" : "save" }
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
selected={this.state.previewSelected === "Edit"}
|
||||
onClick={() => this.setState({ previewSelected: "Edit" })}>
|
||||
Edit
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
key="marked_preview"
|
||||
textColor={'black'}
|
||||
backgroundColor={this.state.previewSelected === "Preview"
|
||||
? "grey"
|
||||
: "white"}
|
||||
selected={this.state.previewSelected === "Preview"}
|
||||
onClick={() => this.setState(() => {
|
||||
const new_state = {
|
||||
previewSelected: "Preview",
|
||||
textarea_text: this.state.textarea_text,
|
||||
combined_text: this.createPreview(
|
||||
this.state.textarea_text).text,
|
||||
};
|
||||
return new_state;
|
||||
})}>
|
||||
Preview
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
key="marked_done"
|
||||
textColor={'black'}
|
||||
backgroundColor={this.state.previewSelected === "confirm"
|
||||
? "red"
|
||||
: this.state.previewSelected === "save"
|
||||
? "grey"
|
||||
: "white"}
|
||||
selected={this.state.previewSelected === "confirm"
|
||||
|| this.state.previewSelected === "save"}
|
||||
onClick={() => {
|
||||
if (this.state.previewSelected === "confirm") {
|
||||
this.finalUpdate(this.state.textarea_text);
|
||||
} else if (this.state.previewSelected === "Edit") {
|
||||
this.setState(() => {
|
||||
const new_state = {
|
||||
previewSelected: "confirm",
|
||||
textarea_text: this.state.textarea_text,
|
||||
combined_text: this.createPreview(
|
||||
this.state.textarea_text).text,
|
||||
};
|
||||
return new_state;
|
||||
});
|
||||
} else {
|
||||
this.setState({ previewSelected: "confirm" });
|
||||
}
|
||||
}}>
|
||||
{ this.state.previewSelected === "confirm" ? "confirm" : "save" }
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1}
|
||||
basis={1}>
|
||||
{this.state.previewSelected === "Edit" && (
|
||||
<TextArea
|
||||
value={this.state.textarea_text}
|
||||
textColor={textColor}
|
||||
fontFamily={fontFamily}
|
||||
height={(window.innerHeight - 80) + "px"}
|
||||
backgroundColor={backgroundColor}
|
||||
onInput={this.onInputHandler.bind(this)} />
|
||||
|
||||
) || (
|
||||
<PaperSheetView
|
||||
value={this.state.combined_text}
|
||||
stamps={stamps}
|
||||
fontFamily={fontFamily}
|
||||
textColor={textColor} />
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1}
|
||||
basis={1}>
|
||||
{previewSelected === "Edit" && (
|
||||
<TextArea
|
||||
value={text}
|
||||
backgroundColor="white"
|
||||
textColor="black"
|
||||
height={(window.innerHeight - 80)+ "px"}
|
||||
onInput={onInputHandler} />
|
||||
) || (
|
||||
<PaperSheetView />
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const PaperSheet = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const {
|
||||
edit_sheet,
|
||||
edit_mode,
|
||||
text,
|
||||
paper_color,
|
||||
pen_color = "black",
|
||||
pen_font = "Verdana",
|
||||
stamps,
|
||||
stamp_class,
|
||||
stamped,
|
||||
} = data;
|
||||
// You might ask why? Because Window/window content do wierd
|
||||
// css stuff with white for some reason
|
||||
const backgroundColor = paper_color && paper_color !== "white"
|
||||
? paper_color
|
||||
: "#FFFFFF";
|
||||
const background_style = {
|
||||
'background-color': backgroundColor,
|
||||
};
|
||||
const stamp_list = !stamps || stamps === null
|
||||
? []
|
||||
: stamps;
|
||||
|
||||
const decide_mode = mode => {
|
||||
switch (mode) {
|
||||
case 0: // min-height="100vh" min-width="100vw"
|
||||
return (<PaperSheetView
|
||||
value={text}
|
||||
stamps={stamp_list}
|
||||
readOnly={1} />);
|
||||
case 1:
|
||||
return (<PaperSheetEdit value={text}
|
||||
textColor={pen_color}
|
||||
fontFamily={pen_font}
|
||||
stamps={stamp_list}
|
||||
backgroundColor={backgroundColor}
|
||||
/>);
|
||||
case 2:
|
||||
return (<PaperSheetStamper value={text}
|
||||
stamps={stamp_list}
|
||||
stamp_class={stamp_class}
|
||||
/>);
|
||||
default:
|
||||
return "ERROR ERROR WE CANNOT BE HERE!!";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Window resizable theme="paper">
|
||||
<Window.Content scrollable>
|
||||
{edit_sheet && (
|
||||
<PaperSheetEdit />
|
||||
) || (
|
||||
<PaperSheetView fillPositionedParent />
|
||||
)}
|
||||
<Window resizable theme="paper" style={background_style}>
|
||||
<Window.Content min-height="100vh" min-width="100vw"
|
||||
style={background_style}>
|
||||
<Box fillPositionedParent={1} min-height="100vh"
|
||||
min-width="100vw" backgroundColor={backgroundColor}>
|
||||
{decide_mode(edit_mode)}
|
||||
</Box>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,8 +6,11 @@
|
||||
@use '../base.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
$text-color: #272727 !default;
|
||||
$border-color: #88bfff !default;
|
||||
$border-radius: base.$border-radius !default;
|
||||
$color-background: #bb9b68 !default;
|
||||
$color-border: #272727 !default;
|
||||
|
||||
.TextArea {
|
||||
overflow: hidden;
|
||||
@@ -17,9 +20,7 @@
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid rgba($border-color, 0.75);
|
||||
border-radius: $border-radius;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
background-color: $color-background;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
@@ -28,7 +29,7 @@
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
width:100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.TextArea_filler {
|
||||
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
|
||||
@@ -67,9 +68,8 @@
|
||||
height: 17px;
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
font-family: Verdana, sans-serif;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
@@ -91,6 +91,8 @@
|
||||
&:-ms-input-placeholder {
|
||||
font-style: italic;
|
||||
color: #777;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
/* color: rgba(255, 255, 255, 0.45);*/
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user