Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide library button & Lock elements from toolbar #5

Merged
merged 6 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<a href="https://alkemio.foundation/" target="blank"><img src="https://alkemio.foundation/uploads/logos/alkemio-logo.svg" width="400" alt="Alkemio Logo" /></a>
<p align="center"> <a href="https://alkemio.foundation/" target="blank"><img src="https://alkemio.foundation/uploads/logos/alkemio-logo.svg" width="400" alt="Alkemio Logo" /></a>

</p>
<p align="center"><i>Enabling society to collaborate. Building a better future, together.</i></p>
# Alkemio fork of Excalidraw v0.17.0
Expand All @@ -11,7 +11,8 @@
git merge v0.17.0
git push --set-upstream origin 0.17.0-alkemio-1
```
- Applied the new styles of the buttons to Alkemio's ZoomToFit added button
- Applied the new styles of the buttons to Alkemio's ZoomToFit added button.
- Modified the paste functionality to avoid pasting elements (such as images) as JSON when editing text.

### For testing you can link the new package from the local client

Expand All @@ -31,6 +32,18 @@ yarn build:umd
yarn pack
yarn publish
```
# Alkemio fork of Excalidraw v0.17.0-alkemio-4
- Added `hideLibraryButton` to the appState to be able to hide the button from outside.
- Changed the toolbar Lock button behavior. Now it locks/unlocks elements instead of the tool in use

# Alkemio fork of Excalidraw v0.17.0-alkemio-3-beta
- Changed behavior. Pasting elements is better handled and now it doesn't end up as a big text node with JSON inside.


# Alkemio fork of Excalidraw v0.17.0

- Upgraded from Excalidraw v0.16.1 to v0.17.0


