feat: zigzag fill easter egg (#6439)
This commit is contained in:
parent
ec215362a1
commit
e4d8ba226f
@ -1,4 +1,5 @@
|
|||||||
import { AppState } from "../../src/types";
|
import { AppState } from "../../src/types";
|
||||||
|
import { trackEvent } from "../analytics";
|
||||||
import { ButtonIconSelect } from "../components/ButtonIconSelect";
|
import { ButtonIconSelect } from "../components/ButtonIconSelect";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
import { IconPicker } from "../components/IconPicker";
|
import { IconPicker } from "../components/IconPicker";
|
||||||
@ -37,6 +38,7 @@ import {
|
|||||||
TextAlignLeftIcon,
|
TextAlignLeftIcon,
|
||||||
TextAlignCenterIcon,
|
TextAlignCenterIcon,
|
||||||
TextAlignRightIcon,
|
TextAlignRightIcon,
|
||||||
|
FillZigZagIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
@ -294,7 +296,12 @@ export const actionChangeBackgroundColor = register({
|
|||||||
export const actionChangeFillStyle = register({
|
export const actionChangeFillStyle = register({
|
||||||
name: "changeFillStyle",
|
name: "changeFillStyle",
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value, app) => {
|
||||||
|
trackEvent(
|
||||||
|
"element",
|
||||||
|
"changeFillStyle",
|
||||||
|
`${value} (${app.device.isMobile ? "mobile" : "desktop"})`,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@ -305,40 +312,55 @@ export const actionChangeFillStyle = register({
|
|||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData }) => (
|
PanelComponent: ({ elements, appState, updateData }) => {
|
||||||
<fieldset>
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
<legend>{t("labels.fill")}</legend>
|
const allElementsZigZag = selectedElements.every(
|
||||||
<ButtonIconSelect
|
(el) => el.fillStyle === "zigzag",
|
||||||
options={[
|
);
|
||||||
{
|
|
||||||
value: "hachure",
|
return (
|
||||||
text: t("labels.hachure"),
|
<fieldset>
|
||||||
icon: FillHachureIcon,
|
<legend>{t("labels.fill")}</legend>
|
||||||
},
|
<ButtonIconSelect
|
||||||
{
|
type="button"
|
||||||
value: "cross-hatch",
|
options={[
|
||||||
text: t("labels.crossHatch"),
|
{
|
||||||
icon: FillCrossHatchIcon,
|
value: "hachure",
|
||||||
},
|
text: t("labels.hachure"),
|
||||||
{
|
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
|
||||||
value: "solid",
|
active: allElementsZigZag ? true : undefined,
|
||||||
text: t("labels.solid"),
|
},
|
||||||
icon: FillSolidIcon,
|
{
|
||||||
},
|
value: "cross-hatch",
|
||||||
]}
|
text: t("labels.crossHatch"),
|
||||||
group="fill"
|
icon: FillCrossHatchIcon,
|
||||||
value={getFormValue(
|
},
|
||||||
elements,
|
{
|
||||||
appState,
|
value: "solid",
|
||||||
(element) => element.fillStyle,
|
text: t("labels.solid"),
|
||||||
appState.currentItemFillStyle,
|
icon: FillSolidIcon,
|
||||||
)}
|
},
|
||||||
onChange={(value) => {
|
]}
|
||||||
updateData(value);
|
value={getFormValue(
|
||||||
}}
|
elements,
|
||||||
/>
|
appState,
|
||||||
</fieldset>
|
(element) => element.fillStyle,
|
||||||
),
|
appState.currentItemFillStyle,
|
||||||
|
)}
|
||||||
|
onClick={(value, event) => {
|
||||||
|
const nextValue =
|
||||||
|
event.altKey &&
|
||||||
|
value === "hachure" &&
|
||||||
|
selectedElements.every((el) => el.fillStyle === "hachure")
|
||||||
|
? "zigzag"
|
||||||
|
: value;
|
||||||
|
|
||||||
|
updateData(nextValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionChangeStrokeWidth = register({
|
export const actionChangeStrokeWidth = register({
|
||||||
|
@ -1,33 +1,59 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
// TODO: It might be "clever" to add option.icon to the existing component <ButtonSelect />
|
// TODO: It might be "clever" to add option.icon to the existing component <ButtonSelect />
|
||||||
export const ButtonIconSelect = <T extends Object>({
|
export const ButtonIconSelect = <T extends Object>(
|
||||||
options,
|
props: {
|
||||||
value,
|
options: {
|
||||||
onChange,
|
value: T;
|
||||||
group,
|
text: string;
|
||||||
}: {
|
icon: JSX.Element;
|
||||||
options: { value: T; text: string; icon: JSX.Element; testId?: string }[];
|
testId?: string;
|
||||||
value: T | null;
|
/** if not supplied, defaults to value identity check */
|
||||||
onChange: (value: T) => void;
|
active?: boolean;
|
||||||
group: string;
|
}[];
|
||||||
}) => (
|
value: T | null;
|
||||||
|
type?: "radio" | "button";
|
||||||
|
} & (
|
||||||
|
| { type?: "radio"; group: string; onChange: (value: T) => void }
|
||||||
|
| {
|
||||||
|
type: "button";
|
||||||
|
onClick: (
|
||||||
|
value: T,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
) => (
|
||||||
<div className="buttonList buttonListIcon">
|
<div className="buttonList buttonListIcon">
|
||||||
{options.map((option) => (
|
{props.options.map((option) =>
|
||||||
<label
|
props.type === "button" ? (
|
||||||
key={option.text}
|
<button
|
||||||
className={clsx({ active: value === option.value })}
|
key={option.text}
|
||||||
title={option.text}
|
onClick={(event) => props.onClick(option.value, event)}
|
||||||
>
|
className={clsx({
|
||||||
<input
|
active: option.active ?? props.value === option.value,
|
||||||
type="radio"
|
})}
|
||||||
name={group}
|
|
||||||
onChange={() => onChange(option.value)}
|
|
||||||
checked={value === option.value}
|
|
||||||
data-testid={option.testId}
|
data-testid={option.testId}
|
||||||
/>
|
title={option.text}
|
||||||
{option.icon}
|
>
|
||||||
</label>
|
{option.icon}
|
||||||
))}
|
</button>
|
||||||
|
) : (
|
||||||
|
<label
|
||||||
|
key={option.text}
|
||||||
|
className={clsx({ active: props.value === option.value })}
|
||||||
|
title={option.text}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={props.group}
|
||||||
|
onChange={() => props.onChange(option.value)}
|
||||||
|
checked={props.value === option.value}
|
||||||
|
data-testid={option.testId}
|
||||||
|
/>
|
||||||
|
{option.icon}
|
||||||
|
</label>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1008,6 +1008,13 @@ export const UngroupIcon = React.memo(({ theme }: { theme: Theme }) =>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const FillZigZagIcon = createIcon(
|
||||||
|
<g strokeWidth={1.25}>
|
||||||
|
<path d="M5.879 2.625h8.242a3.27 3.27 0 0 1 3.254 3.254v8.242a3.27 3.27 0 0 1-3.254 3.254H5.88a3.27 3.27 0 0 1-3.254-3.254V5.88A3.27 3.27 0 0 1 5.88 2.626l-.001-.001ZM4.518 16.118l7.608-12.83m.198 13.934 5.051-9.897M2.778 9.675l9.348-6.387m-7.608 12.83 12.857-8.793" />
|
||||||
|
</g>,
|
||||||
|
modifiedTablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
export const FillHachureIcon = createIcon(
|
export const FillHachureIcon = createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
|
@ -155,6 +155,9 @@
|
|||||||
margin: 1px;
|
margin: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.welcome-screen-menu-item:focus-visible,
|
||||||
|
.dropdown-menu-item:focus-visible,
|
||||||
|
button:focus-visible,
|
||||||
.buttonList label:focus-within,
|
.buttonList label:focus-within,
|
||||||
input:focus-visible {
|
input:focus-visible {
|
||||||
outline: transparent;
|
outline: transparent;
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { MarkNonNullable, ValueOf } from "../utility-types";
|
import { MarkNonNullable, ValueOf } from "../utility-types";
|
||||||
|
|
||||||
export type ChartType = "bar" | "line";
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
|
||||||
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
||||||
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
|
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
|
||||||
export type Theme = typeof THEME[keyof typeof THEME];
|
export type Theme = typeof THEME[keyof typeof THEME];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user