Add shortcut to actions

This commit is contained in:
Jeremie Pardou-Piquemal 2020-06-30 21:47:55 +02:00 committed by Jérémie Pardou-Piquemal
parent cf4e68ca34
commit 03dab656aa
3 changed files with 113 additions and 85 deletions

View file

@ -29,7 +29,7 @@ const Wrapper = styled.div`
position: relative;
`;
const StyledImage = styled.img`
const FrontImage = styled.img`
transition: transform 200ms;
transform: rotateY(${({ visible }) => (visible ? 0 : 180)}deg);
backface-visibility: hidden;
@ -37,7 +37,7 @@ const StyledImage = styled.img`
z-index: -1;
`;
const BackImage = styled(StyledImage)`
const BackImage = styled(FrontImage)`
position: absolute;
top: 0;
left: 0;
@ -86,7 +86,7 @@ const Image = ({
return {
...prevItem,
unflippedFor: currentUser.id,
flipped: false,
flipped: true,
};
}
});
@ -102,8 +102,7 @@ const Image = ({
);
const flippedForMe =
backContent &&
(flipped || (unflippedFor && unflippedFor !== currentUser.id));
backContent && flipped && unflippedFor !== currentUser.id;
return (
<Wrapper onDoubleClick={onDblClick}>
@ -118,7 +117,7 @@ const Image = ({
)}
{flippedForMe && backText && <Label>{backText}</Label>}
{(!flippedForMe || !backText) && text && <Label>{text}</Label>}
<StyledImage
<FrontImage
visible={!flippedForMe}
src={content}
alt=""

View file

@ -4,6 +4,8 @@ import { useRecoilValue } from "recoil";
import { selectedItemsAtom } from "../../Selector";
import debounce from "lodash.debounce";
import styled, { css } from "styled-components";
import Rect from "./Rect";
import Round from "./Round";
import Image from "./Image";
@ -30,21 +32,47 @@ const getComponent = (type) => {
}
};
const ItemWrapper = styled.div.attrs(({ x, y, rotation }) => ({
className: "item",
style: { left: `${x}px`, top: `${y}px`, transform: `rotate(${rotation}deg)` },
}))`
position: absolute;
display: inline-block;
transition: transform 200ms;
z-index: ${({ layer }) => (layer || 0) + 3};
${({ selected }) =>
selected
? css`
border: 2px dashed #ff0000a0;
padding: 2px;
cursor: pointer;
`
: css`
padding: 4px;
`}
${({ locked }) =>
locked &&
css`
pointer-events: none;
user-select: none;
`}
`;
const Item = ({ setState, state }) => {
const selectedItems = useRecoilValue(selectedItemsAtom);
const itemRef = React.useRef(null);
const sizeRef = React.useRef({});
const [unlock, setUnlock] = React.useState(false);
// Allow to operate on locked item if ctrl is pressed
// Allow to operate on locked item if key is pressed
React.useEffect(() => {
const onKeyDown = (e) => {
if (e.key === "Control") {
if (e.key === "u" || e.key === "l") {
setUnlock(true);
}
};
const onKeyUp = (e) => {
if (e.key === "Control") {
if (e.key === "u" || e.key === "l") {
setUnlock(false);
}
};
@ -65,6 +93,7 @@ const Item = ({ setState, state }) => {
[setState, state.id]
);
// Update actual dimension. Usefull when image with own dimensions.
// eslint-disable-next-line react-hooks/exhaustive-deps
const actualSizeCallback = React.useCallback(
debounce((entries) => {
@ -102,43 +131,18 @@ const Item = ({ setState, state }) => {
};
}, [actualSizeCallback]);
const extraStyle = selectedItems.includes(state.id)
? { border: "2px dashed #ff0000a0", padding: "2px" }
: { padding: "4px" };
const content = (
<div
style={{
left: state.x + "px",
top: state.y + "px",
position: "absolute",
display: "inline-block",
transform: `rotate(${rotation}deg)`,
transition: "transform 200ms",
zIndex: (state.layer || 0) + 3,
...extraStyle,
}}
className="item"
return (
<ItemWrapper
x={state.x}
y={state.y}
rotation={rotation}
locked={state.locked && !unlock}
selected={selectedItems.includes(state.id)}
ref={itemRef}
id={state.id}
>
<Component {...state} x={0} y={0} setState={updateState} />
</div>
);
if (!state.locked || unlock) {
return content;
}
return (
<div
style={{
pointerEvents: "none",
userSelect: "none",
}}
>
{content}
</div>
</ItemWrapper>
);
};

View file

@ -5,11 +5,15 @@ import { useRecoilValue } from "recoil";
import { useItems } from "./Board/Items";
import { selectedItemsAtom } from "../components/Board/Selector";
import { insideClass } from "../utils";
import ItemFormFactory from "./Board/Items/Item/forms/ItemFormFactory";
import { confirmAlert } from "react-confirm-alert";
import "react-confirm-alert/src/react-confirm-alert.css";
import { useTranslation } from "react-i18next";
import { useUsers } from "./users";
const SelectedPane = styled.div.attrs(() => ({ className: "casrd" }))`
position: absolute;
@ -32,6 +36,8 @@ export const SelectedItems = ({ edit }) => {
const { t } = useTranslation();
const { currentUser } = useUsers();
const selectedItems = useRecoilValue(selectedItemsAtom);
const selectedItemList = React.useMemo(() => {
@ -66,33 +72,21 @@ export const SelectedItems = ({ edit }) => {
});
}, [selectedItemList, selectedItems, batchUpdateItems]);
const flip = React.useCallback(() => {
batchUpdateItems(selectedItems, (item) => ({
...item,
flipped: true,
}));
}, [selectedItems, batchUpdateItems]);
const tap = React.useCallback(() => {
batchUpdateItems(selectedItems, (item) => ({
...item,
rotation: 90,
}));
}, [selectedItems, batchUpdateItems]);
const untap = React.useCallback(() => {
batchUpdateItems(selectedItems, (item) => ({
...item,
rotation: 0,
}));
}, [selectedItems, batchUpdateItems]);
const toggleTap = React.useCallback(() => {
const tappedCount = selectedItemList.filter(
({ rotation }) => rotation === 90
).length;
let untap = false;
if (tappedCount > selectedItems.length / 2) {
untap = true;
}
batchUpdateItems(selectedItems, (item) => ({
...item,
rotation: item.rotation === 90 ? 0 : 90,
rotation: untap ? 0 : 90,
}));
}, [selectedItems, batchUpdateItems]);
}, [selectedItems, batchUpdateItems, selectedItemList]);
const toggleLock = React.useCallback(() => {
batchUpdateItems(selectedItems, (item) => ({
@ -102,30 +96,63 @@ export const SelectedItems = ({ edit }) => {
}, [selectedItems, batchUpdateItems]);
const toggleFlip = React.useCallback(() => {
batchUpdateItems(selectedItems, (item) => ({
...item,
flipped: !item.flipped,
}));
}, [selectedItems, batchUpdateItems]);
const flippedCount = selectedItemList.filter(({ flipped }) => flipped)
.length;
const unflip = React.useCallback(() => {
let flip = true;
if (flippedCount > selectedItems.length / 2) {
flip = false;
}
batchUpdateItems(selectedItems, (item) => ({
...item,
flipped: false,
flipped: flip,
unflippedFor: undefined,
}));
}, [selectedItems, batchUpdateItems]);
}, [selectedItemList, selectedItems, batchUpdateItems]);
const revealForMe = React.useCallback(() => {
batchUpdateItems(selectedItems, (item) => ({
...item,
flipped: true,
unflippedFor: currentUser.id,
}));
}, [batchUpdateItems, selectedItems, currentUser.id]);
React.useEffect(() => {
const onKeyUp = (e) => {
if (e.key === "f") {
if (insideClass(e.target, "item")) return;
toggleFlip();
}
if (e.key === "t") {
if (insideClass(e.target, "item")) return;
toggleTap();
}
if (e.key === "o") {
if (insideClass(e.target, "item")) return;
revealForMe();
}
};
document.addEventListener("keyup", onKeyUp);
return () => {
document.removeEventListener("keyup", onKeyUp);
};
}, [revealForMe, toggleFlip, toggleTap]);
const onSubmitHandler = React.useCallback(
(formValues) => {
updateItem(formValues.id, (item) => ({
...item,
...formValues,
}));
},
[updateItem]
);
if (selectedItemList.length === 0) {
return null;
}
const onSubmitHandler = (formValues) => {
updateItem(formValues.id, (item) => ({
...item,
...formValues,
}));
};
const onRemove = () => {
confirmAlert({
title: t("Confirmation"),
@ -195,12 +222,10 @@ export const SelectedItems = ({ edit }) => {
<h3>{t("items selected", { count: selectedItems.length })}</h3>
</header>
<section className="content">
<button onClick={shuffleSelectedItems}>{t("Shuffle")}</button>
<button onClick={toggleFlip}>{t("Reveal") + "/" + t("Hide")}</button>
<button onClick={toggleTap}>{t("Tap") + "/" + t("Untap")}</button>
<button onClick={align}>{t("Stack")}</button>
<button onClick={flip}>{t("Hide")}</button>
<button onClick={unflip}>{t("Reveal")}</button>
<button onClick={tap}>{t("Tap")}</button>
<button onClick={untap}>{t("Untap")}</button>
<button onClick={shuffleSelectedItems}>{t("Shuffle")}</button>
{edit && <button onClick={onRemove}>{t("Remove all")}</button>}
</section>
</div>