gemercheung 3 years ago
parent
commit
888d703272

+ 24 - 1
.eslintrc.js

@@ -1,3 +1,26 @@
 module.exports = {
-  extends: "@nighttrax/eslint-config-tsx",
+    parser: '@typescript-eslint/parser',
+    parserOptions: {
+        // project: 'tsconfig.build.json',
+        tsconfigRootDir: __dirname,
+        sourceType: 'module',
+    },
+    plugins: ['@typescript-eslint/eslint-plugin'],
+    extends: [
+        'plugin:@typescript-eslint/recommended',
+        'plugin:prettier/recommended',
+    ],
+    root: true,
+    env: {
+        node: true,
+        jest: true,
+    },
+    ignorePatterns: ['.eslintrc.js'],
+    rules: {
+        '@typescript-eslint/interface-name-prefix': 'off',
+        '@typescript-eslint/explicit-function-return-type': 'off',
+        '@typescript-eslint/explicit-module-boundary-types': 'off',
+        '@typescript-eslint/no-explicit-any': 'off',
+        '@typescript-eslint/ban-types': 'off',
+    },
 };

+ 5 - 0
.prettierrc

@@ -0,0 +1,5 @@
+{
+  "tabWidth": 4,
+  "singleQuote": true,
+  "trailingComma": "all"
+}

+ 5 - 0
.vscode/settings.json

@@ -0,0 +1,5 @@
+{
+    "cSpell.words": [
+        "ondataavailable"
+    ]
+}

+ 5 - 0
package.json

@@ -19,6 +19,11 @@
     "doctoc": "~2.2.0",
     "eslint": "~8.20.0",
     "eslint-plugin-import": "~2.26.0",
