mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-26 10:49:57 +02:00
Compare commits
1 Commits
ryan-di/fi
...
chore/mtol
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b4078b1589 |
23
.github/workflows/stale-issues.yml
vendored
Normal file
23
.github/workflows/stale-issues.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
days-before-issue-stale: 90
|
||||||
|
days-before-issue-close: 180
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 90 days with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 180 days since being marked as stale."
|
||||||
|
exempt-issue-assignees: "dwelle,ryan-di,Mrazator,ad1992,zsviczian,mtolmacs"
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
@@ -4,13 +4,7 @@ import {
|
|||||||
supported as nativeFileSystemSupported,
|
supported as nativeFileSystemSupported,
|
||||||
} from "browser-fs-access";
|
} from "browser-fs-access";
|
||||||
|
|
||||||
import {
|
import { EVENT, MIME_TYPES, debounce } from "@excalidraw/common";
|
||||||
EVENT,
|
|
||||||
MIME_TYPES,
|
|
||||||
debounce,
|
|
||||||
isIOS,
|
|
||||||
isAndroid,
|
|
||||||
} from "@excalidraw/common";
|
|
||||||
|
|
||||||
import { AbortError } from "../errors";
|
import { AbortError } from "../errors";
|
||||||
|
|
||||||
@@ -19,8 +13,6 @@ import type { FileSystemHandle } from "browser-fs-access";
|
|||||||
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
||||||
|
|
||||||
const INPUT_CHANGE_INTERVAL_MS = 500;
|
const INPUT_CHANGE_INTERVAL_MS = 500;
|
||||||
// increase timeout for mobile devices to give more time for file selection
|
|
||||||
const MOBILE_INPUT_CHANGE_INTERVAL_MS = 2000;
|
|
||||||
|
|
||||||
export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
||||||
extensions?: FILE_EXTENSION[];
|
extensions?: FILE_EXTENSION[];
|
||||||
@@ -49,22 +41,13 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
|||||||
mimeTypes,
|
mimeTypes,
|
||||||
multiple: opts.multiple ?? false,
|
multiple: opts.multiple ?? false,
|
||||||
legacySetup: (resolve, reject, input) => {
|
legacySetup: (resolve, reject, input) => {
|
||||||
const isMobile = isIOS || isAndroid;
|
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
|
||||||
const intervalMs = isMobile
|
|
||||||
? MOBILE_INPUT_CHANGE_INTERVAL_MS
|
|
||||||
: INPUT_CHANGE_INTERVAL_MS;
|
|
||||||
const scheduleRejection = debounce(reject, intervalMs);
|
|
||||||
|
|
||||||
const focusHandler = () => {
|
const focusHandler = () => {
|
||||||
checkForFile();
|
checkForFile();
|
||||||
// on mobile, be less aggressive with rejection
|
|
||||||
if (!isMobile) {
|
|
||||||
document.addEventListener(EVENT.KEYUP, scheduleRejection);
|
document.addEventListener(EVENT.KEYUP, scheduleRejection);
|
||||||
document.addEventListener(EVENT.POINTER_UP, scheduleRejection);
|
document.addEventListener(EVENT.POINTER_UP, scheduleRejection);
|
||||||
scheduleRejection();
|
scheduleRejection();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkForFile = () => {
|
const checkForFile = () => {
|
||||||
// this hack might not work when expecting multiple files
|
// this hack might not work when expecting multiple files
|
||||||
if (input.files?.length) {
|
if (input.files?.length) {
|
||||||
@@ -72,15 +55,12 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
|||||||
resolve(ret as RetType);
|
resolve(ret as RetType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
window.addEventListener(EVENT.FOCUS, focusHandler);
|
window.addEventListener(EVENT.FOCUS, focusHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
const interval = window.setInterval(() => {
|
const interval = window.setInterval(() => {
|
||||||
checkForFile();
|
checkForFile();
|
||||||
}, intervalMs);
|
}, INPUT_CHANGE_INTERVAL_MS);
|
||||||
|
|
||||||
return (rejectPromise) => {
|
return (rejectPromise) => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
scheduleRejection.cancel();
|
scheduleRejection.cancel();
|
||||||
@@ -89,9 +69,7 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
|||||||
document.removeEventListener(EVENT.POINTER_UP, scheduleRejection);
|
document.removeEventListener(EVENT.POINTER_UP, scheduleRejection);
|
||||||
if (rejectPromise) {
|
if (rejectPromise) {
|
||||||
// so that something is shown in console if we need to debug this
|
// so that something is shown in console if we need to debug this
|
||||||
console.warn(
|
console.warn("Opening the file was canceled (legacy-fs).");
|
||||||
"Opening the file was canceled (legacy-fs). This may happen on mobile devices.",
|
|
||||||
);
|
|
||||||
rejectPromise(new AbortError());
|
rejectPromise(new AbortError());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,16 +1,9 @@
|
|||||||
import { throttleRAF } from "@excalidraw/common";
|
import { throttleRAF } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import { isInvisiblySmallElement, renderElement } from "@excalidraw/element";
|
||||||
getTargetFrame,
|
|
||||||
isInvisiblySmallElement,
|
|
||||||
renderElement,
|
|
||||||
shouldApplyFrameClip,
|
|
||||||
} from "@excalidraw/element";
|
|
||||||
|
|
||||||
import { bootstrapCanvas, getNormalizedCanvasDimensions } from "./helpers";
|
import { bootstrapCanvas, getNormalizedCanvasDimensions } from "./helpers";
|
||||||
|
|
||||||
import { frameClip } from "./staticScene";
|
|
||||||
|
|
||||||
import type { NewElementSceneRenderConfig } from "../scene/types";
|
import type { NewElementSceneRenderConfig } from "../scene/types";
|
||||||
|
|
||||||
const _renderNewElementScene = ({
|
const _renderNewElementScene = ({
|
||||||
@@ -36,9 +29,8 @@ const _renderNewElementScene = ({
|
|||||||
normalizedHeight,
|
normalizedHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
context.save();
|
|
||||||
|
|
||||||
// Apply zoom
|
// Apply zoom
|
||||||
|
context.save();
|
||||||
context.scale(appState.zoom.value, appState.zoom.value);
|
context.scale(appState.zoom.value, appState.zoom.value);
|
||||||
|
|
||||||
if (newElement && newElement.type !== "selection") {
|
if (newElement && newElement.type !== "selection") {
|
||||||
@@ -50,23 +42,6 @@ const _renderNewElementScene = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frameId = newElement.frameId || appState.frameToHighlight?.id;
|
|
||||||
|
|
||||||
if (
|
|
||||||
frameId &&
|
|
||||||
appState.frameRendering.enabled &&
|
|
||||||
appState.frameRendering.clip
|
|
||||||
) {
|
|
||||||
const frame = getTargetFrame(newElement, elementsMap, appState);
|
|
||||||
|
|
||||||
if (
|
|
||||||
frame &&
|
|
||||||
shouldApplyFrameClip(newElement, frame, appState, elementsMap)
|
|
||||||
) {
|
|
||||||
frameClip(frame, context, renderConfig, appState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderElement(
|
renderElement(
|
||||||
newElement,
|
newElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
@@ -79,8 +54,6 @@ const _renderNewElementScene = ({
|
|||||||
} else {
|
} else {
|
||||||
context.clearRect(0, 0, normalizedWidth, normalizedHeight);
|
context.clearRect(0, 0, normalizedWidth, normalizedHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.restore();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -113,7 +113,7 @@ const strokeGrid = (
|
|||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const frameClip = (
|
const frameClip = (
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
renderConfig: StaticCanvasRenderConfig,
|
renderConfig: StaticCanvasRenderConfig,
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
getDraggedElementsBounds,
|
getDraggedElementsBounds,
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
import { isBoundToContainer } from "@excalidraw/element";
|
import { isBoundToContainer, isFrameLikeElement } from "@excalidraw/element";
|
||||||
|
|
||||||
import { getMaximumGroups } from "@excalidraw/element";
|
import { getMaximumGroups } from "@excalidraw/element";
|
||||||
|
|
||||||
@@ -311,13 +311,20 @@ const getReferenceElements = (
|
|||||||
selectedElements: NonDeletedExcalidrawElement[],
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) =>
|
) => {
|
||||||
getVisibleAndNonSelectedElements(
|
const selectedFrames = selectedElements
|
||||||
|
.filter((element) => isFrameLikeElement(element))
|
||||||
|
.map((frame) => frame.id);
|
||||||
|
|
||||||
|
return getVisibleAndNonSelectedElements(
|
||||||
elements,
|
elements,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
appState,
|
appState,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
|
).filter(
|
||||||
|
(element) => !(element.frameId && selectedFrames.includes(element.frameId)),
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getVisibleGaps = (
|
export const getVisibleGaps = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
Reference in New Issue
Block a user