diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 33879680bd..84709a78b1 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -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]") diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index b0a22f1d54..e9f98f836e 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -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) diff --git a/code/game/world.dm b/code/game/world.dm index 5d54c92661..c60fb5c4d6 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -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" diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 2747932907..acae918d8b 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -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) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index dcce95f448..8dd814c604 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -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, "", assets.css_tag()) + /obj/item/paper/verb/rename() set name = "Rename paper" set category = "Object" @@ -132,13 +150,17 @@ user.visible_message("[user] scratches a grid on [user.p_their()] wrist with the paper! It looks like [user.p_theyre()] trying to commit sudoku...") 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, "This sheet of paper has already been written too!") + if(length(info) >= MAX_PAPER_LENGTH) // Sheet must have less than 1000 charaters + to_chat(user, "This sheet of paper is full!") 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, "[edit_usr] is already working on this sheet!") + 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, "[edit_usr] is already working on this sheet!") 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, "You stamp the paper with your rubber stamp.") + to_chat(user, "You ready your stamp over the paper! ") + 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("[user] accidentally ignites [user.p_them()]self!", \ "You miss the paper and accidentally light yourself on fire!") @@ -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("[user] lights [src] ablaze with [P]!", "You light [src] on fire!") 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("[edit_usr] stamps [src] with [current_stamp]!", "You stamp [src] with [current_stamp]!") + 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, "[edit_usr]") + // 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], "[edit_usr]") + + /// 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 diff --git a/tgui/packages/tgui/interfaces/Canister.js b/tgui/packages/tgui/interfaces/Canister.js index 4e21b331f8..e369f798cd 100644 --- a/tgui/packages/tgui/interfaces/Canister.js +++ b/tgui/packages/tgui/interfaces/Canister.js @@ -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 ( - - The regulator {hasHoldingTank ? 'is' : 'is not'} connected - to a tank. -
act('relabel')} /> + + {!!isPrototype && ( +
{ - 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 "" + text + ""; +}; + +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 = /\[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 "[]"; +}; + +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 ( - +
+ ); +}; +// 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(/ { + const { + value, + stamps, + backgroundColor, + readOnly, + ...rest + } = props; + const readonly = !isFalsy(readOnly); + const stamp_list = stamps || []; + const text_html = { __html: "" + + setInputReadonly(value, readonly) + "" }; + return ( + + + {stamp_list.map((o, i) => ( + + ))} + ); }; -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 ( - - - - setPreviewSelected("Edit")}> - Edit - - setPreviewSelected("Preview")}> - Preview - - 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 ( + + + + + ); + } +} + +// 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 ( 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 ( + + + + { - if (previewSelected === "confirm") { - act('save', { text: DOMPurify.sanitize(text) }); - } else { - setPreviewSelected("confirm"); - } - }}> - { previewSelected === "confirm" ? "confirm" : "save" } - - + selected={this.state.previewSelected === "Edit"} + onClick={() => this.setState({ previewSelected: "Edit" })}> + Edit + + 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 + + { + 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" } + + + + + + {this.state.previewSelected === "Edit" && ( +