+    "@typescript-eslint/eslint-plugin": "^5.30.6",
+    "@typescript-eslint/parser": "^5.30.6",
+    "eslint-config-prettier": "^8.5.0",
+    "eslint-plugin-prettier": "^4.2.1",
+    "prettier": "^2.7.1",
     "typescript": "~4.7.0"
   },
   "pnpm": {

+ 1 - 1
packages/core/src/index.ts

@@ -1 +1 @@
-export * from "./lib/init";
+export * from './lib/init';

+ 1 - 1
packages/core/src/lib/audioConstraints.ts

@@ -1,3 +1,3 @@
-import { BehaviorSubject } from "rxjs";
+import { BehaviorSubject } from 'rxjs';
 
 export const audioConstraints = new BehaviorSubject<MediaTrackConstraints>({});

+ 131 - 4
packages/core/src/lib/basicSimaqRecorder.ts

@@ -1,9 +1,136 @@
+import { audioConstraints } from './audioConstraints';
+import { videoConstraints } from './videoConstraints';
+import { isSupport } from './isSupport';
+import { getVideo } from './videoElement';
+import { BehaviorSubject } from 'rxjs';
+
+export type ResolutionType = '1080p' | '2k' | '4k';
+export interface InitConfigType extends DisplayMediaStreamConstraints {
+    uploadUrl: '';
+    resolution: ResolutionType;
+    autoDownload?: boolean;
+    debug?: boolean;
+}
 export class BasicSimaqRecorder {
-  constructor() {}
+    displayMediaStreamConstraints: DisplayMediaStreamConstraints = {
+        video: videoConstraints.getValue(),
+        audio: audioConstraints.getValue(),
+    };
+    private isStartRecoding = false;
+    private stream: MediaStream;
+    private mediaRecorder: MediaRecorder;
+    public record = new BehaviorSubject<Blob>(null);
+    private recordChunks: Blob[] = [];
+    private autoDownload = false;
+
+    constructor(arg: InitConfigType) {
+        console.log('arg', arg);
+        this.autoDownload = arg.autoDownload;
+        videoConstraints.subscribe((value) => {
+            console.log('subscribe', value);
+        });
+    }
+
+    public async startRecord(): Promise<void> {
+        if (!this.isStartRecoding) {
+            console.log('开始录屏!');
+            if (!isSupport) {
+                console.error('当前浏览器不支持录屏!');
+                return;
+            }
+            this.isStartRecoding = true;
+            const media = await this.getDisplayMedia();
+            if (media) {
+                // console.log('media', media);
+                const video: HTMLVideoElement = getVideo();
+                if (video) {
+                    // console.log('video', video);
+                    video.srcObject = media;
+                    this.stream = media;
+                    this.createMediaRecoder();
+                    this.mediaRecorder.start();
+                    this.stream.getVideoTracks()[0].onended = () => {
+                        console.log('stop-share');
+                        this.endRecord();
+                    };
+                }
+            }
+        }
+    }
+
+    private getDisplayMedia(): Promise<MediaStream | null> {
+        return new Promise(async (resolve) => {
+            try {
+                if (navigator.mediaDevices.getDisplayMedia) {
+                    const res = await navigator.mediaDevices.getDisplayMedia(
+                        this.displayMediaStreamConstraints,
+                    );
+                    return resolve(res);
+                }
+                return resolve(null);
+            } catch (error) {
+                console.log('被动中断');
+                return resolve(null);
+            }
+        });
+    }
+
+    public endRecord(): void {
+        this.isStartRecoding = false;
+        this.streamStop();
+    }
+
+    private streamStop(): void {
+        if (this.stream) {
+            this.stream.getTracks().forEach((track) => track.stop());
+        }
+        if (this.mediaRecorder) {
+            this.mediaRecorder.stop();
+        }
+    }
 
-  public startRecord(): void {}
+    private createMediaRecoder(): void {
+        console.log('video-flag', videoConstraints.value);
+        const mediaRecorder = new MediaRecorder(this.stream, {
+            mimeType: 'video/webm;codecs=H264,opus',
+            audioBitsPerSecond: videoConstraints.value.audioBitsPerSecond,
+            videoBitsPerSecond: videoConstraints.value.videoBitsPerSecond,
+        });
+        this.mediaRecorder = mediaRecorder;
+        this.mediaRecorder.ondataavailable = (event) => {
+            // console.log('ondataavailable', event.data);
+            this.recordChunks.push(event.data);
+            this.record.next(
+                new Blob([event.data], {
+                    type: 'video/mp4; codecs=h264',
+                }),
+            );
+        };
+        this.mediaRecorder.stop = () => {
+            setTimeout(() => {
+                this.handleAutoDownload();
+                this.recordChunks = [];
+                this.record.next(null);
+            }, 1000);
+        };
+    }
 
-  public endRecord(): void {}
+    private handleAutoDownload(): void {
+        if (this.autoDownload) {
+            // console.log('recordChunks', this.recordChunks);
+            const downloadBlob = new Blob(this.recordChunks, {
+                type: 'video/mp4; codecs=h264',
+            });
+            const url = URL.createObjectURL(downloadBlob);
+            const a: HTMLAnchorElement = document.createElement('a');
+            document.body.appendChild(a);
+            a.style.display = 'none';
+            a.href = url;
+            a.download = 'test.mp4';
+            a.click();
+            window.URL.revokeObjectURL(url);
+        }
+    }
 
-  private uploadToServer(): void {}
+    private uploadToServer(): void { }
 }

+ 0 - 6
packages/core/src/lib/createVideo.ts

@@ -1,6 +0,0 @@
-export function createVideo(): void {
-  const videoElement: HTMLVideoElement = document.createElement("video");
-  videoElement.className = "simaq-video";
-  // videoElement.style.display ='none';
-  document.body.appendChild(videoElement);
-}

+ 54 - 18
packages/core/src/lib/init.ts

@@ -1,20 +1,56 @@
-import { BasicSimaqRecorder } from "./basicSimaqRecorder";
-import { audioConstraints } from "./audioConstraints";
-import { videoConstraints } from "./videoConstraints";
-import { isSupport } from "./isSupport";
+import { BasicSimaqRecorder, InitConfigType } from './basicSimaqRecorder';
+import { createVideo, getVideo } from './videoElement';
+// import { audioConstraints } from "./audioConstraints";
+import { videoConstraints } from './videoConstraints';
+export class VideoRecorder extends BasicSimaqRecorder {
+    // public displayMediaStreamConstraints: DisplayMediaStreamConstraints = {
+    //   video: videoConstraints.value,
+    //   audio: audioConstraints.value,
+    // };
+    constructor(params: InitConfigType) {
+        super(params);
+        this.init(params);
+    }
 
-interface InitConfigType {
-  uploadUrl: "";
-}
-export class Simaq extends BasicSimaqRecorder {
-  public displayMediaStreamConstraints: DisplayMediaStreamConstraints = {
-    video: videoConstraints.value,
-    audio: audioConstraints.value,
-  };
-
-  constructor(params: InitConfigType) {
-    super();
-  }
-
-  init(): void {}
+    init(params: InitConfigType): void {
+        document.addEventListener(
+            'DOMContentLoaded',
+            createVideo.apply(this, [params.debug]),
+        );
+        // if (params?.debug) {
+        //     const video = getVideo();
+        //     video.style.display = 'block';
+        // }
+        if (params?.resolution) {
+            switch (params.resolution) {
+                case '4k':
+                    videoConstraints.next({
+                        ...videoConstraints.value,
+                        width: 3840,
+                        height: 2160,
+                        audioBitsPerSecond: 128000,
+                        videoBitsPerSecond: 12000000, // 12m/s
+                    });
+                    break;
+                case '2k':
+                    videoConstraints.next({
+                        ...videoConstraints.value,
+                        width: 2560,
+                        height: 1440,
+                        audioBitsPerSecond: 128000,
+                        videoBitsPerSecond: 6500000, // 6.5m/s
+                    });
+                    break;
+                case '1080p':
+                    videoConstraints.next({
+                        ...videoConstraints.value,
+                        width: 1920,
+                        height: 1080,
+                        audioBitsPerSecond: 128000,
+                        videoBitsPerSecond: 2500000, // 2.5m/s
+                    });
+                    break;
+            }
+        }
+    }
 }

+ 1 - 1
packages/core/src/lib/sdk.ts

@@ -1,3 +1,3 @@
 export class SDK {
-  constructor() {}
+    constructor() {}
 }

+ 10 - 4
packages/core/src/lib/videoConstraints.ts

@@ -1,5 +1,11 @@
-import { BehaviorSubject } from "rxjs";
+import { BehaviorSubject } from 'rxjs';
 
-export const videoConstraints = new BehaviorSubject<MediaTrackConstraints>({
-  frameRate: 30,
-});
+interface CustomMediaTrackConstraints extends MediaTrackConstraints {
+    audioBitsPerSecond?: number;
+    videoBitsPerSecond?: number;
+}
+
+export const videoConstraints =
+    new BehaviorSubject<CustomMediaTrackConstraints>({
+        frameRate: 30,
+    });

+ 29 - 0
packages/core/src/lib/videoElement.ts

@@ -0,0 +1,29 @@
+export function createVideo(show: boolean): void {
+    const videoElement: HTMLVideoElement = document.createElement('video');
+    videoElement.id = 'simaq-video';
+    videoElement.setAttribute('autoplay', 'true');
+    videoElement.setAttribute('mute', 'true');
+    videoElement.style.width = '600px';
+    videoElement.style.height = '400px';
+    videoElement.style.position = 'fixed';
+    videoElement.style.top = '0px';
+    videoElement.style.right = '0px';
+    videoElement.style.zIndex = '1000';
+    videoElement.style.display = show ? 'block' : 'none';
+    console.log('video-show', show);
+    document.body.appendChild(videoElement);
+}
+
+export function getVideo(): HTMLVideoElement {
+    const videoElement: HTMLVideoElement = document.getElementById(
+        'simaq-video',
+    ) as HTMLVideoElement;
+    return videoElement;
+}
+
+export function removeVideo(): void {
+    const videoElement: HTMLVideoElement = document.getElementById(
+        'simaq-video',
+    ) as HTMLVideoElement;
+    document.body.removeChild(videoElement);
+}

+ 31 - 20
play/src/App.vue

@@ -1,39 +1,50 @@
 <script setup lang="ts">
 // This starter template is using Vue 3 <script setup> SFCs
 // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
-import HelloWorld from "./components/HelloWorld.vue";
-import { Simaq } from "@simaq/core";
+import HelloWorld from './components/HelloWorld.vue';
+import { VideoRecorder } from '@simaq/core';
 // import { add } from "lodash-es";
 // import { meaningOfLife } from "@simaq/foo";
-console.log("Simaq", Simaq);
-const recoder = new Simaq({
-  uploadUrl: "",
-});
 
+const videoRecorder = new VideoRecorder({
+    uploadUrl: '',
+    resolution: '4k',
+    autoDownload: true,
+    debug: true,
+});
+console.log('videoRecorder', videoRecorder);
+videoRecorder.record.subscribe((data) => {
+    if (data) {
+        console.log('前端收到', data);
+    }
+});
+// window.videoRecorder = videoRecorder;
 </script>
 
 <template>
-  <div>
-    <a href="https://vitejs.dev" target="_blank">
-      <img src="/vite.svg" class="logo" alt="Vite logo" />
-    </a>
-    <a href="https://vuejs.org/" target="_blank">
-      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
-    </a>
-  </div>
-  <HelloWorld msg="Vite + Vue" />
+    <div>
+        <!-- <a href="https://vitejs.dev" target="_blank">
+            <img src="/vite.svg" class="logo" alt="Vite logo" />
+        </a>
+        <a href="https://vuejs.org/" target="_blank">
+            <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
+        </a> -->
+    </div>
+    <button type="button" @click="videoRecorder.startRecord">开始录屏</button>
+    <button type="button" @click="videoRecorder.endRecord">停止录屏</button>
+    <!-- <HelloWorld msg="Vite + Vue" /> -->
 </template>
 
 <style scoped>
 .logo {
-  height: 6em;
-  padding: 1.5em;
-  will-change: filter;
+    height: 6em;
+    padding: 1.5em;
+    will-change: filter;
 }
 .logo:hover {
-  filter: drop-shadow(0 0 2em #646cffaa);
+    filter: drop-shadow(0 0 2em #646cffaa);
 }
 .logo.vue:hover {
-  filter: drop-shadow(0 0 2em #42b883aa);
+    filter: drop-shadow(0 0 2em #42b883aa);
 }
 </style>

+ 4 - 4
play/src/main.ts

@@ -1,5 +1,5 @@
-import { createApp } from "vue";
-import "./style.css";
-import App from "./App.vue";
+import { createApp } from 'vue';
+import './style.css';
+import App from './App.vue';
 
-createApp(App).mount("#app");
+createApp(App).mount('#app');

+ 4 - 4
play/src/vite-env.d.ts

@@ -1,7 +1,7 @@
 /// <reference types="vite/client" />
 
-declare module "*.vue" {
-  import type { DefineComponent } from "vue";
-  const component: DefineComponent<{}, {}, any>;
-  export default component;
+declare module '*.vue' {
+    import type { DefineComponent } from 'vue';
+    const component: DefineComponent<{}, {}, any>;
+    export default component;
 }

+ 3 - 3
play/vite.config.ts

@@ -1,7 +1,7 @@
-import { defineConfig } from "vite";
-import vue from "@vitejs/plugin-vue";
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [vue()],
+    plugins: [vue()],
 });

+ 200 - 9
pnpm-lock.yaml

@@ -5,15 +5,25 @@ importers:
   .:
     specifiers:
       '@nighttrax/eslint-config-tsx': ~10.0.0-beta.0
+      '@typescript-eslint/eslint-plugin': ^5.30.6
+      '@typescript-eslint/parser': ^5.30.6
       doctoc: ~2.2.0
       eslint: ~8.20.0
+      eslint-config-prettier: ^8.5.0
       eslint-plugin-import: ~2.26.0
+      eslint-plugin-prettier: ^4.2.1
+      prettier: ^2.7.1
       typescript: ~4.7.0
     devDependencies:
-      '@nighttrax/eslint-config-tsx': 10.0.0-beta.2_o42cowhndu3dmvrylnnywvjupe
+      '@nighttrax/eslint-config-tsx': 10.0.0-beta.2_y2wwiwdakqshbkrxdsdnd3mcim
+      '@typescript-eslint/eslint-plugin': 5.31.0_mp5sa5yz4gwf64fr27rrogi6ru
+      '@typescript-eslint/parser': 5.31.0_o42cowhndu3dmvrylnnywvjupe
       doctoc: 2.2.0
       eslint: 8.20.0
-      eslint-plugin-import: 2.26.0_eslint@8.20.0
+      eslint-config-prettier: 8.5.0_eslint@8.20.0
+      eslint-plugin-import: 2.26.0_ficupzxy3q6nk56ibvavehtzue
+      eslint-plugin-prettier: 4.2.1_g4fztgbwjyq2fvmcscny2sj6fy
+      prettier: 2.7.1
       typescript: 4.7.2
 
   packages/core:
@@ -135,7 +145,7 @@ packages:
       eslint-config-airbnb-base: 15.0.0_54w6onclsgshjl66pd6lvxh5h4
       eslint-config-prettier: 8.3.0_eslint@8.20.0
       eslint-plugin-import: 2.25.4_si66yizekdhcbijk2e3rggqv6i
-      eslint-plugin-prettier: 4.0.0_2lfn3wslnw6p5yryh2j642jjde
+      eslint-plugin-prettier: 4.0.0_nbmdt66gewj4zlnmfly4ek6wkq
       prettier: 2.5.1
     dev: true
 
@@ -156,7 +166,7 @@ packages:
       eslint-plugin-react-hooks: 4.3.0_eslint@8.20.0
     dev: true
 
-  /@nighttrax/eslint-config-tsx/10.0.0-beta.2_o42cowhndu3dmvrylnnywvjupe:
+  /@nighttrax/eslint-config-tsx/10.0.0-beta.2_y2wwiwdakqshbkrxdsdnd3mcim:
     resolution: {integrity: sha512-DxonRIz8yJvXL7drkFrSy2IfIC3yNGaQHE0SwXgemYRZQ+nRMNAHGjse5F9g9zg6M15bSSw10MTOoxAvFvypoA==}
     peerDependencies:
       eslint: ^8.5.0
@@ -171,7 +181,7 @@ packages:
       eslint-import-resolver-typescript: 2.5.0_54w6onclsgshjl66pd6lvxh5h4
       eslint-plugin-import: 2.25.4_si66yizekdhcbijk2e3rggqv6i
       eslint-plugin-jsx-a11y: 6.5.1_eslint@8.20.0
-      eslint-plugin-prettier: 4.0.0_2lfn3wslnw6p5yryh2j642jjde
+      eslint-plugin-prettier: 4.0.0_nbmdt66gewj4zlnmfly4ek6wkq
       eslint-plugin-react: 7.28.0_eslint@8.20.0
       eslint-plugin-react-hooks: 4.3.0_eslint@8.20.0
       prettier: 2.5.1
@@ -244,6 +254,33 @@ packages:
     resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
     dev: true
 
+  /@typescript-eslint/eslint-plugin/5.31.0_mp5sa5yz4gwf64fr27rrogi6ru:
+    resolution: {integrity: sha512-VKW4JPHzG5yhYQrQ1AzXgVgX8ZAJEvCz0QI6mLRX4tf7rnFfh5D8SKm0Pq6w5PyNfAWJk6sv313+nEt3ohWMBQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^5.0.0
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 5.31.0_o42cowhndu3dmvrylnnywvjupe
+      '@typescript-eslint/scope-manager': 5.31.0
+      '@typescript-eslint/type-utils': 5.31.0_o42cowhndu3dmvrylnnywvjupe
+      '@typescript-eslint/utils': 5.31.0_o42cowhndu3dmvrylnnywvjupe
+      debug: 4.3.4
+      eslint: 8.20.0
+      functional-red-black-tree: 1.0.1
+      ignore: 5.2.0
+      regexpp: 3.2.0
+      semver: 7.3.7
+      tsutils: 3.21.0_typescript@4.7.2
+      typescript: 4.7.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/eslint-plugin/5.8.1_bj5r7lnfn4sdq5huff5fderzca:
     resolution: {integrity: sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -288,6 +325,26 @@ packages:
       - typescript
     dev: true
 
+  /@typescript-eslint/parser/5.31.0_o42cowhndu3dmvrylnnywvjupe:
+    resolution: {integrity: sha512-UStjQiZ9OFTFReTrN+iGrC6O/ko9LVDhreEK5S3edmXgR396JGq7CoX2TWIptqt/ESzU2iRKXAHfSF2WJFcWHw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/scope-manager': 5.31.0
+      '@typescript-eslint/types': 5.31.0
+      '@typescript-eslint/typescript-estree': 5.31.0_typescript@4.7.2
+      debug: 4.3.4
+      eslint: 8.20.0
+      typescript: 4.7.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/parser/5.8.1_o42cowhndu3dmvrylnnywvjupe:
     resolution: {integrity: sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -308,6 +365,14 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/scope-manager/5.31.0:
+    resolution: {integrity: sha512-8jfEzBYDBG88rcXFxajdVavGxb5/XKXyvWgvD8Qix3EEJLCFIdVloJw+r9ww0wbyNLOTYyBsR+4ALNGdlalLLg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.31.0
+      '@typescript-eslint/visitor-keys': 5.31.0
+    dev: true
+
   /@typescript-eslint/scope-manager/5.8.1:
     resolution: {integrity: sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -316,11 +381,56 @@ packages:
       '@typescript-eslint/visitor-keys': 5.8.1
     dev: true
 
+  /@typescript-eslint/type-utils/5.31.0_o42cowhndu3dmvrylnnywvjupe:
+    resolution: {integrity: sha512-7ZYqFbvEvYXFn9ax02GsPcEOmuWNg+14HIf4q+oUuLnMbpJ6eHAivCg7tZMVwzrIuzX3QCeAOqKoyMZCv5xe+w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/utils': 5.31.0_o42cowhndu3dmvrylnnywvjupe
+      debug: 4.3.4
+      eslint: 8.20.0
+      tsutils: 3.21.0_typescript@4.7.2
+      typescript: 4.7.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/types/5.31.0:
+    resolution: {integrity: sha512-/f/rMaEseux+I4wmR6mfpM2wvtNZb1p9hAV77hWfuKc3pmaANp5dLAZSiE3/8oXTYTt3uV9KW5yZKJsMievp6g==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
   /@typescript-eslint/types/5.8.1:
     resolution: {integrity: sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
+  /@typescript-eslint/typescript-estree/5.31.0_typescript@4.7.2:
+    resolution: {integrity: sha512-3S625TMcARX71wBc2qubHaoUwMEn+l9TCsaIzYI/ET31Xm2c9YQ+zhGgpydjorwQO9pLfR/6peTzS/0G3J/hDw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 5.31.0
+      '@typescript-eslint/visitor-keys': 5.31.0
+      debug: 4.3.4
+      globby: 11.1.0
+      is-glob: 4.0.3
+      semver: 7.3.7
+      tsutils: 3.21.0_typescript@4.7.2
+      typescript: 4.7.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/typescript-estree/5.8.1_typescript@4.7.2:
     resolution: {integrity: sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -342,6 +452,32 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/utils/5.31.0_o42cowhndu3dmvrylnnywvjupe:
+    resolution: {integrity: sha512-kcVPdQS6VIpVTQ7QnGNKMFtdJdvnStkqS5LeALr4rcwx11G6OWb2HB17NMPnlRHvaZP38hL9iK8DdE9Fne7NYg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      '@types/json-schema': 7.0.9
+      '@typescript-eslint/scope-manager': 5.31.0
+      '@typescript-eslint/types': 5.31.0
+      '@typescript-eslint/typescript-estree': 5.31.0_typescript@4.7.2
+      eslint: 8.20.0
+      eslint-scope: 5.1.1
+      eslint-utils: 3.0.0_eslint@8.20.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
+  /@typescript-eslint/visitor-keys/5.31.0:
+    resolution: {integrity: sha512-ZK0jVxSjS4gnPirpVjXHz7mgdOsZUHzNYSfTw2yPa3agfbt9YfqaBiBZFSSxeBWnpWkzCxTfUpnzA3Vily/CSg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.31.0
+      eslint-visitor-keys: 3.3.0
+    dev: true
+
   /@typescript-eslint/visitor-keys/5.8.1:
     resolution: {integrity: sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -696,6 +832,18 @@ packages:
       ms: 2.1.2
     dev: true
 
+  /debug/4.3.4:
+    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.1.2
+    dev: true
+
   /deep-is/0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
     dev: true
@@ -1078,6 +1226,15 @@ packages:
       eslint: 8.20.0
     dev: true
 
+  /eslint-config-prettier/8.5.0_eslint@8.20.0:
+    resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==}
+    hasBin: true
+    peerDependencies:
+      eslint: '>=7.0.0'
+    dependencies:
+      eslint: 8.20.0
+    dev: true
+
   /eslint-import-resolver-node/0.3.6:
     resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==}
     dependencies:
@@ -1132,7 +1289,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-module-utils/2.7.3_ulu2225r2ychl26a37c6o2rfje:
+  /eslint-module-utils/2.7.3_vozeh3qpn3prlhg65r4uuu3jv4:
     resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -1150,6 +1307,7 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
     dependencies:
+      '@typescript-eslint/parser': 5.31.0_o42cowhndu3dmvrylnnywvjupe
       debug: 3.2.7
       eslint-import-resolver-node: 0.3.6
       find-up: 2.1.0
@@ -1188,7 +1346,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-import/2.26.0_eslint@8.20.0:
+  /eslint-plugin-import/2.26.0_ficupzxy3q6nk56ibvavehtzue:
     resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -1198,13 +1356,14 @@ packages:
       '@typescript-eslint/parser':
         optional: true
     dependencies:
+      '@typescript-eslint/parser': 5.31.0_o42cowhndu3dmvrylnnywvjupe
       array-includes: 3.1.4
       array.prototype.flat: 1.2.5
       debug: 2.6.9
       doctrine: 2.1.0
       eslint: 8.20.0
       eslint-import-resolver-node: 0.3.6
-      eslint-module-utils: 2.7.3_ulu2225r2ychl26a37c6o2rfje
+      eslint-module-utils: 2.7.3_vozeh3qpn3prlhg65r4uuu3jv4
       has: 1.0.3
       is-core-module: 2.8.1
       is-glob: 4.0.3
@@ -1239,7 +1398,7 @@ packages:
       minimatch: 3.1.2
     dev: true
 
-  /eslint-plugin-prettier/4.0.0_2lfn3wslnw6p5yryh2j642jjde:
+  /eslint-plugin-prettier/4.0.0_nbmdt66gewj4zlnmfly4ek6wkq:
     resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==}
     engines: {node: '>=6.0.0'}
     peerDependencies:
@@ -1251,10 +1410,28 @@ packages:
         optional: true
     dependencies:
       eslint: 8.20.0
+      eslint-config-prettier: 8.5.0_eslint@8.20.0
       prettier: 2.5.1
       prettier-linter-helpers: 1.0.0
     dev: true
 
+  /eslint-plugin-prettier/4.2.1_g4fztgbwjyq2fvmcscny2sj6fy:
+    resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      eslint: '>=7.28.0'
+      eslint-config-prettier: '*'
+      prettier: '>=2.0.0'
+    peerDependenciesMeta:
+      eslint-config-prettier:
+        optional: true
+    dependencies:
+      eslint: 8.20.0
+      eslint-config-prettier: 8.5.0_eslint@8.20.0
+      prettier: 2.7.1
+      prettier-linter-helpers: 1.0.0
+    dev: true
+
   /eslint-plugin-react-hooks/4.3.0_eslint@8.20.0:
     resolution: {integrity: sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==}
     engines: {node: '>=10'}
@@ -2260,6 +2437,12 @@ packages:
     hasBin: true
     dev: true
 
+  /prettier/2.7.1:
+    resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+    dev: true
+
   /prop-types/15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
     dependencies:
@@ -2411,6 +2594,14 @@ packages:
       lru-cache: 6.0.0
     dev: true
 
+  /semver/7.3.7:
+    resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      lru-cache: 6.0.0
+    dev: true
+
   /shebang-command/2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}