Screenshot for core dump (#2066)
* start of screenshot, need uploader Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * some cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * most things working Signed-off-by: Jess Frazelle <github@jessfraz.com> * bump the world Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * mime type Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@ -38,6 +38,7 @@
|
||||
"decamelize": "^6.0.0",
|
||||
"formik": "^2.4.5",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.4.3",
|
||||
"http-server": "^14.1.1",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
|
12
src/App.tsx
@ -1,4 +1,4 @@
|
||||
import { useCallback, MouseEventHandler, useEffect } from 'react'
|
||||
import { useCallback, MouseEventHandler, useEffect, useRef } from 'react'
|
||||
import { DebugPanel } from './components/DebugPanel'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { PaneType, useStore } from './useStore'
|
||||
@ -41,6 +41,9 @@ export function App() {
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const { onProjectOpen } = useLspContext()
|
||||
// We need the ref for the outermost div so we can screenshot the app for
|
||||
// the coredump.
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const projectName = project?.name || null
|
||||
const projectPath = project?.path || null
|
||||
@ -55,14 +58,20 @@ export function App() {
|
||||
setOpenPanes,
|
||||
didDragInStream,
|
||||
streamDimensions,
|
||||
setHtmlRef,
|
||||
} = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
openPanes: s.openPanes,
|
||||
setOpenPanes: s.setOpenPanes,
|
||||
didDragInStream: s.didDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
setHtmlRef: s.setHtmlRef,
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
setHtmlRef(ref)
|
||||
}, [ref])
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const {
|
||||
modeling: { showDebugPanel },
|
||||
@ -140,6 +149,7 @@ export function App() {
|
||||
<div
|
||||
className="relative h-full flex flex-col"
|
||||
onMouseMove={handleMouseMove}
|
||||
ref={ref}
|
||||
>
|
||||
<AppHeader
|
||||
className={
|
||||
|
@ -78,7 +78,14 @@ export const ModelingMachineProvider = ({
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token, theme.current)
|
||||
const coreDumpManager = new CoreDumpManager(engineCommandManager)
|
||||
const { htmlRef } = useStore((s) => ({
|
||||
htmlRef: s.htmlRef,
|
||||
}))
|
||||
const coreDumpManager = new CoreDumpManager(
|
||||
engineCommandManager,
|
||||
htmlRef,
|
||||
token
|
||||
)
|
||||
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
||||
|
||||
const {
|
||||
|
@ -9,14 +9,39 @@ import {
|
||||
} from '@tauri-apps/plugin-os'
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import screenshot from 'lib/screenshot'
|
||||
import React from 'react'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
// This is a class for getting all the values from the JS world to pass to the Rust world
|
||||
// for a core dump.
|
||||
export class CoreDumpManager {
|
||||
engineCommandManager: EngineCommandManager
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
token: string | undefined
|
||||
baseUrl: string = VITE_KC_API_BASE_URL
|
||||
|
||||
constructor(engineCommandManager: EngineCommandManager) {
|
||||
constructor(
|
||||
engineCommandManager: EngineCommandManager,
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null,
|
||||
token: string | undefined
|
||||
) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
this.htmlRef = htmlRef
|
||||
this.token = token
|
||||
}
|
||||
|
||||
// Get the token.
|
||||
authToken(): string {
|
||||
if (!this.token) {
|
||||
throw new Error('Token not set')
|
||||
}
|
||||
return this.token
|
||||
}
|
||||
|
||||
// Get the base url.
|
||||
baseApiUrl(): string {
|
||||
return this.baseUrl
|
||||
}
|
||||
|
||||
// Get the version of the app from the package.json.
|
||||
@ -111,4 +136,15 @@ export class CoreDumpManager {
|
||||
return JSON.stringify(webrtcStats)
|
||||
})
|
||||
}
|
||||
|
||||
// Return a data URL (png format) of the screenshot of the current page.
|
||||
screenshot(): Promise<string> {
|
||||
return screenshot(this.htmlRef)
|
||||
.then((screenshot: string) => {
|
||||
return screenshot
|
||||
})
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting screenshot: ${error}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
21
src/lib/screenshot.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import html2canvas from 'html2canvas-pro'
|
||||
|
||||
// Return a data URL (png format) of the screenshot of the current page.
|
||||
export default async function screenshot(
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
): Promise<string> {
|
||||
if (htmlRef === null) {
|
||||
throw new Error('htmlRef is null')
|
||||
}
|
||||
if (htmlRef.current === null) {
|
||||
throw new Error('htmlRef is null')
|
||||
}
|
||||
return html2canvas(htmlRef.current)
|
||||
.then((canvas) => {
|
||||
return canvas.toDataURL()
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error
|
||||
})
|
||||
}
|
@ -81,6 +81,8 @@ export interface StoreState {
|
||||
streamWidth: number
|
||||
streamHeight: number
|
||||
}) => void
|
||||
setHtmlRef: (ref: React.RefObject<HTMLDivElement>) => void
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
|
||||
showHomeMenu: boolean
|
||||
setHomeShowMenu: (showMenu: boolean) => void
|
||||
@ -132,6 +134,10 @@ export const useStore = create<StoreState>()(
|
||||
setButtonDownInStream: (buttonDownInStream) => {
|
||||
set({ buttonDownInStream })
|
||||
},
|
||||
setHtmlRef: (htmlRef) => {
|
||||
set({ htmlRef })
|
||||
},
|
||||
htmlRef: null,
|
||||
didDragInStream: false,
|
||||
setDidDragInStream: (didDragInStream) => {
|
||||
set({ didDragInStream })
|
||||
|
12
src/wasm-lib/Cargo.lock
generated
@ -1920,9 +1920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.2.63"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93a332250e08fd715ad3d5826e04d36da1c5bb42d0c1b1ff1f0598278b9ebf3c"
|
||||
checksum = "9e2897244f4600f863115561a0fd1cd7c87fca20253ffecfebc53ef642d0aceb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1958,9 +1958,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-execution-plan"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e913f8e5f3ef7928cddca2e7b53c6582d7be6a8f900d18ce6c31c04083056270"
|
||||
checksum = "acf8ffb148bd09de8889a8a2b3075a23ee86446c3a6e1c6dcf66b40fdc778158"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"gltf-json",
|
||||
@ -2043,9 +2043,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-session"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ee3a24232a086ec12ae4cfee443485c22e6c6959936d861006fa13bebef0904"
|
||||
checksum = "bae9bc47fcc3cc30727b35e738c35666b97e1e5f48f3f4c60ddaeccb69b66559"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"kittycad",
|
||||
|
@ -59,12 +59,12 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
kittycad = { version = "0.2.63", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-execution-plan = "0.1.3"
|
||||
kittycad = { version = "0.2.66", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-execution-plan = "0.1.4"
|
||||
kittycad-execution-plan-macros = "0.1.9"
|
||||
kittycad-execution-plan-traits = "0.1.14"
|
||||
kittycad-modeling-cmds = "0.2.17"
|
||||
kittycad-modeling-session = "0.1.2"
|
||||
kittycad-modeling-session = "0.1.3"
|
||||
|
||||
[[test]]
|
||||
name = "executor"
|
||||
|
@ -777,7 +777,7 @@ fn generate_code_block_test(
|
||||
}
|
||||
let ws = client
|
||||
.modeling()
|
||||
.commands_ws(None, None, None, None, None, Some(false))
|
||||
.commands_ws(None, None, None, None, None,None, Some(false))
|
||||
.await.unwrap();
|
||||
|
||||
let tokens = crate::token::lexer(#code_block);
|
||||
|
@ -14,6 +14,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
|
||||
anyhow = { version = "1.0.82", features = ["backtrace"] }
|
||||
async-recursion = "1.1.0"
|
||||
async-trait = "0.1.79"
|
||||
base64 = "0.22.0"
|
||||
chrono = "0.4.37"
|
||||
clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
||||
dashmap = "5.5.3"
|
||||
|
@ -19,8 +19,16 @@ impl Default for CoreDumper {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl CoreDump for CoreDumper {
|
||||
fn token(&self) -> Result<String> {
|
||||
Ok(std::env::var("KITTYCAD_API_TOKEN").unwrap_or_default())
|
||||
}
|
||||
|
||||
fn base_api_url(&self) -> Result<String> {
|
||||
Ok("https://api.zoo.dev".to_string())
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<String> {
|
||||
Ok(env!("CARGO_PKG_VERSION").to_string())
|
||||
}
|
||||
@ -42,4 +50,9 @@ impl CoreDump for CoreDumper {
|
||||
// TODO: we could actually implement this.
|
||||
Ok(crate::coredump::WebrtcStats::default())
|
||||
}
|
||||
|
||||
async fn screenshot(&self) -> Result<String> {
|
||||
// Take a screenshot of the engine.
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,17 @@ pub mod local;
|
||||
pub mod wasm;
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::Engine;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait CoreDump: Clone {
|
||||
/// Return the authentication token.
|
||||
fn token(&self) -> Result<String>;
|
||||
|
||||
fn base_api_url(&self) -> Result<String>;
|
||||
|
||||
fn version(&self) -> Result<String>;
|
||||
|
||||
async fn os(&self) -> Result<OsInfo>;
|
||||
@ -19,10 +25,43 @@ pub trait CoreDump: Clone {
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<WebrtcStats>;
|
||||
|
||||
/// Return a screenshot of the app.
|
||||
async fn screenshot(&self) -> Result<String>;
|
||||
|
||||
/// Get a screenshot of the app and upload it to public cloud storage.
|
||||
async fn upload_screenshot(&self) -> Result<String> {
|
||||
let screenshot = self.screenshot().await?;
|
||||
let cleaned = screenshot.trim_start_matches("data:image/png;base64,");
|
||||
// Create the zoo client.
|
||||
let mut zoo = kittycad::Client::new(self.token()?);
|
||||
zoo.set_base_url(&self.base_api_url()?);
|
||||
|
||||
// Base64 decode the screenshot.
|
||||
let data = base64::engine::general_purpose::STANDARD.decode(cleaned)?;
|
||||
// Upload the screenshot.
|
||||
let links = zoo
|
||||
.meta()
|
||||
.create_debug_uploads(vec![kittycad::types::multipart::Attachment {
|
||||
name: "".to_string(),
|
||||
filename: Some("modeling-app/core-dump-screenshot.png".to_string()),
|
||||
content_type: Some("image/png".to_string()),
|
||||
data,
|
||||
}])
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
||||
|
||||
if links.is_empty() {
|
||||
anyhow::bail!("Failed to upload screenshot");
|
||||
}
|
||||
|
||||
Ok(links[0].clone())
|
||||
}
|
||||
|
||||
/// Dump the app info.
|
||||
async fn dump(&self) -> Result<AppInfo> {
|
||||
let webrtc_stats = self.get_webrtc_stats().await?;
|
||||
let os = self.os().await?;
|
||||
let screenshot_url = self.upload_screenshot().await?;
|
||||
|
||||
let mut app_info = AppInfo {
|
||||
version: self.version()?,
|
||||
@ -33,7 +72,7 @@ pub trait CoreDump: Clone {
|
||||
webrtc_stats,
|
||||
github_issue_url: None,
|
||||
};
|
||||
app_info.set_github_issue_url()?;
|
||||
app_info.set_github_issue_url(&screenshot_url)?;
|
||||
|
||||
Ok(app_info)
|
||||
}
|
||||
@ -68,12 +107,14 @@ pub struct AppInfo {
|
||||
|
||||
impl AppInfo {
|
||||
/// Set the github issue url.
|
||||
pub fn set_github_issue_url(&mut self) -> Result<()> {
|
||||
pub fn set_github_issue_url(&mut self, screenshot_url: &str) -> Result<()> {
|
||||
let tauri_or_browser_label = if self.tauri { "tauri" } else { "browser" };
|
||||
let labels = ["coredump", "bug", tauri_or_browser_label];
|
||||
let body = format!(
|
||||
r#"[Insert a description of the issue here]
|
||||
|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>Core Dump</b></summary>
|
||||
|
||||
@ -82,6 +123,7 @@ impl AppInfo {
|
||||
```
|
||||
</details>
|
||||
"#,
|
||||
screenshot_url,
|
||||
serde_json::to_string_pretty(&self)?
|
||||
);
|
||||
let urlencoded: String = form_urlencoded::byte_serialize(body.as_bytes()).collect();
|
||||
|
@ -10,6 +10,12 @@ extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub type CoreDumpManager;
|
||||
|
||||
#[wasm_bindgen(method, js_name = authToken, catch)]
|
||||
fn auth_token(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = baseApiUrl, catch)]
|
||||
fn baseApiUrl(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = version, catch)]
|
||||
fn version(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
@ -21,6 +27,9 @@ extern "C" {
|
||||
|
||||
#[wasm_bindgen(method, js_name = getWebrtcStats, catch)]
|
||||
fn get_webrtc_stats(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = screenshot, catch)]
|
||||
fn screenshot(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -37,8 +46,20 @@ impl CoreDumper {
|
||||
unsafe impl Send for CoreDumper {}
|
||||
unsafe impl Sync for CoreDumper {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl CoreDump for CoreDumper {
|
||||
fn token(&self) -> Result<String> {
|
||||
self.manager
|
||||
.auth_token()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from token: {:?}", e))
|
||||
}
|
||||
|
||||
fn base_api_url(&self) -> Result<String> {
|
||||
self.manager
|
||||
.baseApiUrl()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from base api url: {:?}", e))
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<String> {
|
||||
self.manager
|
||||
.version()
|
||||
@ -92,4 +113,22 @@ impl CoreDump for CoreDumper {
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
async fn screenshot(&self) -> Result<String> {
|
||||
let promise = self
|
||||
.manager
|
||||
.screenshot()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get promise from get screenshot: {:?}", e))?;
|
||||
|
||||
let value = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from screenshot: {:?}", e))?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from screenshot: `{:?}`", value))?;
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
|
||||
requests,
|
||||
batch_id: uuid::Uuid::new_v4(),
|
||||
responses: Some(false),
|
||||
};
|
||||
|
||||
let final_req = if self.batch().lock().unwrap().len() == 1 {
|
||||
@ -117,7 +118,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// We pop off the responses to cleanup our mappings.
|
||||
let id_final = match final_req {
|
||||
WebSocketRequest::ModelingCmdBatchReq { requests: _, batch_id } => batch_id,
|
||||
WebSocketRequest::ModelingCmdBatchReq {
|
||||
requests: _,
|
||||
batch_id,
|
||||
responses: _,
|
||||
} => batch_id,
|
||||
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => cmd_id,
|
||||
_ => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
|
@ -29,7 +29,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
|
||||
|
||||
let ws = client
|
||||
.modeling()
|
||||
.commands_ws(None, None, None, None, None, Some(false))
|
||||
.commands_ws(None, None, None, None, None, None, Some(false))
|
||||
.await?;
|
||||
|
||||
// Create a temporary file to write the output to.
|
||||
|
@ -33,7 +33,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
|
||||
|
||||
let ws = client
|
||||
.modeling()
|
||||
.commands_ws(None, None, None, None, None, Some(false))
|
||||
.commands_ws(None, None, None, None, None, None, Some(false))
|
||||
.await?;
|
||||
|
||||
let tokens = kcl_lib::token::lexer(code);
|
||||
|
34
yarn.lock
@ -3516,6 +3516,11 @@ base16@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
|
||||
|
||||
base64-arraybuffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
|
||||
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
|
||||
|
||||
base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
@ -4033,6 +4038,13 @@ crypto-js@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||
|
||||
css-line-break@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
||||
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
css-shorthand-properties@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz#1c808e63553c283f289f2dd56fcee8f3337bd935"
|
||||
@ -5620,6 +5632,14 @@ html-encoding-sniffer@^3.0.0:
|
||||
dependencies:
|
||||
whatwg-encoding "^2.0.0"
|
||||
|
||||
html2canvas-pro@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.4.3.tgz#100124e2d17d4de483700ce03176d7447e90d49f"
|
||||
integrity sha512-RB36SrUGxT9PTjImC7BsGxTinaI3y8cEne76ACdw+E7nRmeJ0jgDntxUP15B9Q9AM2mvEPN6SZo6zmkzwk8HKg==
|
||||
dependencies:
|
||||
css-line-break "^2.1.0"
|
||||
text-segmentation "^1.0.3"
|
||||
|
||||
http-cache-semantics@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
||||
@ -8416,6 +8436,13 @@ tar@^6.1.11:
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
text-segmentation@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
|
||||
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
@ -8803,6 +8830,13 @@ util@^0.12.5:
|
||||
is-typed-array "^1.1.3"
|
||||
which-typed-array "^1.1.2"
|
||||
|
||||
utrie@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
|
||||
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
|
||||
dependencies:
|
||||
base64-arraybuffer "^1.0.2"
|
||||
|
||||
uuid@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
||||
|