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