feature / Video Extraction UI v2 (#68)

* refactor of existing features should work perfect

* redo undo of adding points works

* multi session fix

* undo redo implemented correctly, include exclusion button

* exclusion point functional

* point stack object

* clear extraction data from video node as well
This commit is contained in:
wil authored and GitHub committed 2024-11-26 21:13:59 -05:00
1 parent e2cab07186
commit bef495fc36
16 files changed
+1056 -537

No files matched your search

+2 -4
View File
@@ -33,7 +33,7 @@ import { ToolbarNodeButtonNames } from "~/components/features/ToolbarNode/enums"
import { NavigateFunction } from "react-router-dom";
import { LoadingVideosProvider } from "./EngineUtitlities/LoadingVideosProvider";
import { MediaFile } from "~/Classes/ApiManager/models/MediaFile";
import { VideoExtractionHandler } from "./EngineUtitlities/VideoExtractionHandler";
import { VideoExtractionHandler } from "./EngineUtitlities/VideoExtractionHandler/VideoExtractionHandler";
// for testing loading files from system
// import { FileUtilities } from "./FileUtilities/FileUtilities";
@@ -460,9 +460,7 @@ export class Engine {
});
// Listen to other toolbars
// VideoExtraction Toolbar
uiEvents.toolbarVideoExtraction.DONE.onClick(() => {
this.videoExtractionHandler.endVideoExtraction();
});
this.videoExtractionHandler.listenToToolbarEvents();
// Listen to other requests coming from the UI
uiEvents.onGetStagedImage((image) => {
@@ -1,166 +0,0 @@
import { uiAccess } from "~/signals";
import { VideoNode } from "../Nodes";
import {
NodeIsolator,
SelectionManager,
SelectorSquare,
} from "../NodesManagers";
import { RGBColor } from "../types";
import { UndoStackManager } from "../UndoRedo";
import { CommandManager } from "./CommandManager";
import {
VideoExtractionEventDetails,
VideoExtractionEvents,
} from "../types/events";
export class VideoExtractionHandler {
private node?: VideoNode;
private prevIsChroma?: boolean;
private prevChromaColor?: RGBColor;
// engine subclass references
private commandManagerRef: CommandManager;
private nodeIsolatorRef: NodeIsolator;
private selectionManagerRef: SelectionManager;
private selectorSquareRef: SelectorSquare;
private undoStackManagerRef: UndoStackManager;
constructor({
commandManagerRef,
nodeIsolatorRef,
selectionManagerRef,
selectorSquareRef,
undoStackManagerRef,
}: {
commandManagerRef: CommandManager;
nodeIsolatorRef: NodeIsolator;
selectionManagerRef: SelectionManager;
selectorSquareRef: SelectorSquare;
undoStackManagerRef: UndoStackManager;
}) {
this.commandManagerRef = commandManagerRef;
this.nodeIsolatorRef = nodeIsolatorRef;
this.selectionManagerRef = selectionManagerRef;
this.selectorSquareRef = selectorSquareRef;
this.undoStackManagerRef = undoStackManagerRef;
}
private updateLoadingBar({
detail,
}: CustomEvent<VideoExtractionEventDetails>) {
const { progress, status, message } = detail;
const disabled = progress !== 100;
const shouldShow = status !== VideoExtractionEvents.SESSION_CLOSED;
uiAccess.toolbarVideoExtraction.update({
isShowing: shouldShow,
disabled: disabled,
loadingBarState: {
progress: progress,
status: status,
message: message,
},
});
}
public async startVideoExtraction(node: VideoNode) {
console.log("VideoExtraction on node", node);
if (this.node || this.prevIsChroma || this.prevChromaColor) {
if (import.meta.env.DEV) {
console.error("Video Extraction: Handler already has node");
}
return;
}
this.node = node;
this.prevIsChroma = node.isChroma;
this.prevChromaColor = node.chromaColor;
this.node.progressEvent.addEventListener(
"videoextraction",
this.updateLoadingBar as EventListener,
);
// when the button is pressed to enter extraction mode
console.log("ENGEINE prepare Extraction Session.", node);
// disable most of the UI before we get a session
document.body.style.cursor = "wait";
this.selectionManagerRef.disable();
this.selectorSquareRef.disable();
node.lock();
this.undoStackManagerRef.setDisabled(true);
// if the video has chroma, disable it
if (this.prevIsChroma) {
node.setChroma(false);
}
// if the video is already using extraction
// bring the original video back
if (node.extractionUrl === node.videoComponent.src) {
await node.loadVideoFromUrl({
videoUrl: node.mediaFileUrl,
hasExistingTransform: true,
});
}
// actually start and wait for session
await node.startSegmentation();
this.nodeIsolatorRef.enterIsolation(node);
node.videoSegmentationMode(true);
this.selectionManagerRef.updateContextComponents();
uiAccess.toolbarVideoExtraction.update({
isShowing: true,
disabled: false,
loadingBarState: {
progress: 0,
status: VideoExtractionEvents.SESSION_IDLE,
message: "Select Points for Extraction",
},
});
document.body.style.cursor = "default";
}
public async endVideoExtraction() {
// when the button is pressed to exit extraction mode
console.log("ENGINE Attemping to close Extraction Session.");
document.body.style.cursor = "wait";
if (
!this.node ||
this.prevIsChroma === undefined ||
!this.prevChromaColor
) {
if (import.meta.env.DEV) {
console.error("Video Extraction: node or chroma info unavailable");
}
return;
}
uiAccess.toolbarVideoExtraction.disable();
const endSessionResult = await this.node.endSession();
if (typeof endSessionResult === "string") {
this.node.videoSegmentationMode(false);
this.node.progressEvent.removeEventListener(
"videoextraction",
this.updateLoadingBar as EventListener,
);
this.commandManagerRef.useVideoExtraction({
videoNode: this.node,
extractionUrl: endSessionResult,
prevIsChroma: this.prevIsChroma,
prevChromaColor: this.prevChromaColor,
});
this.nodeIsolatorRef.exitIsolation();
// unlock the ui
this.undoStackManagerRef.setDisabled(false);
this.selectorSquareRef.enable();
this.node.unlock();
this.selectionManagerRef.updateContextComponents();
this.selectionManagerRef.enable();
uiAccess.toolbarVideoExtraction.update({
isShowing: false,
disabled: false,
});
this.node = undefined;
this.prevIsChroma = undefined;
this.prevChromaColor = undefined;
// to close off the session.
} else {
uiAccess.toolbarVideoExtraction.enable();
console.log("Busy Processing Video.");
}
document.body.style.cursor = "default";
}
}
@@ -0,0 +1,131 @@
import { Coordinates } from "~/Classes/ApiManager/SegmentationApi";
import { ICommand } from "~/KonvaApp/UndoRedo/ICommand";
import { uiAccess } from "~/signals";
import { VideoExtractionEvents } from "~/KonvaApp/types/events";
import { NodeUtilities } from "~/KonvaApp/Nodes/NodeUtilities";
import { VideoNode } from "~/KonvaApp/Nodes";
import { SegmentationApi } from "~/Classes/ApiManager/SegmentationApi";
import { PointsStack } from "./PointsStack";
export class AddPointsCommand implements ICommand {
private prevPreviewUrl: string | undefined;
private selectedPointsRef: PointsStack;
private nodeRef: VideoNode;
private newPoint: Coordinates;
private newPreviewUrl: string | undefined;
private api: SegmentationApi;
private sessionId: string;
constructor({
selectedPointsRef,
nodeRef,
newPoint,
api,
sessionId,
}: {
selectedPointsRef: PointsStack;
nodeRef: VideoNode;
newPoint: Coordinates;
api: SegmentationApi;
sessionId: string;
}) {
this.selectedPointsRef = selectedPointsRef;
this.newPoint = newPoint;
this.nodeRef = nodeRef;
this.prevPreviewUrl = this.nodeRef.extractionPreviewUrl;
this.api = api;
this.sessionId = sessionId;
}
async execute() {
this.selectedPointsRef.addPoint(this.newPoint);
if (this.newPreviewUrl) {
// case of redo, no need to do the server calls
await this.nodeRef.setVideoExtractionPreview(this.newPreviewUrl);
return true;
}
// request for a new point.
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 25,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Start Processing Extraction...",
},
});
try {
console.log("Requesting");
const response = await this.api.addPointsToSession(
this.sessionId,
24,
[
{
timestamp: 0,
objects: [
{
style: {
color: [0, 0, 1],
},
object_id: 0,
points: this.selectedPointsRef.get(),
},
],
},
],
false,
);
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 50,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Start Processing Extraction...",
},
});
const previewImageUrl = response.frames[0].preview_image_url;
// TODO: we assumed success of the loading of the AssetUrl
await NodeUtilities.isAssetUrlAvailable({ url: previewImageUrl });
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 75,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Processing Extraction Points...",
},
});
this.newPreviewUrl = previewImageUrl;
await this.nodeRef.setVideoExtractionPreview(previewImageUrl);
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 100,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Extraction of Region Done",
},
});
return true;
} catch (error) {
console.error(error);
("Extraction of Region Done");
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 0,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: `Error:${error} Please try picking extraction points again`,
},
});
return false;
}
}
async undo() {
this.selectedPointsRef.pop();
if (this.prevPreviewUrl) {
await this.nodeRef.setVideoExtractionPreview(this.prevPreviewUrl);
} else {
await this.nodeRef.loadVideoFromUrl({
videoUrl: this.nodeRef.mediaFileUrl,
hasExistingTransform: true,
});
}
}
}
@@ -0,0 +1,35 @@
import { Coordinates } from "~/Classes/ApiManager/SegmentationApi";
import { uiAccess } from "~/signals";
export class PointsStack {
private points: Coordinates[];
constructor() {
this.points = [];
}
public get() {
return this.points;
}
public addPoint(point: Coordinates) {
this.points.push(point);
uiAccess.toolbarVideoExtraction.setReady(true);
}
public pop() {
this.points.pop();
if (this.points.length === 0) {
uiAccess.toolbarVideoExtraction.setReady(false);
}
}
public removePoint(point: Coordinates) {
this.points = this.points.filter(
(curr) =>
curr.coordinates[0] !== point.coordinates[0] &&
curr.coordinates[1] != point.coordinates[1],
);
if (this.points.length === 0) {
uiAccess.toolbarVideoExtraction.setReady(false);
}
}
public clear() {
this.points = [];
uiAccess.toolbarVideoExtraction.setReady(false);
}
}
@@ -0,0 +1,58 @@
import { UndoStackManager } from "~/KonvaApp/UndoRedo";
import { VideoNode } from "~/KonvaApp/Nodes";
import {
SegmentationApi,
Coordinates,
} from "~/Classes/ApiManager/SegmentationApi";
import { AddPointsCommand } from "./AddPointsCommand";
import { PointsStack } from "./PointsStack";
export class VideoExtractionCommandsManager {
private undoStack: UndoStackManager;
private selectedPointsRef: PointsStack;
private api: SegmentationApi;
private nodeRef: VideoNode;
private sessionId: string;
constructor({
selectedPointsRef,
api,
nodeRef,
sessionId,
}: {
selectedPointsRef: PointsStack;
api: SegmentationApi;
nodeRef: VideoNode;
sessionId: string;
}) {
this.selectedPointsRef = selectedPointsRef;
this.undoStack = new UndoStackManager();
this.api = api;
this.nodeRef = nodeRef;
this.sessionId = sessionId;
}
public async addPoint(newPoint: Coordinates) {
const command = new AddPointsCommand({
selectedPointsRef: this.selectedPointsRef,
nodeRef: this.nodeRef,
newPoint: newPoint,
api: this.api,
sessionId: this.sessionId,
});
await this.undoStack.executeCommand(command);
}
// private removeInclusionPoint() {}
public clear() {
this.undoStack.clear();
}
public undo() {
console.log("ve should undo");
this.undoStack.undo();
}
public redo() {
console.log("ve should redo");
this.undoStack.redo();
}
}
@@ -0,0 +1,430 @@
import { uiAccess, uiEvents } from "~/signals";
import { VideoNode } from "../../Nodes";
import {
NodeIsolator,
SelectionManager,
SelectorSquare,
} from "../../NodesManagers";
import { RGBColor } from "../../types";
import { UndoStackManager } from "../../UndoRedo";
import { CommandManager } from "../CommandManager";
import {
VideoExtractionEventDetails,
VideoExtractionEvents,
} from "../../types/events";
import { NodeUtilities } from "../../Nodes/NodeUtilities";
import { SegmentationApi } from "~/Classes/ApiManager/SegmentationApi";
import { VideoExtractionCommandsManager } from "./VideoExtractionCommandsManager";
import { VideoExtractionMode } from "./type";
import { PointsStack } from "./PointsStack";
export class VideoExtractionHandler {
private videoExtractionAPI = new SegmentationApi();
private videoExtractionSession: { session_id: string } | undefined =
undefined;
private isProcessing: boolean = false;
private selectedPoints: PointsStack;
private mode: VideoExtractionMode;
private videoExtractionCommandsManager?: VideoExtractionCommandsManager;
//node references
private node?: VideoNode;
private prevIsChroma?: boolean;
private prevChromaColor?: RGBColor;
// engine subclass references
private commandManagerRef: CommandManager;
private nodeIsolatorRef: NodeIsolator;
private selectionManagerRef: SelectionManager;
private selectorSquareRef: SelectorSquare;
private undoStackManagerRef: UndoStackManager;
constructor({
commandManagerRef,
nodeIsolatorRef,
selectionManagerRef,
selectorSquareRef,
undoStackManagerRef,
}: {
commandManagerRef: CommandManager;
nodeIsolatorRef: NodeIsolator;
selectionManagerRef: SelectionManager;
selectorSquareRef: SelectorSquare;
undoStackManagerRef: UndoStackManager;
}) {
this.commandManagerRef = commandManagerRef;
this.nodeIsolatorRef = nodeIsolatorRef;
this.selectionManagerRef = selectionManagerRef;
this.selectorSquareRef = selectorSquareRef;
this.undoStackManagerRef = undoStackManagerRef;
this.mode = VideoExtractionMode.INCLUSION;
this.selectedPoints = new PointsStack();
uiAccess.toolbarVideoExtraction;
}
private setMode(mode: VideoExtractionMode) {
this.mode = mode;
uiAccess.toolbarVideoExtraction.setMode(mode);
}
private updateLoadingBar({
detail,
}: CustomEvent<VideoExtractionEventDetails>) {
const { progress, status, message } = detail;
const disabled = progress !== 100;
const shouldShow = status !== VideoExtractionEvents.SESSION_CLOSED;
uiAccess.toolbarVideoExtraction.update({
isShowing: shouldShow,
disabled: disabled,
loadingBarState: {
progress: progress,
status: status,
message: message,
},
});
}
public async startVideoExtraction(node: VideoNode) {
console.log("VideoExtraction on node", node);
if (this.node || this.prevIsChroma || this.prevChromaColor) {
if (import.meta.env.DEV) {
console.error("Video Extraction: Handler already has node");
}
return;
}
if (this.videoExtractionSession) {
if (import.meta.env.DEV) {
console.error("Video Extraction: Session already exists");
}
return;
}
this.node = node;
this.prevIsChroma = node.isChroma;
this.prevChromaColor = node.chromaColor;
this.node.progressEvent.addEventListener(
"videoextraction",
this.updateLoadingBar as EventListener,
);
// when the button is pressed to enter extraction mode
console.log("ENGEINE prepare Extraction Session.", node);
// disable most of the UI before we get a session
document.body.style.cursor = "wait";
this.selectionManagerRef.disable();
this.selectorSquareRef.disable();
node.lock();
this.undoStackManagerRef.setDisabled(true);
// if the video has chroma, disable it
if (this.prevIsChroma) {
node.setChroma(false);
}
// if the video is already using extraction
// bring the original video back
if (node.extractionUrl === node.videoComponent.src) {
await node.loadVideoFromUrl({
videoUrl: node.mediaFileUrl,
hasExistingTransform: true,
});
}
node.videoComponent.pause();
node.videoComponent.currentTime = 0;
// actually start and wait for session
// await node.startSegmentation();
uiAccess.toolbarVideoExtraction.update({
isShowing: true,
disabled: true,
loadingBarState: {
progress: 25,
status: VideoExtractionEvents.SESSION_CREATING,
message: "Loading Video Extractor...",
},
});
const blob = await NodeUtilities.urlToBlob(node.mediaFileUrl);
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 75,
status: VideoExtractionEvents.SESSION_CREATING,
message: "Loading Video Extractor...",
},
});
this.videoExtractionSession =
await this.videoExtractionAPI.createSession(blob);
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 100,
status: VideoExtractionEvents.SESSION_CREATING,
message: "Loading Video Extractor...",
},
});
this.nodeIsolatorRef.enterIsolation(node);
node.enableExtractionMode(this.handleExtraction.bind(this));
this.selectionManagerRef.updateContextComponents();
uiAccess.toolbarVideoExtraction.update({
disabled: false,
loadingBarState: {
progress: 0,
status: VideoExtractionEvents.SESSION_IDLE,
message: "Select Points for Extraction",
},
});
this.videoExtractionCommandsManager = new VideoExtractionCommandsManager({
selectedPointsRef: this.selectedPoints,
api: this.videoExtractionAPI,
nodeRef: this.node,
sessionId: this.videoExtractionSession.session_id,
});
document.body.style.cursor = "default";
}
public async endVideoExtraction() {
// when the button is pressed to exit extraction mode
console.log("ENGINE Attemping to close Extraction Session.");
document.body.style.cursor = "wait";
if (
!this.node ||
this.prevIsChroma === undefined ||
!this.prevChromaColor
) {
if (import.meta.env.DEV) {
console.error("Video Extraction: node or chroma info unavailable");
}
return;
}
if (this.isProcessing) {
console.warn(
"Video Extraction: Cannot close while still processing frame, please wait",
);
return false;
}
this.isProcessing = true;
uiAccess.toolbarVideoExtraction.update({
disabled: true,
loadingBarState: {
progress: 100,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Processing Video...",
},
});
const endSessionResult = await this.endSession();
if (typeof endSessionResult === "string") {
this.commandManagerRef.useVideoExtraction({
videoNode: this.node,
extractionUrl: endSessionResult,
prevIsChroma: this.prevIsChroma,
prevChromaColor: this.prevChromaColor,
});
this.unsetUi();
} else {
uiAccess.toolbarVideoExtraction.enable();
console.log("Busy Processing Video.");
}
document.body.style.cursor = "default";
}
private async endSession(): Promise<boolean | string> {
if (!this.videoExtractionSession || !this.node) {
if (import.meta.env.DEV) {
console.error("Video Extraction: Session does not exist");
}
return false;
}
try {
console.log("Requesting a close");
const response = await this.videoExtractionAPI.addPointsToSession(
this.videoExtractionSession.session_id,
24,
[
{
timestamp: 0,
objects: [
{
style: {
color: [0, 0, 1],
},
object_id: 0,
points: this.selectedPoints.get(),
},
],
},
],
true, // propagation
// propagation = true = this requests the entire video to be processed
);
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 50,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Processing Video...",
},
});
// replace the video component and reregister all the other elements.
console.log(
"Extracted Video URL",
response["masked_video_cdn_url"],
response,
);
// TODO: we assume the URL to be checked will eventually return true
const extractionUrl = response["masked_video_cdn_url"];
await NodeUtilities.isAssetUrlAvailable({
url: extractionUrl,
sleepDurationMs: 2000,
});
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 75,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Processing Video...",
},
});
this.node.extractionUrl = extractionUrl;
// set chroma automatically.
this.node.isChroma = true;
await this.node.loadVideoFromUrl({
videoUrl: extractionUrl,
hasExistingTransform: true,
});
uiAccess.toolbarVideoExtraction.update({
loadingBarState: {
progress: 100,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Done Processing Video",
},
});
this.isProcessing = false;
return extractionUrl;
} catch (error) {
console.error(error);
this.isProcessing = false;
return false;
}
}
public async cancelVideoExtraction() {
// when the button is pressed to exit extraction mode
console.log("ENGINE cancel Extraction Session.");
if (!this.node) {
// this case should not happen, without a node nothing could be done
if (import.meta.env.DEV) {
console.error("Video Extraction: node or chroma info unavailable");
}
return;
}
if (this.prevIsChroma === undefined || !this.prevChromaColor) {
// this case should not happen, but could proceed
if (import.meta.env.DEV) {
console.error("Video Extraction: chroma info unavailable");
}
}
await this.node.loadVideoFromUrl({
videoUrl: this.node.mediaFileUrl,
hasExistingTransform: true,
});
this.unsetUi();
this.isProcessing = false;
document.body.style.cursor = "default";
}
private async handleExtraction() {
console.log("Handle Extraction");
if (!this.node) {
console.log("Video Extraction does not have VideoNode");
return;
}
if (!this.videoExtractionSession) {
console.log("Video Extraction Session Not Ready");
return;
}
if (this.isProcessing) {
console.log("Still Processing");
return;
}
this.isProcessing = true;
document.body.style.cursor = "wait";
// Get the local coordinates of the click relative to the rectangle
const kNode = this.node.kNode;
const localPos = kNode.getRelativePointerPosition();
const fileSize = this.node.mediaFileSize;
console.log("Local coordinates:mediaFileSize", localPos);
console.log("mediaFileSize:", this.node.mediaFileSize);
if (!localPos || !fileSize) {
// TODO: error handling
return;
}
const adjustedLocalPos = {
x: (localPos.x / kNode.width()) * kNode.scaleX() * fileSize.width,
y: (localPos.y / kNode.height()) * kNode.scaleY() * fileSize.height,
};
if (this.videoExtractionCommandsManager) {
await this.videoExtractionCommandsManager.addPoint({
coordinates: [adjustedLocalPos.x, adjustedLocalPos.y],
include: this.mode === "inclusion",
});
}
document.body.style.cursor = "default";
this.isProcessing = false;
}
public undo() {
this.videoExtractionCommandsManager?.undo();
}
public redo() {
this.videoExtractionCommandsManager?.redo();
}
private unsetUi() {
// unlock the ui
this.nodeIsolatorRef.exitIsolation();
this.undoStackManagerRef.setDisabled(false);
this.selectorSquareRef.enable();
if (this.node) {
this.node.disableExtractionMode();
this.node.progressEvent.removeEventListener(
"videoextraction",
this.updateLoadingBar as EventListener,
);
this.node.unlock();
}
this.selectionManagerRef.updateContextComponents();
this.selectionManagerRef.enable();
uiAccess.toolbarVideoExtraction.update({
isShowing: false,
disabled: false,
});
// reset to close off the session.
this.videoExtractionCommandsManager?.clear();
this.selectedPoints.clear();
this.node = undefined;
this.prevIsChroma = undefined;
this.prevChromaColor = undefined;
this.videoExtractionSession = undefined;
}
public listenToToolbarEvents() {
uiEvents.toolbarVideoExtraction.INCLUSION_MODE.onClick(() => {
this.setMode(VideoExtractionMode.INCLUSION);
});
uiEvents.toolbarVideoExtraction.EXCLUSION_MODE.onClick(() => {
this.setMode(VideoExtractionMode.EXCLUSION);
});
uiEvents.toolbarVideoExtraction.UNDO.onClick(() => {
this.undo();
});
uiEvents.toolbarVideoExtraction.REDO.onClick(() => {
this.redo();
});
uiEvents.toolbarVideoExtraction.DONE.onClick(() => {
this.endVideoExtraction();
});
uiEvents.toolbarVideoExtraction.CANCEL.onClick(() => {
this.cancelVideoExtraction();
});
}
}
@@ -0,0 +1,4 @@
export enum VideoExtractionMode {
INCLUSION = "inclusion",
EXCLUSION = "exclusion",
}
+1 -1
View File
@@ -33,7 +33,7 @@ export abstract class NetworkedNode extends BaseNode {
protected localFile?: File;
protected mediaFileToken?: string;
protected mediaFileUrl?: string;
protected mediaFileSize?: Size;
public mediaFileSize?: Size;
// loading screen
protected loadingVideosProviderRef?: LoadingVideosProvider;
+18 -252
View File
@@ -15,13 +15,8 @@ import {
} from "../../types";
import { NodeUtilities } from "../NodeUtilities";
import {
Coordinates,
SegmentationApi,
} from "~/Classes/ApiManager/SegmentationApi";
import { LoadingVideosProvider } from "~/KonvaApp/EngineUtitlities/LoadingVideosProvider";
import { VideoExtractionEvents } from "~/KonvaApp/types/events";
interface VideoNodeContructor {
mediaLayerRef: Konva.Layer;
selectionManagerRef: SelectionManager;
@@ -38,6 +33,7 @@ export class VideoNode extends NetworkedNode {
public kNode: Konva.Image;
public mediaFileUrl: string;
public extractionUrl: string | undefined;
public extractionPreviewUrl: string | undefined;
public currentUrl: string;
public videoComponent: HTMLVideoElement;
protected _isVideoEventListening: boolean = false;
@@ -58,15 +54,8 @@ export class VideoNode extends NetworkedNode {
isChroma: boolean;
chromaColor: RGBColor;
// todo move to seg manager
selectedPointsForSegmentation: Coordinates[] = [];
private videoSegmentationAPI = new SegmentationApi();
private frameDidFinishSeeking: Promise<void>;
public isSegmentationMode: boolean = false;
private segmentationSession: { session_id: string } | undefined = undefined;
public videoExtractionClickCallback?: () => void;
constructor({
mediaLayerRef,
@@ -285,7 +274,8 @@ export class VideoNode extends NetworkedNode {
if (this.isProcessing == true) {
return;
}
if (this.isSegmentationMode) {
if (this.videoExtractionClickCallback) {
// if (this.isSegmentationMode) {
// console.log("Not Playing because Segmenting");
return;
}
@@ -458,246 +448,22 @@ export class VideoNode extends NetworkedNode {
}
}
public videoSegmentationMode(on: boolean) {
if (on) {
this.removeListenToBaseKNode();
this.kNode.on("click", this.handleSegmentation.bind(this));
this.isSegmentationMode = true;
} else {
this.isSegmentationMode = false;
this.kNode.off("click", this.handleSegmentation.bind(this));
this.listenToBaseKNode();
public enableExtractionMode(callback: () => void) {
this.removeListenToBaseKNode();
this.kNode.on("click", callback);
this.videoExtractionClickCallback = callback;
}
public disableExtractionMode() {
if (this.videoExtractionClickCallback) {
this.kNode.off("click", this.videoExtractionClickCallback);
}
this.extractionPreviewUrl = undefined;
this.videoExtractionClickCallback = undefined;
this.listenToBaseKNode();
}
public async startSegmentation() {
if (this.segmentationSession === undefined) {
this.didFinishLoading = false;
this.videoComponent.pause();
this.videoComponent.currentTime = 0;
this.setProgress({
name: "videoextraction",
progress: 25,
status: VideoExtractionEvents.SESSION_CREATING,
message: "Loading Video Extractor...",
});
const blob = await NodeUtilities.urlToBlob(this.mediaFileUrl);
this.setProgress({
name: "videoextraction",
progress: 75,
status: VideoExtractionEvents.SESSION_CREATING,
message: "Loading Video Extractor...",
});
this.segmentationSession =
await this.videoSegmentationAPI.createSession(blob);
this.setProgress({
name: "videoextraction",
progress: 100,
status: VideoExtractionEvents.SESSION_CREATING,
message: "Loaded Video Extractor",
});
console.log("Sessions", this.segmentationSession);
}
}
private isStillProcessingSegmentationEvent: Boolean = false;
public async handleSegmentation() {
if (!this.isSegmentationMode) {
console.log("Segmentation Mode Not On");
return;
}
if (!this.segmentationSession) {
console.log("Segmentation Session Not Ready");
return;
}
if (this.isStillProcessingSegmentationEvent) {
console.log("Still Processing");
return;
}
this.isStillProcessingSegmentationEvent = true;
// Get the local coordinates of the click relative to the rectangle
const localPos = this.kNode.getRelativePointerPosition();
console.log("Local coordinates:", localPos);
console.log("mediaFileSize:", this.mediaFileSize);
if (!localPos || !this.mediaFileSize) {
// TODO: error handling
return;
}
document.body.style.cursor = "wait";
const adjustedLocalPos = {
x:
(localPos.x / this.kNode.width()) *
this.kNode.scaleX() *
this.mediaFileSize.width,
y:
(localPos.y / this.kNode.height()) *
this.kNode.scaleY() *
this.mediaFileSize.height,
};
// handle undo as well.
this.selectedPointsForSegmentation.push({
coordinates: [adjustedLocalPos.x, adjustedLocalPos.y],
include: true,
});
this.setProgress({
name: "videoextraction",
progress: 25,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Start Processing Extraction...",
});
try {
console.log("Requesting");
const response = await this.videoSegmentationAPI.addPointsToSession(
this.segmentationSession.session_id,
24,
[
{
timestamp: 0,
objects: [
{
style: {
color: [0, 0, 1],
},
object_id: 0,
points: this.selectedPointsForSegmentation,
},
],
},
],
false,
);
this.setProgress({
name: "videoextraction",
progress: 50,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Processing Extraction Point...",
});
const previewImageUrl = response.frames[0].preview_image_url;
// TODO: we assumed success of the loading of the AssetUrl
await NodeUtilities.isAssetUrlAvailable({ url: previewImageUrl });
this.setProgress({
name: "videoextraction",
progress: 75,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Processing Extraction Point...",
});
await this.setSegementationPreview(previewImageUrl);
this.setProgress({
name: "videoextraction",
progress: 100,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: "Extraction of Region Done",
});
} catch (error) {
console.error(error);
this.setProgress({
name: "videoextraction",
progress: 0,
status: VideoExtractionEvents.EXTRACTION_POINT_REQUEST,
message: `Error:${error} Please try picking extraction points again`,
});
}
document.body.style.cursor = "default";
this.isStillProcessingSegmentationEvent = false;
}
async endSession(): Promise<boolean | string> {
// wait for url to come through and fake load
// edge case
if (this.isStillProcessingSegmentationEvent) {
console.log("Still Processing Frame Cannot Clip Please Wait");
return false;
}
// prevent requests to add points while doing this.
this.isStillProcessingSegmentationEvent = true;
this.setProgress({
name: "videoextraction",
progress: 25,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Processing Video...",
});
if (!this.segmentationSession) {
console.log("Segmentation Session Lost?");
return false;
}
try {
console.log("Requesting a close");
const response = await this.videoSegmentationAPI.addPointsToSession(
this.segmentationSession.session_id,
24,
[
{
timestamp: 0,
objects: [
{
style: {
color: [0, 0, 1],
},
object_id: 0,
points: this.selectedPointsForSegmentation,
},
],
},
],
true, // propagation = true, this requests the entire video to be processed
);
this.setProgress({
name: "videoextraction",
progress: 50,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Processing Video...",
});
// replace the video component and reregister all the other elements.
console.log(
"Extracted Video URL",
response["masked_video_cdn_url"],
response,
);
// TODO: we assume the URL to be checked will eventually return true
const extractionUrl = response["masked_video_cdn_url"];
await NodeUtilities.isAssetUrlAvailable({
url: extractionUrl,
sleepDurationMs: 2000,
});
this.setProgress({
name: "videoextraction",
progress: 75,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Processing Video...",
});
this.extractionUrl = extractionUrl;
// set chroma automatically.
this.isChroma = true;
await this.loadVideoFromUrl({
videoUrl: extractionUrl,
hasExistingTransform: true,
});
this.setProgress({
name: "videoextraction",
progress: 100,
status: VideoExtractionEvents.SESSION_CLOSING,
message: "Done Processing Video",
});
this.isStillProcessingSegmentationEvent = false;
return extractionUrl;
} catch (error) {
console.error(error);
this.isStillProcessingSegmentationEvent = false;
return false;
}
}
private async setSegementationPreview(previewImageUrl: string) {
public async setVideoExtractionPreview(previewImageUrl: string) {
this.extractionPreviewUrl = previewImageUrl;
const imageObj = new Image();
const imageLoadPromise = new Promise<void>((resolve, reject) => {
+1 -1
View File
@@ -1,4 +1,4 @@
export interface ICommand {
execute(): void | boolean;
execute(): void | boolean | Promise<boolean>;
undo(): void;
}
+6 -3
View File
@@ -12,8 +12,8 @@ export class UndoStackManager {
isDisabled() {
return this.disabled;
}
executeCommand(command: ICommand) {
const result = command.execute();
async executeCommand(command: ICommand) {
const result = await command.execute();
if (result !== undefined && result === false) {
//command is not executed if it deliberatly returned false;
//in that case, no need to do anything
@@ -31,7 +31,10 @@ export class UndoStackManager {
}
console.log("command stack:", this.undoStack);
}
clear() {
this.undoStack = [];
this.redoStack = [];
}
undo() {
if (this.disabled) {
return;
@@ -48,6 +48,8 @@ export const SignaledToolbarVideoExtraction = () => {
show={toolbarVideoExtraction.signal.value.isShowing}
>
<ToolbarVideoExtraction
readyToSubmit={toolbarVideoExtraction.signal.value.ready}
extractionMode={toolbarVideoExtraction.signal.value.mode}
disabled={toolbarVideoExtraction.signal.value.disabled}
buttonsProps={buttonProps}
loadingBarProps={loadingBarProps}
@@ -1,8 +1,20 @@
// import { useState } from "react";
import { twMerge } from "tailwind-merge";
import { faScalpel } from "@fortawesome/pro-solid-svg-icons";
import { LoadingBar, LoadingBarStatus, LoadingBarProps } from "~/components/ui";
import {
// ToolbarButton,
faCircleMinus,
faCirclePlus,
faRotateLeft,
faRotateRight,
faScalpel,
} from "@fortawesome/pro-solid-svg-icons";
import {
LoadingBar,
LoadingBarStatus,
LoadingBarProps,
// Slider,
} from "~/components/ui";
import {
ToolbarButton,
ToolbarButtonProps,
} from "~/components/features/ToolbarButton";
import { paperWrapperStyles } from "~/components/styles";
@@ -10,6 +22,8 @@ import { ToolbarVideoExtractionButtonNames } from "./enums";
import { Button, Tooltip } from "~/components/ui";
export interface ToolbarVideoExtractionProps {
extractionMode: "inclusion" | "exclusion";
readyToSubmit: boolean;
disabled?: boolean;
buttonsProps: {
[key in ToolbarVideoExtractionButtonNames]: ToolbarButtonProps;
@@ -17,10 +31,13 @@ export interface ToolbarVideoExtractionProps {
loadingBarProps?: Partial<LoadingBarProps>;
}
export const ToolbarVideoExtraction = ({
extractionMode = "inclusion",
readyToSubmit = false,
disabled,
buttonsProps,
loadingBarProps,
}: ToolbarVideoExtractionProps) => {
// const [frame, setFrame] = useState(0);
const loadingBar = {
colReverse: true,
progress: 0,
@@ -33,19 +50,103 @@ export const ToolbarVideoExtraction = ({
className={twMerge(
paperWrapperStyles,
disabled && "pointer-events-none cursor-default bg-ui-border shadow-md",
"flex items-center justify-center gap-2 pr-4 transition",
"flex flex-col items-center justify-center gap-2 pr-4 transition",
)}
>
<div className="w-72 p-2">
<div className="w-full p-2">
<LoadingBar {...loadingBar} />
{/* <Slider
min={0}
max={100}
onChange={(val) => {
console.log(val);
}}
step={1}
value={frame}
/> */}
</div>
<div className="flex w-full items-center justify-center gap-2">
<Tooltip
tip={
extractionMode === "inclusion"
? "Using Inclusion Points"
: "Use Inclusion Points"
}
>
<div>
<ToolbarButton
icon={faCirclePlus}
buttonProps={{
...buttonsProps.INCLUSION_MODE,
active: extractionMode === "inclusion",
}}
/>
</div>
</Tooltip>
<Tooltip
tip={
extractionMode === "exclusion"
? "Using Exclusion Points"
: "Use Exclusion Points"
}
>
<div>
<ToolbarButton
icon={faCircleMinus}
buttonProps={{
...buttonsProps.EXCLUSION_MODE,
active: extractionMode === "exclusion",
}}
/>
</div>
</Tooltip>
<ToolbarButton
icon={faRotateLeft}
tooltip="Undo"
buttonProps={buttonsProps.UNDO}
/>
<ToolbarButton
icon={faRotateRight}
tooltip="Redo"
buttonProps={buttonsProps.REDO}
/>
<DoneButton {...buttonsProps.DONE} ready={readyToSubmit} />
<CancelButton {...buttonsProps.CANCEL} />
</div>
<DoneButton {...buttonsProps.DONE} />
</div>
);
};
const CancelButton = (buttonProps: ToolbarButtonProps) => {
const {
className: customButtonClassNames,
disabled,
active,
hidden,
onClick,
...restButtonProps
} = buttonProps;
const DoneButton = (buttonProps: ToolbarButtonProps) => {
return (
<Button
className="text-nowrap"
variant="secondary"
disabled={disabled}
{...restButtonProps}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (onClick) {
onClick(e);
}
}}
{...restButtonProps}
>
Cancel
</Button>
);
};
type DoneButtonProps = { ready: boolean } & ToolbarButtonProps;
const DoneButton = ({ ready, ...buttonProps }: DoneButtonProps) => {
const {
className: customButtonClassNames,
disabled,
@@ -58,9 +159,9 @@ const DoneButton = (buttonProps: ToolbarButtonProps) => {
return (
<Tooltip tip="Click to Complete Extraction">
<Button
className="text-nowrap p-4"
className="text-nowrap"
icon={faScalpel}
disabled={disabled}
disabled={disabled || !ready}
{...restButtonProps}
onClick={(e) => {
e.preventDefault();
@@ -1,3 +1,8 @@
export enum ToolbarVideoExtractionButtonNames {
INCLUSION_MODE = "INCLUSION_MODE",
EXCLUSION_MODE = "EXCLUSION_MODE",
REDO = "REDO",
UNDO = "UNDO",
DONE = "DONE",
CANCEL = "CANCEL",
}
@@ -10,11 +10,15 @@ type ButtonStates = {
export interface ToolbarVideoExtractionProps {
isShowing: boolean;
disabled: boolean;
mode: "inclusion" | "exclusion";
ready: boolean;
buttonStates: ButtonStates;
loadingBarState: LoadingBarState;
}
const toolbarVideoExtractionSignal = signal<ToolbarVideoExtractionProps>({
ready: false,
mode: "inclusion",
isShowing: false,
disabled: false,
buttonStates: initButtonStates(),
@@ -33,6 +37,18 @@ export const toolbarVideoExtraction = {
...updateProps,
};
},
setReady(ready: boolean) {
toolbarVideoExtractionSignal.value = {
...toolbarVideoExtractionSignal.value,
ready,
};
},
setMode(mode: "inclusion" | "exclusion") {
toolbarVideoExtractionSignal.value = {
...toolbarVideoExtractionSignal.value,
mode,
};
},
updateProgress(newLoadingBarState: LoadingBarState) {
toolbarVideoExtractionSignal.value = {
...toolbarVideoExtractionSignal.value,
+236 -100
View File
@@ -28,7 +28,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz"
integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.24.5":
"@babel/core@^7.24.5":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz"
integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
@@ -222,6 +222,116 @@
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz"
@@ -263,7 +373,7 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@^9.9.0", "@eslint/js@9.9.1":
"@eslint/js@9.9.1", "@eslint/js@^9.9.0":
version "9.9.1"
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz"
integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==
@@ -314,7 +424,7 @@
resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/6.6.0/fontawesome-common-types-6.6.0.tgz"
integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==
"@fortawesome/fontawesome-svg-core@^6.6.0", "@fortawesome/fontawesome-svg-core@~1 || ~6":
"@fortawesome/fontawesome-svg-core@^6.6.0":
version "6.6.0"
resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/6.6.0/fontawesome-svg-core-6.6.0.tgz"
integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==
@@ -419,7 +529,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -741,6 +851,81 @@
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz"
integrity sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==
"@rollup/rollup-android-arm-eabi@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54"
integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==
"@rollup/rollup-android-arm64@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e"
integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==
"@rollup/rollup-darwin-arm64@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f"
integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==
"@rollup/rollup-darwin-x64@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724"
integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==
"@rollup/rollup-linux-arm-gnueabihf@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb"
integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==
"@rollup/rollup-linux-arm-musleabihf@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3"
integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==
"@rollup/rollup-linux-arm64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496"
integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==
"@rollup/rollup-linux-arm64-musl@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065"
integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==
"@rollup/rollup-linux-powerpc64le-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d"
integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==
"@rollup/rollup-linux-riscv64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983"
integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==
"@rollup/rollup-linux-s390x-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58"
integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==
"@rollup/rollup-linux-x64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b"
integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==
"@rollup/rollup-linux-x64-musl@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127"
integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==
"@rollup/rollup-win32-arm64-msvc@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5"
integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==
"@rollup/rollup-win32-ia32-msvc@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2"
integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==
"@rollup/rollup-win32-x64-msvc@4.24.0":
version "4.24.0"
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz"
@@ -872,7 +1057,7 @@
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/node@^18.0.0 || >=20.0.0", "@types/node@>=13.7.0":
"@types/node@>=13.7.0":
version "22.5.5"
resolved "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz"
integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==
@@ -919,7 +1104,7 @@
natural-compare "^1.4.0"
ts-api-utils "^1.3.0"
"@typescript-eslint/parser@^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser@8.3.0":
"@typescript-eslint/parser@8.3.0":
version "8.3.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz"
integrity sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==
@@ -1011,7 +1196,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.12.0:
acorn@^8.12.0:
version "8.12.1"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
@@ -1058,14 +1243,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
ansi-styles@^4.1.0:
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -1232,12 +1410,7 @@ bin-links@^4.0.4:
read-cmd-shim "^4.0.0"
write-file-atomic "^5.0.0"
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
binary-extensions@^2.3.0:
binary-extensions@^2.0.0, binary-extensions@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
@@ -1264,7 +1437,7 @@ braces@^3.0.3, braces@~3.0.2:
dependencies:
fill-range "^7.1.1"
browserslist@^4.23.1, browserslist@^4.23.3, "browserslist@>= 4.21.0":
browserslist@^4.23.1, browserslist@^4.23.3:
version "4.23.3"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz"
integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
@@ -1414,16 +1587,16 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
@@ -1509,7 +1682,7 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0"
is-data-view "^1.0.1"
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4:
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.6"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz"
integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
@@ -1839,12 +2012,7 @@ eslint-scope@^8.0.2:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-visitor-keys@^3.3.0:
version "3.4.3"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^3.4.3:
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
@@ -1854,7 +2022,7 @@ eslint-visitor-keys@^4.0.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
"eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.9.0, eslint@>=7:
eslint@^9.9.0:
version "9.9.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz"
integrity sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==
@@ -2058,6 +2226,11 @@ fs-minipass@^3.0.0, fs-minipass@^3.0.3:
dependencies:
minipass "^7.0.3"
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@@ -2103,7 +2276,7 @@ get-symbol-description@^1.0.2:
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
glob-parent@^5.1.2:
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2117,13 +2290,6 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob@^10.2.2, glob@^10.3.10, glob@^10.4.5:
version "10.4.5"
resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz"
@@ -2171,12 +2337,7 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
graceful-fs@^4.2.11, graceful-fs@^4.2.6:
graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -2591,7 +2752,7 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jiti@*, jiti@^1.21.0:
jiti@^1.21.0:
version "1.21.6"
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
@@ -2960,16 +3121,16 @@ minipass@^3.0.0:
dependencies:
yallist "^4.0.0"
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.1.1, minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minipass@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.1.1, minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minizlib@^2.1.1, minizlib@^2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz"
@@ -2983,16 +3144,16 @@ mkdirp@^1.0.3:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@^2.1.2:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.2:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
mute-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz"
@@ -3470,15 +3631,7 @@ postcss-nested@^6.0.1:
dependencies:
postcss-selector-parser "^6.1.1"
postcss-selector-parser@^6.0.10:
version "6.1.2"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1:
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1:
version "6.1.2"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
@@ -3491,7 +3644,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.41, postcss@^8.4.43, postcss@>=8.0.9:
postcss@^8.4.23, postcss@^8.4.41, postcss@^8.4.43:
version "8.4.47"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
@@ -3525,7 +3678,7 @@ prettier-plugin-tailwindcss@^0.6.6:
resolved "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.6.tgz"
integrity sha512-OPva5S7WAsPLEsOuOWXATi13QrCKACCiIonFgIR6V4lYv4QLp++UXVhZSzRbZxXGimkQtQT86CC6fQqTOybGng==
prettier@^3.0, prettier@^3.3.3:
prettier@^3.3.3:
version "3.3.3"
resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
@@ -3617,7 +3770,7 @@ react-colorful@^5.6.1:
resolved "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz"
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18, react-dom@^18.0.0, react-dom@^18.3.1, "react-dom@>= 16.8.0", react-dom@>=16.4.0, react-dom@>=16.8, react-dom@>=16.8.0:
react-dom@^18.3.1:
version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@@ -3641,7 +3794,7 @@ react-easy-crop@^5.1.0:
normalize-wheel "^1.0.1"
tslib "^2.0.1"
react-is@^16.13.1, react-is@^16.7.0, "react-is@>= 16.8.0":
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -3666,7 +3819,7 @@ react-router@6.26.1:
dependencies:
"@remix-run/router" "1.19.1"
"react@^16.14.0 || 17.x || 18.x", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", react@^18, react@^18.0.0, react@^18.3.1, "react@>= 16.8.0", react@>=16.3, react@>=16.4.0, react@>=16.8, react@>=16.8.0:
react@^18.3.1:
version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
@@ -3735,16 +3888,7 @@ resolve-from@^4.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.1.7:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
dependencies:
is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.22.2:
resolve@^1.1.7, resolve@^1.22.2:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -3840,12 +3984,7 @@ semver@^6.3.1:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
semver@^7.6.0:
semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -3973,7 +4112,9 @@ spdx-expression-parse@^4.0.0:
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.18"
version "3.0.20"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz#e44ed19ed318dd1e5888f93325cee800f0f51b89"
integrity sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==
sprintf-js@^1.1.3:
version "1.1.3"
@@ -4101,7 +4242,7 @@ strip-json-comments@^3.1.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
styled-components@^5.3.0, "styled-components@>= 2":
styled-components@^5.3.0:
version "5.3.11"
resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz"
integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==
@@ -4204,12 +4345,7 @@ tar@^6.1.11, tar@^6.2.1:
mkdirp "^1.0.3"
yallist "^4.0.0"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
text-table@~0.2.0:
text-table@^0.2.0, text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
@@ -4339,7 +4475,7 @@ typescript-eslint@^8.0.1:
"@typescript-eslint/parser" "8.3.0"
"@typescript-eslint/utils" "8.3.0"
typescript@^5.0.0, typescript@^5.5.3, typescript@>=4.2.0:
typescript@^5.5.3:
version "5.5.4"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz"
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
@@ -4440,7 +4576,7 @@ vite-tsconfig-paths@^5.0.1:
globrex "^0.1.2"
tsconfck "^3.0.3"
vite@*, "vite@^4.2.0 || ^5.0.0", vite@^5.0.0, vite@^5.4.1:
vite@^5.4.1:
version "5.4.10"
resolved "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz"
integrity sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==