mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-29 06:57:42 +01:00
NanoMap QoL Changes (#25487)
* Introduce constants for map size and pixes per turf * Scale markers with zoom * Remove unused state * Center map by default * Fix zooming offset * Add view reset button * Fix exaggerated dragging when zoomed * Remove zoom from local state * Rewrite centering code * Allow opening air alarms from map view * Rename Marker to MarkerIcon * Factor out generic map marker * Implement name highlighting * Fix oversight * Save settings across UI opens * Do not sanitize air alarm names They are already automatically sanitized by Inferno * Build and update tgui * force ci * Make labelStyle optional * Make labelStyle optional again * [ci skip] * Build and update /tg/ui * Reformat /tg/ui * Autodec variables [ci skip] --------- Co-authored-by: Arthri <41360489+a@users.noreply.github.com> Co-authored-by: /tg/ui Builder <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
co-authored by
Arthri
/tg/ui Builder <41898282+github-actions[bot]@users.noreply.github.com>
Burzah
parent
2f91dcb5df
commit
69e7b890a5
@@ -654,7 +654,7 @@
|
||||
/obj/machinery/alarm/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["name"] = sanitize(name)
|
||||
data["name"] = name
|
||||
data["air"] = ui_air_status()
|
||||
data["alarmActivated"] = alarmActivated || danger_level == ATMOS_ALARM_DANGER
|
||||
data["thresholds"] = generate_thresholds_menu()
|
||||
@@ -694,7 +694,7 @@
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/P as anything in alarm_area.vents)
|
||||
var/list/vent_info = list()
|
||||
vent_info["id_tag"] = P.UID()
|
||||
vent_info["name"] = sanitize(P.name)
|
||||
vent_info["name"] = P.name
|
||||
vent_info["power"] = P.on
|
||||
vent_info["direction"] = P.releasing
|
||||
vent_info["checks"] = P.pressure_checks
|
||||
@@ -707,7 +707,7 @@
|
||||
for(var/obj/machinery/atmospherics/unary/vent_scrubber/S as anything in alarm_area.scrubbers)
|
||||
var/list/scrubber_info = list()
|
||||
scrubber_info["id_tag"] = S.UID()
|
||||
scrubber_info["name"] = sanitize(S.name)
|
||||
scrubber_info["name"] = S.name
|
||||
scrubber_info["power"] = S.on
|
||||
scrubber_info["scrubbing"] = S.scrubbing
|
||||
scrubber_info["widenet"] = S.widenet
|
||||
@@ -722,11 +722,11 @@
|
||||
|
||||
/obj/machinery/alarm/proc/get_console_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["name"] = sanitize(name)
|
||||
data["name"] = name
|
||||
data["ref"] = "\ref[src]"
|
||||
data["danger"] = max(danger_level, alarm_area.atmosalm)
|
||||
var/area/A = get_area(src)
|
||||
data["area"] = sanitize(A.name)
|
||||
data["area"] = A.name
|
||||
var/turf/T = get_turf(src)
|
||||
data["x"] = T.x
|
||||
data["y"] = T.y
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
#define MIN_ZOOM 1
|
||||
#define MAX_ZOOM 8
|
||||
#define MIN_TAB_INDEX 0
|
||||
#define MAX_TAB_INDEX 1
|
||||
|
||||
/datum/ui_module/crew_monitor
|
||||
name = "Crew monitor"
|
||||
var/is_advanced = FALSE
|
||||
var/viewing_current_z_level
|
||||
/// If true, we'll see everyone, regardless of their suit sensors.
|
||||
var/ignore_sensors = FALSE
|
||||
/// The ID of the currently opened UI tab
|
||||
var/tab_index = 0
|
||||
/// The zoom level of the UI map view
|
||||
var/zoom = 1
|
||||
/// The X offset of the UI map
|
||||
var/offset_x = 0
|
||||
/// The Y offset of the UI map
|
||||
var/offset_y = 0
|
||||
/// A list of displayed names. Displayed names were intentionally chosen over ckeys,
|
||||
/// refs, or uids, because exposing any of the aforementioned to the client could allow
|
||||
/// an exploit to detect changelings on sensors.
|
||||
var/highlighted_names = list()
|
||||
|
||||
/datum/ui_module/crew_monitor/ui_act(action, params)
|
||||
if(..())
|
||||
@@ -31,6 +48,33 @@
|
||||
if(!is_advanced)
|
||||
return
|
||||
viewing_current_z_level = text2num(params["new_level"])
|
||||
if("set_tab_index")
|
||||
var/new_tab_index = text2num(params["tab_index"])
|
||||
if(isnull(new_tab_index) || new_tab_index < MIN_TAB_INDEX || new_tab_index > MAX_TAB_INDEX)
|
||||
return
|
||||
tab_index = new_tab_index
|
||||
if("set_zoom")
|
||||
var/new_zoom = text2num(params["zoom"])
|
||||
if(isnull(new_zoom) || new_zoom < MIN_ZOOM || new_zoom > MAX_ZOOM)
|
||||
return
|
||||
zoom = new_zoom
|
||||
if("set_offset")
|
||||
var/new_offset_x = text2num(params["offset_x"])
|
||||
var/new_offset_y = text2num(params["offset_y"])
|
||||
if(isnull(new_offset_x) || isnull(new_offset_y))
|
||||
return
|
||||
offset_x = new_offset_x
|
||||
offset_y = new_offset_y
|
||||
if("add_highlighted_name")
|
||||
// Intentionally not sanitized as the name is not used for rendering
|
||||
var/name = params["name"]
|
||||
highlighted_names += list(name)
|
||||
if("remove_highlighted_name")
|
||||
// Intentionally not sanitized as the name is not used for rendering
|
||||
var/name = params["name"]
|
||||
highlighted_names -= list(name)
|
||||
if("clear_highlighted_names")
|
||||
highlighted_names = list()
|
||||
|
||||
/datum/ui_module/crew_monitor/ui_state(mob/user)
|
||||
return GLOB.default_state
|
||||
@@ -56,11 +100,16 @@
|
||||
viewing_current_z_level = level_name_to_num(MAIN_STATION) // by default, set it to the station
|
||||
|
||||
data["viewing_current_z_level"] = viewing_current_z_level
|
||||
data["tabIndex"] = tab_index
|
||||
data["zoom"] = zoom
|
||||
data["offsetX"] = offset_x
|
||||
data["offsetY"] = offset_y
|
||||
|
||||
data["isAI"] = isAI(user)
|
||||
data["isObserver"] = isobserver(user)
|
||||
data["ignoreSensors"] = ignore_sensors
|
||||
data["crewmembers"] = GLOB.crew_repository.health_data(viewing_current_z_level, ignore_sensors)
|
||||
data["highlightedNames"] = highlighted_names
|
||||
data["critThreshold"] = HEALTH_THRESHOLD_CRIT
|
||||
|
||||
return data
|
||||
@@ -82,3 +131,8 @@
|
||||
|
||||
/datum/ui_module/crew_monitor/ghost/ui_state(mob/user)
|
||||
return GLOB.observer_state
|
||||
|
||||
#undef MIN_ZOOM
|
||||
#undef MAX_ZOOM
|
||||
#undef MIN_TAB_INDEX
|
||||
#undef MAX_TAB_INDEX
|
||||
|
||||
@@ -33,6 +33,7 @@ type LabeledListItemProps = {
|
||||
/** @deprecated */
|
||||
content?: any;
|
||||
children?: InfernoNode;
|
||||
labelStyle?: Record<string | symbol, any>;
|
||||
};
|
||||
|
||||
const LabeledListItem = (props: LabeledListItemProps) => {
|
||||
@@ -47,10 +48,16 @@ const LabeledListItem = (props: LabeledListItemProps) => {
|
||||
content,
|
||||
children,
|
||||
preserveWhitespace,
|
||||
labelStyle,
|
||||
} = props;
|
||||
let listItem = (
|
||||
<tr className={classes(['LabeledList__row', className])}>
|
||||
<Box as="td" color={labelColor} className={classes(['LabeledList__cell', 'LabeledList__label'])}>
|
||||
<Box
|
||||
as="td"
|
||||
color={labelColor}
|
||||
className={classes(['LabeledList__cell', 'LabeledList__label'])}
|
||||
style={labelStyle}
|
||||
>
|
||||
{label ? label + ':' : null}
|
||||
</Box>
|
||||
<Box
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Component } from 'inferno';
|
||||
import { Box, Icon, Tooltip } from '.';
|
||||
import { Box, Button, Flex, Icon, Tooltip } from '.';
|
||||
import { useBackend } from '../backend';
|
||||
import { LabeledList } from './LabeledList';
|
||||
import { Slider } from './Slider';
|
||||
import { resolveAsset } from '../assets';
|
||||
|
||||
const MAP_SIZE = 510;
|
||||
/** At zoom = 1 */
|
||||
const PIXELS_PER_TURF = 2;
|
||||
|
||||
const pauseEvent = (e) => {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
@@ -26,13 +30,12 @@ export class NanoMap extends Component {
|
||||
const Ycenter = window.innerHeight / 2 - 256;
|
||||
|
||||
this.state = {
|
||||
offsetX: 128,
|
||||
offsetY: 48,
|
||||
transform: 'none',
|
||||
offsetX: props.offsetX ?? 0,
|
||||
offsetY: props.offsetY ?? 0,
|
||||
dragging: false,
|
||||
originX: null,
|
||||
originY: null,
|
||||
zoom: 1,
|
||||
zoom: props.zoom ?? 1,
|
||||
};
|
||||
|
||||
// Dragging
|
||||
@@ -54,13 +57,14 @@ export class NanoMap extends Component {
|
||||
const newOffsetX = e.screenX - state.originX;
|
||||
const newOffsetY = e.screenY - state.originY;
|
||||
if (prevState.dragging) {
|
||||
state.offsetX += newOffsetX;
|
||||
state.offsetY += newOffsetY;
|
||||
state.offsetX += newOffsetX / state.zoom;
|
||||
state.offsetY += newOffsetY / state.zoom;
|
||||
state.originX = e.screenX;
|
||||
state.originY = e.screenY;
|
||||
} else {
|
||||
state.dragging = true;
|
||||
}
|
||||
props.onOffsetChange?.(e, state);
|
||||
return state;
|
||||
});
|
||||
pauseEvent(e);
|
||||
@@ -80,16 +84,31 @@ 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;
|
||||
state.zoom = newZoom;
|
||||
state.offsetX = state.offsetX - 262 * zoomDiff;
|
||||
state.offsetY = state.offsetY - 256 * zoomDiff;
|
||||
if (props.onZoom) {
|
||||
props.onZoom(state.zoom);
|
||||
}
|
||||
return state;
|
||||
});
|
||||
};
|
||||
|
||||
this.handleReset = (e) => {
|
||||
this.setState((state) => {
|
||||
state.offsetX = 0;
|
||||
state.offsetY = 0;
|
||||
state.zoom = 1;
|
||||
this.handleZoom(e, 1);
|
||||
props.onOffsetChange?.(e, state);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
map: {
|
||||
zoom: this.state.zoom,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -98,14 +117,17 @@ export class NanoMap extends Component {
|
||||
const { children } = this.props;
|
||||
|
||||
const mapUrl = config.map + '_nanomap_z1.png';
|
||||
const mapSize = 510 * zoom + 'px';
|
||||
const mapSize = MAP_SIZE * zoom + 'px';
|
||||
const newStyle = {
|
||||
width: mapSize,
|
||||
height: mapSize,
|
||||
'margin-top': offsetY + 'px',
|
||||
'margin-left': offsetX + 'px',
|
||||
'margin-top': offsetY * zoom + 'px',
|
||||
'margin-left': offsetX * zoom + 'px',
|
||||
'overflow': 'hidden',
|
||||
'position': 'relative',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
'background-size': 'cover',
|
||||
'background-repeat': 'no-repeat',
|
||||
'text-align': 'center',
|
||||
@@ -128,21 +150,35 @@ export class NanoMap extends Component {
|
||||
<img src={resolveAsset(mapUrl)} style={mapStyle} />
|
||||
<Box>{children}</Box>
|
||||
</Box>
|
||||
<NanoMapZoomer zoom={zoom} onZoom={this.handleZoom} />
|
||||
<NanoMapZoomer zoom={zoom} onZoom={this.handleZoom} onReset={this.handleReset} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NanoMapMarker = (props, context) => {
|
||||
const { x, y, zoom = 1, icon, tooltip, color } = props;
|
||||
const rx = x * 2 * zoom - zoom - 3;
|
||||
const ry = y * 2 * zoom - zoom - 3;
|
||||
const {
|
||||
map: { zoom },
|
||||
} = context;
|
||||
const { x, y, icon, tooltip, color, children, ...rest } = props;
|
||||
const pixelsPerTurfAtZoom = PIXELS_PER_TURF * zoom;
|
||||
// For some reason the X and Y are offset by 1
|
||||
const rx = (x - 1) * pixelsPerTurfAtZoom;
|
||||
const ry = (y - 1) * pixelsPerTurfAtZoom;
|
||||
return (
|
||||
<div>
|
||||
<Tooltip content={tooltip}>
|
||||
<Box position="absolute" className="NanoMap__marker" lineHeight="0" bottom={ry + 'px'} left={rx + 'px'}>
|
||||
<Icon name={icon} color={color} fontSize="6px" />
|
||||
<Box
|
||||
position="absolute"
|
||||
className="NanoMap__marker"
|
||||
lineHeight="0"
|
||||
bottom={ry + 'px'}
|
||||
left={rx + 'px'}
|
||||
width={pixelsPerTurfAtZoom + 'px'}
|
||||
height={pixelsPerTurfAtZoom + 'px'}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -151,19 +187,47 @@ const NanoMapMarker = (props, context) => {
|
||||
|
||||
NanoMap.Marker = NanoMapMarker;
|
||||
|
||||
const NanoMapMarkerIcon = (props, context) => {
|
||||
const {
|
||||
map: { zoom },
|
||||
} = context;
|
||||
const { icon, color, ...rest } = props;
|
||||
const markerSize = PIXELS_PER_TURF * zoom + 4 / Math.ceil(zoom / 4);
|
||||
return (
|
||||
<NanoMapMarker {...rest}>
|
||||
<Icon
|
||||
name={icon}
|
||||
color={color}
|
||||
fontSize={`${markerSize}px`}
|
||||
style={{
|
||||
position: 'relative',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
/>
|
||||
</NanoMapMarker>
|
||||
);
|
||||
};
|
||||
|
||||
NanoMap.MarkerIcon = NanoMapMarkerIcon;
|
||||
|
||||
const NanoMapZoomer = (props, context) => {
|
||||
return (
|
||||
<Box className="NanoMap__zoomer">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Zoom">
|
||||
<Slider
|
||||
minValue={1}
|
||||
maxValue={8}
|
||||
stepPixelSize={10}
|
||||
format={(v) => v + 'x'}
|
||||
value={props.zoom}
|
||||
onDrag={(e, v) => props.onZoom(e, v)}
|
||||
/>
|
||||
<LabeledList.Item label="Zoom" labelStyle={{ 'vertical-align': 'middle' }}>
|
||||
<Flex direction="row">
|
||||
<Slider
|
||||
minValue={1}
|
||||
maxValue={8}
|
||||
stepPixelSize={10}
|
||||
format={(v) => v + 'x'}
|
||||
value={props.zoom}
|
||||
onDrag={(e, v) => props.onZoom(e, v)}
|
||||
/>
|
||||
<Button ml="0.5em" float="right" icon="sync" tooltip="Reset View" onClick={(e) => props.onReset?.(e)} />
|
||||
</Flex>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Box>
|
||||
|
||||
@@ -102,24 +102,23 @@ const AtmosControlDataView = (_properties, context) => {
|
||||
};
|
||||
|
||||
const AtmosControlMapView = (_properties, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const [zoom, setZoom] = useLocalState(context, 'zoom', 1);
|
||||
const { act, data } = useBackend(context);
|
||||
const { alarms } = data;
|
||||
return (
|
||||
<Box height="526px" mb="0.5rem" overflow="hidden">
|
||||
<NanoMap onZoom={(v) => setZoom(v)}>
|
||||
<NanoMap>
|
||||
{alarms
|
||||
.filter((a) => a.z === 2)
|
||||
.map((aa) => (
|
||||
// The AA means air alarm, and nothing else
|
||||
<NanoMap.Marker
|
||||
<NanoMap.MarkerIcon
|
||||
key={aa.ref}
|
||||
x={aa.x}
|
||||
y={aa.y}
|
||||
zoom={zoom}
|
||||
icon="circle"
|
||||
tooltip={aa.name}
|
||||
color={getStatusColour(aa.danger)}
|
||||
onClick={() => act('open_alarm', { aref: aa.ref })}
|
||||
/>
|
||||
))}
|
||||
</NanoMap>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Box, Button, Dropdown, Input, NanoMap, Section, Stack, Table, Tabs } fr
|
||||
import { TableCell } from '../components/Table';
|
||||
import { COLORS } from '../constants';
|
||||
import { Window } from '../layouts';
|
||||
import { ButtonCheckbox } from '../components/Button';
|
||||
|
||||
const getStatText = (cm, critThreshold) => {
|
||||
if (cm.dead) {
|
||||
@@ -38,7 +39,11 @@ const getStatColor = (cm, critThreshold) => {
|
||||
|
||||
export const CrewMonitor = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
|
||||
const [tabIndex, setTabIndexInternal] = useLocalState(context, 'tabIndex', data.tabIndex);
|
||||
const setTabIndex = (index) => {
|
||||
setTabIndexInternal(index);
|
||||
act('set_tab_index', { tab_index: index });
|
||||
};
|
||||
const decideTab = (index) => {
|
||||
switch (index) {
|
||||
case 0:
|
||||
@@ -74,7 +79,7 @@ export const CrewMonitor = (props, context) => {
|
||||
const CrewMonitorDataView = (_properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const crew = sortBy((cm) => cm.name)(data.crewmembers || []);
|
||||
const { possible_levels, viewing_current_z_level, is_advanced } = data;
|
||||
const { possible_levels, viewing_current_z_level, is_advanced, highlightedNames } = data;
|
||||
const [search, setSearch] = useLocalState(context, 'search', '');
|
||||
const searcher = createSearch(search, (cm) => {
|
||||
return cm.name + '|' + cm.assignment + '|' + cm.area;
|
||||
@@ -103,96 +108,152 @@ const CrewMonitorDataView = (_properties, context) => {
|
||||
</Stack>
|
||||
<Table m="0.5rem">
|
||||
<Table.Row header>
|
||||
<Table.Cell>
|
||||
<Button tooltip="Clear highlights" icon="square-xmark" onClick={() => act('clear_highlighted_names')} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>Name</Table.Cell>
|
||||
<Table.Cell>Status</Table.Cell>
|
||||
<Table.Cell>Location</Table.Cell>
|
||||
</Table.Row>
|
||||
{crew.filter(searcher).map((cm) => (
|
||||
<Table.Row key={cm.name} bold={!!cm.is_command}>
|
||||
<TableCell>
|
||||
{cm.name} ({cm.assignment})
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box inline color={getStatColor(cm, data.critThreshold)}>
|
||||
{getStatText(cm, data.critThreshold)}
|
||||
</Box>
|
||||
{cm.sensor_type >= 2 || data.ignoreSensors ? (
|
||||
<Box inline ml={1}>
|
||||
{'('}
|
||||
<Box inline color={COLORS.damageType.oxy}>
|
||||
{cm.oxy}
|
||||
</Box>
|
||||
{'|'}
|
||||
<Box inline color={COLORS.damageType.toxin}>
|
||||
{cm.tox}
|
||||
</Box>
|
||||
{'|'}
|
||||
<Box inline color={COLORS.damageType.burn}>
|
||||
{cm.fire}
|
||||
</Box>
|
||||
{'|'}
|
||||
<Box inline color={COLORS.damageType.brute}>
|
||||
{cm.brute}
|
||||
</Box>
|
||||
{')'}
|
||||
{crew.filter(searcher).map((cm) => {
|
||||
const highlighted = highlightedNames.includes(cm.name);
|
||||
return (
|
||||
<Table.Row key={cm.name} bold={!!cm.is_command}>
|
||||
<TableCell>
|
||||
<ButtonCheckbox
|
||||
checked={highlighted}
|
||||
tooltip="Mark on map"
|
||||
onClick={() =>
|
||||
act(highlighted ? 'remove_highlighted_name' : 'add_highlighted_name', { name: cm.name })
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{cm.name} ({cm.assignment})
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box inline color={getStatColor(cm, data.critThreshold)}>
|
||||
{getStatText(cm, data.critThreshold)}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{cm.sensor_type === 3 || data.ignoreSensors ? (
|
||||
data.isAI || data.isObserver ? (
|
||||
<Button
|
||||
fluid
|
||||
icon="location-arrow"
|
||||
content={cm.area + ' (' + cm.x + ', ' + cm.y + ')'}
|
||||
onClick={() =>
|
||||
act('track', {
|
||||
track: cm.ref,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{cm.sensor_type >= 2 || data.ignoreSensors ? (
|
||||
<Box inline ml={1}>
|
||||
{'('}
|
||||
<Box inline color={COLORS.damageType.oxy}>
|
||||
{cm.oxy}
|
||||
</Box>
|
||||
{'|'}
|
||||
<Box inline color={COLORS.damageType.toxin}>
|
||||
{cm.tox}
|
||||
</Box>
|
||||
{'|'}
|
||||
<Box inline color={COLORS.damageType.burn}>
|
||||
{cm.fire}
|
||||
</Box>
|
||||
{'|'}
|
||||
<Box inline color={COLORS.damageType.brute}>
|
||||
{cm.brute}
|
||||
</Box>
|
||||
{')'}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{cm.sensor_type === 3 || data.ignoreSensors ? (
|
||||
data.isAI || data.isObserver ? (
|
||||
<Button
|
||||
fluid
|
||||
icon="location-arrow"
|
||||
content={cm.area + ' (' + cm.x + ', ' + cm.y + ')'}
|
||||
onClick={() =>
|
||||
act('track', {
|
||||
track: cm.ref,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
cm.area + ' (' + cm.x + ', ' + cm.y + ')'
|
||||
)
|
||||
) : (
|
||||
cm.area + ' (' + cm.x + ', ' + cm.y + ')'
|
||||
)
|
||||
) : (
|
||||
<Box inline color="grey">
|
||||
Not Available
|
||||
</Box>
|
||||
)}
|
||||
</TableCell>
|
||||
</Table.Row>
|
||||
))}
|
||||
<Box inline color="grey">
|
||||
Not Available
|
||||
</Box>
|
||||
)}
|
||||
</TableCell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const HighlightedMarker = (props, context) => {
|
||||
const { color, ...rest } = props;
|
||||
return (
|
||||
<NanoMap.Marker {...rest}>
|
||||
<span class={`highlighted-marker color-border-${color}`} />
|
||||
</NanoMap.Marker>
|
||||
);
|
||||
};
|
||||
|
||||
const CrewMonitorMapView = (_properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [zoom, setZoom] = useLocalState(context, 'zoom', 1);
|
||||
const { highlightedNames } = data;
|
||||
return (
|
||||
<Box height="526px" mb="0.5rem" overflow="hidden">
|
||||
<NanoMap onZoom={(v) => setZoom(v)}>
|
||||
<NanoMap
|
||||
zoom={data.zoom}
|
||||
offsetX={data.offsetX}
|
||||
offsetY={data.offsetY}
|
||||
onZoom={(zoom) => act('set_zoom', { zoom })}
|
||||
onOffsetChange={(e, state) =>
|
||||
act('set_offset', {
|
||||
offset_x: state.offsetX,
|
||||
offset_y: state.offsetY,
|
||||
})
|
||||
}
|
||||
>
|
||||
{data.crewmembers
|
||||
.filter((x) => x.sensor_type === 3 || data.ignoreSensors)
|
||||
.map((cm) => (
|
||||
<NanoMap.Marker
|
||||
key={cm.ref}
|
||||
x={cm.x}
|
||||
y={cm.y}
|
||||
zoom={zoom}
|
||||
icon="circle"
|
||||
tooltip={cm.name + ' (' + cm.assignment + ')'}
|
||||
color={getStatColor(cm, data.critThreshold)}
|
||||
onClick={() =>
|
||||
data.isObserver
|
||||
? act('track', {
|
||||
track: cm.ref,
|
||||
})
|
||||
: null
|
||||
}
|
||||
/>
|
||||
))}
|
||||
.map((cm) => {
|
||||
const color = getStatColor(cm, data.critThreshold);
|
||||
const highlighted = highlightedNames.includes(cm.name);
|
||||
const onClick = () =>
|
||||
data.isObserver
|
||||
? act('track', {
|
||||
track: cm.ref,
|
||||
})
|
||||
: null;
|
||||
const onDblClick = () =>
|
||||
act(highlighted ? 'remove_highlighted_name' : 'add_highlighted_name', { name: cm.name });
|
||||
const tooltip = cm.name + ' (' + cm.assignment + ')';
|
||||
if (highlighted) {
|
||||
return (
|
||||
<HighlightedMarker
|
||||
key={cm.ref}
|
||||
x={cm.x}
|
||||
y={cm.y}
|
||||
tooltip={tooltip}
|
||||
color={color}
|
||||
onClick={onClick}
|
||||
onDblClick={onDblClick}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<NanoMap.MarkerIcon
|
||||
key={cm.ref}
|
||||
x={cm.x}
|
||||
y={cm.y}
|
||||
icon="circle"
|
||||
tooltip={tooltip}
|
||||
color={color}
|
||||
onClick={onClick}
|
||||
onDblClick={onDblClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</NanoMap>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -19,3 +19,9 @@ $bg-map: colors.$bg-map !default;
|
||||
background-color: $color-value !important;
|
||||
}
|
||||
}
|
||||
|
||||
@each $color-name, $color-value in $fg-map {
|
||||
.color-border-#{$color-name} {
|
||||
border-color: $color-value !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ $background-color: rgba(0, 0, 0, 0.33) !default;
|
||||
.NanoMap__container {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -19,5 +20,5 @@ $background-color: rgba(0, 0, 0, 0.33) !default;
|
||||
top: 30px;
|
||||
left: 0;
|
||||
padding: 0.5rem;
|
||||
width: 20%;
|
||||
width: 24%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.highlighted-marker {
|
||||
box-sizing: content-box;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
border-style: solid;
|
||||
border-width: 50%;
|
||||
border-radius: 50%;
|
||||
|
||||
animation: 1s infinite mark-shrink;
|
||||
}
|
||||
|
||||
@keyframes mark-shrink {
|
||||
from {
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
}
|
||||
|
||||
to {
|
||||
width: 0%;
|
||||
height: 0%;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@
|
||||
@include meta.load-css('./interfaces/BrigCells.scss');
|
||||
@include meta.load-css('./interfaces/CameraConsole.scss');
|
||||
@include meta.load-css('./interfaces/Contractor.scss');
|
||||
@include meta.load-css('./interfaces/CrewMonitor.scss');
|
||||
@include meta.load-css('./interfaces/ExosuitFabricator.scss');
|
||||
@include meta.load-css('./interfaces/GeneModder.scss');
|
||||
@include meta.load-css('./interfaces/KitchenMachine.scss');
|
||||
|
||||
File diff suppressed because one or more lines are too long
+122
-122
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+32
-32
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user