Add shortcut to actions
This commit is contained in:
parent
cf4e68ca34
commit
03dab656aa
3 changed files with 113 additions and 85 deletions
|
@ -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=""
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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,29 +96,62 @@ 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(() => {
|
||||
batchUpdateItems(selectedItems, (item) => ({
|
||||
...item,
|
||||
flipped: false,
|
||||
}));
|
||||
}, [selectedItems, batchUpdateItems]);
|
||||
|
||||
if (selectedItemList.length === 0) {
|
||||
return null;
|
||||
let flip = true;
|
||||
if (flippedCount > selectedItems.length / 2) {
|
||||
flip = false;
|
||||
}
|
||||
batchUpdateItems(selectedItems, (item) => ({
|
||||
...item,
|
||||
flipped: flip,
|
||||
unflippedFor: undefined,
|
||||
}));
|
||||
}, [selectedItemList, selectedItems, batchUpdateItems]);
|
||||
|
||||
const onSubmitHandler = (formValues) => {
|
||||
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 onRemove = () => {
|
||||
confirmAlert({
|
||||
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue