From 791106c0ec9c987c7a1e22474f27d23f531a141c Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 13:55:36 +1100 Subject: [PATCH 01/15] Add vtf package --- .gitignore | 1 + Cargo.lock | 206 +++++++++++++++++++ Cargo.toml | 6 + packages/vtf/Cargo.toml | 13 ++ packages/vtf/package.json | 12 ++ packages/vtf/rustfmt.toml | 1 + packages/vtf/src/lib.rs | 16 ++ packages/vtf/src/vtf.rs | 418 ++++++++++++++++++++++++++++++++++++++ turbo.json | 4 +- 9 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 packages/vtf/Cargo.toml create mode 100644 packages/vtf/package.json create mode 100644 packages/vtf/rustfmt.toml create mode 100644 packages/vtf/src/lib.rs create mode 100644 packages/vtf/src/vtf.rs diff --git a/.gitignore b/.gitignore index 1ab87c3..f468bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ dist node_modules out +target tsconfig.tsbuildinfo diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..de1062c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,206 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "texpresso" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8277e703c934b9693d0773d5749faacc6366b3d81d012da556a4cfd4ab87f336" +dependencies = [ + "libm", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + +[[package]] +name = "vtf" +version = "0.1.0" +dependencies = [ + "bincode", + "texpresso", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cfbe8b1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["packages/vtf"] +resolver = "2" + +[profile.release] +opt-level = 3 diff --git a/packages/vtf/Cargo.toml b/packages/vtf/Cargo.toml new file mode 100644 index 0000000..216db99 --- /dev/null +++ b/packages/vtf/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "vtf" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +bincode = { version = "2.0.0-rc.3", features = ["derive", "serde"] } +texpresso = "2.0.1" +wasm-bindgen = "0.2.95" +web-sys = { version = "0.3.74", features = ["CanvasRenderingContext2d", "ImageData"] } diff --git a/packages/vtf/package.json b/packages/vtf/package.json new file mode 100644 index 0000000..7da98f3 --- /dev/null +++ b/packages/vtf/package.json @@ -0,0 +1,12 @@ +{ + "name": "vtf", + "main": "./pkg/vtf.js", + "types": "./pkg/vtf.d.ts", + "files": [ + "pkg" + ], + "scripts": { + "dev": "cargo watch -- wasm-pack build --dev --target web", + "build": "wasm-pack build --release --target web" + } +} diff --git a/packages/vtf/rustfmt.toml b/packages/vtf/rustfmt.toml new file mode 100644 index 0000000..cd016f6 --- /dev/null +++ b/packages/vtf/rustfmt.toml @@ -0,0 +1 @@ +max_width = 180 diff --git a/packages/vtf/src/lib.rs b/packages/vtf/src/lib.rs new file mode 100644 index 0000000..c62775b --- /dev/null +++ b/packages/vtf/src/lib.rs @@ -0,0 +1,16 @@ +use vtf::{VTFData, VTFError, VTF}; +use wasm_bindgen::{prelude::wasm_bindgen, Clamped}; +use web_sys::{CanvasRenderingContext2d, ImageData}; + +pub mod vtf; + +#[wasm_bindgen] +impl VTF { + #[wasm_bindgen(js_name = "putImageData")] + pub fn put_image_data(self, context: &CanvasRenderingContext2d, mipmap_index: usize, frame_index: usize) -> Result<(), VTFError> { + let VTFData { width, height, rgba } = self.extract(mipmap_index, frame_index)?; + let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&rgba), width as u32, height as u32).unwrap(); + context.put_image_data(&data, 0.0, 0.0).unwrap(); + Ok(()) + } +} diff --git a/packages/vtf/src/vtf.rs b/packages/vtf/src/vtf.rs new file mode 100644 index 0000000..e15b3b5 --- /dev/null +++ b/packages/vtf/src/vtf.rs @@ -0,0 +1,418 @@ +use std::{ + error::Error, + fmt::Display, + io::{BufReader, Cursor, Seek, SeekFrom}, +}; + +use bincode::{ + error::{AllowedEnumVariants, DecodeError}, + impl_borrow_decode, Decode, +}; +use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; + +#[wasm_bindgen] +#[derive(Debug)] +pub struct VTF { + #[wasm_bindgen(skip)] + pub buf: Vec, + pub header: VTFHeader, + + #[wasm_bindgen(skip)] + pub resources: Option>, + + #[wasm_bindgen(skip)] + pub mipmaps: Result, VTFError>, +} + +#[wasm_bindgen] +#[derive(Debug, Clone, Copy, Decode)] +pub struct VTFHeader { + pub signature: VTFSignature, + pub version_major: u32, + pub version_minor: u32, + pub header_size: u32, + pub width: u16, + pub height: u16, + pub flags: u32, + pub frames: u16, + pub first_frame: u16, + _padding0: [u8; 4], + _reflectivity: [f32; 3], + _padding1: [u8; 4], + pub bumpmap_scale: f32, + pub high_res_image_format: VTFImageFormat, + pub mipmap_count: u8, + pub low_res_image_format: VTFImageFormat, + pub low_res_image_width: u8, + pub low_res_image_height: u8, +} + +#[wasm_bindgen] +#[derive(Debug, Clone, Copy)] +pub struct VTFSignature(); + +impl Display for VTFSignature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "VTF\0") + } +} + +impl Decode for VTFSignature { + fn decode(decoder: &mut D) -> Result { + match <[u8; 4] as bincode::Decode>::decode(decoder)? { + [b'V', b'T', b'F', b'\0'] => Ok(VTFSignature()), + _ => Err(DecodeError::Other("VTF\0")), + } + } +} + +impl_borrow_decode!(VTFSignature); + +#[wasm_bindgen] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum VTFImageFormat { + None = -1, + RGBA8888, + ABGR8888, + RGB888, + BGR888, + RGB565, + I8, + IA88, + P8, + A8, + RGB888BlueScreen, + BGR888BlueScreen, + ARGB8888, + BGRA8888, + DXT1, + DXT3, + DXT5, + BGRX8888, + BGR565, + BGRX5551, + BGRA4444, + DXT1OneBitAlpha, + BGRA5551, + UV88, + UVWQ8888, + RGBA16161616F, + RGBA16161616, + UVLX8888, +} + +impl Decode for VTFImageFormat { + fn decode(decoder: &mut D) -> Result { + let variant_value = ::decode(decoder)?; + match variant_value { + -1 => Ok(VTFImageFormat::None), + 0 => Ok(VTFImageFormat::RGBA8888), + 1 => Ok(VTFImageFormat::ABGR8888), + 2 => Ok(VTFImageFormat::RGB888), + 3 => Ok(VTFImageFormat::BGR888), + 4 => Ok(VTFImageFormat::RGB565), + 5 => Ok(VTFImageFormat::I8), + 6 => Ok(VTFImageFormat::IA88), + 7 => Ok(VTFImageFormat::P8), + 8 => Ok(VTFImageFormat::A8), + 9 => Ok(VTFImageFormat::RGB888BlueScreen), + 10 => Ok(VTFImageFormat::BGR888BlueScreen), + 11 => Ok(VTFImageFormat::ARGB8888), + 12 => Ok(VTFImageFormat::BGRA8888), + 13 => Ok(VTFImageFormat::DXT1), + 14 => Ok(VTFImageFormat::DXT3), + 15 => Ok(VTFImageFormat::DXT5), + 16 => Ok(VTFImageFormat::BGRX8888), + 17 => Ok(VTFImageFormat::BGR565), + 18 => Ok(VTFImageFormat::BGRX5551), + 19 => Ok(VTFImageFormat::BGRA4444), + 20 => Ok(VTFImageFormat::DXT1OneBitAlpha), + 21 => Ok(VTFImageFormat::BGRA5551), + 22 => Ok(VTFImageFormat::UV88), + 23 => Ok(VTFImageFormat::UVWQ8888), + 24 => Ok(VTFImageFormat::RGBA16161616F), + 25 => Ok(VTFImageFormat::RGBA16161616), + 26 => Ok(VTFImageFormat::UVLX8888), + variant => Err(DecodeError::UnexpectedVariant { + type_name: "VTFImageFormat", + allowed: &AllowedEnumVariants::Range { min: 0, max: 26 }, + found: variant as u32, + }), + } + } +} + +impl VTFImageFormat { + pub fn bytes(&self, width: usize, height: usize) -> Result { + match self { + VTFImageFormat::None => Err(VTFImageFormat::None), + VTFImageFormat::RGBA8888 => Ok(width * height * 4), + VTFImageFormat::ABGR8888 => Ok(width * height * 4), + VTFImageFormat::RGB888 => Ok(width * height * 3), + VTFImageFormat::BGR888 => Ok(width * height * 3), + VTFImageFormat::RGB565 => Err(VTFImageFormat::RGB565), + VTFImageFormat::I8 => Ok(width * height * 1), + VTFImageFormat::IA88 => Err(VTFImageFormat::IA88), + VTFImageFormat::P8 => Err(VTFImageFormat::P8), + VTFImageFormat::A8 => Ok(width * height * 1), + VTFImageFormat::RGB888BlueScreen => Err(VTFImageFormat::RGB888BlueScreen), + VTFImageFormat::BGR888BlueScreen => Err(VTFImageFormat::BGR888BlueScreen), + VTFImageFormat::ARGB8888 => Ok(width * height * 4), + VTFImageFormat::BGRA8888 => Ok(width * height * 4), + VTFImageFormat::DXT1 => Ok(texpresso::Format::Bc1.compressed_size(width, height)), + VTFImageFormat::DXT3 => Ok(texpresso::Format::Bc2.compressed_size(width, height)), + VTFImageFormat::DXT5 => Ok(texpresso::Format::Bc3.compressed_size(width, height)), + VTFImageFormat::BGRX8888 => Err(VTFImageFormat::BGRX8888), + VTFImageFormat::BGR565 => Err(VTFImageFormat::BGR565), + VTFImageFormat::BGRX5551 => Err(VTFImageFormat::BGRX5551), + VTFImageFormat::BGRA4444 => Err(VTFImageFormat::BGRA4444), + VTFImageFormat::DXT1OneBitAlpha => Err(VTFImageFormat::DXT1OneBitAlpha), + VTFImageFormat::BGRA5551 => Err(VTFImageFormat::BGRA5551), + VTFImageFormat::UV88 => Err(VTFImageFormat::UV88), + VTFImageFormat::UVWQ8888 => Err(VTFImageFormat::UVWQ8888), + VTFImageFormat::RGBA16161616F => Err(VTFImageFormat::RGBA16161616F), + VTFImageFormat::RGBA16161616 => Err(VTFImageFormat::RGBA16161616), + VTFImageFormat::UVLX8888 => Err(VTFImageFormat::UVLX8888), + } + } +} + +impl_borrow_decode!(VTFImageFormat); + +#[derive(Debug, Decode)] +pub struct VTFResourceEntryInfo { + _tag: [u8; 3], + pub flags: u8, + pub offset: u32, +} + +#[derive(Debug)] +pub struct VTFMipMap { + pub width: u16, + pub height: u16, + pub frames: Vec, +} + +#[derive(Debug)] +pub struct VTFFrame { + pub offset: usize, + pub bytes: usize, +} + +#[derive(Debug)] +pub struct VTFData { + pub width: u16, + pub height: u16, + pub rgba: Vec, +} + +#[derive(Debug)] +pub enum VTFError { + DecodeError(DecodeError), + FormatError(VTFImageFormat), +} + +impl Error for VTFError {} + +impl Display for VTFError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:#?}", self) + } +} + +impl From for VTFError { + fn from(value: DecodeError) -> Self { + VTFError::DecodeError(value) + } +} + +impl From for VTFError { + fn from(value: VTFImageFormat) -> Self { + VTFError::FormatError(value) + } +} + +impl Into for VTFError { + fn into(self) -> JsValue { + JsValue::from(JsError::new(&format!("{:?}", self))) + } +} + +#[wasm_bindgen] +impl VTF { + #[wasm_bindgen(constructor)] + pub fn new(buf: Vec) -> Result { + let mut reader = BufReader::new(Cursor::new(&buf)); + let config = bincode::config::standard().with_fixed_int_encoding(); + + let header: VTFHeader = bincode::decode_from_reader(&mut reader, config)?; + + if header.version_major >= 7 && header.version_minor >= 2 { + let _depth: u16 = bincode::decode_from_reader(&mut reader, config)?; + } + + let num_resources_option = if header.version_major >= 7 && header.version_minor >= 3 { + let _padding2: [u8; 3] = bincode::decode_from_reader(&mut reader, config)?; + let num_resources: u32 = bincode::decode_from_reader(&mut reader, config)?; + Some(num_resources) + } else { + None + }; + + let _padding3: [u8; 8] = bincode::decode_from_reader(&mut reader, config)?; + + let resources = match num_resources_option { + Some(num_resources) => Some( + (0..num_resources) + .map(|_| bincode::decode_from_reader::(&mut reader, config)) + .collect::, DecodeError>>()?, + ), + None => None, + }; + + reader.seek(SeekFrom::Start(header.header_size as u64)).or_else(|_| { + Err(DecodeError::UnexpectedEnd { + additional: header.header_size as usize, + }) + })?; + + let thumbnail_bytes = texpresso::Format::Bc1.compressed_size(header.low_res_image_width as usize, header.low_res_image_height as usize); + reader + .seek_relative(thumbnail_bytes as i64) + .or_else(|_| Err(DecodeError::UnexpectedEnd { additional: thumbnail_bytes }))?; + + let mipmaps = (0..header.mipmap_count) + .rev() + .map(|i| -> Result { + let width = (header.width >> i) as usize; + let height = (header.height >> i) as usize; + + let bytes = header.high_res_image_format.bytes(width, height)?; + + let frames = (0..header.frames) + .map(|_| { + let offset = reader.stream_position().unwrap() as usize; + reader.seek_relative(bytes as i64).or_else(|_| Err(DecodeError::UnexpectedEnd { additional: bytes }))?; + Ok(VTFFrame { offset, bytes }) + }) + .collect::, VTFError>>()?; + + Ok(VTFMipMap { + width: width as u16, + height: height as u16, + frames, + }) + }) + .collect::, VTFError>>(); + + Ok(VTF { buf, header, resources, mipmaps }) + } +} + +impl VTF { + pub fn extract(self, mipmap_index: usize, frame_index: usize) -> Result { + let mipmaps = self.mipmaps?; + let mipmap = mipmaps.get(mipmap_index).ok_or_else(|| VTFError::FormatError(self.header.high_res_image_format))?; + let frame = mipmap.frames.get(frame_index).ok_or_else(|| VTFError::FormatError(self.header.high_res_image_format))?; + + let buf = self + .buf + .get(frame.offset..(frame.offset + frame.bytes)) + .ok_or_else(|| DecodeError::UnexpectedEnd { additional: frame.bytes })?; + + let mut rgba = vec![255; mipmap.width as usize * mipmap.height as usize * 4]; + + match self.header.high_res_image_format { + VTFImageFormat::RGBA8888 => { + rgba.copy_from_slice(buf); + } + VTFImageFormat::ABGR8888 => { + let mut i = 0; + for chunk in buf.chunks_exact(4) { + rgba[i] = chunk[3]; + rgba[i + 1] = chunk[2]; + rgba[i + 2] = chunk[1]; + rgba[i + 3] = chunk[0]; + i += 4; + } + } + VTFImageFormat::RGB888 => { + let mut i = 0; + for chunk in buf.chunks_exact(3) { + rgba[i] = chunk[0]; + rgba[i + 1] = chunk[1]; + rgba[i + 2] = chunk[2]; + rgba[i + 3] = 255; + i += 4; + } + } + VTFImageFormat::BGR888 => { + let mut i = 0; + for chunk in buf.chunks_exact(3) { + rgba[i] = chunk[3]; + rgba[i + 1] = chunk[2]; + rgba[i + 2] = chunk[1]; + rgba[i + 3] = 255; + i += 4; + } + } + VTFImageFormat::I8 => { + let mut i = 0; + for byte in buf { + rgba[i] = *byte; + rgba[i + 1] = *byte; + rgba[i + 2] = *byte; + rgba[i + 3] = 255; + i += 4; + } + } + VTFImageFormat::A8 => { + let mut i = 0; + for byte in buf { + rgba[i] = 0; + rgba[i + 1] = 0; + rgba[i + 2] = 0; + rgba[i + 3] = *byte; + i += 4; + } + } + VTFImageFormat::ARGB8888 => { + let mut i = 0; + for chunk in buf.chunks_exact(4) { + rgba[i] = chunk[1]; + rgba[i + 1] = chunk[2]; + rgba[i + 2] = chunk[3]; + rgba[i + 3] = chunk[0]; + i += 4; + } + } + VTFImageFormat::BGRA8888 => { + let mut i = 0; + for chunk in buf.chunks_exact(4) { + rgba[i] = chunk[2]; + rgba[i + 1] = chunk[1]; + rgba[i + 2] = chunk[0]; + rgba[i + 3] = chunk[3]; + i += 4; + } + } + VTFImageFormat::DXT1 => { + texpresso::Format::Bc1.decompress(&buf, mipmap.width as usize, mipmap.height as usize, &mut rgba); + } + VTFImageFormat::DXT3 => { + texpresso::Format::Bc2.decompress(&buf, mipmap.width as usize, mipmap.height as usize, &mut rgba); + } + VTFImageFormat::DXT5 => { + texpresso::Format::Bc3.decompress(&buf, mipmap.width as usize, mipmap.height as usize, &mut rgba); + } + _variant => unreachable!(), + }; + + Ok(VTFData { + width: mipmap.width, + height: mipmap.height, + rgba, + }) + } +} diff --git a/turbo.json b/turbo.json index 07888bb..9570920 100644 --- a/turbo.json +++ b/turbo.json @@ -11,7 +11,9 @@ ], "outputs": [ ".next/**", - "dist/**" + "dist/**", + "pkg/**", + "target/**" ], "cache": false } From 32f22aebd4ac7fa3d7b9db45d5408c1f5a4bcf29 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 14:13:38 +1100 Subject: [PATCH 02/15] Add vtf-editor --- apps/vtf-editor/index.html | 12 ++ apps/vtf-editor/package.json | 18 ++ apps/vtf-editor/src/App.svelte | 27 +++ apps/vtf-editor/src/VTFViewer.svelte | 292 +++++++++++++++++++++++++++ apps/vtf-editor/src/app.css | 12 ++ apps/vtf-editor/src/global.d.ts | 8 + apps/vtf-editor/src/index.ts | 18 ++ apps/vtf-editor/src/vite-env.d.ts | 2 + apps/vtf-editor/svelte.config.js | 7 + apps/vtf-editor/tsconfig.json | 6 + apps/vtf-editor/vite.config.ts | 10 + 11 files changed, 412 insertions(+) create mode 100644 apps/vtf-editor/index.html create mode 100644 apps/vtf-editor/package.json create mode 100644 apps/vtf-editor/src/App.svelte create mode 100644 apps/vtf-editor/src/VTFViewer.svelte create mode 100644 apps/vtf-editor/src/app.css create mode 100644 apps/vtf-editor/src/global.d.ts create mode 100644 apps/vtf-editor/src/index.ts create mode 100644 apps/vtf-editor/src/vite-env.d.ts create mode 100644 apps/vtf-editor/svelte.config.js create mode 100644 apps/vtf-editor/tsconfig.json create mode 100644 apps/vtf-editor/vite.config.ts diff --git a/apps/vtf-editor/index.html b/apps/vtf-editor/index.html new file mode 100644 index 0000000..8209020 --- /dev/null +++ b/apps/vtf-editor/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ + + diff --git a/apps/vtf-editor/package.json b/apps/vtf-editor/package.json new file mode 100644 index 0000000..fbb9536 --- /dev/null +++ b/apps/vtf-editor/package.json @@ -0,0 +1,18 @@ +{ + "name": "vtf-editor", + "private": true, + "scripts": { + "dev": "vite build --watch", + "build": "vite build" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.1", + "@tsconfig/svelte": "^5.0.4", + "@types/vscode-webview": "^1.57.5", + "client": "workspace:^", + "svelte": "^5.9.0", + "vite": "^6.0.1", + "vtf": "workspace:^" + }, + "type": "module" +} diff --git a/apps/vtf-editor/src/App.svelte b/apps/vtf-editor/src/App.svelte new file mode 100644 index 0000000..75fd36d --- /dev/null +++ b/apps/vtf-editor/src/App.svelte @@ -0,0 +1,27 @@ + + + postMessage({ type: "showErrorMessage", message: (error as Error).message, items: [] })} +> + + diff --git a/apps/vtf-editor/src/VTFViewer.svelte b/apps/vtf-editor/src/VTFViewer.svelte new file mode 100644 index 0000000..6a5beb0 --- /dev/null +++ b/apps/vtf-editor/src/VTFViewer.svelte @@ -0,0 +1,292 @@ + + + + +
+
+
+ File Info + + {#snippet row(name: string, value: any)} + + + + + {/snippet} + + {@render row("Version", `${vtf.header.version_major}.${vtf.header.version_minor}`)} + {@render row("Format", VTFImageFormat[vtf.header.high_res_image_format])} + {@render row("Width", vtf.header.width)} + {@render row("Height", vtf.header.height)} + {@render row("Flags", flags)} + +
{name}:{value}
+
+
+
+ +
+
+
+ Flags + + {#snippet checkbox(label: string | null)} + {@const id = label?.replaceAll(/\s/g, "-").toLowerCase()} + {@const value = 1 << i++} + + {/snippet} + +
+ {@render checkbox("Point Sample")} + {@render checkbox("Trilinear")} + {@render checkbox("Clamp S")} + {@render checkbox("Clamp T")} + {@render checkbox("Anisotropic")} + {@render checkbox("Hint DXT5")} + {@render checkbox("SRGB")} + {@render checkbox("Normal Map")} + {@render checkbox("No Mipmap")} + {@render checkbox("No Level Of Detail")} + {@render checkbox("No Minimum Mipmap")} + {@render checkbox("Procedural")} + {@render checkbox("One Bit Alpha")} + {@render checkbox("Eight Bit Alpha")} + {@render checkbox("Environment Map")} + {@render checkbox("Render Target")} + {@render checkbox("Depth Render Target")} + {@render checkbox("No Debug Override")} + {@render checkbox("Single Copy")} + {@render checkbox(null)} + {@render checkbox(null)} + {@render checkbox(null)} + {@render checkbox(null)} + {@render checkbox("No Depth Buffer")} + {@render checkbox(null)} + {@render checkbox("Clamp U")} + {@render checkbox("Vertex Texture")} + {@render checkbox("SSBump")} + {@render checkbox("Clamp All")} +
+
+
+
+ + diff --git a/apps/vtf-editor/src/app.css b/apps/vtf-editor/src/app.css new file mode 100644 index 0000000..848db3a --- /dev/null +++ b/apps/vtf-editor/src/app.css @@ -0,0 +1,12 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + padding: 0; + user-select: none; + overflow: hidden; +} diff --git a/apps/vtf-editor/src/global.d.ts b/apps/vtf-editor/src/global.d.ts new file mode 100644 index 0000000..cb5bfe6 --- /dev/null +++ b/apps/vtf-editor/src/global.d.ts @@ -0,0 +1,8 @@ +declare global { + interface State { + flags: number + scale: number + } +} + +export { } diff --git a/apps/vtf-editor/src/index.ts b/apps/vtf-editor/src/index.ts new file mode 100644 index 0000000..10ccd08 --- /dev/null +++ b/apps/vtf-editor/src/index.ts @@ -0,0 +1,18 @@ +import { firstValueFrom, fromEvent } from "rxjs" +import { mount } from "svelte" +import init from "vtf" +import App from "./App.svelte" +import "./app.css" + +const app = mount(App, { + target: document.getElementById("app")!, + props: { + vscode: acquireVsCodeApi(), + buf: await Promise.all([ + init(), + (await firstValueFrom(fromEvent>(window, "message"))).data + ]).then(([_, buf]) => buf) + } +}) + +export default app diff --git a/apps/vtf-editor/src/vite-env.d.ts b/apps/vtf-editor/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/apps/vtf-editor/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/apps/vtf-editor/svelte.config.js b/apps/vtf-editor/svelte.config.js new file mode 100644 index 0000000..039026c --- /dev/null +++ b/apps/vtf-editor/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte" + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/apps/vtf-editor/tsconfig.json b/apps/vtf-editor/tsconfig.json new file mode 100644 index 0000000..a648391 --- /dev/null +++ b/apps/vtf-editor/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext" + } +} diff --git a/apps/vtf-editor/vite.config.ts b/apps/vtf-editor/vite.config.ts new file mode 100644 index 0000000..96a312b --- /dev/null +++ b/apps/vtf-editor/vite.config.ts @@ -0,0 +1,10 @@ +import { svelte } from "@sveltejs/vite-plugin-svelte" +import { defineConfig } from "vite" + +export default defineConfig({ + plugins: [svelte()], + base: "", + build: { + target: "ESNext" + } +}) From d150082686fa68d70960db81b68611f9d5e660ca Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 14:16:56 +1100 Subject: [PATCH 03/15] Update VTF Custom Editor --- .../extension/browser/client/src/extension.ts | 2 + apps/extension/desktop/client/src/VTF/VTF.ts | 145 ------ .../desktop/client/src/VTF/VTFDocument.ts | 343 --------------- .../desktop/client/src/VTF/VTFEditor.ts | 411 ------------------ .../client/src/VTF/VTFImageFormats.json | 29 -- .../extension/desktop/client/src/extension.ts | 4 +- package.json | 37 ++ packages/client/src/VTF/VTFDocument.ts | 113 +++++ packages/client/src/VTF/VTFEditor.ts | 136 ++++++ 9 files changed, 290 insertions(+), 930 deletions(-) delete mode 100644 apps/extension/desktop/client/src/VTF/VTF.ts delete mode 100644 apps/extension/desktop/client/src/VTF/VTFDocument.ts delete mode 100644 apps/extension/desktop/client/src/VTF/VTFEditor.ts delete mode 100644 apps/extension/desktop/client/src/VTF/VTFImageFormats.json create mode 100644 packages/client/src/VTF/VTFDocument.ts create mode 100644 packages/client/src/VTF/VTFEditor.ts diff --git a/apps/extension/browser/client/src/extension.ts b/apps/extension/browser/client/src/extension.ts index 252d588..a75c4e8 100644 --- a/apps/extension/browser/client/src/extension.ts +++ b/apps/extension/browser/client/src/extension.ts @@ -4,6 +4,7 @@ import { JSONToVDF } from "client/commands/JSONToVDF" import { showReferences } from "client/commands/showReferences" import { VDFToJSON } from "client/commands/VDFToJSON" import { onDidChangeActiveTextEditor } from "client/decorations" +import { VTFEditor } from "client/VTF/VTFEditor" import { commands, Uri, window, workspace, type ExtensionContext, type TextDocument } from "vscode" import { LanguageClient, type LanguageClientOptions } from "vscode-languageclient/browser" @@ -23,6 +24,7 @@ export function activate(context: ExtensionContext): void { // Window context.subscriptions.push(window.onDidChangeActiveTextEditor(onDidChangeActiveTextEditor)) + context.subscriptions.push(window.registerCustomEditorProvider("vscode-vdf.VTFEditor", new VTFEditor(context.extensionUri, context.subscriptions))) // Language Server diff --git a/apps/extension/desktop/client/src/VTF/VTF.ts b/apps/extension/desktop/client/src/VTF/VTF.ts deleted file mode 100644 index 5bcd41d..0000000 --- a/apps/extension/desktop/client/src/VTF/VTF.ts +++ /dev/null @@ -1,145 +0,0 @@ -import VTFImageFormats from "./VTFImageFormats.json" - -const VTF_VERSION_MAJOR_OFFSET = 4 -const VTF_VERSION_MINOR_OFFSET = 8 -const VTF_WIDTH_OFFSET = 16 -const VTF_HEIGHT_OFFSET = 18 -const VTF_FLAGS_OFFSET = 20 -const VTF_IMAGE_FORMAT_OFFSET = 52 - -export interface VTFBackup { - flags: number - changes: number -} - -/** - * https://developer.valvesoftware.com/wiki/Valve_Texture_Format - */ -export class VTF { - - private readonly buf: Buffer - private readonly versionMajor: number - private readonly versionMinor: number - public get version(): string { return `${this.versionMajor}.${this.versionMinor}` } - public readonly width: number - public readonly height: number - public readonly flags: { - "point-sample": boolean - "trilinear-sample": boolean - "clamp-s": boolean - "clamp-t": boolean - "anisotropic-sampling": boolean - "hint-DXT5": boolean - "SRGB": boolean - "normal-map": boolean - "no-mipmap": boolean - "no-level-of-detail": boolean - "no-minimum-mipmap": boolean - "procedural": boolean - "one-bit-alpha": boolean - "eight-bit-alpha": boolean - "environment-map": boolean - "render-target": boolean - "depth-render-target": boolean - "no-debug-override": boolean - "single-copy": boolean - "pre-SRGB": null - // "one-over-mipmap-level-in-alpha": null - "premultiply-color-by-one-over-mipmap-level": null - "normal-to-DuDv": null - "alpha-test-mipmap-generation": null - "no-depth-buffer": boolean - "nice-filtered": null - "clamp-u": boolean - "vertex-texture": boolean - "SSBump": boolean - "border": null - "clamp-all": boolean // This isn't documented on the Valve Developer Wiki website but exists in VTFEdit - } - public readonly imageFormat: string - - public savedFlags: number - - /** - * - * @param buf VTF file buffer - * @param backup VTF file backup flags number to restore flags from - */ - constructor(buf: Buffer, backup?: VTFBackup) { - this.buf = buf - this.versionMajor = this.buf.readUInt16LE(VTF_VERSION_MAJOR_OFFSET) - this.versionMinor = this.buf.readUInt16LE(VTF_VERSION_MINOR_OFFSET) - this.width = this.buf.readUInt16LE(VTF_WIDTH_OFFSET) - this.height = this.buf.readUInt16LE(VTF_HEIGHT_OFFSET) - this.flags = { - "point-sample": false, - "trilinear-sample": false, - "clamp-s": false, - "clamp-t": false, - "anisotropic-sampling": false, - "hint-DXT5": false, - "SRGB": false, - "normal-map": false, - "no-mipmap": false, - "no-level-of-detail": false, - "no-minimum-mipmap": false, - "procedural": false, - "one-bit-alpha": false, - "eight-bit-alpha": false, - "environment-map": false, - "render-target": false, - "depth-render-target": false, - "no-debug-override": false, - "single-copy": false, - "pre-SRGB": null, - "premultiply-color-by-one-over-mipmap-level": null, - "normal-to-DuDv": null, - "alpha-test-mipmap-generation": null, - "no-depth-buffer": false, - "nice-filtered": null, - "clamp-u": false, - "vertex-texture": false, - "SSBump": false, - "border": null, - "clamp-all": false - } - - this.savedFlags = this.buf.readUInt32LE(VTF_FLAGS_OFFSET) - - const flags = backup != undefined ? backup.flags : this.savedFlags - this.setFlags(flags) - - this.imageFormat = VTFImageFormats[this.buf.readUInt32LE(VTF_IMAGE_FORMAT_OFFSET)] - } - - public getFlags(): number { - return parseInt(Object.values(this.flags).reverse().map(Number).join(""), 2) - } - - public setFlags(flags: number): void { - const documentFlagsBinary = flags.toString(2) - const documentFlags = `${"0".repeat(32 - documentFlagsBinary.length)}${documentFlagsBinary}` - let i = documentFlags.length - 1 - let flagID: keyof VTF["flags"] - for (flagID in this.flags) { - if (this.flags[flagID] != null) { - // @ts-ignore - this.flags[flagID] = documentFlags[i] == "1" - } - i-- - } - } - - public save(): Buffer { - this.savedFlags = this.getFlags() - this.buf.writeUInt32LE(this.savedFlags, VTF_FLAGS_OFFSET) - return this.buf - } - - public saveAs(): Buffer { - const flags = this.getFlags() - const buf = Buffer.from(this.buf) - buf.writeUInt32LE(flags, VTF_FLAGS_OFFSET) - return buf - } -} diff --git a/apps/extension/desktop/client/src/VTF/VTFDocument.ts b/apps/extension/desktop/client/src/VTF/VTFDocument.ts deleted file mode 100644 index a2c7bbc..0000000 --- a/apps/extension/desktop/client/src/VTF/VTFDocument.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { exec } from "child_process" -import { existsSync, rmSync, watch, type FSWatcher } from "fs" -import { mkdir, writeFile } from "fs/promises" -import { tmpdir } from "os" -import { basename, join } from "path" -import { promisify } from "util" -import { EventEmitter, StatusBarAlignment, Uri, window, workspace, type CustomDocument, type StatusBarItem } from "vscode" -import { VTF, type VTFBackup } from "./VTF" - -export interface VTFPropertyChangeEvent { - scale?: number - flags?: Partial -} - -export class VTFDocument implements CustomDocument { - - /** - * VTF Document Uri - */ - public readonly uri: Uri - - /** - * VTF File Watcher - * Runs {@link updateVTF} - */ - private readonly watcher?: FSWatcher - - /** - * VTF Property Change\ - * Invokes a {@link VTFPropertyChangeEvent} when a VTF property is changed by the webview or external file change - */ - public readonly onVTFDidChangeEventEmitter: EventEmitter - - /** - * Notify webview of state changes - * Invokes a {@link VTFPropertyChangeEvent} when the state should be sent to the webview - */ - public readonly onShouldSendStateEventEmitter: EventEmitter - - /** - * VTF Document readonly - */ - public get isReadOnly(): boolean { return this.uri.scheme == "git" } - - /** - * Track changes made in the VTF Document, increments on change, decrements on undo, increments in redo - * 0 = VTF Document has no unsaved changes - * */ - public changes: number - - /** - * Whether the VTF Document has unsaved changes - * See {@link changes} - */ - public get isDirty(): boolean { return !this.isReadOnly && this.changes > 0 } - - /** - * VTF - */ - private VTF: VTF - - /** - * VTF Document Flags - */ - public flags: VTF["flags"] - - /** - * VTF Document Flags Proxy Handler - */ - private readonly handler: ProxyHandler - - /** - * VTF Version - */ - public get version(): string { return this.VTF.version } - - /** - * VTF Image Format - * [VTF Image Formats](VTFImageFormats.json). - */ - public get imageFormat(): string { return this.VTF.imageFormat } - - /** - * VTF Width - */ - public get width(): number { return this.VTF.width } - - /** - * VTF Height - */ - public get height(): number { return this.VTF.height } - - /** - * Absolute path to VTF TGA file - */ - public tgaPath: string - - private _scale: number - /** - * VTF Zoom Scale (Between 0.1 and 2) - */ - public get scale(): number { return this._scale } - public set scale(value: number) { - this._scale = value - const propertyChangedInfo = { scale: this.scale } - this.onPropertyChanged(propertyChangedInfo) - this.setState(propertyChangedInfo) - } - - // Status Bar Item - public readonly statusBarItem: StatusBarItem - - /** - * Asynchronously create a VTF Document from a workspace Uint8Array and VTF absolute file path - * @param extensionPath Extension path - * @param uri VTF File Uri - * @param backup VTF Document backup - * @returns The completed VTF Document - */ - public static async create(extensionPath: string, uri: Uri, backup?: VTFBackup): Promise { - - const vtfPath = uri.scheme == "file" ? uri.fsPath : join(tmpdir(), basename(uri.fsPath)) - - const buf = Buffer.from(await workspace.fs.readFile(uri)) - - if (uri.scheme == "vpk") { - // File does not exist on disk - await writeFile(vtfPath, buf) - } - - const teamFortress2Folder: string = workspace.getConfiguration("vscode-vdf", uri).get("teamFortress2Folder")! - - const tgaFolder = join(extensionPath, ".TGA") - if (!existsSync(tgaFolder)) { - await mkdir(tgaFolder) - } - - const tgaPath = join(tgaFolder, basename(uri.fsPath).split(".vtf").join(".tga")) - - const extract = VTFDocument.extractVTF(teamFortress2Folder, vtfPath, tgaPath) - const document = new VTFDocument(uri, buf, tgaPath, backup) - await extract - - return document - } - - /** - * - * @param uri VTF File Uri - * @param buf Buffer - * @param tgaPath TGA Path - * @param backup VTF Document Backup - */ - private constructor(uri: Uri, buf: Buffer, tgaPath: string, backup?: VTFBackup) { - - this.uri = uri - this.onVTFDidChangeEventEmitter = new EventEmitter() - this.changes = backup?.changes ?? 0 - - // VTF - this.VTF = new VTF(buf, backup) - if (this.uri.scheme == "file") { - this.watcher = watch(this.uri.fsPath, () => { - this.updateVTF() - }) - } - - this.handler = { - get: (target: VTF["flags"], p: K): VTF["flags"][keyof VTF["flags"]] => { - return target[p] - }, - set: (target: VTF["flags"], p: K, value: VTF["flags"][K]): boolean => { - target[p] = value - const propertyChangedInfo = { flags: { [p]: value } } - this.onPropertyChanged(propertyChangedInfo) - this.setState(propertyChangedInfo) - return target[p] == value - } - } - this.flags = new Proxy(this.VTF.flags, this.handler) - - // TGA - this.tgaPath = tgaPath - - // Scale - this._scale = 1 - - this.onShouldSendStateEventEmitter = new EventEmitter() - - // Status Bar Item - this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, Number(uri.scheme == "git")) - this.statusBarItem.text = "" // We will receive the scale from the webview later - this.statusBarItem.command = { - title: "Select VTF Zoom Level", - command: "vscode-vdf.selectVTFZoomLevel", - arguments: [this] - } - this.statusBarItem.show() - this.onVTFDidChangeEventEmitter.event((e) => { - if (e.scale != undefined) { - this.statusBarItem.text = `${this.scale < 1 ? " " : ""}${(this.scale * 100).toFixed()}%` - } - }) - } - - /** - * Extract a VTF using vtf2tga.exe - * @param teamFortress2Folder Absolute path to "Team Fortress 2" folder containing bin/vtf2tga.exe - * @param vtfPath Absolute path to input VTF - * @param tgaPath Absolute path to output TGA - */ - private static async extractVTF(teamFortress2Folder: string, vtfPath: string, tgaPath: string): Promise { - const args = `"${join(teamFortress2Folder, "bin/vtf2tga.exe")}" ${[ - "-i", - `"${vtfPath}"`, - "-o", - `"${tgaPath}"`, - ].join(" ")}` - - const std = await promisify(exec)(args) - - if (std.stderr) { - window.showErrorMessage(std.stderr) - } - } - - /** - * Handle a message receieved from the Custom Editor Webview - * @param message webview message - */ - public onDidReceiveMessage(message: any): void { - const state = message.state - if ("scale" in state) { - this._scale = state.scale - this.onPropertyChanged({ scale: this._scale }) - } - if ("flags" in state) { - for (const id in state.flags) { - // @ts-ignore - this.VTF.flags[id] = state.flags[id] - } - this.onPropertyChanged({ flags: state.flags }) - } - } - - public undo(): void { - this.changes-- - } - - public redo(): void { - this.changes++ - } - - /** - * Notify extension client of VTF Document property changes - * @param propertyChangedInfo - */ - private onPropertyChanged(propertyChangedInfo: VTFPropertyChangeEvent): void { - this.onVTFDidChangeEventEmitter.fire(propertyChangedInfo) - } - - /** - * Notify Custom Editor Webview of VTF Document property changes - * @param propertyChangedInfo State Change - */ - private setState(propertyChangedInfo: VTFPropertyChangeEvent): void { - this.onShouldSendStateEventEmitter.fire(propertyChangedInfo) - } - - /** - * Compile the state of the VTF Document to a JSON serializable object - * @returns - */ - public getBackup(): VTFBackup { - return { - flags: this.getFlags(), - changes: this.changes - } - } - - /** - * Update VTF Document properties from the file system - */ - private async updateVTF(): Promise { - if (this.isDirty) { - return - } - - this.VTF = new VTF(Buffer.from(await workspace.fs.readFile(this.uri))) - this.flags = new Proxy(this.VTF.flags, this.handler) - - const propertyChangedInfo = { flags: this.flags } - this.onPropertyChanged(propertyChangedInfo) - this.setState(propertyChangedInfo) - } - - /** - * Compile the VTF flags to decimal number - */ - public getFlags(): number { - return this.VTF.getFlags() - } - - /** - * Write VTF Flags to Buffer and return the Buffer - */ - public save(): Buffer { - this.changes = 0 - return this.VTF.save() - } - - /** - * Get the saved Buffer without modifying the VTF save state - */ - public saveAs(): Buffer { - return this.VTF.saveAs() - } - - /** - * Revert VTF Document to its last saved state on disk - */ - public revert(): void { - this.VTF.setFlags(this.VTF.savedFlags) - - const propertyChangedInfo = { flags: this.flags } - this.onPropertyChanged(propertyChangedInfo) - this.setState(propertyChangedInfo) - } - - /** - * Dispose VTF Document - */ - public dispose(): void { - this.watcher?.close() - this.onVTFDidChangeEventEmitter.dispose() - this.onShouldSendStateEventEmitter.dispose() - if (existsSync(this.tgaPath)) { - rmSync(this.tgaPath) - } - this.statusBarItem.hide() - this.statusBarItem.dispose() - } -} diff --git a/apps/extension/desktop/client/src/VTF/VTFEditor.ts b/apps/extension/desktop/client/src/VTF/VTFEditor.ts deleted file mode 100644 index cda312b..0000000 --- a/apps/extension/desktop/client/src/VTF/VTFEditor.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, rmSync } from "fs" -import { writeFile } from "fs/promises" -import { dirname } from "path" -import { Disposable, EventEmitter, Uri, commands, window, workspace, type CancellationToken, type CustomDocumentBackup, type CustomDocumentBackupContext, type CustomDocumentEditEvent, type CustomDocumentOpenContext, type CustomEditorProvider, type Event, type ExtensionContext, type WebviewPanel, type WebviewPanelOnDidChangeViewStateEvent } from "vscode" -import type { VTFBackup } from "./VTF" -import { VTFDocument } from "./VTFDocument" - -// @ts-ignore -import tga from "tga-js/dist/umd/tga.js?raw" - -export class VTFEditor implements CustomEditorProvider { - - private readonly context: ExtensionContext - - private readonly onDidChangeCustomDocumentEventEmitter: EventEmitter> - public readonly onDidChangeCustomDocument: Event> - - private readonly selectVTFZoomLevelCommand: Disposable - - constructor(context: ExtensionContext) { - this.context = context - this.onDidChangeCustomDocumentEventEmitter = new EventEmitter>() - this.onDidChangeCustomDocument = this.onDidChangeCustomDocumentEventEmitter.event - this.selectVTFZoomLevelCommand = commands.registerCommand("vscode-vdf.selectVTFZoomLevel", async (document: VTFDocument) => { - const result = await window.showQuickPick(Array.from({ length: 10 }, (_, i) => `${((i + 1) * 2) * 10}%`), { placeHolder: "Select zoom level" }) - if (result != undefined) { - document.scale = parseFloat(result) / 100 - } - }) - context.subscriptions.push(this.onDidChangeCustomDocumentEventEmitter, this.selectVTFZoomLevelCommand) - } - - public saveCustomDocument(document: VTFDocument, cancellation: CancellationToken): Thenable { - if (document.isReadOnly) { - // git:// protocols are readonly - return Promise.resolve() - } - return workspace.fs.writeFile(document.uri, document.save()) - } - - public saveCustomDocumentAs(document: VTFDocument, destination: Uri, cancellation: CancellationToken): Thenable { - if (document.isReadOnly) { - // git:// protocols are readonly - return Promise.resolve() - } - return workspace.fs.writeFile(destination, document.saveAs()) - } - - public revertCustomDocument(document: VTFDocument, cancellation: CancellationToken): Thenable { - return (async (): Promise => { - document.revert() - })() - } - - public backupCustomDocument(document: VTFDocument, context: CustomDocumentBackupContext, cancellation: CancellationToken): Thenable { - return (async (): Promise => { - const backupDirectory = dirname(context.destination.fsPath) - if (!existsSync(backupDirectory)) { - mkdirSync(backupDirectory, { recursive: true }) - } - await writeFile(context.destination.fsPath, JSON.stringify(document.getBackup())) - return { - id: context.destination.fsPath, - delete: (): void => { - if (existsSync(context.destination.fsPath)) { - rmSync(context.destination.fsPath) - } - } - } - })() - } - - public openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): VTFDocument | Thenable { - const backup: VTFBackup | undefined = openContext.backupId != undefined && existsSync(openContext.backupId) ? JSON.parse(readFileSync(openContext.backupId, "utf-8")) : undefined - return VTFDocument.create(this.context.extensionPath, uri, backup) - } - - public resolveCustomEditor(document: VTFDocument, webviewPanel: WebviewPanel, token: CancellationToken): void | Thenable { - - webviewPanel.webview.options = { - enableScripts: true - } - - webviewPanel.webview.html = this.getWebviewContent(document, (path: string) => webviewPanel.webview.asWebviewUri(Uri.file(path)).toString()) - - webviewPanel.onDidChangeViewState((e: WebviewPanelOnDidChangeViewStateEvent) => { - if (e.webviewPanel.visible) { - document.statusBarItem.show() - } - else { - document.statusBarItem.hide() - } - }) - - webviewPanel.webview.onDidReceiveMessage((message) => { - document.onDidReceiveMessage(message) - const state = message.state - if ("flags" in state) { - for (const id in state.flags) { - const value = state.flags[id] - document.changes++ - this.onDidChangeCustomDocumentEventEmitter.fire({ - document, - label: `${value ? "enable" : "disable"} ${id}`, - undo: () => { - // @ts-ignore - document.flags[id] = !value - document.undo() - }, - redo: () => { - // @ts-ignore - document.flags[id] = value - document.redo() - } - }) - } - } - }) - - // Update webview when the document tells us to - // This gets invoked on fs.watch(...) change and vscode's "File: Revert File" command - document.onShouldSendStateEventEmitter.event((e) => { - webviewPanel.webview.postMessage({ state: e }) - }) - } - - private getWebviewContent(document: VTFDocument, createWebviewUri: (path: string) => string): string { - return /* html */` - - - - - - ${document.uri.fsPath} - - - - -
-
-
- File Info - - - - - -
Version:${document.version}
Format:${document.imageFormat}
Width:${document.width}
Height:${document.height}
-
-
-
-
-
-
- Flags -
- -
-
-
-
- - - - - ` - } -} diff --git a/apps/extension/desktop/client/src/VTF/VTFImageFormats.json b/apps/extension/desktop/client/src/VTF/VTFImageFormats.json deleted file mode 100644 index 10678da..0000000 --- a/apps/extension/desktop/client/src/VTF/VTFImageFormats.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - "RGBA8888", - "ABGR8888", - "RGB888", - "BGR888", - "RGB565", - "I8", - "IA88", - "P8", - "A8", - "RGB888_BLUESCREEN", - "BGR888_BLUESCREEN", - "ARGB8888", - "BGRA8888", - "DXT1", - "DXT3", - "DXT5", - "BGRX8888", - "BGR565", - "BGRX5551", - "BGRA4444", - "DXT1_ONEBITALPHA", - "BGRA5551", - "UV88", - "UVWQ8888", - "RGBA16161616F", - "RGBA16161616", - "UVLX8888" -] diff --git a/apps/extension/desktop/client/src/extension.ts b/apps/extension/desktop/client/src/extension.ts index c81a4e1..db67c44 100644 --- a/apps/extension/desktop/client/src/extension.ts +++ b/apps/extension/desktop/client/src/extension.ts @@ -1,4 +1,5 @@ import { Client, VSCodeVDFLanguageIDSchema, VSCodeVDFLanguageNameSchema, type VSCodeVDFLanguageID } from "client" +import { VTFEditor } from "client/VTF/VTFEditor" import { JSONToVDF } from "client/commands/JSONToVDF" import { VDFToJSON } from "client/commands/VDFToJSON" import { copyKeyValuePath } from "client/commands/copyKeyValuePath" @@ -9,7 +10,6 @@ import { join } from "path" import { commands, window, workspace, type ExtensionContext, type TextDocument } from "vscode" import { LanguageClient, TransportKind, type LanguageClientOptions, type ServerOptions } from "vscode-languageclient/node" import { VPKFileSystemProvider } from "./VPK/VPKFileSystemProvider" -import { VTFEditor } from "./VTF/VTFEditor" const languageClients: { -readonly [P in VSCodeVDFLanguageID]?: Client } = {} @@ -28,7 +28,7 @@ export function activate(context: ExtensionContext): void { // Window context.subscriptions.push(window.onDidChangeActiveTextEditor(onDidChangeActiveTextEditor)) - context.subscriptions.push(window.registerCustomEditorProvider("vscode-vdf.VTFEditor", new VTFEditor(context))) + context.subscriptions.push(window.registerCustomEditorProvider("vscode-vdf.VTFEditor", new VTFEditor(context.extensionUri, context.subscriptions))) // Workspace context.subscriptions.push(workspace.registerFileSystemProvider("vpk", new VPKFileSystemProvider(), { isCaseSensitive: false, isReadonly: true })) diff --git a/package.json b/package.json index 0322f9f..f12a3ec 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,43 @@ ], "description": "Controls when to update file diagnostics", "default": "type" + }, + "vscode-vdf.vtf.formats.exclude": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "RGBA8888", + "ABGR8888", + "RGB888", + "BGR888", + "RGB565", + "I8", + "IA88", + "P8", + "A8", + "RGB888BlueScreen", + "BGR888BlueScreen", + "ARGB8888", + "BGRA8888", + "DXT1", + "DXT3", + "DXT5", + "BGRX8888", + "BGR565", + "BGRX5551", + "BGRA4444", + "DXT1OneBitAlpha", + "BGRA5551", + "UV88", + "UVWQ8888", + "RGBA16161616F", + "RGBA16161616", + "UVLX8888" + ] + }, + "default": [], + "description": "List of which VTF formats to ignore" } } }, diff --git a/packages/client/src/VTF/VTFDocument.ts b/packages/client/src/VTF/VTFDocument.ts new file mode 100644 index 0000000..ed703a7 --- /dev/null +++ b/packages/client/src/VTF/VTFDocument.ts @@ -0,0 +1,113 @@ +import { BehaviorSubject } from "rxjs" +import { type CustomDocument, StatusBarAlignment, type StatusBarItem, type Uri, window } from "vscode" + +const VTF_WIDTH_OFFSET = 16 +const VTF_HEIGHT_OFFSET = 18 +const VTF_FLAGS_OFFSET = 20 + +const KB = 1024 +const MB = KB * KB +const GB = MB * KB + +function size(bytes: number) { + if (bytes < KB) { + return `${bytes}B` + } + else if (bytes < MB) { + return `${(bytes / KB).toFixed(2)}KB` + } + else if (bytes < GB) { + return `${(bytes / MB).toFixed(2)}MB` + } + else { + return `${(bytes / GB).toFixed(2)}GB` + } +} + +class DistinctBehaviorSubject extends BehaviorSubject { + public next(value: T): void { + if (value != this.value) { + super.next(value) + } + } +} + +export class VTFDocument implements CustomDocument { + + public readonly uri: Uri + public readonly buf: Uint8Array + public readonly flags$: DistinctBehaviorSubject + public readonly scale$: DistinctBehaviorSubject + + private readonly zoomLevelStatusBarItem: StatusBarItem + private readonly dimensionsStatusBarItem: StatusBarItem + private readonly binarySizeStatusBarItem: StatusBarItem + + public constructor(uri: Uri, buf: Uint8Array) { + this.uri = uri + this.buf = buf + + const dataView = new DataView(this.buf.buffer) + + this.flags$ = new DistinctBehaviorSubject(dataView.getUint32(VTF_FLAGS_OFFSET, true)) + this.scale$ = new BehaviorSubject(100) + + let priority = 100 + + this.zoomLevelStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, priority--) + this.scale$.subscribe((scale) => this.zoomLevelStatusBarItem.text = `${scale}%`) + this.zoomLevelStatusBarItem.command = { + title: "Select VTF Zoom Level", + command: "vscode-vdf.selectVTFZoomLevel", + arguments: [this] + } + + this.dimensionsStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, priority--) + this.dimensionsStatusBarItem.text = `${dataView.getUint16(VTF_WIDTH_OFFSET, true)}x${dataView.getUint16(VTF_HEIGHT_OFFSET, true)}` + + this.binarySizeStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, priority--) + this.binarySizeStatusBarItem.text = size(buf.length) + this.binarySizeStatusBarItem.tooltip = `${buf.length}` + } + + public show() { + this.zoomLevelStatusBarItem.show() + this.dimensionsStatusBarItem.show() + this.binarySizeStatusBarItem.show() + } + + public hide() { + this.zoomLevelStatusBarItem.hide() + this.dimensionsStatusBarItem.hide() + this.binarySizeStatusBarItem.hide() + } + + public save() { + new DataView(this.buf.buffer).setUint32(VTF_FLAGS_OFFSET, this.flags$.value) + return this.buf + } + + public saveAs() { + const buf = new Uint8Array(this.buf) + new DataView(buf.buffer).setUint32(VTF_FLAGS_OFFSET, this.flags$.value) + return buf + } + + public revert() { + this.flags$.next(new DataView(this.buf.buffer).getUint32(VTF_FLAGS_OFFSET, true)) + } + + public backup() { + const buf = new Uint8Array(4) + new DataView(buf.buffer).setUint32(0, this.flags$.value, true) + return buf + } + + public dispose() { + this.flags$.complete() + this.scale$.complete() + this.zoomLevelStatusBarItem.dispose() + this.dimensionsStatusBarItem.dispose() + this.binarySizeStatusBarItem.dispose() + } +} diff --git a/packages/client/src/VTF/VTFEditor.ts b/packages/client/src/VTF/VTFEditor.ts new file mode 100644 index 0000000..3703381 --- /dev/null +++ b/packages/client/src/VTF/VTFEditor.ts @@ -0,0 +1,136 @@ +import { commands, EventEmitter, Uri, window, workspace, type CancellationToken, type CustomDocumentBackup, type CustomDocumentBackupContext, type CustomDocumentEditEvent, type CustomDocumentOpenContext, type CustomEditorProvider, type Event, type WebviewPanel } from "vscode" +import { z } from "zod" +import { VTFDocument } from "./VTFDocument" + +export class VTFEditor implements CustomEditorProvider { + + private static readonly decoder = new TextDecoder("utf-8") + public static readonly commandSchema = z.union([ + z.object({ type: z.literal("flags"), label: z.string(), value: z.number() }), + z.object({ type: z.literal("scale"), scale: z.number().min(10).max(200) }), + z.object({ type: z.literal("showErrorMessage"), message: z.string(), items: z.string().array() }), + z.object({ type: z.literal("unsupportedVTFFormat"), format: z.string() }) + ]) + + public readonly extensionUri: Uri + private readonly onDidChangeCustomDocumentEventEmitter: EventEmitter> + public readonly onDidChangeCustomDocument: Event> + + public constructor(extensionUri: Uri, subscriptions: { dispose(): any }[]) { + this.extensionUri = extensionUri + this.onDidChangeCustomDocumentEventEmitter = new EventEmitter() + this.onDidChangeCustomDocument = this.onDidChangeCustomDocumentEventEmitter.event + + const selectVTFZoomLevelCommand = commands.registerCommand("vscode-vdf.selectVTFZoomLevel", async (document: VTFDocument) => { + const result = await window.showQuickPick(Array.from({ length: 10 }, (_, i) => `${((i + 1) * 2) * 10}%`), { placeHolder: "Select zoom level" }) + if (result != undefined) { + document.scale$.next(parseInt(result)) + } + }) + + subscriptions.push( + this.onDidChangeCustomDocumentEventEmitter, + selectVTFZoomLevelCommand + ) + } + + public async saveCustomDocument(document: VTFDocument, cancellation: CancellationToken): Promise { + return await workspace.fs.writeFile(document.uri, document.save()) + } + + public async saveCustomDocumentAs(document: VTFDocument, destination: Uri, cancellation: CancellationToken): Promise { + return await workspace.fs.writeFile(destination, document.saveAs()) + } + + public async revertCustomDocument(document: VTFDocument, cancellation: CancellationToken): Promise { + document.revert() + } + + public async backupCustomDocument(document: VTFDocument, context: CustomDocumentBackupContext, cancellation: CancellationToken): Promise { + await workspace.fs.writeFile(context.destination, document.backup()) + return { + id: context.destination.toString(), + delete: async () => await workspace.fs.delete(context.destination), + } + } + + public async openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Promise { + return new VTFDocument(uri, new Uint8Array(await workspace.fs.readFile(uri))) + } + + public async resolveCustomEditor(document: VTFDocument, webviewPanel: WebviewPanel, token: CancellationToken): Promise { + + const dist = Uri.joinPath(this.extensionUri, "apps/vtf-editor/dist") + const html = VTFEditor.decoder.decode(await workspace.fs.readFile(Uri.joinPath(dist, "index.html"))) + + webviewPanel.webview.options = { enableScripts: true } + webviewPanel.webview.html = html + .replaceAll("%BASE%", `${webviewPanel.webview.asWebviewUri(dist).toString()}/`) + + webviewPanel.webview.postMessage(document.buf) + document.show() + + webviewPanel.onDidChangeViewState(() => { + if (webviewPanel.visible) { + webviewPanel.webview.postMessage(document.buf) + document.show() + } + else { + document.hide() + } + }) + + document.flags$.subscribe((flags) => { + webviewPanel.webview.postMessage({ type: "flags", flags }) + }) + + document.scale$.subscribe((scale) => { + webviewPanel.webview.postMessage({ type: "scale", value: scale }) + }) + + webviewPanel.webview.onDidReceiveMessage(async (message) => { + const command = VTFEditor.commandSchema.parse(message) + switch (command.type) { + case "flags": { + const prev = document.flags$.value + document.flags$.next(prev ^ command.value) + const next = document.flags$.value + this.onDidChangeCustomDocumentEventEmitter.fire({ + document: document, + undo: () => document.flags$.next(prev), + redo: () => document.flags$.next(next), + label: command.label + }) + break + } + case "scale": { + document.scale$.next(command.scale) + break + } + case "showErrorMessage": { + window.showErrorMessage(command.message, ...command.items) + break + } + case "unsupportedVTFFormat": { + const configuration = workspace.getConfiguration("vscode-vdf.vtf.formats") + const exclude = configuration.get("exclude") ?? [] + if (!exclude.includes(command.format)) { + + const requestSupportMessage = `(Github) Request support for "${command.format}"` + const dontAskAgain = "Don't ask again" + + const result = await window.showErrorMessage(`Unsupported VTF format: "${command.format}"`, requestSupportMessage, dontAskAgain) + if (result == requestSupportMessage) { + const title = `Add support for ${command.format}` + await commands.executeCommand("vscode.open", `https://github.com/cooolbros/vscode-vdf/issues/new?title=${title}`) + } + else if (result == dontAskAgain) { + configuration.update("exclude", [...exclude, command.format], true) + } + break + } + } + } + }) + } +} From a6b2b37005d6d04b8ad0a464dfe40f0001f36b2b Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 14:24:21 +1100 Subject: [PATCH 04/15] Remove tga-js --- apps/extension/desktop/client/package.json | 1 - pnpm-lock.yaml | 673 ++++++++++++++++++++- 2 files changed, 665 insertions(+), 9 deletions(-) diff --git a/apps/extension/desktop/client/package.json b/apps/extension/desktop/client/package.json index d4c01f1..a115152 100644 --- a/apps/extension/desktop/client/package.json +++ b/apps/extension/desktop/client/package.json @@ -7,7 +7,6 @@ "devDependencies": { "@types/node": "^22.9.0", "client": "workspace:^", - "tga-js": "^1.1.1", "tsconfig": "workspace:^", "vpk": "workspace:^", "vscode-languageclient": "*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 378ea11..addbd0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,9 +133,6 @@ importers: client: specifier: workspace:^ version: link:../../../../packages/client/src - tga-js: - specifier: ^1.1.1 - version: 1.1.1 tsconfig: specifier: workspace:^ version: link:../../../../packages/tsconfig @@ -170,6 +167,30 @@ importers: specifier: workspace:^ version: link:../../packages/tsconfig + apps/vtf-editor: + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.1 + version: 5.0.1(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)) + '@tsconfig/svelte': + specifier: ^5.0.4 + version: 5.0.4 + '@types/vscode-webview': + specifier: ^1.57.5 + version: 1.57.5 + client: + specifier: workspace:^ + version: link:../../packages/client/src + svelte: + specifier: ^5.9.0 + version: 5.10.0 + vite: + specifier: ^6.0.1 + version: 6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0) + vtf: + specifier: workspace:^ + version: link:../../packages/vtf + packages/client: devDependencies: '@trpc/server': @@ -316,12 +337,18 @@ importers: specifier: workspace:^ version: link:../tsconfig + packages/vtf: {} + packages: '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@azure/abort-controller@2.1.2': resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} engines: {node: '>=18.0.0'} @@ -384,6 +411,150 @@ packages: '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@headlessui/react@1.7.19': resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} engines: {node: '>=10'} @@ -702,6 +873,116 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@rollup/rollup-android-arm-eabi@4.28.1': + resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.28.1': + resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.28.1': + resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.28.1': + resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.28.1': + resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.28.1': + resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.28.1': + resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.28.1': + resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.28.1': + resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.28.1': + resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': + resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': + resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.28.1': + resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.28.1': + resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.28.1': + resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.28.1': + resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.28.1': + resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.28.1': + resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.28.1': + resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} + cpu: [x64] + os: [win32] + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.0.1': + resolution: {integrity: sha512-D5l5+STmywGoLST07T9mrqqFFU+xgv5fqyTWM+VbxTvQ6jujNn4h3lQNCvlwVYs4Erov8i0K5Rwr3LQtmBYmBw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -745,6 +1026,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tsconfig/svelte@5.0.4': + resolution: {integrity: sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==} + '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} @@ -975,6 +1259,11 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-typescript@1.4.13: + resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} + peerDependencies: + acorn: '>=8.9.0' + acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} @@ -1044,6 +1333,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -1058,6 +1351,10 @@ packages: peerDependencies: postcss: ^8.1.0 + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + azure-devops-node-api@12.5.0: resolution: {integrity: sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==} @@ -1514,6 +1811,10 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1646,6 +1947,11 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1665,11 +1971,17 @@ packages: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} + esm-env@1.2.1: + resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esrap@1.2.3: + resolution: {integrity: sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -2074,6 +2386,9 @@ packages: is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -2219,6 +2534,9 @@ packages: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -2271,6 +2589,9 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + magic-string@0.30.14: + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -2860,6 +3181,10 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -3004,6 +3329,11 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@4.28.1: + resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3248,6 +3578,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte@5.10.0: + resolution: {integrity: sha512-jGJFpB9amHLLQZBbAuQ6csH7WlTvGx4cO4wSSNcgGcx9vDGMTCZzTREf6/wKhVUQDoK+GapgvLQPZHa3e9MOAA==} + engines: {node: '>=18'} + tailwindcss@3.4.14: resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} @@ -3294,9 +3628,6 @@ packages: text-decoder@1.2.1: resolution: {integrity: sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==} - tga-js@1.1.1: - resolution: {integrity: sha512-2EcbDHfFCggAt0DLjUwZzKaQGaCmcQZBQrYscVnWjxzl+2Q1PFp1ABsO2UVevF1pTi2t9mmWkzPaQqUeJA+mhA==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -3548,6 +3879,54 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@6.0.3: + resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.0.4: + resolution: {integrity: sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} @@ -3695,6 +4074,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -3705,6 +4087,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + '@azure/abort-controller@2.1.2': dependencies: tslib: 2.8.1 @@ -3801,6 +4188,78 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.24.0': + optional: true + + '@esbuild/android-arm64@0.24.0': + optional: true + + '@esbuild/android-arm@0.24.0': + optional: true + + '@esbuild/android-x64@0.24.0': + optional: true + + '@esbuild/darwin-arm64@0.24.0': + optional: true + + '@esbuild/darwin-x64@0.24.0': + optional: true + + '@esbuild/freebsd-arm64@0.24.0': + optional: true + + '@esbuild/freebsd-x64@0.24.0': + optional: true + + '@esbuild/linux-arm64@0.24.0': + optional: true + + '@esbuild/linux-arm@0.24.0': + optional: true + + '@esbuild/linux-ia32@0.24.0': + optional: true + + '@esbuild/linux-loong64@0.24.0': + optional: true + + '@esbuild/linux-mips64el@0.24.0': + optional: true + + '@esbuild/linux-ppc64@0.24.0': + optional: true + + '@esbuild/linux-riscv64@0.24.0': + optional: true + + '@esbuild/linux-s390x@0.24.0': + optional: true + + '@esbuild/linux-x64@0.24.0': + optional: true + + '@esbuild/netbsd-x64@0.24.0': + optional: true + + '@esbuild/openbsd-arm64@0.24.0': + optional: true + + '@esbuild/openbsd-x64@0.24.0': + optional: true + + '@esbuild/sunos-x64@0.24.0': + optional: true + + '@esbuild/win32-arm64@0.24.0': + optional: true + + '@esbuild/win32-ia32@0.24.0': + optional: true + + '@esbuild/win32-x64@0.24.0': + optional: true + '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/react-virtual': 3.10.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4063,6 +4522,85 @@ snapshots: '@popperjs/core@2.11.8': {} + '@rollup/rollup-android-arm-eabi@4.28.1': + optional: true + + '@rollup/rollup-android-arm64@4.28.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.28.1': + optional: true + + '@rollup/rollup-darwin-x64@4.28.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.28.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.28.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.28.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.28.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.28.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.28.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.28.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.28.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.28.1': + optional: true + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.1(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)))(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.1(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)) + debug: 4.3.7 + svelte: 5.10.0 + vite: 6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.0.1(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.1(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)))(svelte@5.10.0)(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)) + debug: 4.3.7 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.14 + svelte: 5.10.0 + vite: 6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0) + vitefu: 1.0.4(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)) + transitivePeerDependencies: + - supports-color + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.13': @@ -4104,6 +4642,8 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tsconfig/svelte@5.0.4': {} + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.6 @@ -4385,6 +4925,10 @@ snapshots: dependencies: acorn: 8.14.0 + acorn-typescript@1.4.13(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn-walk@8.3.4: dependencies: acorn: 8.14.0 @@ -4445,6 +4989,8 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.2: {} + astring@1.9.0: {} asynckit@0.4.0: {} @@ -4459,6 +5005,8 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 + axobject-query@4.1.0: {} + azure-devops-node-api@12.5.0: dependencies: tunnel: 0.0.6 @@ -4954,6 +5502,8 @@ snapshots: deep-extend@0.6.0: optional: true + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -5066,6 +5616,33 @@ snapshots: es-module-lexer@1.5.4: {} + esbuild@0.24.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -5079,8 +5656,15 @@ snapshots: esrecurse: 4.3.0 estraverse: 4.3.0 + esm-env@1.2.1: {} + esprima@4.0.1: {} + esrap@1.2.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -5556,6 +6140,10 @@ snapshots: dependencies: '@types/estree': 1.0.6 + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.6 + is-ssh@1.4.0: dependencies: protocols: 2.0.1 @@ -5741,6 +6329,8 @@ snapshots: loader-runner@4.3.0: {} + locate-character@3.0.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -5782,6 +6372,10 @@ snapshots: dependencies: yallist: 4.0.0 + magic-string@0.30.14: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + make-error@1.3.6: {} markdown-extensions@1.1.1: {} @@ -6686,6 +7280,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.4.49: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 @@ -6888,6 +7488,31 @@ snapshots: robust-predicates@3.0.2: {} + rollup@4.28.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.28.1 + '@rollup/rollup-android-arm64': 4.28.1 + '@rollup/rollup-darwin-arm64': 4.28.1 + '@rollup/rollup-darwin-x64': 4.28.1 + '@rollup/rollup-freebsd-arm64': 4.28.1 + '@rollup/rollup-freebsd-x64': 4.28.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 + '@rollup/rollup-linux-arm-musleabihf': 4.28.1 + '@rollup/rollup-linux-arm64-gnu': 4.28.1 + '@rollup/rollup-linux-arm64-musl': 4.28.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 + '@rollup/rollup-linux-riscv64-gnu': 4.28.1 + '@rollup/rollup-linux-s390x-gnu': 4.28.1 + '@rollup/rollup-linux-x64-gnu': 4.28.1 + '@rollup/rollup-linux-x64-musl': 4.28.1 + '@rollup/rollup-win32-arm64-msvc': 4.28.1 + '@rollup/rollup-win32-ia32-msvc': 4.28.1 + '@rollup/rollup-win32-x64-msvc': 4.28.1 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -7147,6 +7772,22 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte@5.10.0: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + acorn: 8.14.0 + acorn-typescript: 1.4.13(acorn@8.14.0) + aria-query: 5.3.2 + axobject-query: 4.1.0 + esm-env: 1.2.1 + esrap: 1.2.3 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.14 + zimmerframe: 1.1.2 + tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: '@alloc/quick-lru': 5.2.0 @@ -7225,8 +7866,6 @@ snapshots: text-decoder@1.2.1: {} - tga-js@1.1.1: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -7529,6 +8168,22 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0): + dependencies: + esbuild: 0.24.0 + postcss: 8.4.49 + rollup: 4.28.1 + optionalDependencies: + '@types/node': 22.9.0 + fsevents: 2.3.3 + jiti: 1.21.6 + terser: 5.36.0 + yaml: 2.6.0 + + vitefu@1.0.4(vite@6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0)): + optionalDependencies: + vite: 6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0) + vscode-jsonrpc@8.2.0: {} vscode-languageclient@9.0.1: @@ -7682,6 +8337,8 @@ snapshots: yocto-queue@0.1.0: {} + zimmerframe@1.1.2: {} + zod@3.23.8: {} zwitch@2.0.4: {} From cbce5d246d06fd4542d19059b32ac200391bfd01 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:01:21 +1100 Subject: [PATCH 05/15] Only wheel to zoom when CTRL is down --- apps/vtf-editor/src/VTFViewer.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/vtf-editor/src/VTFViewer.svelte b/apps/vtf-editor/src/VTFViewer.svelte index 6a5beb0..58383cc 100644 --- a/apps/vtf-editor/src/VTFViewer.svelte +++ b/apps/vtf-editor/src/VTFViewer.svelte @@ -43,7 +43,10 @@ return fromEvent(canvas, "click").pipe(map((event) => (event.ctrlKey ? -1 : 1))) }), ), - fromEvent(document, "wheel").pipe(map((event) => (event.deltaY < 0 ? 1 : -1))), + fromEvent(document, "wheel").pipe( + filter((event) => event.ctrlKey), + map((event) => (event.deltaY < 0 ? 1 : -1)), + ), ).pipe(map((value) => ({ set: false, value: value * 10 }))), fromEvent(window, "message").pipe( map((event) => z.object({ type: z.literal("scale"), value: z.number() }).safeParse(event.data)), From 6b5d42a3f2089597a4bf97c29a7314d69fca53f3 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:01:44 +1100 Subject: [PATCH 06/15] Update svelte + vite --- apps/vtf-editor/package.json | 4 ++-- pnpm-lock.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/vtf-editor/package.json b/apps/vtf-editor/package.json index fbb9536..a0dc0f3 100644 --- a/apps/vtf-editor/package.json +++ b/apps/vtf-editor/package.json @@ -10,8 +10,8 @@ "@tsconfig/svelte": "^5.0.4", "@types/vscode-webview": "^1.57.5", "client": "workspace:^", - "svelte": "^5.9.0", - "vite": "^6.0.1", + "svelte": "^5.10.0", + "vite": "^6.0.3", "vtf": "workspace:^" }, "type": "module" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index addbd0d..a8d270c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,10 +182,10 @@ importers: specifier: workspace:^ version: link:../../packages/client/src svelte: - specifier: ^5.9.0 + specifier: ^5.10.0 version: 5.10.0 vite: - specifier: ^6.0.1 + specifier: ^6.0.3 version: 6.0.3(@types/node@22.9.0)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.0) vtf: specifier: workspace:^ From 8e38353eee3a11a3e071b3732a6dcd8dbed9b9aa Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:25:24 +1100 Subject: [PATCH 07/15] Write flags as little endian --- packages/client/src/VTF/VTFDocument.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/VTF/VTFDocument.ts b/packages/client/src/VTF/VTFDocument.ts index ed703a7..f37b456 100644 --- a/packages/client/src/VTF/VTFDocument.ts +++ b/packages/client/src/VTF/VTFDocument.ts @@ -83,13 +83,13 @@ export class VTFDocument implements CustomDocument { } public save() { - new DataView(this.buf.buffer).setUint32(VTF_FLAGS_OFFSET, this.flags$.value) + new DataView(this.buf.buffer).setUint32(VTF_FLAGS_OFFSET, this.flags$.value, true) return this.buf } public saveAs() { const buf = new Uint8Array(this.buf) - new DataView(buf.buffer).setUint32(VTF_FLAGS_OFFSET, this.flags$.value) + new DataView(buf.buffer).setUint32(VTF_FLAGS_OFFSET, this.flags$.value, true) return buf } From bc1c5a7fdbdd55d1f6ceb330e0e81ce23cce5016 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:29:09 +1100 Subject: [PATCH 08/15] Post change to extension --- apps/vtf-editor/src/VTFViewer.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/vtf-editor/src/VTFViewer.svelte b/apps/vtf-editor/src/VTFViewer.svelte index 58383cc..6ee75f9 100644 --- a/apps/vtf-editor/src/VTFViewer.svelte +++ b/apps/vtf-editor/src/VTFViewer.svelte @@ -150,8 +150,12 @@ id != null && (flags & value) == value, () => (flags ^= value)} + checked={id != null && (flags & value) == value} disabled={id == null} + onchange={() => { + flags ^= value + postMessage({ type: "flags", label: label!, value }) + }} /> {label ?? "Unused"} From 559818319429fac22cc095f5d574c7f0428136c6 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:44:55 +1100 Subject: [PATCH 09/15] Restore flags from backup --- packages/client/src/VTF/VTFDocument.ts | 7 ++++--- packages/client/src/VTF/VTFEditor.ts | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/client/src/VTF/VTFDocument.ts b/packages/client/src/VTF/VTFDocument.ts index f37b456..fe35a07 100644 --- a/packages/client/src/VTF/VTFDocument.ts +++ b/packages/client/src/VTF/VTFDocument.ts @@ -3,7 +3,8 @@ import { type CustomDocument, StatusBarAlignment, type StatusBarItem, type Uri, const VTF_WIDTH_OFFSET = 16 const VTF_HEIGHT_OFFSET = 18 -const VTF_FLAGS_OFFSET = 20 + +export const VTF_FLAGS_OFFSET = 20 const KB = 1024 const MB = KB * KB @@ -43,13 +44,13 @@ export class VTFDocument implements CustomDocument { private readonly dimensionsStatusBarItem: StatusBarItem private readonly binarySizeStatusBarItem: StatusBarItem - public constructor(uri: Uri, buf: Uint8Array) { + public constructor(uri: Uri, buf: Uint8Array, backup: number | null) { this.uri = uri this.buf = buf const dataView = new DataView(this.buf.buffer) - this.flags$ = new DistinctBehaviorSubject(dataView.getUint32(VTF_FLAGS_OFFSET, true)) + this.flags$ = new DistinctBehaviorSubject(backup ?? dataView.getUint32(VTF_FLAGS_OFFSET, true)) this.scale$ = new BehaviorSubject(100) let priority = 100 diff --git a/packages/client/src/VTF/VTFEditor.ts b/packages/client/src/VTF/VTFEditor.ts index 3703381..da8dfba 100644 --- a/packages/client/src/VTF/VTFEditor.ts +++ b/packages/client/src/VTF/VTFEditor.ts @@ -1,6 +1,6 @@ import { commands, EventEmitter, Uri, window, workspace, type CancellationToken, type CustomDocumentBackup, type CustomDocumentBackupContext, type CustomDocumentEditEvent, type CustomDocumentOpenContext, type CustomEditorProvider, type Event, type WebviewPanel } from "vscode" import { z } from "zod" -import { VTFDocument } from "./VTFDocument" +import { VTF_FLAGS_OFFSET, VTFDocument } from "./VTFDocument" export class VTFEditor implements CustomEditorProvider { @@ -55,7 +55,10 @@ export class VTFEditor implements CustomEditorProvider { } public async openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Promise { - return new VTFDocument(uri, new Uint8Array(await workspace.fs.readFile(uri))) + const flags = openContext.backupId != undefined + ? new DataView((await workspace.fs.readFile(Uri.parse(openContext.backupId))).buffer).getUint32(VTF_FLAGS_OFFSET, true) + : null + return new VTFDocument(uri, new Uint8Array(await workspace.fs.readFile(uri)), flags) } public async resolveCustomEditor(document: VTFDocument, webviewPanel: WebviewPanel, token: CancellationToken): Promise { From 7a0487c7ef3b46454c95ca37ffdfa8ba127b114f Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:45:43 +1100 Subject: [PATCH 10/15] Fix scale$ --- packages/client/src/VTF/VTFDocument.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/VTF/VTFDocument.ts b/packages/client/src/VTF/VTFDocument.ts index fe35a07..e8d2f06 100644 --- a/packages/client/src/VTF/VTFDocument.ts +++ b/packages/client/src/VTF/VTFDocument.ts @@ -51,7 +51,7 @@ export class VTFDocument implements CustomDocument { const dataView = new DataView(this.buf.buffer) this.flags$ = new DistinctBehaviorSubject(backup ?? dataView.getUint32(VTF_FLAGS_OFFSET, true)) - this.scale$ = new BehaviorSubject(100) + this.scale$ = new DistinctBehaviorSubject(100) let priority = 100 From 46fcc0b9b184cf18aee9fd7b9dd36ce3fa8f7258 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 18:47:48 +1100 Subject: [PATCH 11/15] Fix backup flags offset --- packages/client/src/VTF/VTFDocument.ts | 3 +-- packages/client/src/VTF/VTFEditor.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/client/src/VTF/VTFDocument.ts b/packages/client/src/VTF/VTFDocument.ts index e8d2f06..56321da 100644 --- a/packages/client/src/VTF/VTFDocument.ts +++ b/packages/client/src/VTF/VTFDocument.ts @@ -3,8 +3,7 @@ import { type CustomDocument, StatusBarAlignment, type StatusBarItem, type Uri, const VTF_WIDTH_OFFSET = 16 const VTF_HEIGHT_OFFSET = 18 - -export const VTF_FLAGS_OFFSET = 20 +const VTF_FLAGS_OFFSET = 20 const KB = 1024 const MB = KB * KB diff --git a/packages/client/src/VTF/VTFEditor.ts b/packages/client/src/VTF/VTFEditor.ts index da8dfba..4200aee 100644 --- a/packages/client/src/VTF/VTFEditor.ts +++ b/packages/client/src/VTF/VTFEditor.ts @@ -1,6 +1,6 @@ import { commands, EventEmitter, Uri, window, workspace, type CancellationToken, type CustomDocumentBackup, type CustomDocumentBackupContext, type CustomDocumentEditEvent, type CustomDocumentOpenContext, type CustomEditorProvider, type Event, type WebviewPanel } from "vscode" import { z } from "zod" -import { VTF_FLAGS_OFFSET, VTFDocument } from "./VTFDocument" +import { VTFDocument } from "./VTFDocument" export class VTFEditor implements CustomEditorProvider { @@ -56,7 +56,7 @@ export class VTFEditor implements CustomEditorProvider { public async openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Promise { const flags = openContext.backupId != undefined - ? new DataView((await workspace.fs.readFile(Uri.parse(openContext.backupId))).buffer).getUint32(VTF_FLAGS_OFFSET, true) + ? new DataView((await workspace.fs.readFile(Uri.parse(openContext.backupId))).buffer).getUint32(0, true) : null return new VTFDocument(uri, new Uint8Array(await workspace.fs.readFile(uri)), flags) } From 0326c2a8165b3263a4b16b94ff3c09405b101c46 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 19:00:37 +1100 Subject: [PATCH 12/15] Fix styles --- apps/vtf-editor/src/VTFViewer.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/vtf-editor/src/VTFViewer.svelte b/apps/vtf-editor/src/VTFViewer.svelte index 6ee75f9..410842d 100644 --- a/apps/vtf-editor/src/VTFViewer.svelte +++ b/apps/vtf-editor/src/VTFViewer.svelte @@ -245,7 +245,9 @@ } span { + width: 100%; padding-right: 2rem; + white-space: nowrap; } &:has(input[disabled]) { @@ -259,6 +261,7 @@ div#vtf-container { grid-row: span 2; margin-top: 9px; + overflow: scroll; :global(canvas) { --max: 90%; From 51e995e2771e0387e3135b0ec03d539a229cb4bf Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 20:55:50 +1100 Subject: [PATCH 13/15] Add document readonly + Disable UI when document is readonly --- apps/vtf-editor/index.html | 1 + apps/vtf-editor/src/App.svelte | 5 ++- apps/vtf-editor/src/VTFViewer.svelte | 53 ++++++++++++++++---------- apps/vtf-editor/src/index.ts | 3 +- packages/client/src/VTF/VTFDocument.ts | 4 +- packages/client/src/VTF/VTFEditor.ts | 10 ++++- 6 files changed, 49 insertions(+), 27 deletions(-) diff --git a/apps/vtf-editor/index.html b/apps/vtf-editor/index.html index 8209020..92c7e3f 100644 --- a/apps/vtf-editor/index.html +++ b/apps/vtf-editor/index.html @@ -3,6 +3,7 @@ + diff --git a/apps/vtf-editor/src/App.svelte b/apps/vtf-editor/src/App.svelte index 75fd36d..93a0409 100644 --- a/apps/vtf-editor/src/App.svelte +++ b/apps/vtf-editor/src/App.svelte @@ -6,10 +6,11 @@ interface Props { vscode: WebviewApi<{ flags: number; scale: number }> + readonly: boolean buf: Uint8Array } - const { vscode, buf }: Props = $props() + const { vscode, readonly, buf }: Props = $props() function postMessage(message: z.input<(typeof VTFEditor)["commandSchema"]>) { vscode.postMessage(message) @@ -23,5 +24,5 @@ postMessage({ type: "showErrorMessage", message: (error as Error).message, items: [] })} > - + diff --git a/apps/vtf-editor/src/VTFViewer.svelte b/apps/vtf-editor/src/VTFViewer.svelte index 410842d..e97515b 100644 --- a/apps/vtf-editor/src/VTFViewer.svelte +++ b/apps/vtf-editor/src/VTFViewer.svelte @@ -6,13 +6,14 @@ import { z } from "zod" interface Props { + readonly: boolean buf: Uint8Array initial: State | undefined postMessage: (message: z.input<(typeof VTFEditor)["commandSchema"]>) => void setState(newState: State): void } - const { buf, initial, postMessage, setState }: Props = $props() + const { readonly, buf, initial, postMessage, setState }: Props = $props() const vtf = new VTF(buf) const { width, height } = vtf.header @@ -146,19 +147,22 @@ {#snippet checkbox(label: string | null)} {@const id = label?.replaceAll(/\s/g, "-").toLowerCase()} {@const value = 1 << i++} - + {@const unused = id == null} +
+ +
{/snippet}
@@ -234,25 +238,32 @@ overflow: hidden scroll; scrollbar-width: thin; - label.checkbox-container { - display: flex; - align-items: center; - gap: 5px; - cursor: pointer; + div.checkbox-container { + label { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; + } > * { display: block; } - span { width: 100%; padding-right: 2rem; white-space: nowrap; } - &:has(input[disabled]) { + &.unused { opacity: 0.2; + } + + &.readonly { cursor: not-allowed; + label { + pointer-events: none; + } } } } diff --git a/apps/vtf-editor/src/index.ts b/apps/vtf-editor/src/index.ts index 10ccd08..b0588e8 100644 --- a/apps/vtf-editor/src/index.ts +++ b/apps/vtf-editor/src/index.ts @@ -7,7 +7,8 @@ import "./app.css" const app = mount(App, { target: document.getElementById("app")!, props: { - vscode: acquireVsCodeApi(), + vscode: acquireVsCodeApi(), + readonly: document.head.querySelector("meta[name=readonly]")?.content == "true", buf: await Promise.all([ init(), (await firstValueFrom(fromEvent>(window, "message"))).data diff --git a/packages/client/src/VTF/VTFDocument.ts b/packages/client/src/VTF/VTFDocument.ts index 56321da..2114427 100644 --- a/packages/client/src/VTF/VTFDocument.ts +++ b/packages/client/src/VTF/VTFDocument.ts @@ -35,6 +35,7 @@ class DistinctBehaviorSubject extends BehaviorSubject { export class VTFDocument implements CustomDocument { public readonly uri: Uri + public readonly readonly: boolean public readonly buf: Uint8Array public readonly flags$: DistinctBehaviorSubject public readonly scale$: DistinctBehaviorSubject @@ -43,8 +44,9 @@ export class VTFDocument implements CustomDocument { private readonly dimensionsStatusBarItem: StatusBarItem private readonly binarySizeStatusBarItem: StatusBarItem - public constructor(uri: Uri, buf: Uint8Array, backup: number | null) { + public constructor(uri: Uri, readonly: boolean, buf: Uint8Array, backup: number | null) { this.uri = uri + this.readonly = readonly this.buf = buf const dataView = new DataView(this.buf.buffer) diff --git a/packages/client/src/VTF/VTFEditor.ts b/packages/client/src/VTF/VTFEditor.ts index 4200aee..52b07ff 100644 --- a/packages/client/src/VTF/VTFEditor.ts +++ b/packages/client/src/VTF/VTFEditor.ts @@ -1,4 +1,4 @@ -import { commands, EventEmitter, Uri, window, workspace, type CancellationToken, type CustomDocumentBackup, type CustomDocumentBackupContext, type CustomDocumentEditEvent, type CustomDocumentOpenContext, type CustomEditorProvider, type Event, type WebviewPanel } from "vscode" +import { commands, EventEmitter, FilePermission, Uri, window, workspace, type CancellationToken, type CustomDocumentBackup, type CustomDocumentBackupContext, type CustomDocumentEditEvent, type CustomDocumentOpenContext, type CustomEditorProvider, type Event, type WebviewPanel } from "vscode" import { z } from "zod" import { VTFDocument } from "./VTFDocument" @@ -55,10 +55,15 @@ export class VTFEditor implements CustomEditorProvider { } public async openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Promise { + const stat = await workspace.fs.stat(uri) + const readonly = stat.permissions + ? (stat.permissions & FilePermission.Readonly) == FilePermission.Readonly + : false + const flags = openContext.backupId != undefined ? new DataView((await workspace.fs.readFile(Uri.parse(openContext.backupId))).buffer).getUint32(0, true) : null - return new VTFDocument(uri, new Uint8Array(await workspace.fs.readFile(uri)), flags) + return new VTFDocument(uri, readonly, new Uint8Array(await workspace.fs.readFile(uri)), flags) } public async resolveCustomEditor(document: VTFDocument, webviewPanel: WebviewPanel, token: CancellationToken): Promise { @@ -68,6 +73,7 @@ export class VTFEditor implements CustomEditorProvider { webviewPanel.webview.options = { enableScripts: true } webviewPanel.webview.html = html + .replaceAll("%READONLY%", `${document.readonly}`) .replaceAll("%BASE%", `${webviewPanel.webview.asWebviewUri(dist).toString()}/`) webviewPanel.webview.postMessage(document.buf) From d799619f9682704aa1795c3c99e89838d024c4d4 Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 20:56:03 +1100 Subject: [PATCH 14/15] Only show scrollbars when needed --- apps/vtf-editor/src/VTFViewer.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/vtf-editor/src/VTFViewer.svelte b/apps/vtf-editor/src/VTFViewer.svelte index e97515b..b84f298 100644 --- a/apps/vtf-editor/src/VTFViewer.svelte +++ b/apps/vtf-editor/src/VTFViewer.svelte @@ -272,7 +272,7 @@ div#vtf-container { grid-row: span 2; margin-top: 9px; - overflow: scroll; + overflow: auto; :global(canvas) { --max: 90%; From dee30a7c1dba4864c60899fd181c5640dcf99e0b Mon Sep 17 00:00:00 2001 From: cooolbros Date: Tue, 10 Dec 2024 21:10:23 +1100 Subject: [PATCH 15/15] Run install wasm-pack --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5eeb6e0..23c6ebf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -14,4 +14,5 @@ jobs: node-version: latest - uses: pnpm/action-setup@v4 - run: pnpm install + - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - run: pnpm build