diff --git a/components/browser/EdgeBrowser.js b/components/browser/EdgeBrowser.js index 88c748c2d..1fc740086 100644 --- a/components/browser/EdgeBrowser.js +++ b/components/browser/EdgeBrowser.js @@ -306,37 +306,42 @@ export default class EdgeBrowser extends React.Component { // index is index of the column where the custom load of an entity is changed // also update if there's column with same parent updateChildColumn(index, customLoad) { - if (index + 1 >= this.state.columns.length) return - const parentIdOfColumn = this.state.columns[index].parentId - const resultColumns = [] - resultColumns.push(this.state.columns[0]) - for (let i = 1; i < this.state.columns.length; i += 1) { - const parentColumn = this.state.columns[i - 1] - const column = this.state.columns[i] - if (parentColumn.parentId === parentIdOfColumn) { - resultColumns.push({ ...column, parentCustomLoad: customLoad }) - } else { - resultColumns.push(column) + this.setState((prevState) => { + if (index + 1 >= prevState.columns.length) return null + const parentIdOfColumn = prevState.columns[index].parentId + const resultColumns = [] + resultColumns.push(prevState.columns[0]) + for (let i = 1; i < prevState.columns.length; i += 1) { + const parentColumn = prevState.columns[i - 1] + const column = prevState.columns[i] + if (parentColumn.parentId === parentIdOfColumn) { + const updatedColumn = { + ...column, + parentCustomLoad: customLoad, + } + resultColumns.push(updatedColumn) + } else { + resultColumns.push(column) + } } - } - this.setState({ columns: resultColumns }) + return { columns: resultColumns } + }) } // set the shouldUpdate property of column at index // and all other columns with same parent // to trigger entites reload of those columns reloadColumnEntities(index) { - const parentIdOfColumn = this.state.columns[index].parentId - const resultColumns = [] - for (let i = 0; i < this.state.columns.length; i += 1) { - const column = this.state.columns[i] - if (column?.parentId === parentIdOfColumn) { - resultColumns.push({ ...column, shouldReloadEntities: !column.shouldReloadEntities }) - } else { - resultColumns.push(column) - } - } - this.setState({ columns: resultColumns }) + this.setState((prevState) => { + const parentIdOfColumn = prevState.columns[index].parentId + const resultColumns = prevState.columns.map((column) => { + if (column?.parentId === parentIdOfColumn) { + return { ...column, shouldReloadEntities: !column.shouldReloadEntities } + } + return column + }) + return { columns: resultColumns } + }) } async lookupSignatures() { diff --git a/components/browser/EditEdgeTextbox.js b/components/browser/EditEdgeTextbox.js new file mode 100644 index 000000000..d2310d530 --- /dev/null +++ b/components/browser/EditEdgeTextbox.js @@ -0,0 +1,114 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable react/destructuring-assignment */ +/* globals $: false */ + +import { useCallback, useEffect, useState } from 'react' +import { debounce } from 'lodash' +import { getTooltipTitle } from '../../lib/edge-utils' +import LoadingSpinner from '../LoadingSpinner' +import Icon from '../Icon' + +const EditEdgeTextbox = ({ + existingEdge, + canAddEdge, + label, + selected, + addEdge, + removeEdge, + type, + editEdgeTemplate, +}) => { + const showTrashButton = existingEdge?.writers?.length !== 0 + const [isLoading, setIsLoading] = useState(false) + const [immediateValue, setImmediateValue] = useState(null) + + const handleHover = (target) => { + if (!existingEdge) return + const title = getTooltipTitle(existingEdge) + $(target).tooltip({ + title, + trigger: 'hover', + container: 'body', + }) + } + + const delayedAddEdge = useCallback( + debounce(async (e) => { + setIsLoading(true) + const result = await addEdge({ + e, + existingEdge, + editEdgeTemplate, + updatedEdgeFields: { [type]: Number(e.target.value) }, + }) + setIsLoading(false) + if (!result) setImmediateValue(selected) + }, 500), + [existingEdge] + ) + + const handleRemoveEdge = async (e) => { + e.stopPropagation() + setIsLoading(true) + await removeEdge() + setIsLoading(false) + } + + useEffect(() => { + setIsLoading(false) + if (selected !== null && selected !== undefined) { + setImmediateValue(selected) + } else { + setImmediateValue(null) + } + }, [existingEdge, canAddEdge]) + + if (!existingEdge && !canAddEdge) return null + return ( +
{ + e.stopPropagation() + }} + > + +
+ e.stopPropagation()} + onChange={(e) => { + e.stopPropagation() + setImmediateValue(e.target.value) + delayedAddEdge(e) + }} + /> +
+
+ {isLoading && } + {existingEdge && showTrashButton && ( + + )} +
+
+ ) +} + +export default EditEdgeTextbox diff --git a/components/browser/ProfileEntity.js b/components/browser/ProfileEntity.js index 3bf12efe4..4ea51e4ba 100644 --- a/components/browser/ProfileEntity.js +++ b/components/browser/ProfileEntity.js @@ -22,6 +22,7 @@ import EditEdgeToggle from './EditEdgeToggle' import EditEdgeTwoDropdowns from './EditEdgeTwoDropdowns' import ScoresList from './ScoresList' import useQuery from '../../hooks/useQuery' +import EditEdgeTextbox from './EditEdgeTextbox' export default function ProfileEntity(props) { const { @@ -143,7 +144,7 @@ export default function ProfileEntity(props) { if (isTraverseInvitation) { props.removeEdgeFromEntity(id, result) } else if (isCustomLoadInvitation) { - props.updateChildColumn(props.columnIndex, null) + props.updateChildColumn(props.columnIndex, defaultWeight) } props.reloadColumnEntities() } catch (error) { @@ -204,7 +205,7 @@ export default function ProfileEntity(props) { ) if (version === 1 && (!signatures || signatures.length === 0)) { promptError("You don't have permission to edit this edge") - return + return false } const { @@ -241,8 +242,10 @@ export default function ProfileEntity(props) { promptMessage( `Invitation has been sent to ${body.tail} and it's waiting for the response.` ) + return true } catch (error) { promptError(error.message) + return false } } @@ -317,10 +320,27 @@ export default function ProfileEntity(props) { if (!edge && content?.isInvitedProfile && isEmergencyReviewerStage && !isInviteInvitation) return null + const editEdgeTextbox = (type) => ( + <> + p?.invitation === invitation.id).length === 0 || + invitation.multiReply + } + label={invitation.name} + selected={edge?.[type]} + addEdge={addEdge} + removeEdge={() => removeEdge(edge)} + type={type} // label or weight + editEdgeTemplate={editEdgeTemplates?.find((p) => p?.invitation === invitation.id)} + /> + + ) + const editEdgeDropdown = (type, controlType) => ( p?.invitation === invitation.id).length === 0 || invitation.multiReply @@ -386,12 +406,14 @@ export default function ProfileEntity(props) { const shouldRenderWeightRadio = weightRadio && !invitation.label const shouldRenderLabelDropdown = labelDropdown && !invitation.weight const shouldRenderWeightDropdown = weightDropdown && !invitation.label + const shouldRenderWeightTextbox = invitation.weight?.['value-textbox'] if (shouldRenderTwoRadio) return editEdgeTwoDropdowns('value-radio') if (shouldRenderTwoDropdown) return editEdgeTwoDropdowns('value-dropdown') if (shouldRenderLabelRadio) return editEdgeDropdown('label', 'value-radio') // for now treat radio the same as dropdown if (shouldRenderWeightRadio) return editEdgeDropdown('weight', 'value-radio') // for now treat radio the same as dropdown if (shouldRenderLabelDropdown) return editEdgeDropdown('label', 'value-dropdown') + if (shouldRenderWeightTextbox) return editEdgeTextbox('weight') if (shouldRenderWeightDropdown) return editEdgeDropdown('weight', 'value-dropdown') return editEdgeToggle() } diff --git a/lib/edge-utils.js b/lib/edge-utils.js index 8c4680a06..b570cfd83 100644 --- a/lib/edge-utils.js +++ b/lib/edge-utils.js @@ -358,6 +358,9 @@ export function translateFieldSpec(invitation, fieldName, version) { if (field.param?.enum) { spec['value-dropdown'] = field.param.enum } + if (field.param?.input === 'text') { + spec['value-textbox'] = true + } return spec } const field = invitation.reply.content[fieldName] diff --git a/styles/pages/edge-browser.scss b/styles/pages/edge-browser.scss index 581a77c04..8ae30440b 100644 --- a/styles/pages/edge-browser.scss +++ b/styles/pages/edge-browser.scss @@ -191,6 +191,33 @@ main.edge-browser { cursor: no-drop; } } + .edit-edge-textbox { + width: 15%; + padding-left: 0.5rem; + + .edit-edge-input { + font-size: 0.75rem; + padding: 3px 5px; + height: fit-content; + + &:disabled { + cursor: no-drop; + } + } + } + .edit-edge-spinner { + display: inline-flex; + vertical-align: middle; + + .spinner-small { + height: 100%; + display: inline-block; + width: 40px; + & > div { + background-color: constants.$orRed; + } + } + } .span-disabled { pointer-events: none; }