Added font color picker
This commit is contained in:
parent
6d45430344
commit
09ae07ed7f
@ -51,6 +51,7 @@ import {
|
||||
getContainerElement,
|
||||
} from "../element/textElement";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
isBoundToContainer,
|
||||
isLinearElement,
|
||||
isLinearElementType,
|
||||
@ -106,6 +107,7 @@ const getFormValue = function <T>(
|
||||
appState: AppState,
|
||||
getAttribute: (element: ExcalidrawElement) => T,
|
||||
defaultValue?: T,
|
||||
onlyBoundTextElements: boolean = false,
|
||||
): T | null {
|
||||
const editingElement = appState.editingElement;
|
||||
const nonDeletedElements = getNonDeletedElements(elements);
|
||||
@ -116,6 +118,7 @@ const getFormValue = function <T>(
|
||||
nonDeletedElements,
|
||||
appState,
|
||||
getAttribute,
|
||||
onlyBoundTextElements,
|
||||
)
|
||||
: defaultValue) ??
|
||||
null
|
||||
@ -196,8 +199,8 @@ const changeFontSize = (
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const actionChangeStrokeColor = register({
|
||||
name: "changeStrokeColor",
|
||||
export const actionChangeFontColor = register({
|
||||
name: "changeFontColor",
|
||||
perform: (elements, appState, value) => {
|
||||
return {
|
||||
...(value.currentItemStrokeColor && {
|
||||
@ -205,7 +208,7 @@ export const actionChangeStrokeColor = register({
|
||||
elements,
|
||||
appState,
|
||||
(el) => {
|
||||
return hasStrokeColor(el.type)
|
||||
return isTextElement(el)
|
||||
? newElementWith(el, {
|
||||
strokeColor: value.currentItemStrokeColor,
|
||||
})
|
||||
@ -221,28 +224,107 @@ export const actionChangeStrokeColor = register({
|
||||
commitToHistory: !!value.currentItemStrokeColor,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||
<ColorPicker
|
||||
type="elementStroke"
|
||||
label={t("labels.stroke")}
|
||||
color={getFormValue(
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
return (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.fontColor")}</h3>
|
||||
<ColorPicker
|
||||
type="elementFontColor"
|
||||
label={t("labels.fontColor")}
|
||||
color={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => element.strokeColor,
|
||||
appState.currentItemStrokeColor,
|
||||
true,
|
||||
)}
|
||||
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
||||
isActive={appState.openPopup === "fontColorPicker"}
|
||||
setActive={(active) =>
|
||||
updateData({ openPopup: active ? "fontColorPicker" : null })
|
||||
}
|
||||
elements={elements}
|
||||
appState={appState}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const actionChangeStrokeColor = register({
|
||||
name: "changeStrokeColor",
|
||||
perform: (elements, appState, value) => {
|
||||
const targetElements = getTargetElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
|
||||
const hasOnlyContainersWithBoundText =
|
||||
targetElements.length > 1 &&
|
||||
targetElements.every(
|
||||
(element) =>
|
||||
hasBoundTextElement(element) || isBoundToContainer(element),
|
||||
);
|
||||
|
||||
return {
|
||||
...(value.currentItemStrokeColor && {
|
||||
elements: changeProperty(
|
||||
elements,
|
||||
appState,
|
||||
(element) => element.strokeColor,
|
||||
appState.currentItemStrokeColor,
|
||||
)}
|
||||
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
||||
isActive={appState.openPopup === "strokeColorPicker"}
|
||||
setActive={(active) =>
|
||||
updateData({ openPopup: active ? "strokeColorPicker" : null })
|
||||
}
|
||||
elements={elements}
|
||||
appState={appState}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
(el) => {
|
||||
return (hasStrokeColor(el.type) &&
|
||||
!hasOnlyContainersWithBoundText) ||
|
||||
!isBoundToContainer(el)
|
||||
? newElementWith(el, {
|
||||
strokeColor: value.currentItemStrokeColor,
|
||||
})
|
||||
: el;
|
||||
},
|
||||
true,
|
||||
),
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
...value,
|
||||
},
|
||||
commitToHistory: !!value.currentItemStrokeColor,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
const targetElements = getTargetElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
|
||||
const hasOnlyContainersWithBoundText = targetElements.every(
|
||||
(element) => hasBoundTextElement(element) || isBoundToContainer(element),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||
<ColorPicker
|
||||
type="elementStroke"
|
||||
label={t("labels.stroke")}
|
||||
color={getFormValue(
|
||||
hasOnlyContainersWithBoundText
|
||||
? elements.filter((element) => !isTextElement(element))
|
||||
: elements,
|
||||
appState,
|
||||
(element) => element.strokeColor,
|
||||
appState.currentItemStrokeColor,
|
||||
)}
|
||||
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
||||
isActive={appState.openPopup === "strokeColorPicker"}
|
||||
setActive={(active) =>
|
||||
updateData({ openPopup: active ? "strokeColorPicker" : null })
|
||||
}
|
||||
elements={elements}
|
||||
appState={appState}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const actionChangeBackgroundColor = register({
|
||||
|
@ -8,6 +8,7 @@ export {
|
||||
export { actionSelectAll } from "./actionSelectAll";
|
||||
export { actionDuplicateSelection } from "./actionDuplicateSelection";
|
||||
export {
|
||||
actionChangeFontColor,
|
||||
actionChangeStrokeColor,
|
||||
actionChangeBackgroundColor,
|
||||
actionChangeStrokeWidth,
|
||||
|
@ -49,6 +49,7 @@ export type ActionName =
|
||||
| "gridMode"
|
||||
| "zenMode"
|
||||
| "stats"
|
||||
| "changeFontColor"
|
||||
| "changeStrokeColor"
|
||||
| "changeBackgroundColor"
|
||||
| "changeFillStyle"
|
||||
|
@ -68,8 +68,15 @@ export const SelectedShapeActions = ({
|
||||
}
|
||||
}
|
||||
|
||||
const hasOnlyContainersWithBoundText =
|
||||
targetElements.length > 1 &&
|
||||
targetElements.every(
|
||||
(element) => hasBoundTextElement(element) || isBoundToContainer(element),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="panelColumn">
|
||||
{hasOnlyContainersWithBoundText && renderAction("changeFontColor")}
|
||||
{((hasStrokeColor(elementType) &&
|
||||
elementType !== "image" &&
|
||||
commonSelectedType !== "image") ||
|
||||
|
@ -1840,7 +1840,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.key === KEYS.G || event.key === KEYS.S) {
|
||||
if (
|
||||
event.key === KEYS.G ||
|
||||
event.key === KEYS.S ||
|
||||
event.key === KEYS.C
|
||||
) {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getElements(),
|
||||
this.state,
|
||||
@ -1862,6 +1866,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (event.key === KEYS.S) {
|
||||
this.setState({ openPopup: "strokeColorPicker" });
|
||||
}
|
||||
if (event.key === KEYS.C) {
|
||||
this.setState({ openPopup: "fontColorPicker" });
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -255,7 +255,8 @@
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.color-picker-type-elementStroke .color-picker-keybinding {
|
||||
.color-picker-type-elementStroke .color-picker-keybinding,
|
||||
.color-picker-type-elementFontColor .color-picker-keybinding {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
|
@ -101,19 +101,24 @@ const Picker = ({
|
||||
onClose: () => void;
|
||||
label: string;
|
||||
showInput: boolean;
|
||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||
type:
|
||||
| "canvasBackground"
|
||||
| "elementBackground"
|
||||
| "elementStroke"
|
||||
| "elementFontColor";
|
||||
elements: readonly ExcalidrawElement[];
|
||||
}) => {
|
||||
const firstItem = React.useRef<HTMLButtonElement>();
|
||||
const activeItem = React.useRef<HTMLButtonElement>();
|
||||
const gallery = React.useRef<HTMLDivElement>();
|
||||
const colorInput = React.useRef<HTMLInputElement>();
|
||||
const colorType = type === "elementFontColor" ? "elementStroke" : type;
|
||||
|
||||
const [customColors] = React.useState(() => {
|
||||
if (type === "canvasBackground") {
|
||||
if (colorType === "canvasBackground") {
|
||||
return [];
|
||||
}
|
||||
return getCustomColors(elements, type);
|
||||
return getCustomColors(elements, colorType);
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -356,7 +361,11 @@ export const ColorPicker = ({
|
||||
elements,
|
||||
appState,
|
||||
}: {
|
||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||
type:
|
||||
| "canvasBackground"
|
||||
| "elementBackground"
|
||||
| "elementStroke"
|
||||
| "elementFontColor";
|
||||
color: string | null;
|
||||
onChange: (color: string) => void;
|
||||
label: string;
|
||||
@ -366,7 +375,7 @@ export const ColorPicker = ({
|
||||
appState: AppState;
|
||||
}) => {
|
||||
const pickerButton = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
const colorType = type === "elementFontColor" ? "elementStroke" : type;
|
||||
return (
|
||||
<div>
|
||||
<div className="color-picker-control-container">
|
||||
@ -393,7 +402,7 @@ export const ColorPicker = ({
|
||||
}
|
||||
>
|
||||
<Picker
|
||||
colors={colors[type]}
|
||||
colors={colors[colorType]}
|
||||
color={color || null}
|
||||
onChange={(changedColor) => {
|
||||
onChange(changedColor);
|
||||
|
@ -47,6 +47,7 @@ export const KEYS = {
|
||||
COMMA: ",",
|
||||
|
||||
A: "a",
|
||||
C: "c",
|
||||
D: "d",
|
||||
E: "e",
|
||||
G: "g",
|
||||
|
@ -16,6 +16,7 @@
|
||||
"delete": "Delete",
|
||||
"copyStyles": "Copy styles",
|
||||
"pasteStyles": "Paste styles",
|
||||
"fontColor": "Font color",
|
||||
"stroke": "Stroke",
|
||||
"background": "Background",
|
||||
"fill": "Fill",
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
} from "../element/types";
|
||||
import { getElementAbsoluteCoords, getElementBounds } from "../element";
|
||||
import { AppState } from "../types";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import { isBoundToContainer, isTextElement } from "../element/typeChecks";
|
||||
|
||||
export const getElementsWithinSelection = (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
@ -41,12 +41,15 @@ export const getCommonAttributeOfSelectedElements = <T>(
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
getAttribute: (element: ExcalidrawElement) => T,
|
||||
onlyBoundTextElements: boolean = false,
|
||||
): T | null => {
|
||||
const attributes = Array.from(
|
||||
new Set(
|
||||
getSelectedElements(elements, appState).map((element) =>
|
||||
getAttribute(element),
|
||||
),
|
||||
getSelectedElements(elements, appState, onlyBoundTextElements)
|
||||
.filter((element) =>
|
||||
onlyBoundTextElements ? isTextElement(element) : true,
|
||||
)
|
||||
.map((element) => getAttribute(element)),
|
||||
),
|
||||
);
|
||||
return attributes.length === 1 ? attributes[0] : null;
|
||||
|
@ -113,6 +113,7 @@ export type AppState = {
|
||||
| "canvasColorPicker"
|
||||
| "backgroundColorPicker"
|
||||
| "strokeColorPicker"
|
||||
| "fontColorPicker"
|
||||
| null;
|
||||
lastPointerDownWith: PointerType;
|
||||
selectedElementIds: { [id: string]: boolean };
|
||||
|
Loading…
x
Reference in New Issue
Block a user