Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hashfrog_tracker",
"version": "0.7.1",
"version": "0.7.2",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.4",
Expand Down
27 changes: 26 additions & 1 deletion src/components/CustomReactSelect.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Select from "react-select";

import { useHintEntry } from "../context/trackerContext";

const CustomReactSelect = props => {
const {
id = "960b29a364ca444abb5969c97580d973",
Expand All @@ -15,6 +17,10 @@ const CustomReactSelect = props => {
const [inputValue, setInputValue] = useState("");
const [hueRotate, setHueRotate] = useState(0);

const { setHintEntry, savedHintEntry } = useHintEntry(id);
const hintRestoredRef = useRef(false);
const isMountRef = useRef(true);

const customStyles = useMemo(() => {
return {
control: provided => ({
Expand Down Expand Up @@ -101,6 +107,25 @@ const CustomReactSelect = props => {
onValueCallback(value);
}, [onValueCallback, value]);

// Persist hint changes. Skip the initial mount so an empty input doesn't
// clobber a saved entry before the resume restore below can apply it.
useEffect(() => {
if (isMountRef.current) {
isMountRef.current = false;
return;
}
setHintEntry(value ? value.value : null);
}, [value, setHintEntry]);

// Restore a saved hint once, after a resumed session populates it.
useEffect(() => {
if (hintRestoredRef.current) { return; }
if (savedHintEntry !== null) {
setValue({ label: savedHintEntry, value: savedHintEntry });
hintRestoredRef.current = true;
}
}, [savedHintEntry]);

return (
<div onContextMenu={handleRightClick} onAuxClick={handleOnClick} style={{ flex: 1, overflow: "hidden" }}>
<Select
Expand Down
73 changes: 60 additions & 13 deletions src/components/Element.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from "lodash";
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";

import { useElement, useItems, useLabelSelect } from "../context/trackerContext";
import { useDraggedIcon, useElement, useIconCache, useItems, useLabelSelect } from "../context/trackerContext";

// Base Icons
import icon_check from "../assets/icons/check_16x16.png";
Expand All @@ -28,21 +28,29 @@ const Element = props => {
icons = [],
countConfig = [0, 5], // min, max
receiver = false, // if draggin overrides item
persistIcon = false, // remember the displayed icon across sessions
dragCurrent = false, // if dragging should default or drag the current selected
selectedStartingIndex = 0, // on which of the icons we start
items = [],
hidden = false
} = props;

const { markCounter, markItem, startingIndex: trackerContextStartingIndex, startingItem } = useItems(items, id);
const {
markCounter, markItem, startingIndex: trackerContextStartingIndex, startingItem, savedIndex, savedCounter, savedLabelValue,
} = useItems(items, id, name);
useElement(id, startingItem);
const labelSelect = useLabelSelect();
const { persistDraggedIcon, savedDraggedIcon } = useDraggedIcon(id);
const { iconUrlByName, iconNameByUrl } = useIconCache();

const resolvedStartingIndex = savedIndex !== null ? savedIndex : trackerContextStartingIndex;

const [selected, setSelected] = useState(trackerContextStartingIndex || selectedStartingIndex);
const [selected, setSelected] = useState(resolvedStartingIndex || selectedStartingIndex);
const [counter, setCounter] = useState(0);
const [iconHash, setIconHash] = useState(null);
const [draggedIcon, setDraggedIcon] = useState(null);
const hasUserInteracted = useRef(false);
const draggedIconRestoredRef = useRef(false);

// Whenever a change in icon list is detected, reset the selection.
// Only reset if icons actually changed AND we don't have a starting item.
Expand All @@ -51,23 +59,50 @@ const Element = props => {
return (acc += cv);
}, "");

if (iconHash !== null && hash !== iconHash && trackerContextStartingIndex === 0) {
if (iconHash !== null && hash !== iconHash && resolvedStartingIndex === 0) {
setSelected(0);
}

setIconHash(hash);
}, [icons, iconHash, name, trackerContextStartingIndex]);
}, [icons, iconHash, name, resolvedStartingIndex]);

// Sync selected state when starting items change
// Sync selected state when the restored/starting item index changes
useEffect(() => {
if (trackerContextStartingIndex > 0) {
// This element should claim the starting item
setSelected(trackerContextStartingIndex);
if (resolvedStartingIndex > 0) {
// This element should claim the restored or starting item
setSelected(resolvedStartingIndex);
} else if (!hasUserInteracted.current) {
// Another element claimed the item and user hasn't interacted - reset to uncollected
setSelected(0);
}
}, [trackerContextStartingIndex]);
}, [resolvedStartingIndex]);

// Restore a saved counter value when one is present
useEffect(() => {
if (savedCounter !== null) {
setCounter(savedCounter);
}
}, [savedCounter]);

// Restore a receiver's saved icon once on resume. The saved value is a stable
// icon name, so resolve it to this session's url. If it's one of this
// element's own icons it was cycled to by clicking, so restore that index;
// otherwise it was dragged in from elsewhere, so show it as an override.
useEffect(() => {
if (draggedIconRestoredRef.current) { return; }
if (savedDraggedIcon === null) { return; }

const url = iconUrlByName[savedDraggedIcon];
if (!url) { return; } // icon cache not ready yet; wait for it to populate

const idx = icons.indexOf(url);
if (idx >= 0) {
setSelected(idx);
} else {
setDraggedIcon(url);
}
draggedIconRestoredRef.current = true;
}, [savedDraggedIcon, icons, iconUrlByName]);

const icon = useMemo(() => {
return icons[selected];
Expand Down Expand Up @@ -96,6 +131,7 @@ const Element = props => {
if (!isCounter) {
setDraggedIcon(null);
setSelected(updated);
if (receiver || persistIcon) { persistDraggedIcon(iconNameByUrl[icons[updated]] ?? null); }
} else {
setCounter(updated);
}
Expand All @@ -107,7 +143,7 @@ const Element = props => {
markCounter(updated, name);
}
},
[id, icons, type, countConfig, selected, items, markCounter, markItem, counter, name],
[id, icons, type, countConfig, selected, items, markCounter, markItem, counter, name, receiver, persistIcon, persistDraggedIcon, iconNameByUrl],
);

const wheelHandler = useCallback(
Expand Down Expand Up @@ -144,9 +180,10 @@ const Element = props => {
const { icon: droppedIcon } = JSON.parse(item);
setDraggedIcon(droppedIcon);
setSelected(0) //reset selected so if the dragged item gets cleared, the user will see the hashfrog
persistDraggedIcon(iconNameByUrl[droppedIcon] ?? null);
}
},
[receiver],
[receiver, persistDraggedIcon, iconNameByUrl],
);

return (
Expand Down Expand Up @@ -176,11 +213,13 @@ const Element = props => {
label={label}
labelStartingIndex={labelStartingIndex}
labelBackgroundColor={labelBackgroundColor}
savedValue={savedLabelValue}
onLabelChange={(value) => labelSelect(id, name, value)}
/>
)}
{type === "nested" && (
<Element
id={`${id}_nested`}
name={`${name}_nested`}
type="simple"
icons={[icon_unknown, icon_check]}
Expand All @@ -194,9 +233,17 @@ const Element = props => {
);
};

const ElementLabel = ({ label, labelStartingIndex, labelBackgroundColor, onLabelChange }) => {
const ElementLabel = ({ label, labelStartingIndex, labelBackgroundColor, savedValue, onLabelChange }) => {
const [index, setIndex] = useState(labelStartingIndex);

// Restore a saved label selection by resolving its value back to an index
useEffect(() => {
if (savedValue !== null && Array.isArray(label)) {
const idx = label.indexOf(savedValue);
if (idx >= 0) { setIndex(idx); }
}
}, [savedValue, label]);

const display = useMemo(() => {
if (Array.isArray(label)) {
return label[index];
Expand Down
2 changes: 2 additions & 0 deletions src/components/HintsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const HintsTable = props => {
hintRows.push(
<td key={`${i}-${id}`} style={{ padding }}>
<SometimesHint
id={`${id}-${i}`}
labels={labels}
width={width}
color={color}
Expand All @@ -51,6 +52,7 @@ const HintsTable = props => {
hintRows.push(
<td key={`${i}-${id}`} style={{ padding }}>
<LocationHint
id={`${id}-${i}`}
labels={labels}
width={width}
color={color}
Expand Down
13 changes: 7 additions & 6 deletions src/components/LocationHint.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const LocationHint = props => {
icons={(bossElementsIcons.length && bossElementsIcons) || bossIcons}
customStyle={{ marginRight: "0.25rem" }}
receiver={bossReceiver}
persistIcon
/>
)}
<CustomReactSelect
Expand All @@ -73,26 +74,26 @@ const LocationHint = props => {
receiver
/>
<Element
id={`locations_item1_${id}`}
name={`locations_item1_${name}`}
id={`locations_item2_${id}`}
name={`locations_item2_${name}`}
type="simple"
size={[20, 20]}
icons={itemsIcons}
customStyle={{ marginLeft: "0.25rem" }}
receiver
/>
<Element
id={`locations_item1_${id}`}
name={`locations_item1_${name}`}
id={`locations_item3_${id}`}
name={`locations_item3_${name}`}
type="simple"
size={[20, 20]}
icons={itemsIcons}
customStyle={{ marginLeft: "0.25rem" }}
receiver
/>
<Element
id={`locations_item1_${id}`}
name={`locations_item1_${name}`}
id={`locations_item4_${id}`}
name={`locations_item4_${name}`}
type="simple"
size={[20, 20]}
icons={itemsIcons}
Expand Down
4 changes: 2 additions & 2 deletions src/components/SometimesHint.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ const SometimesHint = props => {
)}
{showIcon && dual && (
<Element
id={`sometimes_item_${id}`}
name={`sometimes_item_${name}`}
id={`sometimes_item2_${id}`}
name={`sometimes_item2_${name}`}
type="simple"
size={[20, 20]}
icons={icons}
Expand Down
Loading
Loading