Skip to content

Commit

Permalink
Merge pull request #124 from public-accountability/develop
Browse files Browse the repository at this point in the history
interlocks2
  • Loading branch information
aepyornis authored Nov 17, 2022
2 parents c3cf6f3 + 8ac2b88 commit dbedd72
Show file tree
Hide file tree
Showing 21 changed files with 410 additions and 202 deletions.
5 changes: 4 additions & 1 deletion app/components/Annotation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from "react"
import Typography from "@mui/material/Typography"
import { Annotation } from "../util/annotations"

export default function Annotation({ annotation: { header, text } }: { annotation: Annotation }) {
return (
<div className="oligrapher-annotation">
<div className="oligrapher-annotation-header">{header}</div>
<div className="oligrapher-annotation-header">
<Typography variant="h4">{header}</Typography>
</div>

<div className="oligrapher-annotation-text" dangerouslySetInnerHTML={{ __html: text }}></div>
</div>
Expand Down
74 changes: 44 additions & 30 deletions app/components/Annotations.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import React, { useCallback } from "react"
import { useDispatch, useSelector } from "react-redux"
import Button from "@mui/material/Button"

import Stack from "@mui/material/Stack"
import Typography from "@mui/material/Typography"
import Annotation from "./Annotation"
import RemoveAnnotationButton from "./RemoveAnnotationButton"
import AnnotationsNav from "./AnnotationsNav"
import AnnotationsTracker from "./AnnotationsTracker"
import HideAnnotationsButton from "./HideAnnotationsButton"
import { annotationsListSelector } from "../util/selectors"
import { annotationsListSelector, editModeSelector, svgHeightSelector } from "../util/selectors"
import AnnotationForm from "./AnnotationForm"
import AnnotationList from "./AnnotationList"

export function Annotations() {
const dispatch = useDispatch()
const create = useCallback(() => dispatch({ type: "CREATE_ANNOTATION" }), [dispatch])

const editing = useSelector(state => state.display.modes.editor)
const svgHeight = useSelector(svgHeightSelector)
const editing = useSelector(editModeSelector)
const { currentIndex } = useSelector(state => state.annotations)
const list = useSelector(annotationsListSelector)
const annotation = list[currentIndex]
Expand All @@ -34,38 +36,50 @@ export function Annotations() {
const show = useCallback(index => dispatch({ type: "SHOW_ANNOTATION", index }), [dispatch])

return (
<div id="oligrapher-annotations">
<div id="oligrapher-annotations-nav">
{editing && <span className="oligrapher-annotations-header">Edit Annotations</span>}

{!editing && list.length > 1 && (
<AnnotationsNav count={list.length} currentIndex={currentIndex} prev={prev} next={next} />
)}

{!editing && list.length > 1 && (
<AnnotationsTracker count={list.length} currentIndex={currentIndex} show={show} />
)}

{!storyModeOnly && <HideAnnotationsButton />}
</div>

{!editing && <Annotation annotation={annotation} />}
<div id="oligrapher-annotations" style={{ height: svgHeight - 10, overflow: "auto" }}>
<div id="oligrapher-annotations-body">
<div id="oligrapher-annotations-nav">
{editing && (
<Typography variant="h4" className="oligrapher-annotations-header">
Edit Annotations
</Typography>
)}

{!editing && list.length > 1 && (
<AnnotationsNav
count={list.length}
currentIndex={currentIndex}
prev={prev}
next={next}
/>
)}

{!editing && list.length > 1 && (
<AnnotationsTracker count={list.length} currentIndex={currentIndex} show={show} />
)}

{!storyModeOnly && <HideAnnotationsButton />}
</div>

{editing && <AnnotationList list={list} currentIndex={currentIndex} />}
{!editing && <Annotation annotation={annotation} />}

<br />
{editing && <AnnotationList list={list} currentIndex={currentIndex} />}

{editing && (
<div className="oligrapher-annotations-actions">
<Button onClick={create} variant="outlined" size="small">
Add Annotation
</Button>
<br />

{annotation && <RemoveAnnotationButton annotation={annotation} />}
</div>
)}
{editing && (
<div className="oligrapher-annotations-actions">
<Stack direction="row" spacing={1} sx={{ mt: 1 }}>
<Button onClick={create} variant="outlined" size="small">
Add Annotation
</Button>
{annotation && <RemoveAnnotationButton annotation={annotation} />}
</Stack>
</div>
)}

{editing && annotation && <AnnotationForm annotation={annotation} key={annotation.id} />}
{editing && annotation && <AnnotationForm annotation={annotation} key={annotation.id} />}
</div>
</div>
)
}
Expand Down
6 changes: 4 additions & 2 deletions app/components/HideAnnotationsButton.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React, { useCallback } from "react"
import { useDispatch } from "react-redux"
import IconButton from "@mui/material/IconButton"

import { MdClose } from "react-icons/md"

export default function HideAnnotationsButton() {
const dispatch = useDispatch()
const hide = useCallback(() => dispatch({ type: "TOGGLE_ANNOTATIONS" }), [dispatch])

return (
<div className="oligrapher-annotations-hide" onClick={hide} title="Hide annotations">
<IconButton aria-label="Hide annotations" title="Hide annotations" onClick={hide}>
<MdClose />
</div>
</IconButton>
)
}
34 changes: 34 additions & 0 deletions app/components/InterlocksActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react"
import { useDispatch } from "react-redux"
import IconButton from "@mui/material/IconButton"
import { MdAddCircle, MdSelectAll } from "react-icons/md"

export default function InterlocksActionButton({ interlocksState }) {
const dispatch = useDispatch()

const selectPreviousBatch =
interlocksState.previousNodes &&
interlocksState.previousNodes.length > 0 &&
interlocksState.nodes === null

if (selectPreviousBatch) {
return (
<IconButton
aria-label="select added nodes"
onClick={() => dispatch({ type: "SELECT_PREVIOUS_INTERLOCKS" })}
>
<MdSelectAll />
</IconButton>
)
} else {
return (
<IconButton
aria-label="add all to map"
disabled={interlocksState.nodes === null || interlocksState.nodes.length === 0}
onClick={() => dispatch({ type: "ADD_ALL_INTERLOCKS" })}
>
<MdAddCircle />
</IconButton>
)
}
}
30 changes: 30 additions & 0 deletions app/components/InterlocksNodeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react"
import List from "@mui/material/List"
import ListItemText from "@mui/material/ListItemText"
import ListItemButton from "@mui/material/ListItemButton"
import type { LsNode } from "../datasources/littlesis3"
import { useDispatch } from "react-redux"

function NodeListItem({ node }) {
const dispatch = useDispatch()

return (
<ListItemButton
alignItems="flex-start"
dense={true}
onClick={() => dispatch({ type: "ADD_INTERLOCKS_NODE", id: node.id })}
>
<ListItemText primary={node.name} secondary={node.description} />
</ListItemButton>
)
}

export default function InterlocksNodeList({ nodes }: { nodes: LsNode[] }) {
return (
<List className="oligrapher-interlocks-node-list">
{nodes.map(n => (
<NodeListItem key={n.id} node={n} />
))}
</List>
)
}
62 changes: 41 additions & 21 deletions app/components/InterlocksTool2.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,58 @@
import React from "react"
import React, { useState } from "react"
import { useDispatch, useSelector } from "react-redux"
import Button from "@mui/material/Button"
import { IoIosLink } from "react-icons/io"

import Toolbox from "./Toolbox"
import { isLittleSisId } from "../util/helpers"
import { interlocksStateSelector } from "../util/selectors"
import InterlocksNodeList from "./InterlocksNodeList"
import { interlocksStateSelector, selectedLsNodesSelector } from "../util/selectors"
import { useClientRect } from "../util/helpers"
import InterlocksActionButton from "./InterlocksActionButton"

export default function InterlocksTool2() {
const dispatch = useDispatch()
const lsNodes = useSelector(state => state.display.selection.node.filter(isLittleSisId))
const lsNodes = useSelector(selectedLsNodesSelector)
const interlocksState = useSelector(interlocksStateSelector)
const buttonDisabled = !!interlocksState.status || lsNodes.length < 2
const [maxHeight, setMaxHeight] = useState<number>(600)

const nodeListContainer = useClientRect(rect => {
if (!rect) {
return
}

const { bottom, height } = rect
const diff = bottom - window.innerHeight
setMaxHeight(height - diff - 100)
})

return (
<Toolbox title="Interlocks2">
<Toolbox title="Interlocks">
<div className="oligrapher-interlocks">
<p>
Select nodes that were imported from LittleSis <IoIosLink /> to fetch their interlocks.
</p>
<p>
LittleSis Nodes Selected: <code>{lsNodes.length}</code>
</p>
<Button
disabled={buttonDisabled}
onClick={() => dispatch({ type: "INTERLOCKS_REQUESTED_2", selectedNodes: lsNodes })}
variant="contained"
color="primary"
disableElevation={true}
>
Get interlocks
</Button>
<div>
{interlocksState.nodes && <pre>{JSON.stringify(interlocksState.nodes, null, 2)}</pre>}
<p>
Select nodes that were imported from LittleSis <IoIosLink /> to fetch their interlocks.
</p>
<p>
LittleSis Nodes Selected: <code>{lsNodes.length}</code>
</p>

<div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between" }}>
<Button
disabled={buttonDisabled}
onClick={() => dispatch({ type: "INTERLOCKS_REQUESTED_2", selectedNodes: lsNodes })}
variant="contained"
color="primary"
disableElevation={true}
>
Get interlocks
</Button>
<InterlocksActionButton interlocksState={interlocksState} />
</div>
</div>

<div ref={nodeListContainer} style={{ overflow: "auto", maxHeight: `${maxHeight}px` }}>
{interlocksState.nodes && <InterlocksNodeList nodes={interlocksState.nodes} />}
</div>
</div>
</Toolbox>
Expand Down
2 changes: 1 addition & 1 deletion app/components/LockManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function LockManager() {
}

return () => {
if (consumer.subscriptions.subscriptions.length > 0) {
if (consumer && consumer.subscriptions.subscriptions.length > 0) {
consumer.remove(consumer.subscriptions.subscriptions[0])
}
}
Expand Down
6 changes: 4 additions & 2 deletions app/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
showFloatingEditorsSelector,
editModeSelector,
debugModeSelector,
svgHeightSelector,
} from "../util/selectors"

export const ROOT_CONTAINER_ID = "oligrapher-container"
Expand Down Expand Up @@ -64,6 +65,7 @@ export function Root(props: { cable?: any }) {
const editorMode = useSelector(editModeSelector)
const debugMode = useSelector(debugModeSelector)
const showFloatingEditors = useSelector(showFloatingEditorsSelector)
const svgHeight = useSelector(svgHeightSelector)

const showAnnotationsOnRight = showAnnotations && !smallScreen
const showAnnotationsOnBottom = showAnnotations && smallScreen
Expand Down Expand Up @@ -116,7 +118,7 @@ export function Root(props: { cable?: any }) {
<SvgRefContext.Provider value={svgRef}>
<div id={ROOT_CONTAINER_ID} ref={containerRef}>
{showHeader && <Header />}
<div style={{ flex: 1 }}>
<div style={{ flex: 1, height: "100%", overflow: "hidden" }}>
<Grid container style={{ height: "100%" }}>
<Grid sm={showAnnotationsOnRight ? 8 : 12}>
<div id="oligrapher-graph-container">
Expand All @@ -128,7 +130,7 @@ export function Root(props: { cable?: any }) {
</div>
</Grid>
{showAnnotationsOnRight && (
<Grid sm={4}>
<Grid sm={4} style={{ flex: 1, height: "100%" }}>
<Annotations />
</Grid>
)}
Expand Down
4 changes: 1 addition & 3 deletions app/components/Svg.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, useContext } from "react"
import { useDispatch, useSelector } from "react-redux"

import Caption from "../graph/caption"
import { xy, Point } from "../util/geometry"
import {
svgCoordinatesFromMouseEvent,
Expand All @@ -16,7 +15,6 @@ import {
pannableSelector,
scrollToZoomSelector,
svgHeightSelector,
textToolOpenSelector,
} from "../util/selectors"
import SvgRefContext from "../util/SvgRefContext"

Expand Down Expand Up @@ -48,7 +46,7 @@ export default function Svg(props: { children: React.ReactNode }) {
}

const divAttrs: React.HTMLProps<HTMLDivElement> = {
style: { height: svgHeight, width: "100%" },
style: { height: svgHeight - 10, width: "100%", overflow: "hidden" },
}

if (scrollToZoom) {
Expand Down
23 changes: 16 additions & 7 deletions app/components/Toolbox.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import React, { useCallback } from "react"
import { useDispatch } from "react-redux"
import { useDispatch, useSelector } from "react-redux"
import { useHotkeys } from "react-hotkeys-hook"
import { MdClose } from "react-icons/md"
import { svgHeightSelector } from "../util/selectors"
import Typography from "@mui/material/Typography"
import IconButton from "@mui/material/IconButton"

// This isn't a box of tools. It's a box with one tool
export default function Toolbox({ title, children }: ToolboxProps) {
const dispatch = useDispatch()
const closeTool = useCallback(() => dispatch({ type: "CLOSE_TOOL" }), [dispatch])
const svgHeight = useSelector(svgHeightSelector)

useHotkeys("escape", closeTool)

return (
<div className="oligrapher-toolbox">
<header>
{title}
<button onClick={closeTool}>
<div
className="oligrapher-toolbox"
style={{ display: "flex", flexDirection: "column", maxHeight: svgHeight - 25 }}
>
<header style={{ display: "flex", flexDirection: "row" }}>
<Typography variant="h5" sx={{ flexGrow: 1 }}>
{title}
</Typography>
<IconButton onClick={closeTool}>
<MdClose />
</button>
</IconButton>
</header>
<main>{children}</main>
<main style={{ flexGrow: 1 }}>{children}</main>
</div>
)
}
Expand Down
Loading

0 comments on commit dbedd72

Please sign in to comment.