feat: Support hyperlinks 🔥 (#4620)
* feat: Support hypelinks * dont show edit when link not present * auto submit on blur * Add link button in sidebar and do it react way * add key to hyperlink to remount when element selection changes * autofocus input * remove click handler and use pointerup/down to show /hide popup * add keydown and support enter/escape to submit * show extrrnal link icon when element has link * use icons and open link in new tab * dnt submit unless link updated * renamed ffiles * remove unnecessary changes * update snap * hide link popup once user starts interacting with element and show again only if clicked outside and clicked on element again * render link icon outside the element * fix hit testing * rewrite implementation to render hyperlinks outside elements and hide when element selected * remove * remove * tweak icon position and size * rotate link icon when element rotated, handle zooming and render exactly where ne resize handle is rendered * no need to create a new reference anymore for element when link added/updated * rotate the link image as well when rotating element * calculate hitbox of link icon and show pointer when hovering over link icon * open link when clicked on link icon * show tooltip when hovering over link icon * show link action only when single element selected * support other protocols * add shortcut cmd/ctrl+k to edit/update link * don't hide popup after submit * renderes decreased woo * Add context mneu label to add/edit link * fix tests * remove tick and show trash when in edit mode * show edit view when element contains link * fix snap * horizontally center the hyperlink container with respect to elemnt * fix padding * remove checkcircle * show popup on hover of selected element and dismiss when outside hitbox * check if element has link before setting popup state * move logic of auto hide to hyperlink and dnt hide when editing * hide popover when drag/resize/rotate * unmount during autohide * autohide after 500ms * fix regression * prevent cmd/ctrl+k when inside link editor * submit when input not updated * allow custom urls * fix centering of popup when zoomed * fix hitbox during zoom * fix * tweak link normalization * touch hyperlink tooltip DOM only if needed * consider 0 if no offsetY * reduce hitbox of link icon and make sure link icon doesn't show on top of higher z-index elements * show link tooltip only if element has higher z-index * dnt show hyperlink popup when selection changes from element with link to element with no link and also hide popover when element type changes from selection to something else * lint: EOL * fix link icon tooltip positioning * open the link only when last pointer down and last pointer up hit the link hitbox * render tooltip after 300ms delay * ensure link popup and editor input have same height * wip: cache the link icon canvas * fix the image quality after caching using device pixel ratio yay * some cleanup * remove unused selectedElementIds from renderConfig * Update src/renderer/renderElement.ts * fix `opener` vulnerability * tweak styling * decrease padding * open local links in the same tab * fix caching * code style refactor * remove unnecessary save & restore * show link shortcut in help dialog * submit on cmd/ctrl+k * merge state props * Add title for link * update editview if prop changes * tweak link action logic * make `Hyperlink` compo editor state fully controlled * dont show popup when context menu open * show in contextMenu only for single selection & change pos * set button `selected` state * set contextMenuOpen on pointerdown * set contextMenyOpen to false when action triggered * don't render link icons on export * fix tests * fix buttons wrap * move focus states to input top-level rule * fix elements sharing `Hyperlink` state * fix hitbox for link icon in case of rect * Early return if hitting link icon Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
59cbf5fde5
commit
f47ddb988f
@ -81,3 +81,4 @@ export { actionToggleZenMode } from "./actionToggleZenMode";
|
|||||||
|
|
||||||
export { actionToggleStats } from "./actionToggleStats";
|
export { actionToggleStats } from "./actionToggleStats";
|
||||||
export { actionUnbindText } from "./actionUnbindText";
|
export { actionUnbindText } from "./actionUnbindText";
|
||||||
|
export { actionLink } from "../element/Hyperlink";
|
||||||
|
@ -25,7 +25,8 @@ export type ShortcutName =
|
|||||||
| "addToLibrary"
|
| "addToLibrary"
|
||||||
| "viewMode"
|
| "viewMode"
|
||||||
| "flipHorizontal"
|
| "flipHorizontal"
|
||||||
| "flipVertical";
|
| "flipVertical"
|
||||||
|
| "link";
|
||||||
|
|
||||||
const shortcutMap: Record<ShortcutName, string[]> = {
|
const shortcutMap: Record<ShortcutName, string[]> = {
|
||||||
cut: [getShortcutKey("CtrlOrCmd+X")],
|
cut: [getShortcutKey("CtrlOrCmd+X")],
|
||||||
@ -62,6 +63,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
|||||||
flipHorizontal: [getShortcutKey("Shift+H")],
|
flipHorizontal: [getShortcutKey("Shift+H")],
|
||||||
flipVertical: [getShortcutKey("Shift+V")],
|
flipVertical: [getShortcutKey("Shift+V")],
|
||||||
viewMode: [getShortcutKey("Alt+R")],
|
viewMode: [getShortcutKey("Alt+R")],
|
||||||
|
link: [getShortcutKey("CtrlOrCmd+K")],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getShortcutFromShortcutName = (name: ShortcutName) => {
|
export const getShortcutFromShortcutName = (name: ShortcutName) => {
|
||||||
|
@ -104,7 +104,8 @@ export type ActionName =
|
|||||||
| "toggleTheme"
|
| "toggleTheme"
|
||||||
| "increaseFontSize"
|
| "increaseFontSize"
|
||||||
| "decreaseFontSize"
|
| "decreaseFontSize"
|
||||||
| "unbindText";
|
| "unbindText"
|
||||||
|
| "link";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
@ -84,6 +84,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
},
|
},
|
||||||
viewModeEnabled: false,
|
viewModeEnabled: false,
|
||||||
pendingImageElement: null,
|
pendingImageElement: null,
|
||||||
|
showHyperlinkPopup: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,6 +175,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
zoom: { browser: true, export: false, server: false },
|
zoom: { browser: true, export: false, server: false },
|
||||||
viewModeEnabled: { browser: false, export: false, server: false },
|
viewModeEnabled: { browser: false, export: false, server: false },
|
||||||
pendingImageElement: { browser: false, export: false, server: false },
|
pendingImageElement: { browser: false, export: false, server: false },
|
||||||
|
showHyperlinkPopup: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
|
@ -158,6 +158,7 @@ export const SelectedShapeActions = ({
|
|||||||
{renderAction("deleteSelectedElements")}
|
{renderAction("deleteSelectedElements")}
|
||||||
{renderAction("group")}
|
{renderAction("group")}
|
||||||
{renderAction("ungroup")}
|
{renderAction("ungroup")}
|
||||||
|
{targetElements.length === 1 && renderAction("link")}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
)}
|
)}
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
actionUnbindText,
|
actionUnbindText,
|
||||||
actionUngroup,
|
actionUngroup,
|
||||||
|
actionLink,
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
@ -141,6 +142,7 @@ import {
|
|||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
FileId,
|
FileId,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getCenter, getDistance } from "../gesture";
|
import { getCenter, getDistance } from "../gesture";
|
||||||
import {
|
import {
|
||||||
@ -239,6 +241,14 @@ import {
|
|||||||
getBoundTextElementId,
|
getBoundTextElementId,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||||
|
import {
|
||||||
|
normalizeLink,
|
||||||
|
showHyperlinkTooltip,
|
||||||
|
hideHyperlinkToolip,
|
||||||
|
Hyperlink,
|
||||||
|
isPointHittingLinkIcon,
|
||||||
|
isLocalLink,
|
||||||
|
} from "../element/Hyperlink";
|
||||||
|
|
||||||
const IsMobileContext = React.createContext(false);
|
const IsMobileContext = React.createContext(false);
|
||||||
export const useIsMobile = () => useContext(IsMobileContext);
|
export const useIsMobile = () => useContext(IsMobileContext);
|
||||||
@ -298,6 +308,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
public files: BinaryFiles = {};
|
public files: BinaryFiles = {};
|
||||||
public imageCache: AppClassProperties["imageCache"] = new Map();
|
public imageCache: AppClassProperties["imageCache"] = new Map();
|
||||||
|
|
||||||
|
hitLinkElement?: NonDeletedExcalidrawElement;
|
||||||
|
lastPointerDown: React.PointerEvent<HTMLCanvasElement> | null = null;
|
||||||
|
lastPointerUp: React.PointerEvent<HTMLElement> | PointerEvent | null = null;
|
||||||
|
contextMenuOpen: boolean = false;
|
||||||
|
|
||||||
constructor(props: AppProps) {
|
constructor(props: AppProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const defaultAppState = getDefaultAppState();
|
const defaultAppState = getDefaultAppState();
|
||||||
@ -320,6 +335,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
name,
|
name,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
|
showHyperlinkPopup: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
@ -433,7 +449,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { zenModeEnabled, viewModeEnabled } = this.state;
|
const { zenModeEnabled, viewModeEnabled } = this.state;
|
||||||
|
const selectedElement = getSelectedElements(
|
||||||
|
this.scene.getElements(),
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
onCollabButtonClick,
|
onCollabButtonClick,
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
@ -499,6 +518,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/>
|
/>
|
||||||
<div className="excalidraw-textEditorContainer" />
|
<div className="excalidraw-textEditorContainer" />
|
||||||
<div className="excalidraw-contextMenuContainer" />
|
<div className="excalidraw-contextMenuContainer" />
|
||||||
|
{selectedElement.length === 1 && this.state.showHyperlinkPopup && (
|
||||||
|
<Hyperlink
|
||||||
|
key={selectedElement[0].id}
|
||||||
|
element={selectedElement[0]}
|
||||||
|
appState={this.state}
|
||||||
|
setAppState={this.setAppState}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{this.state.showStats && (
|
{this.state.showStats && (
|
||||||
<Stats
|
<Stats
|
||||||
appState={this.state}
|
appState={this.state}
|
||||||
@ -537,6 +564,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private syncActionResult = withBatchedUpdates(
|
private syncActionResult = withBatchedUpdates(
|
||||||
(actionResult: ActionResult) => {
|
(actionResult: ActionResult) => {
|
||||||
|
// Since context menu closes when action triggered so setting to false
|
||||||
|
this.contextMenuOpen = false;
|
||||||
if (this.unmounted || actionResult === false) {
|
if (this.unmounted || actionResult === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1012,6 +1041,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
|
// Hide hyperlink popup if shown when element type is not selection
|
||||||
|
if (
|
||||||
|
prevState.elementType === "selection" &&
|
||||||
|
this.state.elementType !== "selection" &&
|
||||||
|
this.state.showHyperlinkPopup
|
||||||
|
) {
|
||||||
|
this.setState({ showHyperlinkPopup: false });
|
||||||
|
}
|
||||||
if (prevProps.langCode !== this.props.langCode) {
|
if (prevProps.langCode !== this.props.langCode) {
|
||||||
this.updateLanguage();
|
this.updateLanguage();
|
||||||
}
|
}
|
||||||
@ -1157,6 +1194,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
renderScrollbars: !this.isMobile,
|
renderScrollbars: !this.isMobile,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (scrollBars) {
|
if (scrollBars) {
|
||||||
currentScrollBars = scrollBars;
|
currentScrollBars = scrollBars;
|
||||||
}
|
}
|
||||||
@ -1481,6 +1519,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
removePointer = (event: React.PointerEvent<HTMLElement> | PointerEvent) => {
|
removePointer = (event: React.PointerEvent<HTMLElement> | PointerEvent) => {
|
||||||
|
this.lastPointerUp = event;
|
||||||
// remove touch handler for context menu on touch devices
|
// remove touch handler for context menu on touch devices
|
||||||
if (event.pointerType === "touch" && touchTimeout) {
|
if (event.pointerType === "touch" && touchTimeout) {
|
||||||
clearTimeout(touchTimeout);
|
clearTimeout(touchTimeout);
|
||||||
@ -2083,6 +2122,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
.filter(
|
.filter(
|
||||||
(element) => !(isTextElement(element) && element.containerId),
|
(element) => !(isTextElement(element) && element.containerId),
|
||||||
);
|
);
|
||||||
|
|
||||||
return getElementsAtPosition(elements, (element) =>
|
return getElementsAtPosition(elements, (element) =>
|
||||||
hitTest(element, this.state, x, y),
|
hitTest(element, this.state, x, y),
|
||||||
);
|
);
|
||||||
@ -2308,6 +2348,69 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getElementLinkAtPosition = (
|
||||||
|
scenePointer: Readonly<{ x: number; y: number }>,
|
||||||
|
hitElement: NonDeletedExcalidrawElement | null,
|
||||||
|
): ExcalidrawElement | undefined => {
|
||||||
|
// Reversing so we traverse the elements in decreasing order
|
||||||
|
// of z-index
|
||||||
|
const elements = this.scene.getElements().slice().reverse();
|
||||||
|
let hitElementIndex = Infinity;
|
||||||
|
|
||||||
|
return elements.find((element, index) => {
|
||||||
|
if (hitElement && element.id === hitElement.id) {
|
||||||
|
hitElementIndex = index;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
element.link &&
|
||||||
|
isPointHittingLinkIcon(element, this.state, [
|
||||||
|
scenePointer.x,
|
||||||
|
scenePointer.y,
|
||||||
|
]) &&
|
||||||
|
index <= hitElementIndex
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private redirectToLink = () => {
|
||||||
|
const lastPointerDownCoords = viewportCoordsToSceneCoords(
|
||||||
|
this.lastPointerDown!,
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
const lastPointerDownHittingLinkIcon = isPointHittingLinkIcon(
|
||||||
|
this.hitLinkElement!,
|
||||||
|
this.state,
|
||||||
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
||||||
|
);
|
||||||
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
||||||
|
this.lastPointerUp!,
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
const LastPointerUpHittingLinkIcon = isPointHittingLinkIcon(
|
||||||
|
this.hitLinkElement!,
|
||||||
|
this.state,
|
||||||
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
||||||
|
);
|
||||||
|
if (lastPointerDownHittingLinkIcon && LastPointerUpHittingLinkIcon) {
|
||||||
|
const url = this.hitLinkElement?.link;
|
||||||
|
if (url) {
|
||||||
|
const target = isLocalLink(url) ? "_self" : "_blank";
|
||||||
|
const newWindow = window.open(undefined, target);
|
||||||
|
// https://mathiasbynens.github.io/rel-noopener/
|
||||||
|
if (newWindow) {
|
||||||
|
newWindow.opener = null;
|
||||||
|
newWindow.location = normalizeLink(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private attachLinkListener = () => {
|
||||||
|
this.canvas?.addEventListener("click", this.redirectToLink);
|
||||||
|
};
|
||||||
|
private detachLinkListener = () => {
|
||||||
|
this.canvas?.removeEventListener("click", this.redirectToLink);
|
||||||
|
};
|
||||||
|
|
||||||
private handleCanvasPointerMove = (
|
private handleCanvasPointerMove = (
|
||||||
event: React.PointerEvent<HTMLCanvasElement>,
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
@ -2540,6 +2643,30 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
);
|
);
|
||||||
|
this.hitLinkElement = this.getElementLinkAtPosition(
|
||||||
|
scenePointer,
|
||||||
|
hitElement,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.hitLinkElement &&
|
||||||
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
||||||
|
) {
|
||||||
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
|
showHyperlinkTooltip(this.hitLinkElement, this.state);
|
||||||
|
this.attachLinkListener();
|
||||||
|
} else {
|
||||||
|
hideHyperlinkToolip();
|
||||||
|
this.detachLinkListener();
|
||||||
|
if (
|
||||||
|
hitElement &&
|
||||||
|
hitElement.link &&
|
||||||
|
this.state.selectedElementIds[hitElement.id] &&
|
||||||
|
!this.contextMenuOpen &&
|
||||||
|
!this.state.showHyperlinkPopup
|
||||||
|
) {
|
||||||
|
this.setState({ showHyperlinkPopup: "info" });
|
||||||
|
}
|
||||||
if (this.state.elementType === "text") {
|
if (this.state.elementType === "text") {
|
||||||
setCursor(
|
setCursor(
|
||||||
this.canvas,
|
this.canvas,
|
||||||
@ -2553,6 +2680,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const element = LinearElementEditor.getElement(
|
const element = LinearElementEditor.getElement(
|
||||||
this.state.editingLinearElement.elementId,
|
this.state.editingLinearElement.elementId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
element &&
|
element &&
|
||||||
isHittingElementNotConsideringBoundingBox(element, this.state, [
|
isHittingElementNotConsideringBoundingBox(element, this.state, [
|
||||||
@ -2577,6 +2705,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
} else {
|
} else {
|
||||||
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// set touch moving for mobile context menu
|
// set touch moving for mobile context menu
|
||||||
@ -2594,7 +2723,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (selection?.anchorNode) {
|
if (selection?.anchorNode) {
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
|
this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
|
||||||
this.maybeCleanupAfterMissingPointerUp(event);
|
this.maybeCleanupAfterMissingPointerUp(event);
|
||||||
|
|
||||||
@ -2612,7 +2740,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (isPanning) {
|
if (isPanning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.lastPointerDown = event;
|
||||||
this.setState({
|
this.setState({
|
||||||
lastPointerDownWith: event.pointerType,
|
lastPointerDownWith: event.pointerType,
|
||||||
cursorButton: "down",
|
cursorButton: "down",
|
||||||
@ -2646,6 +2774,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since context menu closes on pointer down so setting to false
|
||||||
|
this.contextMenuOpen = false;
|
||||||
this.clearSelectionIfNotUsingSelection();
|
this.clearSelectionIfNotUsingSelection();
|
||||||
this.updateBindingEnabledOnPointerMove(event);
|
this.updateBindingEnabledOnPointerMove(event);
|
||||||
|
|
||||||
@ -3072,7 +3202,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hitElement may already be set above, so check first
|
// hitElement may already be set above, so check first
|
||||||
pointerDownState.hit.element =
|
pointerDownState.hit.element =
|
||||||
pointerDownState.hit.element ??
|
pointerDownState.hit.element ??
|
||||||
@ -3082,6 +3211,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (pointerDownState.hit.element) {
|
if (pointerDownState.hit.element) {
|
||||||
|
// Early return if pointer is hitting link icon
|
||||||
|
if (
|
||||||
|
isPointHittingLinkIcon(pointerDownState.hit.element, this.state, [
|
||||||
|
pointerDownState.origin.x,
|
||||||
|
pointerDownState.origin.y,
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
pointerDownState.hit.hasHitElementInside =
|
pointerDownState.hit.hasHitElementInside =
|
||||||
isHittingElementNotConsideringBoundingBox(
|
isHittingElementNotConsideringBoundingBox(
|
||||||
pointerDownState.hit.element,
|
pointerDownState.hit.element,
|
||||||
@ -3163,6 +3301,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
[hitElement.id]: true,
|
[hitElement.id]: true,
|
||||||
},
|
},
|
||||||
|
showHyperlinkPopup: hitElement.link ? "info" : false,
|
||||||
},
|
},
|
||||||
this.scene.getElements(),
|
this.scene.getElements(),
|
||||||
);
|
);
|
||||||
@ -3819,6 +3958,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
: null),
|
: null),
|
||||||
},
|
},
|
||||||
|
showHyperlinkPopup:
|
||||||
|
elementsWithinSelection.length === 1 &&
|
||||||
|
elementsWithinSelection[0].link
|
||||||
|
? "info"
|
||||||
|
: false,
|
||||||
},
|
},
|
||||||
this.scene.getElements(),
|
this.scene.getElements(),
|
||||||
),
|
),
|
||||||
@ -4970,6 +5114,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
type: "canvas" | "element",
|
type: "canvas" | "element",
|
||||||
) => {
|
) => {
|
||||||
|
if (this.state.showHyperlinkPopup) {
|
||||||
|
this.setState({ showHyperlinkPopup: false });
|
||||||
|
}
|
||||||
|
this.contextMenuOpen = true;
|
||||||
const maybeGroupAction = actionGroup.contextItemPredicate!(
|
const maybeGroupAction = actionGroup.contextItemPredicate!(
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
this.actionManager.getElementsIncludingDeleted(),
|
||||||
this.actionManager.getAppState(),
|
this.actionManager.getAppState(),
|
||||||
@ -5116,6 +5264,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
maybeFlipHorizontal && actionFlipHorizontal,
|
maybeFlipHorizontal && actionFlipHorizontal,
|
||||||
maybeFlipVertical && actionFlipVertical,
|
maybeFlipVertical && actionFlipVertical,
|
||||||
(maybeFlipHorizontal || maybeFlipVertical) && separator,
|
(maybeFlipHorizontal || maybeFlipVertical) && separator,
|
||||||
|
actionLink.contextItemPredicate(elements, this.state) && actionLink,
|
||||||
actionDuplicateSelection,
|
actionDuplicateSelection,
|
||||||
actionDeleteSelected,
|
actionDeleteSelected,
|
||||||
],
|
],
|
||||||
|
@ -205,6 +205,10 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
label={t("helpDialog.preventBinding")}
|
label={t("helpDialog.preventBinding")}
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd")]}
|
shortcuts={[getShortcutKey("CtrlOrCmd")]}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("toolBar.link")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+K")]}
|
||||||
|
/>
|
||||||
</ShortcutIsland>
|
</ShortcutIsland>
|
||||||
<ShortcutIsland caption={t("helpDialog.view")}>
|
<ShortcutIsland caption={t("helpDialog.view")}>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
|
@ -2,7 +2,7 @@ import "./Tooltip.scss";
|
|||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
const getTooltipDiv = () => {
|
export const getTooltipDiv = () => {
|
||||||
const existingDiv = document.querySelector<HTMLDivElement>(
|
const existingDiv = document.querySelector<HTMLDivElement>(
|
||||||
".excalidraw-tooltip",
|
".excalidraw-tooltip",
|
||||||
);
|
);
|
||||||
@ -15,6 +15,50 @@ const getTooltipDiv = () => {
|
|||||||
return div;
|
return div;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateTooltipPosition = (
|
||||||
|
tooltip: HTMLDivElement,
|
||||||
|
item: {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
},
|
||||||
|
position: "bottom" | "top" = "bottom",
|
||||||
|
) => {
|
||||||
|
const tooltipRect = tooltip.getBoundingClientRect();
|
||||||
|
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
const margin = 5;
|
||||||
|
|
||||||
|
let left = item.left + item.width / 2 - tooltipRect.width / 2;
|
||||||
|
if (left < 0) {
|
||||||
|
left = margin;
|
||||||
|
} else if (left + tooltipRect.width >= viewportWidth) {
|
||||||
|
left = viewportWidth - tooltipRect.width - margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
let top: number;
|
||||||
|
|
||||||
|
if (position === "bottom") {
|
||||||
|
top = item.top + item.height + margin;
|
||||||
|
if (top + tooltipRect.height >= viewportHeight) {
|
||||||
|
top = item.top - tooltipRect.height - margin;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
top = item.top - tooltipRect.height - margin;
|
||||||
|
if (top < 0) {
|
||||||
|
top = item.top + item.height + margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(tooltip.style, {
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const updateTooltip = (
|
const updateTooltip = (
|
||||||
item: HTMLDivElement,
|
item: HTMLDivElement,
|
||||||
tooltip: HTMLDivElement,
|
tooltip: HTMLDivElement,
|
||||||
@ -27,35 +71,8 @@ const updateTooltip = (
|
|||||||
|
|
||||||
tooltip.textContent = label;
|
tooltip.textContent = label;
|
||||||
|
|
||||||
const {
|
const itemRect = item.getBoundingClientRect();
|
||||||
x: itemX,
|
updateTooltipPosition(tooltip, itemRect);
|
||||||
bottom: itemBottom,
|
|
||||||
top: itemTop,
|
|
||||||
width: itemWidth,
|
|
||||||
} = item.getBoundingClientRect();
|
|
||||||
|
|
||||||
const { width: labelWidth, height: labelHeight } =
|
|
||||||
tooltip.getBoundingClientRect();
|
|
||||||
|
|
||||||
const viewportWidth = window.innerWidth;
|
|
||||||
const viewportHeight = window.innerHeight;
|
|
||||||
|
|
||||||
const margin = 5;
|
|
||||||
|
|
||||||
const left = itemX + itemWidth / 2 - labelWidth / 2;
|
|
||||||
const offsetLeft =
|
|
||||||
left + labelWidth >= viewportWidth ? left + labelWidth - viewportWidth : 0;
|
|
||||||
|
|
||||||
const top = itemBottom + margin;
|
|
||||||
const offsetTop =
|
|
||||||
top + labelHeight >= viewportHeight
|
|
||||||
? itemBottom - itemTop + labelHeight + margin * 2
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
Object.assign(tooltip.style, {
|
|
||||||
top: `${top - offsetTop}px`,
|
|
||||||
left: `${left - offsetLeft}px`,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type TooltipProps = {
|
type TooltipProps = {
|
||||||
@ -75,7 +92,6 @@ export const Tooltip = ({
|
|||||||
return () =>
|
return () =>
|
||||||
getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
|
getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="excalidraw-tooltip-wrapper"
|
className="excalidraw-tooltip-wrapper"
|
||||||
|
@ -892,3 +892,11 @@ export const publishIcon = createIcon(
|
|||||||
/>,
|
/>,
|
||||||
{ width: 640, height: 512 },
|
{ width: 640, height: 512 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const editIcon = createIcon(
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M402.3 344.9l32-32c5-5 13.7-1.5 13.7 5.7V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h273.5c7.1 0 10.7 8.6 5.7 13.7l-32 32c-1.5 1.5-3.5 2.3-5.7 2.3H48v352h352V350.5c0-2.1.8-4.1 2.3-5.6zm156.6-201.8L296.3 405.7l-90.4 10c-26.2 2.9-48.5-19.2-45.6-45.6l10-90.4L432.9 17.1c22.9-22.9 59.9-22.9 82.7 0l43.2 43.2c22.9 22.9 22.9 60 .1 82.8zM460.1 174L402 115.9 216.2 301.8l-7.3 65.3 65.3-7.3L460.1 174zm64.8-79.7l-43.2-43.2c-4.1-4.1-10.8-4.1-14.8 0L436 82l58.1 58.1 30.9-30.9c4-4.2 4-10.8-.1-14.9z"
|
||||||
|
></path>,
|
||||||
|
{ width: 640, height: 512 },
|
||||||
|
);
|
||||||
|
@ -115,6 +115,7 @@ export const TOAST_TIMEOUT = 5000;
|
|||||||
export const VERSION_TIMEOUT = 30000;
|
export const VERSION_TIMEOUT = 30000;
|
||||||
export const SCROLL_TIMEOUT = 100;
|
export const SCROLL_TIMEOUT = 100;
|
||||||
export const ZOOM_STEP = 0.1;
|
export const ZOOM_STEP = 0.1;
|
||||||
|
export const HYPERLINK_TOOLTIP_DELAY = 300;
|
||||||
|
|
||||||
// Report a user inactive after IDLE_THRESHOLD milliseconds
|
// Report a user inactive after IDLE_THRESHOLD milliseconds
|
||||||
export const IDLE_THRESHOLD = 60_000;
|
export const IDLE_THRESHOLD = 60_000;
|
||||||
|
@ -105,6 +105,7 @@ const restoreElementWithProperties = <
|
|||||||
? element.boundElementIds.map((id) => ({ type: "arrow", id }))
|
? element.boundElementIds.map((id) => ({ type: "arrow", id }))
|
||||||
: element.boundElements ?? [],
|
: element.boundElements ?? [],
|
||||||
updated: element.updated ?? getUpdatedTimestamp(),
|
updated: element.updated ?? getUpdatedTimestamp(),
|
||||||
|
link: element.link ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
74
src/element/Hyperlink.scss
Normal file
74
src/element/Hyperlink.scss
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw-hyperlinkContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: absolute;
|
||||||
|
box-shadow: 0px 2px 4px 0 rgb(0 0 0 / 30%);
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--island-bg-color);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-sizing: border-box;
|
||||||
|
// to account for LS due to rendering icons after new link created
|
||||||
|
min-height: 42px;
|
||||||
|
|
||||||
|
&-input,
|
||||||
|
button {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input,
|
||||||
|
&-link {
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 8px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--ui-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 18rem;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-link {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: $oc-blue-6;
|
||||||
|
background-color: transparent !important;
|
||||||
|
font-weight: 500;
|
||||||
|
&.excalidraw-hyperlinkContainer--remove {
|
||||||
|
color: $oc-red-9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--remove .ToolIcon__icon svg {
|
||||||
|
color: $oc-red-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon__icon {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
429
src/element/Hyperlink.tsx
Normal file
429
src/element/Hyperlink.tsx
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
import { AppState, Point } from "../types";
|
||||||
|
import {
|
||||||
|
getShortcutKey,
|
||||||
|
sceneCoordsToViewportCoords,
|
||||||
|
viewportCoordsToSceneCoords,
|
||||||
|
} from "../utils";
|
||||||
|
import { mutateElement } from "./mutateElement";
|
||||||
|
import { NonDeletedExcalidrawElement } from "./types";
|
||||||
|
|
||||||
|
import { register } from "../actions/register";
|
||||||
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { editIcon, link, trash } from "../components/icons";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { KEYS } from "../keys";
|
||||||
|
import { DEFAULT_LINK_SIZE } from "../renderer/renderElement";
|
||||||
|
import { rotate } from "../math";
|
||||||
|
import { EVENT, HYPERLINK_TOOLTIP_DELAY, MIME_TYPES } from "../constants";
|
||||||
|
import { Bounds } from "./bounds";
|
||||||
|
import { getTooltipDiv, updateTooltipPosition } from "../components/Tooltip";
|
||||||
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { isPointHittingElementBoundingBox } from "./collision";
|
||||||
|
import { getElementAbsoluteCoords } from "./";
|
||||||
|
|
||||||
|
import "./Hyperlink.scss";
|
||||||
|
|
||||||
|
const CONTAINER_WIDTH = 320;
|
||||||
|
const SPACE_BOTTOM = 85;
|
||||||
|
const CONTAINER_PADDING = 5;
|
||||||
|
const CONTAINER_HEIGHT = 42;
|
||||||
|
const AUTO_HIDE_TIMEOUT = 500;
|
||||||
|
|
||||||
|
export const EXTERNAL_LINK_IMG = document.createElement("img");
|
||||||
|
EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1971c2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`,
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
let IS_HYPERLINK_TOOLTIP_VISIBLE = false;
|
||||||
|
|
||||||
|
export const Hyperlink = ({
|
||||||
|
element,
|
||||||
|
appState,
|
||||||
|
setAppState,
|
||||||
|
}: {
|
||||||
|
element: NonDeletedExcalidrawElement;
|
||||||
|
appState: AppState;
|
||||||
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
}) => {
|
||||||
|
const linkVal = element.link || "";
|
||||||
|
|
||||||
|
const [inputVal, setInputVal] = useState(linkVal);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const isEditing = appState.showHyperlinkPopup === "editor" || !linkVal;
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
if (!inputRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = normalizeLink(inputRef.current.value);
|
||||||
|
|
||||||
|
mutateElement(element, { link });
|
||||||
|
setAppState({ showHyperlinkPopup: "info" });
|
||||||
|
}, [element, setAppState]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
return () => {
|
||||||
|
handleSubmit();
|
||||||
|
};
|
||||||
|
}, [handleSubmit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeoutId: number | null = null;
|
||||||
|
const handlePointerMove = (event: PointerEvent) => {
|
||||||
|
if (isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
const shouldHide = shouldHideLinkPopup(element, appState, [
|
||||||
|
event.clientX,
|
||||||
|
event.clientY,
|
||||||
|
]) as boolean;
|
||||||
|
if (shouldHide) {
|
||||||
|
timeoutId = window.setTimeout(() => {
|
||||||
|
setAppState({ showHyperlinkPopup: false });
|
||||||
|
}, AUTO_HIDE_TIMEOUT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener(EVENT.POINTER_MOVE, handlePointerMove, false);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(EVENT.POINTER_MOVE, handlePointerMove, false);
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [appState, element, isEditing, setAppState]);
|
||||||
|
|
||||||
|
const handleRemove = useCallback(() => {
|
||||||
|
mutateElement(element, { link: null });
|
||||||
|
if (isEditing) {
|
||||||
|
inputRef.current!.value = "";
|
||||||
|
}
|
||||||
|
setAppState({ showHyperlinkPopup: false });
|
||||||
|
}, [setAppState, element, isEditing]);
|
||||||
|
|
||||||
|
const onEdit = () => {
|
||||||
|
setAppState({ showHyperlinkPopup: "editor" });
|
||||||
|
};
|
||||||
|
const { x, y } = getCoordsForPopover(element, appState);
|
||||||
|
if (
|
||||||
|
appState.draggingElement ||
|
||||||
|
appState.resizingElement ||
|
||||||
|
appState.isRotating
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="excalidraw-hyperlinkContainer"
|
||||||
|
style={{
|
||||||
|
top: `${y}px`,
|
||||||
|
left: `${x}px`,
|
||||||
|
width: CONTAINER_WIDTH,
|
||||||
|
padding: CONTAINER_PADDING,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEditing ? (
|
||||||
|
<input
|
||||||
|
className={clsx("excalidraw-hyperlinkContainer-input")}
|
||||||
|
placeholder="Type or paste your link here"
|
||||||
|
ref={inputRef}
|
||||||
|
value={inputVal}
|
||||||
|
onChange={(event) => setInputVal(event.target.value)}
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
// prevent cmd/ctrl+k shortcut when editing link
|
||||||
|
if (event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (event.key === KEYS.ENTER || event.key === KEYS.ESCAPE) {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
href={element.link || ""}
|
||||||
|
className={clsx("excalidraw-hyperlinkContainer-link", {
|
||||||
|
"d-none": isEditing,
|
||||||
|
})}
|
||||||
|
target={isLocalLink(element.link) ? "_self" : "_blank"}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{element.link}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div className="excalidraw-hyperlinkContainer__buttons">
|
||||||
|
{!isEditing && (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
title={t("buttons.edit")}
|
||||||
|
aria-label={t("buttons.edit")}
|
||||||
|
label={t("buttons.edit")}
|
||||||
|
onClick={onEdit}
|
||||||
|
className="excalidraw-hyperlinkContainer--edit"
|
||||||
|
icon={editIcon}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{linkVal && (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
title={t("buttons.remove")}
|
||||||
|
aria-label={t("buttons.remove")}
|
||||||
|
label={t("buttons.remove")}
|
||||||
|
onClick={handleRemove}
|
||||||
|
className="excalidraw-hyperlinkContainer--remove"
|
||||||
|
icon={trash}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCoordsForPopover = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
const { x: viewPortX, y: viewPortY } = sceneCoordsToViewportCoords(
|
||||||
|
{ sceneX: element.x + element.width / 2, sceneY: element.y },
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
const x = viewPortX - CONTAINER_WIDTH / 2;
|
||||||
|
const y = viewPortY - SPACE_BOTTOM;
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeLink = (link: string) => {
|
||||||
|
link = link.trim();
|
||||||
|
if (link) {
|
||||||
|
// prefix with protocol if not fully-qualified
|
||||||
|
if (!link.includes("://") && !/^[[\\/]/.test(link)) {
|
||||||
|
link = `https://${link}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return link;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLocalLink = (link: string | null) => {
|
||||||
|
return !!(link?.includes(location.origin) || link?.startsWith("/"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionLink = register({
|
||||||
|
name: "link",
|
||||||
|
perform: (elements, appState) => {
|
||||||
|
if (appState.showHyperlinkPopup === "editor") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
elements,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
showHyperlinkPopup: "editor",
|
||||||
|
},
|
||||||
|
commitToHistory: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
|
||||||
|
contextItemLabel: (elements, appState) =>
|
||||||
|
getContextMenuLabel(elements, appState),
|
||||||
|
contextItemPredicate: (elements, appState) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
|
return selectedElements.length === 1;
|
||||||
|
},
|
||||||
|
PanelComponent: ({ elements, appState, updateData }) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={link}
|
||||||
|
aria-label={t(getContextMenuLabel(elements, appState))}
|
||||||
|
title={`${t("labels.link.label")} - ${getShortcutKey("CtrlOrCmd+K")}`}
|
||||||
|
onClick={() => updateData(null)}
|
||||||
|
selected={selectedElements.length === 1 && !!selectedElements[0].link}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getContextMenuLabel = (
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
|
const label = selectedElements[0]!.link
|
||||||
|
? "labels.link.edit"
|
||||||
|
: "labels.link.create";
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
export const getLinkHandleFromCoords = (
|
||||||
|
[x1, y1, x2, y2]: Bounds,
|
||||||
|
angle: number,
|
||||||
|
appState: AppState,
|
||||||
|
): [x: number, y: number, width: number, height: number] => {
|
||||||
|
const size = DEFAULT_LINK_SIZE;
|
||||||
|
const linkWidth = size / appState.zoom.value;
|
||||||
|
const linkHeight = size / appState.zoom.value;
|
||||||
|
const linkMarginY = size / appState.zoom.value;
|
||||||
|
const centerX = (x1 + x2) / 2;
|
||||||
|
const centerY = (y1 + y2) / 2;
|
||||||
|
const centeringOffset = (size - 8) / (2 * appState.zoom.value);
|
||||||
|
const dashedLineMargin = 4 / appState.zoom.value;
|
||||||
|
|
||||||
|
// Same as `ne` resize handle
|
||||||
|
const x = x2 + dashedLineMargin - centeringOffset;
|
||||||
|
const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
|
||||||
|
|
||||||
|
const [rotatedX, rotatedY] = rotate(
|
||||||
|
x + linkWidth / 2,
|
||||||
|
y + linkHeight / 2,
|
||||||
|
centerX,
|
||||||
|
centerY,
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
rotatedX - linkWidth / 2,
|
||||||
|
rotatedY - linkHeight / 2,
|
||||||
|
linkWidth,
|
||||||
|
linkHeight,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPointHittingLinkIcon = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
appState: AppState,
|
||||||
|
[x, y]: Point,
|
||||||
|
) => {
|
||||||
|
const threshold = 4 / appState.zoom.value;
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
|
||||||
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||||
|
[x1, y1, x2, y2],
|
||||||
|
element.angle,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
const hitLink =
|
||||||
|
x > linkX - threshold &&
|
||||||
|
x < linkX + threshold + linkWidth &&
|
||||||
|
y > linkY - threshold &&
|
||||||
|
y < linkY + linkHeight + threshold;
|
||||||
|
|
||||||
|
return hitLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
let HYPERLINK_TOOLTIP_TIMEOUT_ID: number | null = null;
|
||||||
|
export const showHyperlinkTooltip = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
||||||
|
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
||||||
|
}
|
||||||
|
HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
|
||||||
|
() => renderTooltip(element, appState),
|
||||||
|
HYPERLINK_TOOLTIP_DELAY,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTooltip = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
if (!element.link) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipDiv = getTooltipDiv();
|
||||||
|
|
||||||
|
tooltipDiv.classList.add("excalidraw-tooltip--visible");
|
||||||
|
tooltipDiv.style.maxWidth = "20rem";
|
||||||
|
tooltipDiv.textContent = element.link;
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
|
||||||
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||||
|
[x1, y1, x2, y2],
|
||||||
|
element.angle,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const linkViewportCoords = sceneCoordsToViewportCoords(
|
||||||
|
{ sceneX: linkX, sceneY: linkY },
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
|
||||||
|
updateTooltipPosition(
|
||||||
|
tooltipDiv,
|
||||||
|
{
|
||||||
|
left: linkViewportCoords.x,
|
||||||
|
top: linkViewportCoords.y,
|
||||||
|
width: linkWidth,
|
||||||
|
height: linkHeight,
|
||||||
|
},
|
||||||
|
"top",
|
||||||
|
);
|
||||||
|
|
||||||
|
IS_HYPERLINK_TOOLTIP_VISIBLE = true;
|
||||||
|
};
|
||||||
|
export const hideHyperlinkToolip = () => {
|
||||||
|
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
||||||
|
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
||||||
|
}
|
||||||
|
if (IS_HYPERLINK_TOOLTIP_VISIBLE) {
|
||||||
|
IS_HYPERLINK_TOOLTIP_VISIBLE = false;
|
||||||
|
getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shouldHideLinkPopup = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
appState: AppState,
|
||||||
|
[clientX, clientY]: Point,
|
||||||
|
): Boolean => {
|
||||||
|
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||||
|
{ clientX, clientY },
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const threshold = 15 / appState.zoom.value;
|
||||||
|
// hitbox to prevent hiding when hovered in element bounding box
|
||||||
|
if (isPointHittingElementBoundingBox(element, [sceneX, sceneY], threshold)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hit box to prevent hiding when hovered in the vertical area between element and popover
|
||||||
|
if (
|
||||||
|
sceneX >= element.x &&
|
||||||
|
sceneX <= element.x + element.width &&
|
||||||
|
sceneY <= element.y &&
|
||||||
|
sceneY >= element.y - SPACE_BOTTOM
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// hit box to prevent hiding when hovered around popover within threshold
|
||||||
|
const { x: popoverX, y: popoverY } = getCoordsForPopover(element, appState);
|
||||||
|
|
||||||
|
if (
|
||||||
|
clientX >= popoverX - threshold &&
|
||||||
|
clientX <= popoverX + CONTAINER_WIDTH + CONTAINER_PADDING * 2 + threshold &&
|
||||||
|
clientY >= popoverY - threshold &&
|
||||||
|
clientY <= popoverY + threshold + CONTAINER_PADDING * 2 + CONTAINER_HEIGHT
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
@ -96,7 +96,6 @@ export const isHittingElementNotConsideringBoundingBox = (
|
|||||||
: isElementDraggableFromInside(element)
|
: isElementDraggableFromInside(element)
|
||||||
? isInsideCheck
|
? isInsideCheck
|
||||||
: isNearCheck;
|
: isNearCheck;
|
||||||
|
|
||||||
return hitTestPointAgainstElement({ element, point, threshold, check });
|
return hitTestPointAgainstElement({ element, point, threshold, check });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,7 +104,7 @@ const isElementSelected = (
|
|||||||
element: NonDeleted<ExcalidrawElement>,
|
element: NonDeleted<ExcalidrawElement>,
|
||||||
) => appState.selectedElementIds[element.id];
|
) => appState.selectedElementIds[element.id];
|
||||||
|
|
||||||
const isPointHittingElementBoundingBox = (
|
export const isPointHittingElementBoundingBox = (
|
||||||
element: NonDeleted<ExcalidrawElement>,
|
element: NonDeleted<ExcalidrawElement>,
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
|
@ -35,6 +35,7 @@ type ElementConstructorOpts = MarkOptional<
|
|||||||
| "seed"
|
| "seed"
|
||||||
| "version"
|
| "version"
|
||||||
| "versionNonce"
|
| "versionNonce"
|
||||||
|
| "link"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const _newElementBase = <T extends ExcalidrawElement>(
|
const _newElementBase = <T extends ExcalidrawElement>(
|
||||||
@ -55,6 +56,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||||||
groupIds = [],
|
groupIds = [],
|
||||||
strokeSharpness,
|
strokeSharpness,
|
||||||
boundElements = null,
|
boundElements = null,
|
||||||
|
link = null,
|
||||||
...rest
|
...rest
|
||||||
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
||||||
) => {
|
) => {
|
||||||
@ -81,6 +83,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||||||
isDeleted: false as false,
|
isDeleted: false as false,
|
||||||
boundElements,
|
boundElements,
|
||||||
updated: getUpdatedTimestamp(),
|
updated: getUpdatedTimestamp(),
|
||||||
|
link,
|
||||||
};
|
};
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
@ -52,6 +52,7 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
| null;
|
| null;
|
||||||
/** epoch (ms) timestamp of last element update */
|
/** epoch (ms) timestamp of last element update */
|
||||||
updated: number;
|
updated: number;
|
||||||
|
link: string | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ExcalidrawSelectionElement = _ExcalidrawElementBase & {
|
export type ExcalidrawSelectionElement = _ExcalidrawElementBase & {
|
||||||
|
@ -61,6 +61,7 @@ export const KEYS = {
|
|||||||
X: "x",
|
X: "x",
|
||||||
Y: "y",
|
Y: "y",
|
||||||
Z: "z",
|
Z: "z",
|
||||||
|
K: "k",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type Key = keyof typeof KEYS;
|
export type Key = keyof typeof KEYS;
|
||||||
|
@ -105,7 +105,12 @@
|
|||||||
"excalidrawLib": "Excalidraw Library",
|
"excalidrawLib": "Excalidraw Library",
|
||||||
"decreaseFontSize": "Decrease font size",
|
"decreaseFontSize": "Decrease font size",
|
||||||
"increaseFontSize": "Increase font size",
|
"increaseFontSize": "Increase font size",
|
||||||
"unbindText": "Unbind text"
|
"unbindText": "Unbind text",
|
||||||
|
"link": {
|
||||||
|
"edit": "Edit link",
|
||||||
|
"create": "Create link",
|
||||||
|
"label": "Link"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Reset the canvas",
|
"clearReset": "Reset the canvas",
|
||||||
@ -188,7 +193,8 @@
|
|||||||
"text": "Text",
|
"text": "Text",
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
"lock": "Keep selected tool active after drawing",
|
"lock": "Keep selected tool active after drawing",
|
||||||
"penMode": "Prevent pinch-zoom and accept freedraw input only from pen"
|
"penMode": "Prevent pinch-zoom and accept freedraw input only from pen",
|
||||||
|
"link": "Add/ Update link for a selected shape"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Canvas actions",
|
"canvasActions": "Canvas actions",
|
||||||
|
@ -145,6 +145,8 @@ const generateElementCanvas = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_LINK_SIZE = 14;
|
||||||
|
|
||||||
const IMAGE_PLACEHOLDER_IMG = document.createElement("img");
|
const IMAGE_PLACEHOLDER_IMG = document.createElement("img");
|
||||||
IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES.svg},${encodeURIComponent(
|
IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES.svg},${encodeURIComponent(
|
||||||
`<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="image" class="svg-inline--fa fa-image fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#888" d="M464 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM112 120c-30.928 0-56 25.072-56 56s25.072 56 56 56 56-25.072 56-56-25.072-56-56-56zM64 384h384V272l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L208 320l-55.515-55.515c-4.686-4.686-12.284-4.686-16.971 0L64 336v48z"></path></svg>`,
|
`<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="image" class="svg-inline--fa fa-image fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#888" d="M464 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM112 120c-30.928 0-56 25.072-56 56s25.072 56 56 56 56-25.072 56-56-25.072-56-56-56zM64 384h384V272l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L208 320l-55.515-55.515c-4.686-4.686-12.284-4.686-16.971 0L64 336v48z"></path></svg>`,
|
||||||
|
@ -50,6 +50,10 @@ import {
|
|||||||
import { viewportCoordsToSceneCoords, supportsEmoji } from "../utils";
|
import { viewportCoordsToSceneCoords, supportsEmoji } from "../utils";
|
||||||
import { UserIdleState } from "../types";
|
import { UserIdleState } from "../types";
|
||||||
import { THEME_FILTER } from "../constants";
|
import { THEME_FILTER } from "../constants";
|
||||||
|
import {
|
||||||
|
EXTERNAL_LINK_IMG,
|
||||||
|
getLinkHandleFromCoords,
|
||||||
|
} from "../element/Hyperlink";
|
||||||
|
|
||||||
const hasEmojiSupport = supportsEmoji();
|
const hasEmojiSupport = supportsEmoji();
|
||||||
|
|
||||||
@ -260,6 +264,9 @@ export const renderScene = (
|
|||||||
visibleElements.forEach((element) => {
|
visibleElements.forEach((element) => {
|
||||||
try {
|
try {
|
||||||
renderElement(element, rc, context, renderConfig);
|
renderElement(element, rc, context, renderConfig);
|
||||||
|
if (!isExporting) {
|
||||||
|
renderLinkIcon(element, context, appState);
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@ -740,6 +747,61 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let linkCanvasCache: any;
|
||||||
|
const renderLinkIcon = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
if (element.link && !appState.selectedElementIds[element.id]) {
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
const [x, y, width, height] = getLinkHandleFromCoords(
|
||||||
|
[x1, y1, x2, y2],
|
||||||
|
element.angle,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
const centerX = x + width / 2;
|
||||||
|
const centerY = y + height / 2;
|
||||||
|
context.save();
|
||||||
|
context.translate(appState.scrollX + centerX, appState.scrollY + centerY);
|
||||||
|
context.rotate(element.angle);
|
||||||
|
|
||||||
|
if (!linkCanvasCache || linkCanvasCache.zoom !== appState.zoom.value) {
|
||||||
|
linkCanvasCache = document.createElement("canvas");
|
||||||
|
linkCanvasCache.zoom = appState.zoom.value;
|
||||||
|
linkCanvasCache.width =
|
||||||
|
width * window.devicePixelRatio * appState.zoom.value;
|
||||||
|
linkCanvasCache.height =
|
||||||
|
height * window.devicePixelRatio * appState.zoom.value;
|
||||||
|
const linkCanvasCacheContext = linkCanvasCache.getContext("2d")!;
|
||||||
|
linkCanvasCacheContext.scale(
|
||||||
|
window.devicePixelRatio * appState.zoom.value,
|
||||||
|
window.devicePixelRatio * appState.zoom.value,
|
||||||
|
);
|
||||||
|
linkCanvasCacheContext.fillStyle = "#fff";
|
||||||
|
linkCanvasCacheContext.fillRect(0, 0, width, height);
|
||||||
|
linkCanvasCacheContext.drawImage(EXTERNAL_LINK_IMG, 0, 0, width, height);
|
||||||
|
linkCanvasCacheContext.restore();
|
||||||
|
context.drawImage(
|
||||||
|
linkCanvasCache,
|
||||||
|
x - centerX,
|
||||||
|
y - centerY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.drawImage(
|
||||||
|
linkCanvasCache,
|
||||||
|
x - centerX,
|
||||||
|
y - centerY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isVisibleElement = (
|
const isVisibleElement = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
canvasWidth: number,
|
canvasWidth: number,
|
||||||
|
@ -64,6 +64,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -89,6 +90,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -143,6 +145,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -232,6 +235,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -257,6 +261,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -284,6 +289,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -338,6 +344,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -376,6 +383,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -400,6 +408,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -438,6 +447,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -462,6 +472,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -551,6 +562,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -576,6 +588,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -603,6 +616,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -657,6 +671,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -695,6 +710,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -719,6 +735,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -757,6 +774,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -781,6 +799,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -870,6 +889,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -895,6 +915,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -949,6 +970,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -1036,6 +1058,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -1061,6 +1084,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1115,6 +1139,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1151,6 +1176,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1240,6 +1266,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -1265,6 +1292,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1292,6 +1320,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0_copy",
|
"id": "id0_copy",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -1346,6 +1375,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1384,6 +1414,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1408,6 +1439,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0_copy",
|
"id": "id0_copy",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -1503,6 +1535,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -1530,6 +1563,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1559,6 +1593,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -1613,6 +1648,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1651,6 +1687,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1675,6 +1712,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -1719,6 +1757,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1745,6 +1784,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -1834,6 +1874,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -1859,6 +1900,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1886,6 +1928,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 400692809,
|
"seed": 400692809,
|
||||||
@ -1940,6 +1983,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -1978,6 +2022,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2002,6 +2047,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2040,6 +2086,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2064,6 +2111,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2102,6 +2150,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2126,6 +2175,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2164,6 +2214,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2188,6 +2239,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2226,6 +2278,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2250,6 +2303,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2288,6 +2342,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2312,6 +2367,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2350,6 +2406,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2374,6 +2431,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 400692809,
|
"seed": 400692809,
|
||||||
@ -2412,6 +2470,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2436,6 +2495,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 400692809,
|
"seed": 400692809,
|
||||||
@ -2474,6 +2534,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2498,6 +2559,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 400692809,
|
"seed": 400692809,
|
||||||
@ -2587,6 +2649,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -2612,6 +2675,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2639,6 +2703,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2693,6 +2758,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2731,6 +2797,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2755,6 +2822,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2793,6 +2861,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2817,6 +2886,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -2906,6 +2976,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -2931,6 +3002,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -2958,6 +3030,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3012,6 +3085,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3050,6 +3124,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3074,6 +3149,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -3112,6 +3188,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -3136,6 +3213,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3229,6 +3307,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -3254,6 +3333,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -3281,6 +3361,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -3335,6 +3416,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -3373,6 +3455,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -3397,6 +3480,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -3441,6 +3525,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -3467,6 +3552,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -3507,6 +3593,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -3531,6 +3618,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -3626,6 +3714,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -3651,6 +3740,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3678,6 +3768,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -3732,6 +3823,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3770,6 +3862,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -3794,6 +3887,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 453191,
|
"seed": 453191,
|
||||||
@ -3891,6 +3985,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -3918,6 +4013,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -3947,6 +4043,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -4001,6 +4098,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -4039,6 +4137,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -4063,6 +4162,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -4108,6 +4208,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -4134,6 +4235,7 @@ Object {
|
|||||||
"height": 10,
|
"height": 10,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -4221,6 +4323,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -4324,6 +4427,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -4403,6 +4507,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
@ -4428,6 +4533,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -4455,6 +4561,7 @@ Object {
|
|||||||
"height": 200,
|
"height": 200,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -4482,6 +4589,7 @@ Object {
|
|||||||
"height": 200,
|
"height": 200,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
@ -4536,6 +4644,7 @@ Object {
|
|||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
|
@ -15,6 +15,7 @@ Object {
|
|||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -56,6 +57,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -85,6 +87,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -115,6 +118,7 @@ Object {
|
|||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -156,6 +160,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
|
@ -10,6 +10,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0_copy",
|
"id": "id0_copy",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 401146281,
|
"seed": 401146281,
|
||||||
@ -37,6 +38,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -64,6 +66,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -96,6 +99,7 @@ Object {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -128,6 +132,7 @@ Object {
|
|||||||
"height": 300,
|
"height": 300,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
@ -162,6 +167,7 @@ Object {
|
|||||||
"id": "id2",
|
"id": "id2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
|
@ -16,6 +16,7 @@ Object {
|
|||||||
70,
|
70,
|
||||||
110,
|
110,
|
||||||
],
|
],
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -65,6 +66,7 @@ Object {
|
|||||||
70,
|
70,
|
||||||
110,
|
110,
|
||||||
],
|
],
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ Object {
|
|||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -55,6 +56,7 @@ Object {
|
|||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -94,6 +96,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -121,6 +124,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
@ -148,6 +152,7 @@ Object {
|
|||||||
"height": 50,
|
"height": 50,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 337897,
|
"seed": 337897,
|
||||||
|
@ -136,6 +136,7 @@ describe("contextMenu element", () => {
|
|||||||
"sendToBack",
|
"sendToBack",
|
||||||
"bringToFront",
|
"bringToFront",
|
||||||
"duplicateSelection",
|
"duplicateSelection",
|
||||||
|
"link",
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(contextMenu).not.toBeNull();
|
expect(contextMenu).not.toBeNull();
|
||||||
|
@ -13,6 +13,7 @@ Object {
|
|||||||
"id": "id-arrow01",
|
"id": "id-arrow01",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -56,6 +57,7 @@ Object {
|
|||||||
"height": 200,
|
"height": 200,
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 10,
|
"opacity": 10,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
@ -87,6 +89,7 @@ Object {
|
|||||||
"height": 200,
|
"height": 200,
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 10,
|
"opacity": 10,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
@ -118,6 +121,7 @@ Object {
|
|||||||
"height": 200,
|
"height": 200,
|
||||||
"id": "3",
|
"id": "3",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 10,
|
"opacity": 10,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
@ -146,6 +150,7 @@ Object {
|
|||||||
"id": "id-freedraw01",
|
"id": "id-freedraw01",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [],
|
"points": Array [],
|
||||||
"pressures": Array [],
|
"pressures": Array [],
|
||||||
@ -179,6 +184,7 @@ Object {
|
|||||||
"id": "id-line01",
|
"id": "id-line01",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -221,6 +227,7 @@ Object {
|
|||||||
"id": "id-draw01",
|
"id": "id-draw01",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"points": Array [
|
"points": Array [
|
||||||
Array [
|
Array [
|
||||||
@ -264,6 +271,7 @@ Object {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"id": "id-text01",
|
"id": "id-text01",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"originalText": "text",
|
"originalText": "text",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@ -299,6 +307,7 @@ Object {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"id": "id-text01",
|
"id": "id-text01",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
"link": null,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"originalText": "test",
|
"originalText": "test",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
1
src/tests/fixtures/elementFixture.ts
vendored
1
src/tests/fixtures/elementFixture.ts
vendored
@ -22,6 +22,7 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
|
|||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
boundElements: null,
|
boundElements: null,
|
||||||
updated: 1,
|
updated: 1,
|
||||||
|
link: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rectangleFixture: ExcalidrawElement = {
|
export const rectangleFixture: ExcalidrawElement = {
|
||||||
|
@ -60,6 +60,7 @@ Object {
|
|||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
"shouldCacheIgnoreZoom": false,
|
"shouldCacheIgnoreZoom": false,
|
||||||
"showHelpDialog": false,
|
"showHelpDialog": false,
|
||||||
|
"showHyperlinkPopup": false,
|
||||||
"showStats": false,
|
"showStats": false,
|
||||||
"startBoundElement": null,
|
"startBoundElement": null,
|
||||||
"suggestedBindings": Array [],
|
"suggestedBindings": Array [],
|
||||||
|
@ -74,7 +74,7 @@ exports[`exportToSvg with default arguments 1`] = `
|
|||||||
exports[`exportToSvg with exportEmbedScene 1`] = `
|
exports[`exportToSvg with exportEmbedScene 1`] = `
|
||||||
"
|
"
|
||||||
<!-- svg-source:excalidraw -->
|
<!-- svg-source:excalidraw -->
|
||||||
<!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1STU9cdTAwMDMhXHUwMDEwvfdXbPDapLtrv+ytWmNMjFx1MDAxZXpoovFAl9mFlFx1MDAwMlx1MDAwNbZcdTAwMWZp+t9cdTAwMDXaLrrx4l1cdTAwMGUk83hvZph5x06SIHtQgCZcdIJ9gTkjXHUwMDFh71DX41vQhknhnvJcdTAwMTBcdTAwMWJZ61wiMKm1atLrcelcdTAwMDRUXHUwMDFhe+ZcdTAwMDOHNVxia1x1MDAxY+PDxUlyXGa3e2HEq7ZcdTAwMGK9eZuWKyZIvinWo5fZ9Ok9SFx1MDAwM2nvOP2s38RcdTAwMDdf+HbUxDtGLHVYlqZcckaBVdS2QCwq7tuMiLFaruBBcql9IzdpOLH0XHUwMDEyXHUwMDE3q0rLWpDIyVx1MDAwNlx1MDAxOC/LyClcdTAwMTnnc3vg51x1MDAwMeCC1lx1MDAxYVCrwuLaYlx1MDAwYm90RrpcdTAwMDFHlStZUVx1MDAwMcb80EiFXHUwMDBiZlx1MDAwZq1f+f7UM1x00/1s56dYq0tcdTAwMWVkfPCtM1x1MDAwMFx1MDAxMlL1s+FgdJeOm5e43yxP2+irXHUwMDE0YddZNlx1MDAxZadpP1x1MDAxZlxyXHUwMDFiXHUwMDA2MzO3alx1MDAxYtKWmFx1MDAxYohz9CN8jDZcdTAwMTA1581jrVxiPoviV6/WI1xmr6UgKOCn7r97/t3zXHUwMDA391x1MDAwMOdMXHUwMDE5uLjH3eGHXGIrNbdO5ChnL6Etg939L9sqw/H64D2/LfBcdTAwMWRcdTAwMWNPndNcdTAwMTfZZ1DTIn0=<!-- payload-end -->
|
<!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1STU9cdTAwMDMhXHUwMDEwvfdXbPDapLtrv+ytWmNMjFx1MDAxZXpoovFAl9mFlFx1MDAwMlx1MDAwNbZcdTAwMWZp+t9cdTAwMDXaLrrx5lVcdTAwMGUk83hvZph5x06SIHtQgCZcdIJ9gTkjXHUwMDFh71DX41vQhknhnvJcdTAwMTBcdTAwMWJZ61wiMKm1atLrcelcdTAwMDRUXHUwMDFhe+ZcdTAwMDOHNVxia1x1MDAxY+PDxUlyXGa3e2HEq7ZcdTAwMGK9eZuWKyZIvinWo5fZ9Ok9SFx1MDAwM2nvOP2s38RcdTAwMDdf+HbUxDtGLHVYlqZcckaBVdS2QCwq7tuMiLFaruBBcql9IzdpOLH0XHUwMDEyXHUwMDE3q0rLWpDIyVx1MDAwNlx1MDAxOC/LyClcdTAwMTnnc3vg51x1MDAwMeCC1lx1MDAxYVCrwuLaYlx1MDAwYm90RrpcdTAwMDFHlStZUVx1MDAwMcb80EiFXHUwMDBiZlx1MDAwZq1f+f7UM1x00/1s56dYq0tcdTAwMWVkfPCtM1x1MDAwMFx1MDAxMlL1s+FgdJeOm5e43yxP2+irXHUwMDE0YddZNlx1MDAxZadpP1x1MDAxZlxyXHUwMDFiXHUwMDA2MzO3alx1MDAxYtKWmFx1MDAxYohz9CN8jDZcdTAwMTA1581jrVxiPoviVzlcdTAwMTOrNu9qR8LwWlxuglx1MDAwMn7q/jvq31F/dFx1MDAxNHDOlIGLo9xcdTAwMWR+jbBSc+tcdTAwMTI5ytlfaMtgd//LXHUwMDA2y3C8PvjRb1x1MDAxMHxXx1Pn9Fx1MDAwNbeWWs0ifQ==<!-- payload-end -->
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
|
@ -151,6 +151,7 @@ export type AppState = {
|
|||||||
};
|
};
|
||||||
/** imageElement waiting to be placed on canvas */
|
/** imageElement waiting to be placed on canvas */
|
||||||
pendingImageElement: NonDeleted<ExcalidrawImageElement> | null;
|
pendingImageElement: NonDeleted<ExcalidrawImageElement> | null;
|
||||||
|
showHyperlinkPopup: false | "info" | "editor";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NormalizedZoomValue = number & { _brand: "normalizedZoom" };
|
export type NormalizedZoomValue = number & { _brand: "normalizedZoom" };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user