Compare commits

...

4 Commits

Author SHA1 Message Date
Aakansha Doshi
9809eb5ef2 Add comps 2022-07-13 16:51:06 +05:30
Aakansha Doshi
e20b3325e9 export hook for mobile 2022-07-13 16:49:17 +05:30
Aakansha Doshi
274294a367 handle mobile 2022-07-13 16:22:00 +05:30
Aakansha Doshi
67bc10f33b feat: render footer as a component instead of render prop 2022-07-13 12:12:20 +05:30
10 changed files with 155 additions and 140 deletions

View File

@ -474,12 +474,8 @@ class App extends React.Component<AppProps, AppState> {
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
this.state, this.state,
); );
const { const { onCollabButtonClick, renderTopRightUI, renderCustomStats } =
onCollabButtonClick, this.props;
renderTopRightUI,
renderFooter,
renderCustomStats,
} = this.props;
return ( return (
<div <div
@ -520,7 +516,6 @@ class App extends React.Component<AppProps, AppState> {
langCode={getLanguage().code} langCode={getLanguage().code}
isCollaborating={this.props.isCollaborating} isCollaborating={this.props.isCollaborating}
renderTopRightUI={renderTopRightUI} renderTopRightUI={renderTopRightUI}
renderCustomFooter={renderFooter}
renderCustomStats={renderCustomStats} renderCustomStats={renderCustomStats}
viewModeEnabled={viewModeEnabled} viewModeEnabled={viewModeEnabled}
showExitZenModeBtn={ showExitZenModeBtn={
@ -537,7 +532,9 @@ class App extends React.Component<AppProps, AppState> {
library={this.library} library={this.library}
id={this.id} id={this.id}
onImageAction={this.onImageAction} onImageAction={this.onImageAction}
/> >
{this.props.children}
</LayerUI>
<div className="excalidraw-textEditorContainer" /> <div className="excalidraw-textEditorContainer" />
<div className="excalidraw-contextMenuContainer" /> <div className="excalidraw-contextMenuContainer" />
{selectedElement.length === 1 && this.state.showHyperlinkPopup && ( {selectedElement.length === 1 && this.state.showHyperlinkPopup && (

View File

@ -58,7 +58,6 @@ interface LayerUIProps {
langCode: Language["code"]; langCode: Language["code"];
isCollaborating: boolean; isCollaborating: boolean;
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"]; renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
renderCustomFooter?: ExcalidrawProps["renderFooter"];
renderCustomStats?: ExcalidrawProps["renderCustomStats"]; renderCustomStats?: ExcalidrawProps["renderCustomStats"];
viewModeEnabled: boolean; viewModeEnabled: boolean;
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"]; libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
@ -67,6 +66,7 @@ interface LayerUIProps {
library: Library; library: Library;
id: string; id: string;
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void; onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
children?: React.ReactNode;
} }
const LayerUI = ({ const LayerUI = ({
actionManager, actionManager,
@ -85,7 +85,6 @@ const LayerUI = ({
toggleZenMode, toggleZenMode,
isCollaborating, isCollaborating,
renderTopRightUI, renderTopRightUI,
renderCustomFooter,
renderCustomStats, renderCustomStats,
viewModeEnabled, viewModeEnabled,
libraryReturnUrl, libraryReturnUrl,
@ -94,6 +93,7 @@ const LayerUI = ({
library, library,
id, id,
onImageAction, onImageAction,
children,
}: LayerUIProps) => { }: LayerUIProps) => {
const device = useDevice(); const device = useDevice();
@ -454,7 +454,7 @@ const LayerUI = ({
}, },
)} )}
> >
{renderCustomFooter?.(false, appState)} {children}
</div> </div>
<div <div
className={clsx( className={clsx(
@ -542,13 +542,14 @@ const LayerUI = ({
onPenModeToggle={onPenModeToggle} onPenModeToggle={onPenModeToggle}
canvas={canvas} canvas={canvas}
isCollaborating={isCollaborating} isCollaborating={isCollaborating}
renderCustomFooter={renderCustomFooter}
viewModeEnabled={viewModeEnabled} viewModeEnabled={viewModeEnabled}
showThemeBtn={showThemeBtn} showThemeBtn={showThemeBtn}
onImageAction={onImageAction} onImageAction={onImageAction}
renderTopRightUI={renderTopRightUI} renderTopRightUI={renderTopRightUI}
renderStats={renderStats} renderStats={renderStats}
/> >
{children}
</MobileMenu>
</> </>
) : ( ) : (
<> <>
@ -606,7 +607,6 @@ const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[]; const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
return ( return (
prev.renderCustomFooter === next.renderCustomFooter &&
prev.langCode === next.langCode && prev.langCode === next.langCode &&
prev.elements === next.elements && prev.elements === next.elements &&
prev.files === next.files && prev.files === next.files &&

View File

@ -32,10 +32,7 @@ type MobileMenuProps = {
onPenModeToggle: () => void; onPenModeToggle: () => void;
canvas: HTMLCanvasElement | null; canvas: HTMLCanvasElement | null;
isCollaborating: boolean; isCollaborating: boolean;
renderCustomFooter?: (
isMobile: boolean,
appState: AppState,
) => JSX.Element | null;
viewModeEnabled: boolean; viewModeEnabled: boolean;
showThemeBtn: boolean; showThemeBtn: boolean;
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void; onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
@ -44,6 +41,7 @@ type MobileMenuProps = {
appState: AppState, appState: AppState,
) => JSX.Element | null; ) => JSX.Element | null;
renderStats: () => JSX.Element | null; renderStats: () => JSX.Element | null;
children?: React.ReactNode;
}; };
export const MobileMenu = ({ export const MobileMenu = ({
@ -59,12 +57,12 @@ export const MobileMenu = ({
onPenModeToggle, onPenModeToggle,
canvas, canvas,
isCollaborating, isCollaborating,
renderCustomFooter,
viewModeEnabled, viewModeEnabled,
showThemeBtn, showThemeBtn,
onImageAction, onImageAction,
renderTopRightUI, renderTopRightUI,
renderStats, renderStats,
children,
}: MobileMenuProps) => { }: MobileMenuProps) => {
const renderToolbar = () => { const renderToolbar = () => {
return ( return (
@ -201,7 +199,7 @@ export const MobileMenu = ({
<div className="panelColumn"> <div className="panelColumn">
<Stack.Col gap={4}> <Stack.Col gap={4}>
{renderCanvasActions()} {renderCanvasActions()}
{renderCustomFooter?.(true, appState)} {children}
{appState.collaborators.size > 0 && ( {appState.collaborators.size > 0 && (
<fieldset> <fieldset>
<legend>{t("labels.collaborators")}</legend> <legend>{t("labels.collaborators")}</legend>

View File

@ -0,0 +1,79 @@
import { shield } from "../../components/icons";
import { Tooltip } from "../../components/Tooltip";
import { t } from "../../i18n";
import { languages, useDevice } from "../../packages/excalidraw/index";
import { LanguageList } from "./LanguageList";
import PlusAppLink, { isExcalidrawPlusSignedUser } from "./PlusAppLink";
import PlusLPLink from "./PlusLPLink";
const EncryptedIcon = () => (
<a
className="encrypted-icon tooltip"
href="https://blog.excalidraw.com/end-to-end-encryption/"
target="_blank"
rel="noopener noreferrer"
aria-label={t("encrypted.link")}
>
<Tooltip label={t("encrypted.tooltip")} long={true}>
{shield}
</Tooltip>
</a>
);
const Footer = ({
langCode,
onLangChange,
}: {
langCode: string;
onLangChange: (langCode: string) => void;
}) => {
const device = useDevice();
if (device.isMobile) {
const isTinyDevice = window.innerWidth < 362;
return (
<div
style={{
display: "flex",
flexDirection: isTinyDevice ? "column" : "row",
}}
>
<fieldset>
<legend>{t("labels.language")}</legend>
<LanguageList
onChange={onLangChange}
languages={languages}
currentLangCode={langCode}
/>
</fieldset>
{/* FIXME remove after 2021-05-20 */}
<div
style={{
width: "24ch",
fontSize: "0.7em",
textAlign: "center",
marginTop: isTinyDevice ? 16 : undefined,
marginLeft: "auto",
marginRight: isTinyDevice ? "auto" : undefined,
padding: isExcalidrawPlusSignedUser ? undefined : "4px 2px",
border: isExcalidrawPlusSignedUser ? undefined : "1px dashed #aaa",
borderRadius: 12,
}}
>
{isExcalidrawPlusSignedUser ? <PlusAppLink /> : <PlusLPLink />}
</div>
</div>
);
}
return (
<>
<EncryptedIcon />
<LanguageList
onChange={onLangChange}
languages={languages}
currentLangCode={langCode}
/>
</>
);
};
export default Footer;

View File

@ -0,0 +1,20 @@
import { COOKIES } from "../../constants";
export const isExcalidrawPlusSignedUser = document.cookie.includes(
COOKIES.AUTH_STATE_COOKIE,
);
const PlusAppLink = () => {
return (
<a
href={`${process.env.REACT_APP_PLUS_APP}/#excalidraw-redirect`}
target="_blank"
rel="noreferrer"
className="plus-button"
>
Go to Excalidraw+
</a>
);
};
export default PlusAppLink;

View File

@ -0,0 +1,17 @@
const PlusLPLink = () => {
return (
<p style={{ direction: "ltr", unicodeBidi: "embed" }}>
Introducing Excalidraw+
<br />
<a
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=banner&utm_campaign=launch"
target="_blank"
rel="noreferrer"
>
Try out now!
</a>
</p>
);
};
export default PlusLPLink;

View File

@ -4,13 +4,7 @@ import { trackEvent } from "../analytics";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import { ErrorDialog } from "../components/ErrorDialog"; import { ErrorDialog } from "../components/ErrorDialog";
import { TopErrorBoundary } from "../components/TopErrorBoundary"; import { TopErrorBoundary } from "../components/TopErrorBoundary";
import { import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
APP_NAME,
COOKIES,
EVENT,
TITLE_TIMEOUT,
VERSION_TIMEOUT,
} from "../constants";
import { loadFromBlob } from "../data/blob"; import { loadFromBlob } from "../data/blob";
import { import {
ExcalidrawElement, ExcalidrawElement,
@ -19,11 +13,7 @@ import {
} from "../element/types"; } from "../element/types";
import { useCallbackRefState } from "../hooks/useCallbackRefState"; import { useCallbackRefState } from "../hooks/useCallbackRefState";
import { t } from "../i18n"; import { t } from "../i18n";
import { import { Excalidraw, defaultLang } from "../packages/excalidraw/index";
Excalidraw,
defaultLang,
languages,
} from "../packages/excalidraw/index";
import { import {
AppState, AppState,
LibraryItems, LibraryItems,
@ -51,7 +41,6 @@ import Collab, {
collabDialogShownAtom, collabDialogShownAtom,
isCollaboratingAtom, isCollaboratingAtom,
} from "./collab/Collab"; } from "./collab/Collab";
import { LanguageList } from "./components/LanguageList";
import { import {
exportToBackend, exportToBackend,
getCollaborationLinkData, getCollaborationLinkData,
@ -65,8 +54,6 @@ import {
} from "./data/localStorage"; } from "./data/localStorage";
import CustomStats from "./CustomStats"; import CustomStats from "./CustomStats";
import { restore, restoreAppState, RestoredDataState } from "../data/restore"; import { restore, restoreAppState, RestoredDataState } from "../data/restore";
import { Tooltip } from "../components/Tooltip";
import { shield } from "../components/icons";
import "./index.scss"; import "./index.scss";
import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus"; import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus";
@ -82,10 +69,11 @@ import { Provider, useAtom } from "jotai";
import { jotaiStore, useAtomWithInitialValue } from "../jotai"; import { jotaiStore, useAtomWithInitialValue } from "../jotai";
import { reconcileElements } from "./collab/reconciliation"; import { reconcileElements } from "./collab/reconciliation";
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library"; import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
import Footer from "./components/Footer";
const isExcalidrawPlusSignedUser = document.cookie.includes( import PlusAppLink, {
COOKIES.AUTH_STATE_COOKIE, isExcalidrawPlusSignedUser,
); } from "./components/PlusAppLink";
import PlusLPLink from "./components/PlusLPLink";
const languageDetector = new LanguageDetector(); const languageDetector = new LanguageDetector();
languageDetector.init({ languageDetector.init({
@ -197,31 +185,6 @@ const initializeScene = async (opts: {
return { scene: null, isExternalScene: false }; return { scene: null, isExternalScene: false };
}; };
const PlusLPLinkJSX = (
<p style={{ direction: "ltr", unicodeBidi: "embed" }}>
Introducing Excalidraw+
<br />
<a
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=banner&utm_campaign=launch"
target="_blank"
rel="noreferrer"
>
Try out now!
</a>
</p>
);
const PlusAppLinkJSX = (
<a
href={`${process.env.REACT_APP_PLUS_APP}/#excalidraw-redirect`}
target="_blank"
rel="noreferrer"
className="plus-button"
>
Go to Excalidraw+
</a>
);
const ExcalidrawWrapper = () => { const ExcalidrawWrapper = () => {
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
let currentLangCode = languageDetector.detect() || defaultLang.code; let currentLangCode = languageDetector.detect() || defaultLang.code;
@ -581,80 +544,13 @@ const ExcalidrawWrapper = () => {
textAlign: "center", textAlign: "center",
}} }}
> >
{isExcalidrawPlusSignedUser ? PlusAppLinkJSX : PlusLPLinkJSX} {isExcalidrawPlusSignedUser ? <PlusAppLink /> : <PlusLPLink />}
</div> </div>
); );
}, },
[], [],
); );
const renderFooter = useCallback(
(isMobile: boolean) => {
const renderEncryptedIcon = () => (
<a
className="encrypted-icon tooltip"
href="https://blog.excalidraw.com/end-to-end-encryption/"
target="_blank"
rel="noopener noreferrer"
aria-label={t("encrypted.link")}
>
<Tooltip label={t("encrypted.tooltip")} long={true}>
{shield}
</Tooltip>
</a>
);
const renderLanguageList = () => (
<LanguageList
onChange={(langCode) => setLangCode(langCode)}
languages={languages}
currentLangCode={langCode}
/>
);
if (isMobile) {
const isTinyDevice = window.innerWidth < 362;
return (
<div
style={{
display: "flex",
flexDirection: isTinyDevice ? "column" : "row",
}}
>
<fieldset>
<legend>{t("labels.language")}</legend>
{renderLanguageList()}
</fieldset>
{/* FIXME remove after 2021-05-20 */}
<div
style={{
width: "24ch",
fontSize: "0.7em",
textAlign: "center",
marginTop: isTinyDevice ? 16 : undefined,
marginLeft: "auto",
marginRight: isTinyDevice ? "auto" : undefined,
padding: isExcalidrawPlusSignedUser ? undefined : "4px 2px",
border: isExcalidrawPlusSignedUser
? undefined
: "1px dashed #aaa",
borderRadius: 12,
}}
>
{isExcalidrawPlusSignedUser ? PlusAppLinkJSX : PlusLPLinkJSX}
</div>
</div>
);
}
return (
<>
{renderEncryptedIcon()}
{renderLanguageList()}
</>
);
},
[langCode],
);
const renderCustomStats = () => { const renderCustomStats = () => {
return ( return (
<CustomStats <CustomStats
@ -710,14 +606,18 @@ const ExcalidrawWrapper = () => {
}, },
}} }}
renderTopRightUI={renderTopRightUI} renderTopRightUI={renderTopRightUI}
renderFooter={renderFooter}
langCode={langCode} langCode={langCode}
renderCustomStats={renderCustomStats} renderCustomStats={renderCustomStats}
detectScroll={false} detectScroll={false}
handleKeyboardGlobally={true} handleKeyboardGlobally={true}
onLibraryChange={onLibraryChange} onLibraryChange={onLibraryChange}
autoFocus={true} autoFocus={true}
>
<Footer
langCode={langCode}
onLangChange={(langCode) => setLangCode(langCode)}
/> />
</Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />} {excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && ( {errorMessage && (
<ErrorDialog <ErrorDialog

View File

@ -654,11 +654,12 @@ export default function App() {
name="Custom name of drawing" name="Custom name of drawing"
UIOptions={{ canvasActions: { loadScene: false } }} UIOptions={{ canvasActions: { loadScene: false } }}
renderTopRightUI={renderTopRightUI} renderTopRightUI={renderTopRightUI}
renderFooter={renderFooter}
onLinkOpen={onLinkOpen} onLinkOpen={onLinkOpen}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
onScrollChange={rerenderCommentIcons} onScrollChange={rerenderCommentIcons}
/> >
{renderFooter()}
</Excalidraw>
{Object.keys(commentIcons || []).length > 0 && renderCommentIcons()} {Object.keys(commentIcons || []).length > 0 && renderCommentIcons()}
{comment && renderComment()} {comment && renderComment()}
</div> </div>

View File

@ -21,7 +21,6 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
isCollaborating = false, isCollaborating = false,
onPointerUpdate, onPointerUpdate,
renderTopRightUI, renderTopRightUI,
renderFooter,
langCode = defaultLang.code, langCode = defaultLang.code,
viewModeEnabled, viewModeEnabled,
zenModeEnabled, zenModeEnabled,
@ -39,6 +38,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
onLinkOpen, onLinkOpen,
onPointerDown, onPointerDown,
onScrollChange, onScrollChange,
children,
} = props; } = props;
const canvasActions = props.UIOptions?.canvasActions; const canvasActions = props.UIOptions?.canvasActions;
@ -86,7 +86,6 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
isCollaborating={isCollaborating} isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate} onPointerUpdate={onPointerUpdate}
renderTopRightUI={renderTopRightUI} renderTopRightUI={renderTopRightUI}
renderFooter={renderFooter}
langCode={langCode} langCode={langCode}
viewModeEnabled={viewModeEnabled} viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled} zenModeEnabled={zenModeEnabled}
@ -105,7 +104,9 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
onLinkOpen={onLinkOpen} onLinkOpen={onLinkOpen}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
onScrollChange={onScrollChange} onScrollChange={onScrollChange}
/> >
{children}
</App>
</Provider> </Provider>
</InitializeApp> </InitializeApp>
); );
@ -225,3 +226,5 @@ export {
sceneCoordsToViewportCoords, sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords, viewportCoordsToSceneCoords,
} from "../../utils"; } from "../../utils";
export { useDevice } from "../../components/App";

View File

@ -280,7 +280,6 @@ export interface ExcalidrawProps {
isMobile: boolean, isMobile: boolean,
appState: AppState, appState: AppState,
) => JSX.Element | null; ) => JSX.Element | null;
renderFooter?: (isMobile: boolean, appState: AppState) => JSX.Element | null;
langCode?: Language["code"]; langCode?: Language["code"];
viewModeEnabled?: boolean; viewModeEnabled?: boolean;
zenModeEnabled?: boolean; zenModeEnabled?: boolean;
@ -312,6 +311,7 @@ export interface ExcalidrawProps {
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
) => void; ) => void;
onScrollChange?: (scrollX: number, scrollY: number) => void; onScrollChange?: (scrollX: number, scrollY: number) => void;
children?: React.ReactNode;
} }
export type SceneData = { export type SceneData = {