# Alkemio fork of Excalidraw v0.16.1

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
},
"homepage": "https://github.com/alkem-io/excalidraw",
"name": "@alkemio/excalidraw",
"version": "0.17.0-alkemio-2",
"version": "0.17.0-alkemio-4",
"prettier": "@excalidraw/prettier-config",
"private": false,
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions src/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const getDefaultAppState = (): Omit<
value: 1 as NormalizedZoomValue,
},
viewModeEnabled: false,
hideLibraryButton: false,
pendingImageElementId: null,
showHyperlinkPopup: false,
selectedLinearElement: null,
Expand Down Expand Up @@ -209,6 +210,7 @@ const APP_STATE_STORAGE_CONF = (<
zenModeEnabled: { browser: true, export: false, server: false },
zoom: { browser: true, export: false, server: false },
viewModeEnabled: { browser: false, export: false, server: false },
hideLibraryButton: { browser: false, export: false, server: false },
pendingImageElementId: { browser: false, export: false, server: false },
showHyperlinkPopup: { browser: false, export: false, server: false },
selectedLinearElement: { browser: true, export: false, server: false },
Expand Down
36 changes: 18 additions & 18 deletions src/clipboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,39 @@ import {
import { API } from "./tests/helpers/api";

describe("parseClipboard()", () => {
it("should parse JSON as plaintext if not excalidraw-api/clipboard data", async () => {
it("should parse JSON as plaintext if not excalidraw-api/clipboard data", () => {
let text;
let clipboardData;
// -------------------------------------------------------------------------

text = "123";
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({ types: { "text/plain": text } }),
);
expect(clipboardData.text).toBe(text);

// -------------------------------------------------------------------------

text = "[123]";
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({ types: { "text/plain": text } }),
);
expect(clipboardData.text).toBe(text);

// -------------------------------------------------------------------------

text = JSON.stringify({ val: 42 });
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({ types: { "text/plain": text } }),
);
expect(clipboardData.text).toBe(text);
});

it("should parse valid excalidraw JSON if inside text/plain", async () => {
it("should parse valid excalidraw JSON if inside text/plain", () => {
const rect = API.createElement({ type: "rectangle" });

const json = serializeAsClipboardJSON({ elements: [rect], files: null });
const clipboardData = await parseClipboard(
const clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/plain": json,
Expand All @@ -48,14 +48,14 @@ describe("parseClipboard()", () => {
expect(clipboardData.elements).toEqual([rect]);
});

it("should parse valid excalidraw JSON if inside text/html", async () => {
it("should parse valid excalidraw JSON if inside text/html", () => {
const rect = API.createElement({ type: "rectangle" });

let json;
let clipboardData;
// -------------------------------------------------------------------------
json = serializeAsClipboardJSON({ elements: [rect], files: null });
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": json,
Expand All @@ -65,7 +65,7 @@ describe("parseClipboard()", () => {
expect(clipboardData.elements).toEqual([rect]);
// -------------------------------------------------------------------------
json = serializeAsClipboardJSON({ elements: [rect], files: null });
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": `<div> ${json}</div>`,
Expand All @@ -76,10 +76,10 @@ describe("parseClipboard()", () => {
// -------------------------------------------------------------------------
});

it("should parse <image> `src` urls out of text/html", async () => {
it("should parse <image> `src` urls out of text/html", () => {
let clipboardData;
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": `<img src="https://example.com/image.png" />`,
Expand All @@ -93,7 +93,7 @@ describe("parseClipboard()", () => {
},
]);
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": `<div><img src="https://example.com/image.png" /></div><a><img src="https://example.com/image2.png" /></a>`,
Expand All @@ -112,8 +112,8 @@ describe("parseClipboard()", () => {
]);
});

it("should parse text content alongside <image> `src` urls out of text/html", async () => {
const clipboardData = await parseClipboard(
it("should parse text content alongside <image> `src` urls out of text/html", () => {
const clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": `<a href="https://example.com">hello </a><div><img src="https://example.com/image.png" /></div><b>my friend!</b>`,
Expand All @@ -137,10 +137,10 @@ describe("parseClipboard()", () => {
]);
});

it("should parse spreadsheet from either text/plain and text/html", async () => {
it("should parse spreadsheet from either text/plain and text/html", () => {
let clipboardData;
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/plain": `a b
Expand All @@ -156,7 +156,7 @@ describe("parseClipboard()", () => {
values: [2, 5, 10],
});
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": `a b
Expand All @@ -172,7 +172,7 @@ describe("parseClipboard()", () => {
values: [2, 5, 10],
});
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
clipboardData = parseClipboard(
createPasteEvent({
types: {
"text/html": `<html>
Expand Down
10 changes: 5 additions & 5 deletions src/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,10 @@ export const readSystemClipboard = async () => {
/**
* Parses "paste" ClipboardEvent.
*/
const parseClipboardEvent = async (
const parseClipboardEvent = (
event: ClipboardEvent,
isPlainPaste = false,
): Promise<ParsedClipboardEvent> => {
): ParsedClipboardEvent => {
try {
const mixedContent = !isPlainPaste && event && maybeParseHTMLPaste(event);

Expand Down Expand Up @@ -326,11 +326,11 @@ const parseClipboardEvent = async (
/**
* Attempts to parse clipboard. Prefers system clipboard.
*/
export const parseClipboard = async (
export const parseClipboard = (
event: ClipboardEvent,
isPlainPaste = false,
): Promise<ClipboardData> => {
const parsedEventData = await parseClipboardEvent(event, isPlainPaste);
): ClipboardData => {
const parsedEventData = parseClipboardEvent(event, isPlainPaste);

if (parsedEventData.type === "mixedContent") {
return {
Expand Down
13 changes: 6 additions & 7 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2237,14 +2237,19 @@ class App extends React.Component<AppProps, AppState> {
return;
}

// These two lines are moved up by Alkemio to avoid pasting images json as text inside text elements
let file = event?.clipboardData?.files[0];
const data = parseClipboard(event, isPlainPaste);

const elementUnderCursor = document.elementFromPoint(
this.lastViewportPosition.x,
this.lastViewportPosition.y,
);
if (
event &&
(!(elementUnderCursor instanceof HTMLCanvasElement) ||
isWritableElement(target))
isWritableElement(target)) &&
!data.elements // If there are any elements, they will not be inserted as text
) {
return;
}
Expand All @@ -2257,12 +2262,6 @@ class App extends React.Component<AppProps, AppState> {
this.state,
);

// must be called in the same frame (thus before any awaits) as the paste
// event else some browsers (FF...) will clear the clipboardData
// (something something security)
let file = event?.clipboardData?.files[0];

const data = await parseClipboard(event, isPlainPaste);
if (!file && !isPlainPaste) {
if (data.mixedContent) {
return this.addElementsFromMixedContentPaste(data.mixedContent, {
Expand Down
31 changes: 30 additions & 1 deletion src/components/LayerUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { FixedSideContainer } from "./FixedSideContainer";
import { HintViewer } from "./HintViewer";
import { Island } from "./Island";
import { LoadingMessage } from "./LoadingMessage";
import { LockButton } from "./LockButton";
import { LockElementButton } from "./LockElementButton";
import { MobileMenu } from "./MobileMenu";
import { PasteChartDialog } from "./PasteChartDialog";
import { Section } from "./Section";
Expand Down Expand Up @@ -56,6 +56,7 @@ import { mutateElement } from "../element/mutateElement";
import { ShapeCache } from "../scene/ShapeCache";
import Scene from "../scene/Scene";
import { LaserPointerButton } from "./LaserTool/LaserPointerButton";
import { actionToggleElementLock } from "../actions";

interface LayerUIProps {
actionManager: ActionManager;
Expand Down Expand Up @@ -214,6 +215,15 @@ const LayerUI = ({
</Section>
);

const allElementsLocked = (elementsIds: string[]) => {
if (elementsIds.length === 0) {
return false;
}
return elements
.filter((element) => elementsIds.includes(element.id))
.every((element) => element.locked);
};

const renderFixedSideContainer = () => {
const shouldRenderSelectedShapeActions = showSelectedShapeActions(
appState,
Expand Down Expand Up @@ -262,11 +272,29 @@ const LayerUI = ({
title={t("toolBar.penMode")}
penDetected={appState.penDetected}
/>
{/*
Removed:
<LockButton
checked={appState.activeTool.locked}
onChange={onLockToggle}
title={t("toolBar.lock")}
/>
*/}
<LockElementButton
disabled={
Object.keys(appState.selectedElementIds)
.length === 0
}
checked={allElementsLocked(
Object.keys(appState.selectedElementIds),
)}
onChange={() =>
actionManager.executeAction(
actionToggleElementLock,
)
}
title={t("toolBar.lockElements")}
/>

<div className="App-toolbar__divider" />

Expand Down Expand Up @@ -320,6 +348,7 @@ const LayerUI = ({
<UserList collaborators={appState.collaborators} />
{renderTopRightUI?.(device.editor.isMobile, appState)}
{!appState.viewModeEnabled &&
!appState.hideLibraryButton &&
// hide button when sidebar docked
(!isSidebarDocked ||
appState.openSidebar?.name !== DEFAULT_SIDEBAR.name) && (
Expand Down
53 changes: 53 additions & 0 deletions src/components/LockElementButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import "./ToolIcon.scss";

import clsx from "clsx";
import { ToolButtonSize } from "./ToolButton";
import { LockedIcon, UnlockedIcon } from "./icons";

type LockElementIconProps = {
title?: string;
name?: string;
checked: boolean;
disabled?: boolean;
onChange?(): void;
isMobile?: boolean;
};

const DEFAULT_SIZE: ToolButtonSize = "medium";

const ICONS = {
CHECKED: LockedIcon,
UNCHECKED: UnlockedIcon,
};

export const LockElementButton = (props: LockElementIconProps) => {
return (
<label
className={clsx(
"ToolIcon ToolIcon__lock",
`ToolIcon_size_${DEFAULT_SIZE}`,
{
"is-mobile": props.isMobile,
},
{
disabled: props.disabled,
},
)}
title={`${props.title} — Ctrl + Shift + L`}
>
<input
className="ToolIcon_type_checkbox"
type="checkbox"
name={props.name}
onChange={props.onChange}
checked={props.checked}
disabled={props.disabled}
aria-label={props.title}
data-testid="toolbar-lock"
/>
<div className="ToolIcon__icon">
{props.checked ? ICONS.CHECKED : ICONS.UNCHECKED}
</div>
</label>
);
};
4 changes: 4 additions & 0 deletions src/components/ToolIcon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

@include toolbarButtonColorStates;
}
.ToolIcon.disabled {
cursor: default;
opacity: 0.5;
}

.ToolIcon--plain {
background-color: transparent;
Expand Down
Loading
Loading