diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 3602bb6606..a4a8a4c5ff 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -12,24 +12,14 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - excalidraw/excalidraw - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - name: Login to DockerHub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v3 with: context: . push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: excalidraw/excalidraw:latest diff --git a/package.json b/package.json index 5816786e3c..57bdfe562f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ ] }, "dependencies": { - "@dwelle/tunnel-rat": "0.1.1", + "@radix-ui/react-tabs": "1.0.2", "@sentry/browser": "6.2.5", "@sentry/integrations": "6.2.5", "@testing-library/jest-dom": "5.16.2", @@ -51,7 +51,7 @@ "roughjs": "4.5.2", "sass": "1.51.0", "socket.io-client": "2.3.1", - "tunnel-rat": "0.1.0", + "tunnel-rat": "0.1.2", "workbox-background-sync": "^6.5.4", "workbox-broadcast-update": "^6.5.4", "workbox-cacheable-response": "^6.5.4", diff --git a/src/appState.ts b/src/appState.ts index 6f4db75572..aaac948795 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -58,7 +58,7 @@ export const getDefaultAppState = (): Omit< fileHandle: null, gridSize: null, isBindingEnabled: true, - isSidebarDocked: false, + defaultSidebarDockedPreference: false, isLoading: false, isResizing: false, isRotating: false, @@ -150,7 +150,11 @@ const APP_STATE_STORAGE_CONF = (< gridSize: { browser: true, export: true, server: true }, height: { browser: false, export: false, server: false }, isBindingEnabled: { browser: false, export: false, server: false }, - isSidebarDocked: { browser: true, export: false, server: false }, + defaultSidebarDockedPreference: { + browser: true, + export: false, + server: false, + }, isLoading: { browser: false, export: false, server: false }, isResizing: { browser: false, export: false, server: false }, isRotating: { browser: false, export: false, server: false }, diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 3bbc0ff1a6..875d8447ca 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -14,7 +14,7 @@ import { hasText, } from "../scene"; import { SHAPES } from "../shapes"; -import { AppState, Zoom } from "../types"; +import { UIAppState, Zoom } from "../types"; import { capitalizeString, isTransparent, @@ -28,19 +28,20 @@ import { trackEvent } from "../analytics"; import { hasBoundTextElement } from "../element/typeChecks"; import clsx from "clsx"; import { actionToggleZenMode } from "../actions"; -import "./Actions.scss"; import { Tooltip } from "./Tooltip"; import { shouldAllowVerticalAlign, suppportsHorizontalAlign, } from "../element/textElement"; +import "./Actions.scss"; + export const SelectedShapeActions = ({ appState, elements, renderAction, }: { - appState: AppState; + appState: UIAppState; elements: readonly ExcalidrawElement[]; renderAction: ActionManager["renderAction"]; }) => { @@ -215,10 +216,10 @@ export const ShapesSwitcher = ({ appState, }: { canvas: HTMLCanvasElement | null; - activeTool: AppState["activeTool"]; - setAppState: React.Component["setState"]; + activeTool: UIAppState["activeTool"]; + setAppState: React.Component["setState"]; onImageAction: (data: { pointerType: PointerType | null }) => void; - appState: AppState; + appState: UIAppState; }) => ( <> {SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => { diff --git a/src/components/App.tsx b/src/components/App.tsx index 14ce7992df..f587a95a66 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -211,6 +211,8 @@ import { PointerDownState, SceneData, Device, + SidebarName, + SidebarTabName, } from "../types"; import { debounce, @@ -300,6 +302,9 @@ import { activeConfirmDialogAtom } from "./ActiveConfirmDialog"; import { actionWrapTextInContainer } from "../actions/actionBoundText"; import BraveMeasureTextError from "./BraveMeasureTextError"; +const AppContext = React.createContext(null!); +const AppPropsContext = React.createContext(null!); + const deviceContextInitialValue = { isSmScreen: false, isMobile: false, @@ -341,6 +346,8 @@ const ExcalidrawActionManagerContext = React.createContext( ); ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext"; +export const useApp = () => useContext(AppContext); +export const useAppProps = () => useContext(AppPropsContext); export const useDevice = () => useContext(DeviceContext); export const useExcalidrawContainer = () => useContext(ExcalidrawContainerContext); @@ -401,7 +408,7 @@ class App extends React.Component { private nearestScrollableContainer: HTMLElement | Document | undefined; public library: AppClassProperties["library"]; public libraryItemsFromStorage: LibraryItems | undefined; - private id: string; + public id: string; private history: History; private excalidrawContainerValue: { container: HTMLDivElement | null; @@ -439,7 +446,7 @@ class App extends React.Component { width: window.innerWidth, height: window.innerHeight, showHyperlinkPopup: false, - isSidebarDocked: false, + defaultSidebarDockedPreference: false, }; this.id = nanoid(); @@ -470,7 +477,7 @@ class App extends React.Component { setActiveTool: this.setActiveTool, setCursor: this.setCursor, resetCursor: this.resetCursor, - toggleMenu: this.toggleMenu, + toggleSidebar: this.toggleSidebar, } as const; if (typeof excalidrawRef === "function") { excalidrawRef(api); @@ -578,101 +585,91 @@ class App extends React.Component { this.props.handleKeyboardGlobally ? undefined : this.onKeyDown } > - - - - - - - - this.addElementsFromPasteOrLibrary({ - elements, - position: "center", - files: null, - }) - } - langCode={getLanguage().code} - renderTopRightUI={renderTopRightUI} - renderCustomStats={renderCustomStats} - renderCustomSidebar={this.props.renderSidebar} - showExitZenModeBtn={ - typeof this.props?.zenModeEnabled === "undefined" && - this.state.zenModeEnabled - } - libraryReturnUrl={this.props.libraryReturnUrl} - UIOptions={this.props.UIOptions} - focusContainer={this.focusContainer} - library={this.library} - id={this.id} - onImageAction={this.onImageAction} - renderWelcomeScreen={ - !this.state.isLoading && - this.state.showWelcomeScreen && - this.state.activeTool.type === "selection" && - !this.scene.getElementsIncludingDeleted().length - } + + + + + + + - {this.props.children} - -
-
- {selectedElement.length === 1 && - !this.state.contextMenu && - this.state.showHyperlinkPopup && ( - + - )} - {this.state.toast !== null && ( - this.setToast(null)} - duration={this.state.toast.duration} - closable={this.state.toast.closable} - /> - )} - {this.state.contextMenu && ( - - )} -
{this.renderCanvas()}
- - {" "} - - - - + actionManager={this.actionManager} + elements={this.scene.getNonDeletedElements()} + onLockToggle={this.toggleLock} + onPenModeToggle={this.togglePenMode} + onHandToolToggle={this.onHandToolToggle} + langCode={getLanguage().code} + renderTopRightUI={renderTopRightUI} + renderCustomStats={renderCustomStats} + showExitZenModeBtn={ + typeof this.props?.zenModeEnabled === "undefined" && + this.state.zenModeEnabled + } + UIOptions={this.props.UIOptions} + onImageAction={this.onImageAction} + renderWelcomeScreen={ + !this.state.isLoading && + this.state.showWelcomeScreen && + this.state.activeTool.type === "selection" && + !this.scene.getElementsIncludingDeleted().length + } + > + {this.props.children} +
+
+
+ {selectedElement.length === 1 && + !this.state.contextMenu && + this.state.showHyperlinkPopup && ( + + )} + {this.state.toast !== null && ( + this.setToast(null)} + duration={this.state.toast.duration} + closable={this.state.toast.closable} + /> + )} + {this.state.contextMenu && ( + + )} +
{this.renderCanvas()}
+ + {" "} + + + + + +
); } public focusContainer: AppClassProperties["focusContainer"] = () => { - if (this.props.autoFocus) { - this.excalidrawContainerRef.current?.focus(); - } + this.excalidrawContainerRef.current?.focus(); }; public getSceneElementsIncludingDeleted = () => { @@ -683,6 +680,14 @@ class App extends React.Component { return this.scene.getNonDeletedElements(); }; + public onInsertElements = (elements: readonly ExcalidrawElement[]) => { + this.addElementsFromPasteOrLibrary({ + elements, + position: "center", + files: null, + }); + }; + private syncActionResult = withBatchedUpdates( (actionResult: ActionResult) => { if (this.unmounted || actionResult === false) { @@ -952,7 +957,7 @@ class App extends React.Component { this.scene.addCallback(this.onSceneUpdated); this.addEventListeners(); - if (this.excalidrawContainerRef.current) { + if (this.props.autoFocus && this.excalidrawContainerRef.current) { this.focusContainer(); } @@ -1680,7 +1685,7 @@ class App extends React.Component { openSidebar: this.state.openSidebar && this.device.canDeviceFitSidebar && - this.state.isSidebarDocked + this.state.defaultSidebarDockedPreference ? this.state.openSidebar : null, selectedElementIds: newElements.reduce( @@ -2020,30 +2025,24 @@ class App extends React.Component { /** * @returns whether the menu was toggled on or off */ - public toggleMenu = ( - type: "library" | "customSidebar", - force?: boolean, - ): boolean => { - if (type === "customSidebar" && !this.props.renderSidebar) { - console.warn( - `attempting to toggle "customSidebar", but no "props.renderSidebar" is defined`, - ); - return false; + public toggleSidebar = ({ + name, + tab, + force, + }: { + name: SidebarName; + tab?: SidebarTabName; + force?: boolean; + }): boolean => { + let nextName; + if (force === undefined) { + nextName = this.state.openSidebar?.name === name ? null : name; + } else { + nextName = force ? name : null; } + this.setState({ openSidebar: nextName ? { name: nextName, tab } : null }); - if (type === "library" || type === "customSidebar") { - let nextValue; - if (force === undefined) { - nextValue = this.state.openSidebar === type ? null : type; - } else { - nextValue = force ? type : null; - } - this.setState({ openSidebar: nextValue }); - - return !!nextValue; - } - - return false; + return !!nextName; }; private updateCurrentCursorPosition = withBatchedUpdates( diff --git a/src/components/BraveMeasureTextError.tsx b/src/components/BraveMeasureTextError.tsx index 8a4a71e4fd..1932d7a294 100644 --- a/src/components/BraveMeasureTextError.tsx +++ b/src/components/BraveMeasureTextError.tsx @@ -1,39 +1,40 @@ -import { t } from "../i18n"; +import Trans from "./Trans"; + const BraveMeasureTextError = () => { return (

- {t("errors.brave_measure_text_error.start")}   - - {t("errors.brave_measure_text_error.aggressive_block_fingerprint")} - {" "} - {t("errors.brave_measure_text_error.setting_enabled")}. -
-
- {t("errors.brave_measure_text_error.break")}{" "} - - {t("errors.brave_measure_text_error.text_elements")} - {" "} - {t("errors.brave_measure_text_error.in_your_drawings")}. + {el}} + />

- {t("errors.brave_measure_text_error.strongly_recommend")}{" "} - - {" "} - {t("errors.brave_measure_text_error.steps")} - {" "} - {t("errors.brave_measure_text_error.how")}. + {el}} + />

- {t("errors.brave_measure_text_error.disable_setting")}{" "} - - {t("errors.brave_measure_text_error.issue")} - {" "} - {t("errors.brave_measure_text_error.write")}{" "} - - {t("errors.brave_measure_text_error.discord")} - - . + ( + + {el} + + )} + /> +

+

+ ( + + {el} + + )} + discordLink={(el) => {el}.} + />

); diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 3303c3ebf8..bf548d72fb 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,8 +1,12 @@ +import clsx from "clsx"; +import { composeEventHandlers } from "../utils"; import "./Button.scss"; interface ButtonProps extends React.HTMLAttributes { type?: "button" | "submit" | "reset"; onSelect: () => any; + /** whether button is in active state */ + selected?: boolean; children: React.ReactNode; className?: string; } @@ -15,18 +19,18 @@ interface ButtonProps extends React.HTMLAttributes { export const Button = ({ type = "button", onSelect, + selected, children, className = "", ...rest }: ButtonProps) => { return (
@@ -334,21 +332,21 @@ const LayerUI = ({ }; const renderSidebars = () => { - return appState.openSidebar === "customSidebar" ? ( - renderCustomSidebar?.() || null - ) : appState.openSidebar === "library" ? ( - { + trackEvent( + "sidebar", + `toggleDock (${docked ? "dock" : "undock"})`, + `(${device.isMobile ? "mobile" : "desktop"})`, + ); + }} /> - ) : null; + ); }; - const [hostSidebarCounters] = useAtom(hostSidebarCountersAtom, jotaiScope); + const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope); const layerUIJSX = ( <> @@ -358,8 +356,25 @@ const LayerUI = ({ {children} {/* render component fallbacks. Can be rendered anywhere as they'll be tunneled away. We only render tunneled components that actually - have defaults when host do not render anything. */} + have defaults when host do not render anything. */} + { + if (open) { + trackEvent( + "sidebar", + `${DEFAULT_SIDEBAR.name} (open)`, + `button (${device.isMobile ? "mobile" : "desktop"})`, + ); + } + }} + tab={DEFAULT_SIDEBAR.defaultTab} + > + {t("toolBar.library")} + {/* ------------------------------------------------------------------ */} {appState.isLoading && } @@ -382,7 +397,6 @@ const LayerUI = ({ setAppState({ pasteDialog: { shown: false, data: null }, @@ -410,7 +424,6 @@ const LayerUI = ({ renderWelcomeScreen={renderWelcomeScreen} /> )} - {!device.isMobile && ( <>
- {renderWelcomeScreen && } + {renderWelcomeScreen && } {renderFixedSideContainer()}