This commit is contained in:
dwelle
2023-09-04 00:31:27 +02:00
parent fb4e5948fa
commit 07897a2174
36 changed files with 434 additions and 310 deletions
+7 -4
View File
@@ -29,10 +29,13 @@ export const actionSelectAllElementsInFrame = register({
elements,
appState: {
...appState,
selectedElementIds: elementsInFrame.reduce((acc, element) => {
acc[element.id] = true;
return acc;
}, {} as Record<ExcalidrawElement["id"], true>),
selectedElementIds: elementsInFrame.reduce(
(acc, element) => {
acc[element.id] = true;
return acc;
},
{} as Record<ExcalidrawElement["id"], true>,
),
},
commitToHistory: false,
};
+2 -2
View File
@@ -215,11 +215,11 @@ const _clearAppStateForStorage = <
exportType: ExportType,
) => {
type ExportableKeys = {
[K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
[K in keyof typeof APP_STATE_STORAGE_CONF]: (typeof APP_STATE_STORAGE_CONF)[K][ExportType] extends true
? K
: never;
}[keyof typeof APP_STATE_STORAGE_CONF];
const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
const stateForExport = {} as { [K in ExportableKeys]?: (typeof appState)[K] };
for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
const propConfig = APP_STATE_STORAGE_CONF[key];
if (propConfig?.[exportType]) {
+9 -6
View File
@@ -6,12 +6,15 @@ const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
source: R,
keys: K,
) => {
return keys.reduce((acc, key: K[number]) => {
if (key in source) {
acc[key] = source[key];
}
return acc;
}, {} as Pick<R, K[number]>) as Pick<R, K[number]>;
return keys.reduce(
(acc, key: K[number]) => {
if (key in source) {
acc[key] = source[key];
}
return acc;
},
{} as Pick<R, K[number]>,
) as Pick<R, K[number]>;
};
export type ColorPickerColor =
+6 -5
View File
@@ -3080,7 +3080,7 @@ class App extends React.Component<AppProps, AppState> {
tool:
| {
type:
| typeof SHAPES[number]["value"]
| (typeof SHAPES)[number]["value"]
| "eraser"
| "hand"
| "frame"
@@ -4770,12 +4770,13 @@ class App extends React.Component<AppProps, AppState> {
),
// we need to duplicate because we'll be updating this state
lastCoords: { ...origin },
originalElements: this.scene
.getNonDeletedElements()
.reduce((acc, element) => {
originalElements: this.scene.getNonDeletedElements().reduce(
(acc, element) => {
acc.set(element.id, deepCopyElement(element));
return acc;
}, new Map() as PointerDownState["originalElements"]),
},
new Map() as PointerDownState["originalElements"],
),
resize: {
handleType: false,
isResizing: false,
+2 -1
View File
@@ -107,7 +107,8 @@
border-radius: 1rem;
background-color: var(--button-color);
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.28),
box-shadow:
0 3px 5px -1px rgba(0, 0, 0, 0.28),
0 6px 10px 0 rgba(0, 0, 0, 0.14);
font-family: Cascadia;
+6 -2
View File
@@ -70,12 +70,16 @@
font-weight: 500;
opacity: 0;
visibility: hidden;
transition: visibility 0s linear 0s, opacity 0.5s;
transition:
visibility 0s linear 0s,
opacity 0.5s;
&--visible {
opacity: 1;
visibility: visible;
transition: visibility 0s linear 300ms, opacity 0.5s;
transition:
visibility 0s linear 300ms,
opacity 0.5s;
transition-delay: 0.8s;
}
}
+9 -3
View File
@@ -391,13 +391,19 @@
appearance: none;
background-image: var(--dropdown-icon);
background-repeat: no-repeat;
background-position: right 0.7rem top 50%, 0 0;
background-position:
right 0.7rem top 50%,
0 0;
:root[dir="rtl"] & {
background-position: left 0.7rem top 50%, 0 0;
background-position:
left 0.7rem top 50%,
0 0;
}
background-size: 0.65em auto, 100%;
background-size:
0.65em auto,
100%;
&:focus {
box-shadow: 0 0 0 2px var(--focus-highlight-color);
+1 -1
View File
@@ -273,7 +273,7 @@ export const resizeImageFile = async (
file: File,
opts: {
/** undefined indicates auto */
outputType?: typeof MIME_TYPES["jpg"];
outputType?: (typeof MIME_TYPES)["jpg"];
maxWidthOrHeight: number;
},
): Promise<File> => {
+69 -72
View File
@@ -51,84 +51,81 @@ export type ValidLinearElement = {
textAlign?: TextAlign;
verticalAlign?: VerticalAlign;
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
end?:
end?: (
| (
| (
| {
type: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
id?: ExcalidrawGenericElement["id"];
}
| {
id: ExcalidrawGenericElement["id"];
type?: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
}
)
| ((
| {
type: "text";
text: string;
}
| {
type?: "text";
id: ExcalidrawTextElement["id"];
text: string;
}
) &
Partial<ExcalidrawTextElement>)
| {
type: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
id?: ExcalidrawGenericElement["id"];
}
| {
id: ExcalidrawGenericElement["id"];
type?: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
}
)
| ((
| {
type: "text";
text: string;
}
| {
type?: "text";
id: ExcalidrawTextElement["id"];
text: string;
}
) &
MarkOptional<ElementConstructorOpts, "x" | "y">;
start?:
Partial<ExcalidrawTextElement>)
) &
MarkOptional<ElementConstructorOpts, "x" | "y">;
start?: (
| (
| (
| {
type: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
id?: ExcalidrawGenericElement["id"];
}
| {
id: ExcalidrawGenericElement["id"];
type?: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
}
)
| ((
| {
type: "text";
text: string;
}
| {
type?: "text";
id: ExcalidrawTextElement["id"];
text: string;
}
) &
Partial<ExcalidrawTextElement>)
| {
type: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
id?: ExcalidrawGenericElement["id"];
}
| {
id: ExcalidrawGenericElement["id"];
type?: Exclude<
ExcalidrawBindableElement["type"],
"image" | "text" | "frame" | "embeddable"
>;
}
)
| ((
| {
type: "text";
text: string;
}
| {
type?: "text";
id: ExcalidrawTextElement["id"];
text: string;
}
) &
MarkOptional<ElementConstructorOpts, "x" | "y">;
Partial<ExcalidrawTextElement>)
) &
MarkOptional<ElementConstructorOpts, "x" | "y">;
} & Partial<ExcalidrawLinearElement>;
export type ValidContainer =
| {
type: Exclude<ExcalidrawGenericElement["type"], "selection">;
id?: ExcalidrawGenericElement["id"];
label?: {
text: string;
fontSize?: number;
fontFamily?: FontFamilyValues;
textAlign?: TextAlign;
verticalAlign?: VerticalAlign;
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
} & ElementConstructorOpts;
export type ValidContainer = {
type: Exclude<ExcalidrawGenericElement["type"], "selection">;
id?: ExcalidrawGenericElement["id"];
label?: {
text: string;
fontSize?: number;
fontFamily?: FontFamilyValues;
textAlign?: TextAlign;
verticalAlign?: VerticalAlign;
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
} & ElementConstructorOpts;
export type ExcalidrawElementSkeleton =
| Extract<
+1 -1
View File
@@ -31,7 +31,7 @@ const _ce = ({
width: w,
height: h,
angle: a,
} as ExcalidrawElement);
}) as ExcalidrawElement;
describe("getElementAbsoluteCoords", () => {
it("test x1 coordinate", () => {
+3 -3
View File
@@ -404,7 +404,7 @@ export class LinearElementEditor {
static getEditorMidPoints = (
element: NonDeleted<ExcalidrawLinearElement>,
appState: InteractiveCanvasAppState,
): typeof editorMidPointsCache["points"] => {
): (typeof editorMidPointsCache)["points"] => {
const boundText = getBoundTextElement(element);
// Since its not needed outside editor unless 2 pointer lines or bound text
@@ -501,7 +501,7 @@ export class LinearElementEditor {
}
}
let index = 0;
const midPoints: typeof editorMidPointsCache["points"] =
const midPoints: (typeof editorMidPointsCache)["points"] =
LinearElementEditor.getEditorMidPoints(element, appState);
while (index < midPoints.length) {
if (midPoints[index] !== null) {
@@ -609,7 +609,7 @@ export class LinearElementEditor {
hitElement: NonDeleted<ExcalidrawElement> | null;
linearElementEditor: LinearElementEditor | null;
} {
const ret: ReturnType<typeof LinearElementEditor["handlePointerDown"]> = {
const ret: ReturnType<(typeof LinearElementEditor)["handlePointerDown"]> = {
didAddPoint: false,
hitElement: null,
linearElementEditor: null,
+1 -1
View File
@@ -767,7 +767,7 @@ export const resizeMultipleElements = (
false,
);
const update: typeof elementsAndUpdates[0]["update"] = {
const update: (typeof elementsAndUpdates)[0]["update"] = {
x,
y,
width,
+20 -14
View File
@@ -70,20 +70,26 @@ export const getElementWithTransformHandleType = (
zoom: Zoom,
pointerType: PointerType,
) => {
return elements.reduce((result, element) => {
if (result) {
return result;
}
const transformHandleType = resizeTest(
element,
appState,
scenePointerX,
scenePointerY,
zoom,
pointerType,
);
return transformHandleType ? { element, transformHandleType } : null;
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
return elements.reduce(
(result, element) => {
if (result) {
return result;
}
const transformHandleType = resizeTest(
element,
appState,
scenePointerX,
scenePointerY,
zoom,
pointerType,
);
return transformHandleType ? { element, transformHandleType } : null;
},
null as {
element: NonDeletedExcalidrawElement;
transformHandleType: MaybeTransformHandleType;
} | null,
);
};
export const getTransformHandleTypeFromCoords = (
+4 -4
View File
@@ -11,18 +11,18 @@ import { MarkNonNullable, ValueOf } from "../utility-types";
export type ChartType = "bar" | "line";
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
export type Theme = typeof THEME[keyof typeof THEME];
export type FontFamilyValues = (typeof FONT_FAMILY)[FontFamilyKeys];
export type Theme = (typeof THEME)[keyof typeof THEME];
export type FontString = string & { _brand: "fontString" };
export type GroupId = string;
export type PointerType = "mouse" | "pen" | "touch";
export type StrokeRoundness = "round" | "sharp";
export type RoundnessType = ValueOf<typeof ROUNDNESS>;
export type StrokeStyle = "solid" | "dashed" | "dotted";
export type TextAlign = typeof TEXT_ALIGN[keyof typeof TEXT_ALIGN];
export type TextAlign = (typeof TEXT_ALIGN)[keyof typeof TEXT_ALIGN];
type VerticalAlignKeys = keyof typeof VERTICAL_ALIGN;
export type VerticalAlign = typeof VERTICAL_ALIGN[VerticalAlignKeys];
export type VerticalAlign = (typeof VERTICAL_ALIGN)[VerticalAlignKeys];
type _ExcalidrawElementBase = Readonly<{
id: string;
+28 -25
View File
@@ -104,35 +104,38 @@ class History {
): DehydratedHistoryEntry =>
this.dehydrateHistoryEntry({
appState: clearAppStatePropertiesForHistory(appState),
elements: elements.reduce((elements, element) => {
if (
isLinearElement(element) &&
appState.multiElement &&
appState.multiElement.id === element.id
) {
// don't store multi-point arrow if still has only one point
elements: elements.reduce(
(elements, element) => {
if (
isLinearElement(element) &&
appState.multiElement &&
appState.multiElement.id === element.id &&
element.points.length < 2
appState.multiElement.id === element.id
) {
return elements;
}
// don't store multi-point arrow if still has only one point
if (
appState.multiElement &&
appState.multiElement.id === element.id &&
element.points.length < 2
) {
return elements;
}
elements.push({
...element,
// don't store last point if not committed
points:
element.lastCommittedPoint !==
element.points[element.points.length - 1]
? element.points.slice(0, -1)
: element.points,
});
} else {
elements.push(element);
}
return elements;
}, [] as Mutable<typeof elements>),
elements.push({
...element,
// don't store last point if not committed
points:
element.lastCommittedPoint !==
element.points[element.points.length - 1]
? element.points.slice(0, -1)
: element.points,
});
} else {
elements.push(element);
}
return elements;
},
[] as Mutable<typeof elements>,
),
});
shouldCreateEntry(nextEntry: HistoryEntry): boolean {
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
+1 -1
View File
@@ -36,7 +36,7 @@ const hashSelectionOpts = (
type _ = Assert<
SameType<
Required<HashableKeys>,
Pick<Required<HashableKeys>, typeof keys[number]>
Pick<Required<HashableKeys>, (typeof keys)[number]>
>
>;
+8 -8
View File
@@ -167,20 +167,20 @@ export const _generateElementShape = (
rightX - verticalRadius
} ${rightY - horizontalRadius}
C ${rightX} ${rightY}, ${rightX} ${rightY}, ${
rightX - verticalRadius
} ${rightY + horizontalRadius}
rightX - verticalRadius
} ${rightY + horizontalRadius}
L ${bottomX + verticalRadius} ${bottomY - horizontalRadius}
C ${bottomX} ${bottomY}, ${bottomX} ${bottomY}, ${
bottomX - verticalRadius
} ${bottomY - horizontalRadius}
bottomX - verticalRadius
} ${bottomY - horizontalRadius}
L ${leftX + verticalRadius} ${leftY + horizontalRadius}
C ${leftX} ${leftY}, ${leftX} ${leftY}, ${leftX + verticalRadius} ${
leftY - horizontalRadius
}
leftY - horizontalRadius
}
L ${topX - verticalRadius} ${topY + horizontalRadius}
C ${topX} ${topY}, ${topX} ${topY}, ${topX + verticalRadius} ${
topY + horizontalRadius
}`,
topY + horizontalRadius
}`,
generateRoughOptions(element, true),
);
} else {
+9 -6
View File
@@ -167,8 +167,8 @@ export const exportToSvg = async (
exportingFrameClipPath = `<clipPath id=${exportingFrame.id}>
<rect transform="translate(${exportingFrame.x + offsetX} ${
exportingFrame.y + offsetY
}) rotate(${exportingFrame.angle} ${cx} ${cy})"
exportingFrame.y + offsetY
}) rotate(${exportingFrame.angle} ${cx} ${cy})"
width="${exportingFrame.width}"
height="${exportingFrame.height}"
>
@@ -235,10 +235,13 @@ const getCanvasSize = (
if (!isExportingWholeCanvas || onlyExportingSingleFrame) {
const frames = elements.filter((element) => element.type === "frame");
const exportedFrameIds = frames.reduce((acc, frame) => {
acc[frame.id] = true;
return acc;
}, {} as Record<string, true>);
const exportedFrameIds = frames.reduce(
(acc, frame) => {
acc[frame.id] = true;
return acc;
},
{} as Record<string, true>,
);
// elements in a frame do not affect the canvas size if we're not exporting
// the whole canvas
+7 -4
View File
@@ -34,10 +34,13 @@ const { h } = window;
export class API {
static setSelectedElements = (elements: ExcalidrawElement[]) => {
h.setState({
selectedElementIds: elements.reduce((acc, element) => {
acc[element.id] = true;
return acc;
}, {} as Record<ExcalidrawElement["id"], true>),
selectedElementIds: elements.reduce(
(acc, element) => {
acc[element.id] = true;
return acc;
},
{} as Record<ExcalidrawElement["id"], true>,
),
});
};
+2 -2
View File
@@ -88,7 +88,7 @@ export type BinaryFiles = Record<ExcalidrawElement["id"], BinaryFileData>;
export type LastActiveTool =
| {
type:
| typeof SHAPES[number]["value"]
| (typeof SHAPES)[number]["value"]
| "eraser"
| "hand"
| "frame"
@@ -196,7 +196,7 @@ export type AppState = {
} & (
| {
type:
| typeof SHAPES[number]["value"]
| (typeof SHAPES)[number]["value"]
| "eraser"
| "hand"
| "frame"
+1 -1
View File
@@ -372,7 +372,7 @@ export const updateActiveTool = (
data: (
| {
type:
| typeof SHAPES[number]["value"]
| (typeof SHAPES)[number]["value"]
| "eraser"
| "hand"
| "frame"
+8 -5
View File
@@ -188,11 +188,14 @@ const getTargetElementsMap = <T extends ExcalidrawElement>(
elements: readonly T[],
indices: number[],
) => {
return indices.reduce((acc, index) => {
const element = elements[index];
acc[element.id] = element;
return acc;
}, {} as Record<string, ExcalidrawElement>);
return indices.reduce(
(acc, index) => {
const element = elements[index];
acc[element.id] = element;
return acc;
},
{} as Record<string, ExcalidrawElement>,
);
};
const _shiftElements = (