mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-31 10:54:33 +01:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			v0.17.0
			...
			zsviczian-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 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 }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user