mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-15 10:15:03 +01:00
feat: add comments/presi eplus promos for discoveribility (#10294)
This commit is contained in:
@@ -138,6 +138,9 @@ import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
|
|||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
|
||||||
|
import { ExcalidrawPlusPromoBanner } from "./components/ExcalidrawPlusPromoBanner";
|
||||||
|
import { AppSidebar } from "./components/AppSidebar";
|
||||||
|
|
||||||
import type { CollabAPI } from "./collab/Collab";
|
import type { CollabAPI } from "./collab/Collab";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
@@ -851,8 +854,15 @@ const ExcalidrawWrapper = () => {
|
|||||||
if (isMobile || !collabAPI || isCollabDisabled) {
|
if (isMobile || !collabAPI || isCollabDisabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="excalidraw-ui-top-right">
|
<div className="excalidraw-ui-top-right">
|
||||||
|
{excalidrawAPI?.getEditorInterface().formFactor === "desktop" && (
|
||||||
|
<ExcalidrawPlusPromoBanner
|
||||||
|
isSignedIn={isExcalidrawPlusSignedUser}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{collabError.message && <CollabError collabError={collabError} />}
|
{collabError.message && <CollabError collabError={collabError} />}
|
||||||
<LiveCollaborationTrigger
|
<LiveCollaborationTrigger
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
@@ -945,6 +955,8 @@ const ExcalidrawWrapper = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AppSidebar />
|
||||||
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<ErrorDialog onClose={() => setErrorMessage("")}>
|
<ErrorDialog onClose={() => setErrorMessage("")}>
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { isExcalidrawPlusSignedUser } from "../app_constants";
|
|||||||
|
|
||||||
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
|
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
|
||||||
import { EncryptedIcon } from "./EncryptedIcon";
|
import { EncryptedIcon } from "./EncryptedIcon";
|
||||||
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
|
|
||||||
|
|
||||||
export const AppFooter = React.memo(
|
export const AppFooter = React.memo(
|
||||||
({ onChange }: { onChange: () => void }) => {
|
({ onChange }: { onChange: () => void }) => {
|
||||||
@@ -19,11 +18,7 @@ export const AppFooter = React.memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isVisualDebuggerEnabled() && <DebugFooter onChange={onChange} />}
|
{isVisualDebuggerEnabled() && <DebugFooter onChange={onChange} />}
|
||||||
{isExcalidrawPlusSignedUser ? (
|
{!isExcalidrawPlusSignedUser && <EncryptedIcon />}
|
||||||
<ExcalidrawPlusAppLink />
|
|
||||||
) : (
|
|
||||||
<EncryptedIcon />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Footer>
|
</Footer>
|
||||||
);
|
);
|
||||||
|
|||||||
36
excalidraw-app/components/AppSidebar.scss
Normal file
36
excalidraw-app/components/AppSidebar.scss
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
.excalidraw {
|
||||||
|
.app-sidebar-promo-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-promo-image {
|
||||||
|
margin: 1rem 0;
|
||||||
|
|
||||||
|
height: 16.25rem;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle,
|
||||||
|
transparent 60%,
|
||||||
|
var(--sidebar-bg-color) 100%
|
||||||
|
),
|
||||||
|
var(--image-source);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-promo-text {
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
excalidraw-app/components/AppSidebar.tsx
Normal file
79
excalidraw-app/components/AppSidebar.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { DefaultSidebar, Sidebar, THEME } from "@excalidraw/excalidraw";
|
||||||
|
import {
|
||||||
|
messageCircleIcon,
|
||||||
|
presentationIcon,
|
||||||
|
} from "@excalidraw/excalidraw/components/icons";
|
||||||
|
import { LinkButton } from "@excalidraw/excalidraw/components/LinkButton";
|
||||||
|
import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
|
||||||
|
|
||||||
|
import "./AppSidebar.scss";
|
||||||
|
|
||||||
|
export const AppSidebar = () => {
|
||||||
|
const { theme, openSidebar } = useUIAppState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultSidebar>
|
||||||
|
<DefaultSidebar.TabTriggers>
|
||||||
|
<Sidebar.TabTrigger
|
||||||
|
tab="comments"
|
||||||
|
style={{ opacity: openSidebar?.tab === "comments" ? 1 : 0.4 }}
|
||||||
|
>
|
||||||
|
{messageCircleIcon}
|
||||||
|
</Sidebar.TabTrigger>
|
||||||
|
<Sidebar.TabTrigger
|
||||||
|
tab="presentation"
|
||||||
|
style={{ opacity: openSidebar?.tab === "presentation" ? 1 : 0.4 }}
|
||||||
|
>
|
||||||
|
{presentationIcon}
|
||||||
|
</Sidebar.TabTrigger>
|
||||||
|
</DefaultSidebar.TabTriggers>
|
||||||
|
<Sidebar.Tab tab="comments">
|
||||||
|
<div className="app-sidebar-promo-container">
|
||||||
|
<div
|
||||||
|
className="app-sidebar-promo-image"
|
||||||
|
style={{
|
||||||
|
["--image-source" as any]: `url(/oss_promo_comments_${
|
||||||
|
theme === THEME.DARK ? "dark" : "light"
|
||||||
|
}.jpg)`,
|
||||||
|
opacity: 0.7,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="app-sidebar-promo-text">
|
||||||
|
Make comments with Excalidraw+
|
||||||
|
</div>
|
||||||
|
<LinkButton
|
||||||
|
href={`${
|
||||||
|
import.meta.env.VITE_APP_PLUS_LP
|
||||||
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=comments_promo#excalidraw-redirect`}
|
||||||
|
>
|
||||||
|
Sign up now
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
</Sidebar.Tab>
|
||||||
|
<Sidebar.Tab tab="presentation" className="px-3">
|
||||||
|
<div className="app-sidebar-promo-container">
|
||||||
|
<div
|
||||||
|
className="app-sidebar-promo-image"
|
||||||
|
style={{
|
||||||
|
["--image-source" as any]: `url(/oss_promo_presentations_${
|
||||||
|
theme === THEME.DARK ? "dark" : "light"
|
||||||
|
}.svg)`,
|
||||||
|
backgroundSize: "60%",
|
||||||
|
opacity: 0.4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="app-sidebar-promo-text">
|
||||||
|
Create presentations with Excalidraw+
|
||||||
|
</div>
|
||||||
|
<LinkButton
|
||||||
|
href={`${
|
||||||
|
import.meta.env.VITE_APP_PLUS_LP
|
||||||
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=presentations_promo#excalidraw-redirect`}
|
||||||
|
>
|
||||||
|
Sign up now
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
</Sidebar.Tab>
|
||||||
|
</DefaultSidebar>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
|
||||||
|
|
||||||
export const ExcalidrawPlusAppLink = () => {
|
|
||||||
if (!isExcalidrawPlusSignedUser) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={`${
|
|
||||||
import.meta.env.VITE_APP_PLUS_APP
|
|
||||||
}?utm_source=excalidraw&utm_medium=app&utm_content=signedInUserRedirectButton#excalidraw-redirect`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
className="plus-button"
|
|
||||||
>
|
|
||||||
Go to Excalidraw+
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
22
excalidraw-app/components/ExcalidrawPlusPromoBanner.tsx
Normal file
22
excalidraw-app/components/ExcalidrawPlusPromoBanner.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const ExcalidrawPlusPromoBanner = ({
|
||||||
|
isSignedIn,
|
||||||
|
}: {
|
||||||
|
isSignedIn: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`${
|
||||||
|
isSignedIn
|
||||||
|
? import.meta.env.VITE_APP_PLUS_LP
|
||||||
|
: import.meta.env.VITE_APP_PLUS_APP
|
||||||
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=${
|
||||||
|
isSignedIn ? "signedInBanner" : "guestBanner"
|
||||||
|
}#excalidraw-redirect`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
className="plus-banner"
|
||||||
|
>
|
||||||
|
Excalidraw+
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import "../packages/excalidraw/css/variables.module.scss";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
--color-primary-contrast-offset: #625ee0; // to offset Chubb illusion
|
--color-primary-contrast-offset: #625ee0; // to offset Chubb illusion
|
||||||
|
|
||||||
@@ -84,22 +86,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plus-button {
|
.plus-banner {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid var(--color-primary);
|
border: 1px solid var(--color-primary);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.875rem;
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-lg);
|
||||||
background-color: var(--island-bg-color);
|
background-color: var(--island-bg-color);
|
||||||
color: var(--color-primary) !important;
|
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
|
|
||||||
font-size: 0.75rem;
|
font-family: var(--ui-font);
|
||||||
|
font-size: 0.8333rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: var(--lg-button-size);
|
height: var(--lg-button-size);
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 0 0 1px var(--color-surface-lowest);
|
||||||
|
background-color: var(--color-surface-low);
|
||||||
|
color: var(--button-color, var(--color-on-surface)) !important;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 0 0 1px var(--color-brand-active);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
color: white !important;
|
color: white !important;
|
||||||
@@ -111,7 +122,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark {
|
.theme--dark {
|
||||||
.plus-button {
|
.plus-banner {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export type ButtonColor =
|
|||||||
export type ButtonSize = "medium" | "large";
|
export type ButtonSize = "medium" | "large";
|
||||||
|
|
||||||
export type FilledButtonProps = {
|
export type FilledButtonProps = {
|
||||||
label: string;
|
label?: string;
|
||||||
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onClick?: (event: React.MouseEvent) => void;
|
onClick?: (event: React.MouseEvent) => void;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import React from "react";
|
|||||||
import {
|
import {
|
||||||
CLASSES,
|
CLASSES,
|
||||||
DEFAULT_SIDEBAR,
|
DEFAULT_SIDEBAR,
|
||||||
MQ_MIN_WIDTH_DESKTOP,
|
|
||||||
TOOL_TYPE,
|
TOOL_TYPE,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
capitalizeString,
|
capitalizeString,
|
||||||
@@ -48,7 +47,7 @@ import MainMenu from "./main-menu/MainMenu";
|
|||||||
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
||||||
import { useEditorInterface, useStylesPanelMode } from "./App";
|
import { useEditorInterface, useStylesPanelMode } from "./App";
|
||||||
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
||||||
import { LibraryIcon } from "./icons";
|
import { sidebarRightIcon } from "./icons";
|
||||||
import { DefaultSidebar } from "./DefaultSidebar";
|
import { DefaultSidebar } from "./DefaultSidebar";
|
||||||
import { TTDDialog } from "./TTDDialog/TTDDialog";
|
import { TTDDialog } from "./TTDDialog/TTDDialog";
|
||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
@@ -473,7 +472,7 @@ const LayerUI = ({
|
|||||||
<DefaultMainMenu UIOptions={UIOptions} />
|
<DefaultMainMenu UIOptions={UIOptions} />
|
||||||
<DefaultSidebar.Trigger
|
<DefaultSidebar.Trigger
|
||||||
__fallback
|
__fallback
|
||||||
icon={LibraryIcon}
|
icon={sidebarRightIcon}
|
||||||
title={capitalizeString(t("toolBar.library"))}
|
title={capitalizeString(t("toolBar.library"))}
|
||||||
onToggle={(open) => {
|
onToggle={(open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -487,11 +486,7 @@ const LayerUI = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
tab={DEFAULT_SIDEBAR.defaultTab}
|
tab={DEFAULT_SIDEBAR.defaultTab}
|
||||||
>
|
/>
|
||||||
{stylesPanelMode === "full" &&
|
|
||||||
appState.width >= MQ_MIN_WIDTH_DESKTOP &&
|
|
||||||
t("toolBar.library")}
|
|
||||||
</DefaultSidebar.Trigger>
|
|
||||||
<DefaultOverwriteConfirmDialog />
|
<DefaultOverwriteConfirmDialog />
|
||||||
{appState.openDialog?.name === "ttd" && <TTDDialog __fallback />}
|
{appState.openDialog?.name === "ttd" && <TTDDialog __fallback />}
|
||||||
{/* ------------------------------------------------------------------ */}
|
{/* ------------------------------------------------------------------ */}
|
||||||
|
|||||||
15
packages/excalidraw/components/LinkButton.tsx
Normal file
15
packages/excalidraw/components/LinkButton.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { FilledButton } from "./FilledButton";
|
||||||
|
|
||||||
|
export const LinkButton = ({
|
||||||
|
children,
|
||||||
|
href,
|
||||||
|
}: {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<a href={href} target="_blank" rel="noopener" className="link-button">
|
||||||
|
<FilledButton>{children}</FilledButton>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2335,3 +2335,41 @@ export const pencilIcon = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const chevronLeftIcon = createIcon(
|
||||||
|
<g strokeWidth={1}>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M11 7l-5 5l5 5" />
|
||||||
|
<path d="M17 7l-5 5l5 5" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const sidebarRightIcon = createIcon(
|
||||||
|
<g strokeWidth="1.75">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" />
|
||||||
|
<path d="M15 4l0 16" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const messageCircleIcon = createIcon(
|
||||||
|
<g strokeWidth="1.25">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M3 20l1.3 -3.9c-2.324 -3.437 -1.426 -7.872 2.1 -10.374c3.526 -2.501 8.59 -2.296 11.845 .48c3.255 2.777 3.695 7.266 1.029 10.501c-2.666 3.235 -7.615 4.215 -11.574 2.293l-4.7 1" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const presentationIcon = createIcon(
|
||||||
|
<g strokeWidth="1.25">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M3 4l18 0" />
|
||||||
|
<path d="M4 4v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-10" />
|
||||||
|
<path d="M12 16l0 4" />
|
||||||
|
<path d="M9 20l6 0" />
|
||||||
|
<path d="M8 12l3 -3l2 2l3 -3" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ body.excalidraw-cursor-resize * {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
// browser default. Looks kinda good on low-dpi.
|
||||||
|
font-size: 0.8333rem;
|
||||||
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
label {
|
label {
|
||||||
@include buttonNoHighlight;
|
@include buttonNoHighlight;
|
||||||
@@ -708,6 +713,11 @@ body.excalidraw-cursor-resize * {
|
|||||||
margin-top: 0rem;
|
margin-top: 0rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
display: flex;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ErrorSplash.excalidraw {
|
.ErrorSplash.excalidraw {
|
||||||
|
|||||||
BIN
public/oss_promo_comments_dark.jpg
Normal file
BIN
public/oss_promo_comments_dark.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
public/oss_promo_comments_light.jpg
Normal file
BIN
public/oss_promo_comments_light.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
1
public/oss_promo_presentations_dark.svg
Normal file
1
public/oss_promo_presentations_dark.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
1
public/oss_promo_presentations_light.svg
Normal file
1
public/oss_promo_presentations_light.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user