Skip to content

Commit

Permalink
Merge pull request #1 from interviewstreet/comments
Browse files Browse the repository at this point in the history
feat: Comment Elements
  • Loading branch information
vivek9patel authored May 26, 2022
2 parents 1ed1529 + 3f33fb3 commit 26ca72e
Show file tree
Hide file tree
Showing 32 changed files with 738 additions and 132 deletions.
5 changes: 4 additions & 1 deletion src/actions/actionCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { newElementWith } from "../element/mutateElement";
import { getDefaultAppState, isEraserActive } from "../appState";
import ClearCanvas from "../components/ClearCanvas";
import clsx from "clsx";
import { isCommentElement } from "../element/typeChecks";

export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor",
Expand Down Expand Up @@ -56,7 +57,9 @@ export const actionClearCanvas = register({
app.imageCache.clear();
return {
elements: elements.map((element) =>
newElementWith(element, { isDeleted: true }),
isCommentElement(element)
? element
: newElementWith(element, { isDeleted: true }),
),
appState: {
...getDefaultAppState(),
Expand Down
2 changes: 1 addition & 1 deletion src/actions/actionClipboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const actionCut = register({
trackEvent: { category: "element" },
perform: (elements, appState, data, app) => {
actionCopy.perform(elements, appState, data, app);
return actionDeleteSelected.perform(elements, appState);
return actionDeleteSelected.perform(elements, appState, data, app);
},
contextItemLabel: "labels.cut",
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.X,
Expand Down
72 changes: 56 additions & 16 deletions src/actions/actionDeleteSelected.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,65 @@ import { trash } from "../components/icons";
import { t } from "../i18n";
import { register } from "./register";
import { getNonDeletedElements } from "../element";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { ExcalidrawCommentElement, ExcalidrawElement } from "../element/types";
import { AppState, UserProp } from "../types";
import { newElementWith } from "../element/mutateElement";
import { getElementsInGroup } from "../groups";
import { LinearElementEditor } from "../element/linearElementEditor";
import { fixBindingsAfterDeletion } from "../element/binding";
import { isBoundToContainer } from "../element/typeChecks";
import { isBoundToContainer, isCommentElement } from "../element/typeChecks";
import { arrayToMap } from "../utils";

const deleteSelectedElements = (
elements: readonly ExcalidrawElement[],
appState: AppState,
currentUser?: UserProp,
canCommentElementBeDeleted = true,
forceDeleteElements?: ExcalidrawElement[],
) => {
return {
elements: elements.map((el) => {
if (appState.selectedElementIds[el.id]) {
return newElementWith(el, { isDeleted: true });
let isActiveCommentDeleted = false;
const forceDeleteElementsMap = arrayToMap(forceDeleteElements || []);
const nextElements = elements.map((el) => {
if (forceDeleteElementsMap.has(el.id)) {
return newElementWith(el, { isDeleted: true });
}
if (appState.selectedElementIds[el.id]) {
if (appState.activeComment?.element.id === el.id) {
isActiveCommentDeleted = true;
}
if (
isBoundToContainer(el) &&
appState.selectedElementIds[el.containerId]
) {
return newElementWith(el, { isDeleted: true });
if (isCommentElement(el)) {
if (
(currentUser && el.owner.email !== currentUser.email) ||
!canCommentElementBeDeleted
) {
return el;
}
}
return newElementWith(el, { isDeleted: true });
}
if (isBoundToContainer(el) && appState.selectedElementIds[el.containerId]) {
if (appState.activeComment?.element.id === el.id) {
isActiveCommentDeleted = true;
}
return el;
}),
if (isCommentElement(el)) {
if (
(currentUser &&
(el as ExcalidrawCommentElement).owner.email !==
currentUser.email) ||
!canCommentElementBeDeleted
) {
return el;
}
}
return newElementWith(el, { isDeleted: true });
}
return el;
});
return {
elements: nextElements,
appState: {
...appState,
activeComment: isActiveCommentDeleted ? null : appState.activeComment,
selectedElementIds: {},
},
};
Expand Down Expand Up @@ -59,7 +91,7 @@ const handleGroupEditingState = (
export const actionDeleteSelected = register({
name: "deleteSelectedElements",
trackEvent: { category: "element", action: "delete" },
perform: (elements, appState) => {
perform: (elements, appState, forceDeleteElements, app) => {
if (appState.editingLinearElement) {
const {
elementId,
Expand Down Expand Up @@ -121,8 +153,15 @@ export const actionDeleteSelected = register({
commitToHistory: true,
};
}

let { elements: nextElements, appState: nextAppState } =
deleteSelectedElements(elements, appState);
deleteSelectedElements(
elements,
appState,
app.props.user,
false,
forceDeleteElements,
);
fixBindingsAfterDeletion(
nextElements,
elements.filter(({ id }) => appState.selectedElementIds[id]),
Expand All @@ -136,6 +175,7 @@ export const actionDeleteSelected = register({
...nextAppState,
activeTool: { ...appState.activeTool, type: "selection" },
multiElement: null,
activeComment: null,
},
commitToHistory: isSomeElementSelected(
getNonDeletedElements(elements),
Expand Down
33 changes: 27 additions & 6 deletions src/actions/actionHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { isWindows, KEYS } from "../keys";
import { newElementWith } from "../element/mutateElement";
import { fixBindingsAfterDeletion } from "../element/binding";
import { arrayToMap } from "../utils";
import { isCommentElement } from "../element/typeChecks";

const writeData = (
prevElements: readonly ExcalidrawElement[],
Expand All @@ -34,20 +35,40 @@ const writeData = (
const deletedElements = prevElements.filter(
(prevElement) => !nextElementMap.has(prevElement.id),
);

const shouldNotDeleteCommentElementsMap = arrayToMap(
prevElements.filter((e) => {
return isCommentElement(e) && !nextElementMap.has(e.id) && !e.isDeleted;
}),
);

const elements = nextElements
.map((nextElement) =>
newElementWith(
.map((nextElement) => {
if (isCommentElement(nextElement)) {
// case #1: if comment is previously deleted it shouldn't rendered again;
// example: (1) Add comment (2) Delete it (3) on UNDO/REDO operation, comment shouldn't render
if (
prevElementMap.has(nextElement.id) &&
prevElementMap.get(nextElement.id)?.isDeleted
) {
return newElementWith(nextElement, { isDeleted: true });
}
}
return newElementWith(
prevElementMap.get(nextElement.id) || nextElement,
nextElement,
),
)
);
})
.concat(
// case #2: if comment is previously added it shouldn't be deleted unless forceDeleted by parent (HackerDraw)
// example: (1) Add comment (2) on UNDO/REDO operation, comment shouldn't delete
deletedElements.map((prevElement) =>
newElementWith(prevElement, { isDeleted: true }),
newElementWith(prevElement, {
isDeleted: !shouldNotDeleteCommentElementsMap.has(prevElement.id),
}),
),
);
fixBindingsAfterDeletion(elements, deletedElements);

return {
elements,
appState: { ...appState, ...data.appState },
Expand Down
6 changes: 4 additions & 2 deletions src/actions/actionProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from "../element/textElement";
import {
isBoundToContainer,
isCommentElement,
isLinearElement,
isLinearElementType,
} from "../element/typeChecks";
Expand Down Expand Up @@ -92,8 +93,9 @@ const changeProperty = (
);
return elements.map((element) => {
if (
selectedElementIds.get(element.id) ||
element.id === appState.editingElement?.id
!isCommentElement(element) &&
(selectedElementIds.get(element.id) ||
element.id === appState.editingElement?.id)
) {
return callback(element);
}
Expand Down
2 changes: 2 additions & 0 deletions src/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const getDefaultAppState = (): Omit<
viewModeEnabled: false,
pendingImageElement: null,
showHyperlinkPopup: false,
activeComment: null,
};
};

Expand Down Expand Up @@ -178,6 +179,7 @@ const APP_STATE_STORAGE_CONF = (<
viewModeEnabled: { browser: false, export: false, server: false },
pendingImageElement: { browser: false, export: false, server: false },
showHyperlinkPopup: { browser: false, export: false, server: false },
activeComment: { browser: false, export: false, server: false },
});

const _clearAppStateForStorage = <
Expand Down
13 changes: 10 additions & 3 deletions src/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import { AppState, BinaryFiles } from "./types";
import { SVG_EXPORT_TAG } from "./scene/export";
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "./constants";
import { isInitializedImageElement } from "./element/typeChecks";
import {
isCommentElement,
isInitializedImageElement,
} from "./element/typeChecks";
import { isPromiseLike } from "./utils";
import { getSelectedElements } from "./scene";

type ElementsClipboard = {
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
Expand Down Expand Up @@ -58,12 +62,15 @@ export const copyToClipboard = async (
appState: AppState,
files: BinaryFiles | null,
) => {
const selectedElements = getSelectedElements(elements, appState, true).filter(
(element) => !isCommentElement(element),
);
// select binded text elements when copying
const contents: ElementsClipboard = {
type: EXPORT_DATA_TYPES.excalidrawClipboard,
elements,
elements: selectedElements,
files: files
? elements.reduce((acc, element) => {
? selectedElements.reduce((acc, element) => {
if (isInitializedImageElement(element) && files[element.fileId]) {
acc[element.fileId] = files[element.fileId];
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ export const ShapesSwitcher = ({
}) => (
<>
{SHAPES.map(({ value, icon, key }, index) => {
if (value === "comment") {
return null;
}
const label = t(`toolBar.${value}`);
const letter = key && (typeof key === "string" ? key : key[0]);
const shortcut = letter
Expand Down
Loading

0 comments on commit 26ca72e

Please sign in to comment.