Compare commits
2 Commits
master
...
aakansha-v
Author | SHA1 | Date | |
---|---|---|---|
|
bba0117377 | ||
|
dcedba88a2 |
@ -66,4 +66,10 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shareable-link--viewonly {
|
||||||
|
svg {
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { exportToCanvas, getExportSize } from "../scene/export";
|
|||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import "./ExportDialog.scss";
|
import "./ExportDialog.scss";
|
||||||
import { clipboard, exportFile, link } from "./icons";
|
import { clipboard, exportFile, eyeIcon, link } from "./icons";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
|
|
||||||
@ -66,7 +66,10 @@ const ExportModal = ({
|
|||||||
onExportToPng: ExportCB;
|
onExportToPng: ExportCB;
|
||||||
onExportToSvg: ExportCB;
|
onExportToSvg: ExportCB;
|
||||||
onExportToClipboard: ExportCB;
|
onExportToClipboard: ExportCB;
|
||||||
onExportToBackend?: ExportCB;
|
onExportToBackend?: (
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
viewonly: boolean,
|
||||||
|
) => void;
|
||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
||||||
@ -155,13 +158,23 @@ const ExportModal = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{onExportToBackend && (
|
{onExportToBackend && (
|
||||||
<ToolButton
|
<>
|
||||||
type="button"
|
<ToolButton
|
||||||
icon={link}
|
type="button"
|
||||||
title={t("buttons.getShareableLink")}
|
icon={link}
|
||||||
aria-label={t("buttons.getShareableLink")}
|
title={t("buttons.getShareableLink")}
|
||||||
onClick={() => onExportToBackend(exportedElements)}
|
aria-label={t("buttons.getShareableLink")}
|
||||||
/>
|
onClick={() => onExportToBackend(exportedElements, false)}
|
||||||
|
/>
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={eyeIcon}
|
||||||
|
className="shareable-link--viewonly"
|
||||||
|
title={t("buttons.getViewonlyShareableLink")}
|
||||||
|
aria-label={t("buttons.getShareableLink")}
|
||||||
|
onClick={() => onExportToBackend(exportedElements, true)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
<div className="ExportDialog__name">
|
<div className="ExportDialog__name">
|
||||||
@ -236,7 +249,10 @@ export const ExportDialog = ({
|
|||||||
onExportToPng: ExportCB;
|
onExportToPng: ExportCB;
|
||||||
onExportToSvg: ExportCB;
|
onExportToSvg: ExportCB;
|
||||||
onExportToClipboard: ExportCB;
|
onExportToClipboard: ExportCB;
|
||||||
onExportToBackend?: ExportCB;
|
onExportToBackend?: (
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
viewonly: boolean,
|
||||||
|
) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [modalIsShown, setModalIsShown] = useState(false);
|
const [modalIsShown, setModalIsShown] = useState(false);
|
||||||
const triggerButton = useRef<HTMLButtonElement>(null);
|
const triggerButton = useRef<HTMLButtonElement>(null);
|
||||||
|
@ -58,6 +58,7 @@ interface LayerUIProps {
|
|||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
onExportToBackend?: (
|
onExportToBackend?: (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
viewonly: boolean,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => void;
|
) => void;
|
||||||
@ -368,9 +369,9 @@ const LayerUI = ({
|
|||||||
onExportToClipboard={createExporter("clipboard")}
|
onExportToClipboard={createExporter("clipboard")}
|
||||||
onExportToBackend={
|
onExportToBackend={
|
||||||
onExportToBackend
|
onExportToBackend
|
||||||
? (elements) => {
|
? (elements, viewonly) => {
|
||||||
onExportToBackend &&
|
onExportToBackend &&
|
||||||
onExportToBackend(elements, appState, canvas);
|
onExportToBackend(elements, viewonly, appState, canvas);
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,10 @@ export const questionCircle = createIcon(
|
|||||||
{ mirror: true },
|
{ mirror: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const eyeIcon = createIcon(
|
||||||
|
"M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z",
|
||||||
|
);
|
||||||
|
|
||||||
// Icon imported form Storybook
|
// Icon imported form Storybook
|
||||||
// Storybook is licensed under MIT https://github.com/storybookjs/storybook/blob/next/LICENSE
|
// Storybook is licensed under MIT https://github.com/storybookjs/storybook/blob/next/LICENSE
|
||||||
export const resetZoom = createIcon(
|
export const resetZoom = createIcon(
|
||||||
|
@ -251,6 +251,7 @@ export const loadScene = async (
|
|||||||
export const exportToBackend = async (
|
export const exportToBackend = async (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
viewonly: boolean,
|
||||||
) => {
|
) => {
|
||||||
const json = serializeAsJSON(elements, appState);
|
const json = serializeAsJSON(elements, appState);
|
||||||
const encoded = new TextEncoder().encode(json);
|
const encoded = new TextEncoder().encode(json);
|
||||||
@ -288,9 +289,13 @@ export const exportToBackend = async (
|
|||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.id) {
|
if (json.id) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
|
url.hash = "#";
|
||||||
|
if (viewonly) {
|
||||||
|
url.hash = `${url.hash}viewonly=true&`;
|
||||||
|
}
|
||||||
// We need to store the key (and less importantly the id) as hash instead
|
// We need to store the key (and less importantly the id) as hash instead
|
||||||
// of queryParam in order to never send it to the server
|
// of queryParam in order to never send it to the server
|
||||||
url.hash = `json=${json.id},${exportedKey.k!}`;
|
url.hash = `${url.hash}json=${json.id},${exportedKey.k!}`;
|
||||||
const urlString = url.toString();
|
const urlString = url.toString();
|
||||||
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
|
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
|
||||||
} else if (json.error_class === "RequestTooLargeError") {
|
} else if (json.error_class === "RequestTooLargeError") {
|
||||||
|
@ -70,9 +70,8 @@ const initializeScene = async (opts: {
|
|||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
const id = searchParams.get("id");
|
const id = searchParams.get("id");
|
||||||
const jsonMatch = window.location.hash.match(
|
const jsonMatch = window.location.hash.match(
|
||||||
/^#json=([0-9]+),([a-zA-Z0-9_-]+)$/,
|
/json=([0-9]+),([a-zA-Z0-9_-]+)/,
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialData = importFromLocalStorage();
|
const initialData = importFromLocalStorage();
|
||||||
|
|
||||||
let scene = await loadScene(null, null, initialData);
|
let scene = await loadScene(null, null, initialData);
|
||||||
@ -94,6 +93,10 @@ const initializeScene = async (opts: {
|
|||||||
} else if (jsonMatch) {
|
} else if (jsonMatch) {
|
||||||
scene = await loadScene(jsonMatch[1], jsonMatch[2], initialData);
|
scene = await loadScene(jsonMatch[1], jsonMatch[2], initialData);
|
||||||
}
|
}
|
||||||
|
const isViewModeEnabled = !!window.location.hash.match(/#viewonly=true/);
|
||||||
|
if (isViewModeEnabled) {
|
||||||
|
scene.appState.viewModeEnabled = true;
|
||||||
|
}
|
||||||
if (!roomLinkData) {
|
if (!roomLinkData) {
|
||||||
window.history.replaceState({}, APP_NAME, window.location.origin);
|
window.history.replaceState({}, APP_NAME, window.location.origin);
|
||||||
}
|
}
|
||||||
@ -134,6 +137,7 @@ function ExcalidrawWrapper() {
|
|||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const currentLangCode = languageDetector.detect() || defaultLang.code;
|
const currentLangCode = languageDetector.detect() || defaultLang.code;
|
||||||
const [langCode, setLangCode] = useState(currentLangCode);
|
const [langCode, setLangCode] = useState(currentLangCode);
|
||||||
|
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
@ -157,7 +161,6 @@ function ExcalidrawWrapper() {
|
|||||||
if (!initialStatePromiseRef.current.promise) {
|
if (!initialStatePromiseRef.current.promise) {
|
||||||
initialStatePromiseRef.current.promise = resolvablePromise<ImportedDataState | null>();
|
initialStatePromiseRef.current.promise = resolvablePromise<ImportedDataState | null>();
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Delayed so that the app has a time to load the latest SW
|
// Delayed so that the app has a time to load the latest SW
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -178,12 +181,18 @@ function ExcalidrawWrapper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializeScene({ collabAPI }).then((scene) => {
|
initializeScene({ collabAPI }).then((scene) => {
|
||||||
|
if (scene?.appState?.viewModeEnabled) {
|
||||||
|
setViewModeEnabled(true);
|
||||||
|
}
|
||||||
initialStatePromiseRef.current.promise.resolve(scene);
|
initialStatePromiseRef.current.promise.resolve(scene);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onHashChange = (_: HashChangeEvent) => {
|
const onHashChange = (_: HashChangeEvent) => {
|
||||||
initializeScene({ collabAPI }).then((scene) => {
|
initializeScene({ collabAPI }).then((scene) => {
|
||||||
if (scene) {
|
if (scene) {
|
||||||
|
if (scene?.appState?.viewModeEnabled) {
|
||||||
|
setViewModeEnabled(true);
|
||||||
|
}
|
||||||
excalidrawAPI.updateScene(scene);
|
excalidrawAPI.updateScene(scene);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -224,6 +233,7 @@ function ExcalidrawWrapper() {
|
|||||||
|
|
||||||
const onExportToBackend = async (
|
const onExportToBackend = async (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
viewonly: boolean,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => {
|
) => {
|
||||||
@ -232,12 +242,16 @@ function ExcalidrawWrapper() {
|
|||||||
}
|
}
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
try {
|
try {
|
||||||
await exportToBackend(exportedElements, {
|
await exportToBackend(
|
||||||
...appState,
|
exportedElements,
|
||||||
viewBackgroundColor: appState.exportBackground
|
{
|
||||||
? appState.viewBackgroundColor
|
...appState,
|
||||||
: getDefaultAppState().viewBackgroundColor,
|
viewBackgroundColor: appState.exportBackground
|
||||||
});
|
? appState.viewBackgroundColor
|
||||||
|
: getDefaultAppState().viewBackgroundColor,
|
||||||
|
},
|
||||||
|
viewonly,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name !== "AbortError") {
|
if (error.name !== "AbortError") {
|
||||||
const { width, height } = canvas;
|
const { width, height } = canvas;
|
||||||
@ -272,7 +286,6 @@ function ExcalidrawWrapper() {
|
|||||||
},
|
},
|
||||||
[langCode],
|
[langCode],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
@ -287,6 +300,7 @@ function ExcalidrawWrapper() {
|
|||||||
onExportToBackend={onExportToBackend}
|
onExportToBackend={onExportToBackend}
|
||||||
renderFooter={renderFooter}
|
renderFooter={renderFooter}
|
||||||
langCode={langCode}
|
langCode={langCode}
|
||||||
|
viewModeEnabled={viewModeEnabled ? viewModeEnabled : undefined}
|
||||||
/>
|
/>
|
||||||
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
|
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
|
@ -106,6 +106,7 @@
|
|||||||
"saveAs": "Save as",
|
"saveAs": "Save as",
|
||||||
"load": "Load",
|
"load": "Load",
|
||||||
"getShareableLink": "Get shareable link",
|
"getShareableLink": "Get shareable link",
|
||||||
|
"getViewonlyShareableLink": "Get View only shareable link",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"selectLanguage": "Select language",
|
"selectLanguage": "Select language",
|
||||||
"scrollBackToContent": "Scroll back to content",
|
"scrollBackToContent": "Scroll back to content",
|
||||||
|
@ -180,6 +180,7 @@ export interface ExcalidrawProps {
|
|||||||
}) => void;
|
}) => void;
|
||||||
onExportToBackend?: (
|
onExportToBackend?: (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
viewonly: boolean,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => void;
|
) => void;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user