diff --git a/modular_ss220/camera_nanomap/README.md b/modular_ss220/camera_nanomap/README.md new file mode 100644 index 000000000000..a59c858b4fb3 --- /dev/null +++ b/modular_ss220/camera_nanomap/README.md @@ -0,0 +1,7 @@ +В этом модуле, для добавления наномапы в камеры, были затронуты НЕ модульно следующие файлы: +"NanoMap.js" +"NanoMap.scss" +"CameraConsole.scss" +"ByondUI.js" + +К сожалению, иной путь мог создать больше проблем в будущем чем такой топорный. diff --git a/modular_ss220/camera_nanomap/camera.dm b/modular_ss220/camera_nanomap/camera.dm new file mode 100644 index 000000000000..1d58dd99e9d4 --- /dev/null +++ b/modular_ss220/camera_nanomap/camera.dm @@ -0,0 +1,4 @@ +/datum/modpack/camera_nanomap + name = "Карта в терминале камер" + desc = "В названии всё сказано" + author = "Aylong220, RV666" diff --git a/modular_ss220/camera_nanomap/camera.dme b/modular_ss220/camera_nanomap/camera.dme new file mode 100644 index 000000000000..6c5dfc2f4a7c --- /dev/null +++ b/modular_ss220/camera_nanomap/camera.dme @@ -0,0 +1,3 @@ +#include "camera.dm" + +#include "code/camera.dm" diff --git a/modular_ss220/camera_nanomap/code/camera.dm b/modular_ss220/camera_nanomap/code/camera.dm new file mode 100644 index 000000000000..ef291d602cd9 --- /dev/null +++ b/modular_ss220/camera_nanomap/code/camera.dm @@ -0,0 +1,54 @@ +/obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + // Update UI + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + if(!ui) + var/user_uid = user.UID() + var/is_living = isliving(user) + // Ghosts shouldn't count towards concurrent users, which produces + // an audible terminal_on click. + if(is_living) + watchers += user_uid + // Turn on the console + if(length(watchers) == 1 && is_living) + if(!silent_console) + playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) + use_power(active_power_consumption) + // Register map objects + user.client.register_map_obj(cam_screen) + for(var/plane in cam_plane_masters) + user.client.register_map_obj(plane) + user.client.register_map_obj(cam_background) + // Open UI + ui = new(user, src, ui_key, "CameraConsole220", name, 1170, 755, master_ui, state) + ui.open() + +/obj/machinery/computer/security/ui_data() + var/list/data = list() + data["network"] = network + data["activeCamera"] = null + if(active_camera) + data["activeCamera"] = list( + name = active_camera.c_tag, + status = active_camera.status, + ) + var/list/cameras = get_available_cameras() + data["cameras"] = list() + for(var/i in cameras) + var/obj/machinery/camera/C = cameras[i] + data["cameras"] += list(list( + name = C.c_tag, + x = C.x, + y = C.y, + z = C.z, + status = C.status + )) + return data + +/obj/machinery/computer/security/ui_static_data() + var/list/data = list() + data["mapRef"] = map_name + data["stationLevel"] = level_name_to_num(MAIN_STATION) + return data diff --git a/modular_ss220/modular_ss220.dme b/modular_ss220/modular_ss220.dme index d61bbd588a83..c3bf96c79f69 100644 --- a/modular_ss220/modular_ss220.dme +++ b/modular_ss220/modular_ss220.dme @@ -38,6 +38,7 @@ #include "agent_id_tgui/_agent_id_tgui.dme" #include "balance/_balance.dme" #include "bureaucracy/_bureaucracy.dme" +#include "camera_nanomap/camera.dme" #include "cinematics/_cinematics.dme" #include "closet_picklocking/_closet_picklocking.dme" #include "crawl_speed/_crawl_speed.dme" diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.js index 7bd3cf884986..a5e59bc113ea 100644 --- a/tgui/packages/tgui/components/ByondUi.js +++ b/tgui/packages/tgui/components/ByondUi.js @@ -56,7 +56,7 @@ window.addEventListener('beforeunload', () => { /** * Get the bounding box of the DOM element. */ -const getBoundingBox = (element) => { +export const getBoundingBox = element => { // SS220 EDIT - ORIGINAL: const getBoundingBox = element => { const rect = element.getBoundingClientRect(); return { pos: [rect.left, rect.top], diff --git a/tgui/packages/tgui/components/NanoMap.js b/tgui/packages/tgui/components/NanoMap.js index 42a31b4db238..6f9218d46ed0 100644 --- a/tgui/packages/tgui/components/NanoMap.js +++ b/tgui/packages/tgui/components/NanoMap.js @@ -1,8 +1,9 @@ import { Component } from 'inferno'; -import { Box, Icon, Tooltip } from '.'; +import { Box, Icon, Tooltip, Button } from '.'; import { useBackend } from '../backend'; import { LabeledList } from './LabeledList'; import { Slider } from './Slider'; +import { getBoundingBox } from "./ByondUi"; const pauseEvent = (e) => { if (e.stopPropagation) { @@ -25,8 +26,8 @@ export class NanoMap extends Component { const Ycenter = window.innerHeight / 2 - 256; this.state = { - offsetX: 128, - offsetY: 48, + offsetX: Xcenter, + offsetY: Ycenter, transform: 'none', dragging: false, originX: null, @@ -79,10 +80,22 @@ export class NanoMap extends Component { this.handleZoom = (_e, value) => { this.setState((state) => { const newZoom = Math.min(Math.max(value, 1), 8); - let zoomDiff = (newZoom - state.zoom) * 1.5; + const zoomDiff = newZoom / state.zoom; + if (zoomDiff === 1) { + return; + } + state.zoom = newZoom; - state.offsetX = state.offsetX - 262 * zoomDiff; - state.offsetY = state.offsetY - 256 * zoomDiff; + + const container = document.getElementsByClassName('NanoMap__container'); + if (container.length) { + const bounds = getBoundingBox(container[0]); + const currentCenterX = bounds.size[0] / 2 - state.offsetX; + const currentCenterY = bounds.size[1] / 2 - state.offsetY; + state.offsetX += currentCenterX - (currentCenterX * zoomDiff); + state.offsetY += currentCenterY - (currentCenterY * zoomDiff); + } + if (props.onZoom) { props.onZoom(state.zoom); } @@ -127,8 +140,17 @@ export class NanoMap extends Component { } } -const NanoMapMarker = (props, context) => { - const { x, y, zoom = 1, icon, tooltip, color } = props; +const NanoMapMarker = props => { + const { + x, + y, + zoom = 1, + icon, + tooltip, + color, + onClick, + size = 6, + } = props; const rx = x * 2 * zoom - zoom - 3; const ry = y * 2 * zoom - zoom - 3; return ( @@ -137,19 +159,70 @@ const NanoMapMarker = (props, context) => { position="absolute" className="NanoMap__marker" lineHeight="0" - bottom={ry + 'px'} - left={rx + 'px'} - > - + bottom={ry + "px"} + left={rx + "px"} + onClick={onClick}> + ); }; +let ActiveButton; +class NanoButton extends Component { + constructor(props) { + super(props); + const { act } = useBackend(this.props.context); + this.state = { + color: this.props.color, + }; + this.handleClick = e => { + if (ActiveButton !== undefined) { + ActiveButton.setState({ + color: "blue", + }); + } + act('switch_camera', { + name: this.props.name, + }); + ActiveButton = this; + this.setState({ + color: "green", + }); + }; + } + render() { + let rx = ((this.props.x * 2 * this.props.zoom) - this.props.zoom) - 3; + let ry = ((this.props.y * 2 * this.props.zoom) - this.props.zoom) - 3; + + return ( + + + + + ); + } +} +NanoMap.NanoButton = NanoButton; NanoMap.Marker = NanoMapMarker; -const NanoMapZoomer = (props, context) => { + +const NanoMapZoomer = props => { return ( diff --git a/tgui/packages/tgui/interfaces/CameraConsole220.js b/tgui/packages/tgui/interfaces/CameraConsole220.js new file mode 100644 index 000000000000..4892b13f85cb --- /dev/null +++ b/tgui/packages/tgui/interfaces/CameraConsole220.js @@ -0,0 +1,227 @@ +import { filter, sortBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { classes } from 'common/react'; +import { createSearch } from 'common/string'; +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Button, ByondUi, Input, Section, Box, NanoMap, Tabs, Icon } from '../components'; +import { refocusLayout, Window } from '../layouts'; + +/** + * Returns previous and next camera names relative to the currently + * active camera. + */ +const prevNextCamera = (cameras, activeCamera) => { + if (!activeCamera) { + return []; + } + const index = cameras.findIndex(camera => ( + camera.name === activeCamera.name + )); + return [ + cameras[index - 1]?.name, + cameras[index + 1]?.name, + ]; +}; + +/** + * Camera selector. + * + * Filters cameras, applies search terms and sorts the alphabetically. + */ +const selectCameras = (cameras, searchText = '') => { + const testSearch = createSearch(searchText, camera => camera.name); + return flow([ + // Null camera filter + filter(camera => camera?.name), + // Optional search term + searchText && filter(testSearch), + // Slightly expensive, but way better than sorting in BYOND + sortBy(camera => camera.name), + ])(cameras); +}; + +export const CameraConsole220 = (props, context) => { + const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0); + const decideTab = index => { + switch (index) { + case 0: + return ; + case 1: + return ; + default: + return "WE SHOULDN'T BE HERE!"; + } + }; + + return ( + + + + + setTabIndex(0)}> + Карта + + setTabIndex(1)}> + Список + + + {decideTab(tabIndex)} + + + + ); +}; + +export const CameraConsoleMapContent = (props, context) => { + const { act, data, config } = useBackend(context); + const cameras = selectCameras(data.cameras); + const [zoom, setZoom] = useLocalState(context, 'zoom', 1); + const { mapRef, activeCamera, stationLevel } = data; + const [prevCameraName, nextCameraName] = prevNextCamera(cameras, activeCamera); + return ( + + + setZoom(v)}> + {cameras.filter(cam => cam.z === stationLevel).map(cm => ( + + ))} + + + + + + Камера: + {activeCamera + && activeCamera.name + || '—'} + + + act('switch_camera', { + name: prevCameraName, + })} /> + act('switch_camera', { + name: nextCameraName, + })} /> + + + + + + ); +}; + +export const CameraConsoleOldContent = (props, context) => { + const { act, data, config } = useBackend(context); + const { mapRef, activeCamera } = data; + const [ + searchText, + setSearchText, + ] = useLocalState(context, 'searchText', ''); + const cameras = selectCameras(data.cameras, searchText); + const [ + prevCameraName, + nextCameraName, + ] = prevNextCamera(cameras, activeCamera); + return ( + + + + + setSearchText(value)} /> + + {cameras.map(camera => ( + // We're not using the component here because performance + // would be absolutely abysmal (50+ ms for each re-render). + { + refocusLayout(); + act('switch_camera', { + name: camera.name, + }); + }}> + {camera.name} + + ))} + + + + + + + Камера: + {activeCamera + && activeCamera.name + || '—'} + + + act('switch_camera', { + name: prevCameraName, + })} /> + act('switch_camera', { + name: nextCameraName, + })} /> + + + + + ); +}; diff --git a/tgui/packages/tgui/styles/components/NanoMap.scss b/tgui/packages/tgui/styles/components/NanoMap.scss index 69d33473c5a0..13b581238da7 100644 --- a/tgui/packages/tgui/styles/components/NanoMap.scss +++ b/tgui/packages/tgui/styles/components/NanoMap.scss @@ -12,6 +12,18 @@ $color-background: rgba(0, 0, 0, 0.33) !default; margin: 0px; } +// SS220 ADDITION - START +.NanoMap__button{ + padding: 3px 3px; + font-size: 12px; + border: 2px solid black; +} + +.NanoMap__button:hover { + background-color: greenyellow; +} +// SS220 ADDITION - END + .NanoMap__zoomer { z-index: 20; background-color: $color-background; diff --git a/tgui/packages/tgui/styles/interfaces/CameraConsole.scss b/tgui/packages/tgui/styles/interfaces/CameraConsole.scss index f5239ff6870d..9d61ca2ba1ab 100644 --- a/tgui/packages/tgui/styles/interfaces/CameraConsole.scss +++ b/tgui/packages/tgui/styles/interfaces/CameraConsole.scss @@ -2,7 +2,7 @@ $color-background: rgba(0, 0, 0, 0.33) !default; .CameraConsole__left { position: absolute; - top: 0; + top: 23px; bottom: 0; left: 0; width: 220px; @@ -17,36 +17,27 @@ $color-background: rgba(0, 0, 0, 0.33) !default; background-color: $color-background; } -.CameraConsole__toolbar { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 24px; - line-height: 24px; - margin: 3px 12px 0; - // background-color: #0a0; +.CameraConsole__new__right { + position: relative; + display: flex; + flex: 1; + height: 90%; + flex-direction: column; + background-color: $color-background; } -.CameraConsole__toolbarRight { - position: absolute; - top: 0; - right: 0; +.CameraConsole__header { + display: flex; + justify-content: space-between; height: 24px; line-height: 24px; margin: 4px 6px 0; - // background-color: #aa0; } .CameraConsole__map { - position: absolute; - top: 26px; - bottom: 0; - left: 0; - right: 0; - // background-color: #00a; - margin: 6px; - text-align: center; + display: flex; + overflow: hidden; + background-color: #00a; .NoticeBox { margin-top: calc(50% - 24px);