Skip to content

Commit

Permalink
Merge pull request #465 from amelioro/score-with-keyboard
Browse files Browse the repository at this point in the history
touchup: allow scoring with keyboard
  • Loading branch information
keyserj authored Jul 19, 2024
2 parents 47befeb + f13d0c3 commit 7e5d0f6
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 9 deletions.
7 changes: 2 additions & 5 deletions src/web/topic/components/Score/Score.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useFlowZoom } from "@/web/topic/hooks/flowHooks";
import { useUserScores } from "@/web/topic/store/scoreHooks";
import { playgroundUsername } from "@/web/topic/store/store";
import { useOnPlayground } from "@/web/topic/store/topicHooks";
import { userCanEditScores } from "@/web/topic/utils/score";
import { useReadonlyMode } from "@/web/view/actionConfigStore";
import { usePerspectives } from "@/web/view/perspectiveStore";

Expand All @@ -29,11 +30,7 @@ export const Score = ({ graphPartId }: ScoreProps) => {

const myUsername = onPlayground ? playgroundUsername : sessionUser?.username;
const perspectives = usePerspectives();
const canEdit =
!readonlyMode &&
perspectives.length === 1 &&
myUsername !== undefined &&
perspectives[0] === myUsername;
const canEdit = userCanEditScores(myUsername, perspectives, readonlyMode);

const [selected, setSelected] = useState(false);
const [hovering, setHovering] = useState(false);
Expand Down
29 changes: 25 additions & 4 deletions src/web/topic/components/TopicWorkspace/TopicWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,43 @@ import { useEffect } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { ContextMenu } from "@/web/common/components/ContextMenu/ContextMenu";
import { useSessionUser } from "@/web/common/hooks";
import { CriteriaTable } from "@/web/topic/components/CriteriaTable/CriteriaTable";
import { Diagram } from "@/web/topic/components/Diagram/Diagram";
import { TopicToolbar } from "@/web/topic/components/Surface/TopicToolbar";
import { TopicPane } from "@/web/topic/components/TopicPane/TopicPane";
import { setScore } from "@/web/topic/store/actions";
import { playgroundUsername } from "@/web/topic/store/store";
import { isOnPlayground } from "@/web/topic/store/utilActions";
import { Score, possibleScores } from "@/web/topic/utils/graph";
import { hotkeys } from "@/web/topic/utils/hotkeys";
import { toggleReadonlyMode } from "@/web/view/actionConfigStore";
import { setSelected, useFormat } from "@/web/view/currentViewStore/store";
import { userCanEditScores } from "@/web/topic/utils/score";
import { getReadonlyMode, toggleReadonlyMode } from "@/web/view/actionConfigStore";
import { getSelectedGraphPartId, setSelected, useFormat } from "@/web/view/currentViewStore/store";
import { getPerspectives } from "@/web/view/perspectiveStore";
import { setHasVisitedWorkspace, useHasVisitedWorkspace } from "@/web/view/userConfigStore";

const useWorkspaceHotkeys = () => {
const useWorkspaceHotkeys = (user: { username: string } | null | undefined) => {
useHotkeys([hotkeys.deselectPart], () => setSelected(null));
useHotkeys([hotkeys.readonlyMode], () => toggleReadonlyMode());

useHotkeys([hotkeys.score], (_, hotkeysEvent) => {
const selectedPartId = getSelectedGraphPartId();
if (!selectedPartId || !hotkeysEvent.keys) return;
const [score] = hotkeysEvent.keys;
if (!score || !possibleScores.some((s) => score === s)) return;

// seems slightly awkward that there's logic here not reused with the Score component, but hard to reuse that in a clean way
const myUsername = isOnPlayground() ? playgroundUsername : user?.username;
if (!userCanEditScores(myUsername, getPerspectives(), getReadonlyMode())) return;
setScore(myUsername, selectedPartId, score as Score);
});
};

export const TopicWorkspace = () => {
useWorkspaceHotkeys();
const { sessionUser } = useSessionUser();

useWorkspaceHotkeys(sessionUser);

const format = useFormat();
const theme = useTheme();
Expand Down
4 changes: 4 additions & 0 deletions src/web/topic/store/utilActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export const getScoringUsernames = () => {
return Object.keys(userScores);
};

export const isOnPlayground = () => {
return isPlaygroundTopic(useTopicStore.getState().topic);
};

export const setTopicData = (state: TopicStoreState, sessionUsername?: string) => {
// Don't override topic - this way, topic data from playground can be downloaded and uploaded as
// a means of saving playground data to the db.
Expand Down
1 change: 1 addition & 0 deletions src/web/topic/utils/hotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const hotkeys = {
deselectPart: "Esc",
readonlyMode: "Alt + R",
pan: "Up, Down, Left, Right",
score: "1, 2, 3, 4, 5, 6, 7, 8, 9, -",
zoomIn: "Ctrl + =",
zoomOut: "Ctrl + -",
};
15 changes: 15 additions & 0 deletions src/web/topic/utils/score.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ export const getAverageScore = (userScores: Score[]): Score => {
export const getNumericScore = (score: Score): number => {
return score === "-" ? 5 : Number(score);
};

// hard to say if this is worth extracting, but at least it guarantees some commonality between
// checking editability for the hotkey implementation and the implementation in the Score component
export const userCanEditScores = (
username: string | undefined,
perspectives: string[],
readonlyMode: boolean,
): username is string => {
return (
!readonlyMode &&
perspectives.length === 1 &&
username !== undefined &&
perspectives[0] === username
);
};
4 changes: 4 additions & 0 deletions src/web/view/actionConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ export const getUnrestrictedEditing = () => {
export const getFlashlightMode = () => {
return useActionConfigStore.getState().flashlightMode;
};

export const getReadonlyMode = () => {
return useActionConfigStore.getState().readonlyMode;
};
4 changes: 4 additions & 0 deletions src/web/view/currentViewStore/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ export const getView = () => {
return useCurrentViewStore.getState();
};

export const getSelectedGraphPartId = () => {
return useCurrentViewStore.getState().selectedGraphPartId;
};

// misc
emitter.on("overwroteTopicData", () => {
resetView();
Expand Down
5 changes: 5 additions & 0 deletions src/web/view/perspectiveStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ export const comparePerspectives = () => {
const scoringUsernames = getScoringUsernames();
usePerspectiveStore.setState({ perspectives: scoringUsernames });
};

// utils
export const getPerspectives = () => {
return usePerspectiveStore.getState().perspectives;
};

0 comments on commit 7e5d0f6

Please sign in to comment.