From f76517a14dd96bf903b049dc154542f7210a58c5 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Sat, 26 Jun 2021 21:44:48 +0100 Subject: [PATCH 001/365] Add a movable canvas with matricies (#175) * Convert polygon and rectangle tool to kurbo::BezPath * Add glam * Add affine transform to elipse and remove circle * Format * Add svg group and add matrix for group * Convert all operations to use matricies * Work uses same transform as root * Format * Frontend fixed to render changes to working colors when changed from backend (#180) * Backend and Frontend modification to show working color mods * Remove comments & change precedence for tool and doc actions * Add keybind for resetting work colors * Minor Frontend changes * Remove early sample "greet" code * Add a contributing section to the project README * Add moving document around * Add document transform for tools * Update to GraphiteEditor's fork * Use write in foreach for rendering group / folder * Add missing TranslateDown action * Use points for line operation * Format * Add todo to change to shape's aspect ratio * Remove empty if * Initial pass at refactor * Fix polyline test * Use document message to modify document transform * Messages -> Operations * Transform layer * Format * Use DAffine2::IDENTITY * Clean up kurbo generation for line and rect * Use .into for rectangle points * Rename cols to transform * Rename other cols to transform * Add todo for into_iter * Remove unnecessary clone Co-authored-by: akshay1992kalbhor Co-authored-by: Keavon Chambers --- Cargo.lock | 11 +- core/document/Cargo.toml | 5 +- core/document/src/document.rs | 115 +++++++--------- core/document/src/layers/circle.rs | 32 ----- core/document/src/layers/ellipse.rs | 33 ++--- core/document/src/layers/folder.rs | 15 +- core/document/src/layers/line.rs | 32 +++-- core/document/src/layers/mod.rs | 74 ++++++++-- core/document/src/layers/polyline.rs | 36 +++-- core/document/src/layers/rect.rs | 41 +++--- core/document/src/layers/shape.rs | 74 +++++++--- core/document/src/lib.rs | 2 +- core/document/src/operation.rs | 35 ++--- core/document/src/shape_points.rs | 128 ------------------ core/editor/Cargo.toml | 1 + core/editor/src/communication/dispatcher.rs | 2 +- core/editor/src/document/document_file.rs | 3 +- .../src/document/document_message_handler.rs | 42 +++++- core/editor/src/frontend/layer_panel.rs | 1 - core/editor/src/input/input_mapper.rs | 3 + core/editor/src/tool/tools/ellipse.rs | 33 ++--- core/editor/src/tool/tools/line.rs | 30 ++-- core/editor/src/tool/tools/pen.rs | 29 ++-- core/editor/src/tool/tools/rectangle.rs | 26 ++-- core/editor/src/tool/tools/shape.rs | 36 ++--- 25 files changed, 394 insertions(+), 445 deletions(-) delete mode 100644 core/document/src/layers/circle.rs delete mode 100644 core/document/src/shape_points.rs diff --git a/Cargo.lock b/Cargo.lock index e6a513ba01..74e2147412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + [[package]] name = "graphite-cli" version = "0.1.0" @@ -50,6 +56,7 @@ version = "0.1.0" name = "graphite-document-core" version = "0.1.0" dependencies = [ + "glam", "kurbo", "log", "serde", @@ -60,6 +67,7 @@ name = "graphite-editor-core" version = "0.1.0" dependencies = [ "bitflags", + "glam", "graphite-document-core", "graphite-proc-macros", "log", @@ -111,8 +119,7 @@ dependencies = [ [[package]] name = "kurbo" version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30b1df631d23875f230ed3ddd1a88c231f269a04b2044eb6ca87e763b5f4c42" +source = "git+https://github.com/GraphiteEditor/kurbo.git?branch=bezpath-partial-eq#3f86bda9ba77d3b1279612bc5779e425f2d8714d" dependencies = [ "arrayvec", ] diff --git a/core/document/Cargo.toml b/core/document/Cargo.toml index 74f5e1ecd5..0f7882eab1 100644 --- a/core/document/Cargo.toml +++ b/core/document/Cargo.toml @@ -10,5 +10,8 @@ license = "Apache-2.0" [dependencies] log = "0.4" -kurbo = "0.8.0" +# TODO: Swich to kurbo release when derive `PartialEq` is available for BezPath +#kurbo = "0.8.0" +kurbo = {git="https://github.com/GraphiteEditor/kurbo.git", branch="bezpath-partial-eq"} serde = { version = "1.0", features = ["derive"] } +glam = "0.16" diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 7dc43e48fb..5ccbf6f6b9 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -1,12 +1,14 @@ +use glam::DAffine2; + use crate::{ - layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, PolyLine, Rect, Shape}, + layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape}, DocumentError, DocumentResponse, LayerId, Operation, }; #[derive(Debug, Clone, PartialEq)] pub struct Document { - pub root: layers::Folder, - pub work: Folder, + pub root: Layer, + pub work: Layer, pub work_mount_path: Vec, pub work_operations: Vec, pub work_mounted: bool, @@ -15,8 +17,8 @@ pub struct Document { impl Default for Document { fn default() -> Self { Self { - root: Folder::default(), - work: Folder::default(), + root: Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()), + work: Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()), work_mount_path: Vec::new(), work_operations: Vec::new(), work_mounted: false, @@ -41,8 +43,11 @@ impl Document { return; } if path.as_slice() == self.work_mount_path { - self.document_folder_mut(path).unwrap().render(svg); - self.work.render(svg); + // TODO: Handle if mounted in nested folders + let transform = self.document_folder(path).unwrap().transform; + self.document_folder_mut(path).unwrap().render_as_folder(svg); + self.work.transform = transform; + self.work.render_as_folder(svg); path.pop(); } let ids = self.folder(path).unwrap().layer_ids.clone(); @@ -68,10 +73,10 @@ impl Document { /// This function respects mounted folders and will thus not contain the layers already /// present in the document if a temporary folder is mounted on top. pub fn folder(&self, mut path: &[LayerId]) -> Result<&Folder, DocumentError> { - let mut root = &self.root; + let mut root = self.root.as_folder()?; if self.is_mounted(self.work_mount_path.as_slice(), path) { path = &path[self.work_mount_path.len()..]; - root = &self.work; + root = self.work.as_folder()?; } for id in path { root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?; @@ -87,9 +92,9 @@ impl Document { pub fn folder_mut(&mut self, mut path: &[LayerId]) -> Result<&mut Folder, DocumentError> { let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) { path = &path[self.work_mount_path.len()..]; - &mut self.work + self.work.as_folder_mut()? } else { - &mut self.root + self.root.as_folder_mut()? }; for id in path { root = root.folder_mut(*id).ok_or(DocumentError::LayerNotFound)?; @@ -101,10 +106,10 @@ impl Document { /// or if the requested layer is not of type folder. /// This function does **not** respect mounted folders and will always return the current /// state of the document, disregarding any temporary modifications. - pub fn document_folder(&self, path: &[LayerId]) -> Result<&Folder, DocumentError> { + pub fn document_folder(&self, path: &[LayerId]) -> Result<&Layer, DocumentError> { let mut root = &self.root; for id in path { - root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?; + root = root.as_folder()?.layer(*id).ok_or(DocumentError::LayerNotFound)?; } Ok(root) } @@ -114,10 +119,10 @@ impl Document { /// This function does **not** respect mounted folders and will always return the current /// state of the document, disregarding any temporary modifications. /// If you manually edit the folder you have to set the cache_dirty flag yourself. - pub fn document_folder_mut(&mut self, path: &[LayerId]) -> Result<&mut Folder, DocumentError> { + pub fn document_folder_mut(&mut self, path: &[LayerId]) -> Result<&mut Layer, DocumentError> { let mut root = &mut self.root; for id in path { - root = root.folder_mut(*id).ok_or(DocumentError::LayerNotFound)?; + root = root.as_folder_mut()?.layer_mut(*id).ok_or(DocumentError::LayerNotFound)?; } Ok(root) } @@ -137,7 +142,7 @@ impl Document { /// Replaces the layer at the specified `path` with `layer`. pub fn set_layer(&mut self, path: &[LayerId], layer: Layer) -> Result<(), DocumentError> { - let mut folder = &mut self.root; + let mut folder = self.root.as_folder_mut()?; if let Ok((path, id)) = split_path(path) { self.layer_mut(path)?.cache_dirty = true; folder = self.folder_mut(path)?; @@ -163,7 +168,7 @@ impl Document { pub fn delete(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { let (path, id) = split_path(path)?; let _ = self.layer_mut(path).map(|x| x.cache_dirty = true); - self.document_folder_mut(path)?.remove_layer(id)?; + self.document_folder_mut(path)?.as_folder_mut()?.remove_layer(id)?; Ok(()) } @@ -171,73 +176,46 @@ impl Document { /// reaction from the frontend, responses may be returned. pub fn handle_operation(&mut self, operation: Operation) -> Result>, DocumentError> { let responses = match &operation { - Operation::AddCircle { path, insert_index, cx, cy, r, style } => { - let id = self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((*cx, *cy), *r, *style))), *insert_index)?; + Operation::AddEllipse { path, insert_index, transform, style } => { + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new()), *transform, *style), *insert_index)?; let path = [path.clone(), vec![id]].concat(); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } - Operation::AddEllipse { - path, - insert_index, - cx, - cy, - rx, - ry, - rot, - style, - } => { - let id = self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((*cx, *cy), (*rx, *ry), *rot, *style))), *insert_index)?; + Operation::AddRect { path, insert_index, transform, style } => { + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new()), *transform, *style), *insert_index)?; let path = [path.clone(), vec![id]].concat(); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } - Operation::AddRect { - path, - insert_index, - x0, - y0, - x1, - y1, - style, - } => { - let id = self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; + Operation::AddLine { path, insert_index, transform, style } => { + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new()), *transform, *style), *insert_index)?; let path = [path.clone(), vec![id]].concat(); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } - Operation::AddLine { + Operation::AddPen { path, insert_index, - x0, - y0, - x1, - y1, + points, + transform, style, } => { - let id = self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; - let path = [path.clone(), vec![id]].concat(); - - Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) - } - Operation::AddPen { path, insert_index, points, style } => { - let points: Vec = points.iter().map(|&it| it.into()).collect(); - let polyline = PolyLine::new(points, *style); - self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline)), *insert_index)?; + let points: Vec = points.iter().map(|&it| it.into()).collect(); + let polyline = PolyLine::new(points); + self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline), *transform, *style), *insert_index)?; Some(vec![DocumentResponse::DocumentChanged]) } Operation::AddShape { path, insert_index, - x0, - y0, - x1, - y1, + transform, + equal_sides, sides, style, } => { - let s = Shape::new((*x0, *y0), (*x1, *y1), *sides, *style); - let id = self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), *insert_index)?; + let s = Shape::new(*equal_sides, *sides); + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s), *transform, *style), *insert_index)?; let path = [path.clone(), vec![id]].concat(); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) @@ -256,27 +234,34 @@ impl Document { Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: folder_path.to_vec() }]) } Operation::AddFolder { path } => { - self.set_layer(&path, Layer::new(LayerDataTypes::Folder(Folder::default())))?; + self.set_layer(&path, Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()))?; Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.clone() }]) } Operation::MountWorkingFolder { path } => { self.work_mount_path = path.clone(); self.work_operations.clear(); - self.work = Folder::default(); + self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()); self.work_mounted = true; None } + Operation::TransformLayer { path, transform } => { + let transform = self.root.transform * DAffine2::from_cols_array(&transform); + let layer = self.document_folder_mut(path).unwrap(); + layer.transform = transform; + layer.cache_dirty = true; + Some(vec![DocumentResponse::DocumentChanged]) + } Operation::DiscardWorkingFolder => { self.work_operations.clear(); self.work_mount_path = vec![]; - self.work = Folder::default(); + self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()); self.work_mounted = false; Some(vec![DocumentResponse::DocumentChanged]) } Operation::ClearWorkingFolder => { self.work_operations.clear(); - self.work = Folder::default(); + self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()); Some(vec![DocumentResponse::DocumentChanged]) } Operation::CommitTransaction => { @@ -286,7 +271,7 @@ impl Document { std::mem::swap(&mut ops, &mut self.work_operations); self.work_mounted = false; self.work_mount_path = vec![]; - self.work = Folder::default(); + self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()); let mut responses = vec![]; for operation in ops.into_iter() { if let Some(mut op_responses) = self.handle_operation(operation)? { diff --git a/core/document/src/layers/circle.rs b/core/document/src/layers/circle.rs deleted file mode 100644 index ad9f066f2a..0000000000 --- a/core/document/src/layers/circle.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::style; -use super::LayerData; - -use std::fmt::Write; - -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct Circle { - shape: kurbo::Circle, - style: style::PathStyle, -} - -impl Circle { - pub fn new(center: impl Into, radius: f64, style: style::PathStyle) -> Circle { - Circle { - shape: kurbo::Circle::new(center, radius), - style, - } - } -} - -impl LayerData for Circle { - fn render(&mut self, svg: &mut String) { - let _ = write!( - svg, - r#""#, - self.shape.center.x, - self.shape.center.y, - self.shape.radius, - self.style.render(), - ); - } -} diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index fe22124f50..56a07157c0 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -1,37 +1,24 @@ +use kurbo::Shape; + use super::style; use super::LayerData; use std::fmt::Write; #[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct Ellipse { - shape: kurbo::Ellipse, - style: style::PathStyle, -} +pub struct Ellipse {} impl Ellipse { - pub fn new(center: impl Into, radii: impl Into, rotation: f64, style: style::PathStyle) -> Ellipse { - Ellipse { - shape: kurbo::Ellipse::new(center, radii, rotation), - style, - } + pub fn new() -> Ellipse { + Ellipse {} } } impl LayerData for Ellipse { - fn render(&mut self, svg: &mut String) { - let kurbo::Vec2 { x: rx, y: ry } = self.shape.radii(); - let kurbo::Point { x: cx, y: cy } = self.shape.center(); - - let _ = write!( - svg, - r#""#, - rx, - ry, - cx, - cy, - self.shape.rotation().to_degrees(), - self.style.render(), - ); + fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.1) + } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { + let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } } diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index 71896e2ca4..bafff20fd2 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -1,6 +1,6 @@ use crate::{DocumentError, LayerId}; -use super::{Layer, LayerData, LayerDataTypes}; +use super::{style, Layer, LayerData, LayerDataTypes}; use std::fmt::Write; @@ -12,10 +12,21 @@ pub struct Folder { } impl LayerData for Folder { - fn render(&mut self, svg: &mut String) { + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, _style: style::PathStyle) { + let _ = writeln!(svg, r#""#); + for layer in &mut self.layers { let _ = writeln!(svg, "{}", layer.render()); } + let _ = writeln!(svg, ""); + } + + fn to_kurbo_path(&mut self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath { + unimplemented!() } } diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index e4b62aeabf..7595380fce 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,28 +1,34 @@ +use glam::DVec2; +use kurbo::Point; + use super::style; use super::LayerData; use std::fmt::Write; #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Line { - shape: kurbo::Line, - style: style::PathStyle, -} +pub struct Line {} impl Line { - pub fn new(p0: impl Into, p1: impl Into, style: style::PathStyle) -> Line { - Line { - shape: kurbo::Line::new(p0, p1), - style, - } + pub fn new() -> Line { + Line {} } } impl LayerData for Line { - fn render(&mut self, svg: &mut String) { - let kurbo::Point { x: x1, y: y1 } = self.shape.p0; - let kurbo::Point { x: x2, y: y2 } = self.shape.p1; + fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + fn new_point(a: DVec2) -> Point { + Point::new(a.x, a.y) + } + let mut path = kurbo::BezPath::new(); + path.move_to(new_point(transform.translation)); + path.line_to(new_point(transform.transform_point2(DVec2::ONE))); + path + } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { + let [x1, y1] = transform.translation.to_array(); + let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array(); - let _ = write!(svg, r#""#, x1, y1, x2, y2, self.style.render(),); + let _ = write!(svg, r#""#, x1, y1, x2, y2, style.render(),); } } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index e107249da1..2ee4ad8c10 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -1,12 +1,10 @@ pub mod style; -pub mod circle; -pub use circle::Circle; - pub mod ellipse; pub use ellipse::Ellipse; pub mod line; +use kurbo::BezPath; pub use line::Line; pub mod rect; @@ -21,14 +19,16 @@ pub use shape::Shape; pub mod folder; pub use folder::Folder; +use crate::DocumentError; + pub trait LayerData { - fn render(&mut self, svg: &mut String); + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); + fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; } #[derive(Debug, Clone, PartialEq)] pub enum LayerDataTypes { Folder(Folder), - Circle(Circle), Ellipse(Ellipse), Rect(Rect), Line(Line), @@ -37,19 +37,36 @@ pub enum LayerDataTypes { } macro_rules! call_render { - ($self:ident.render($svg:ident) { $($variant:ident),* }) => { + ($self:ident.render($svg:ident, $transform:ident, $style:ident) { $($variant:ident),* }) => { match $self { - $(Self::$variant(x) => x.render($svg)),* + $(Self::$variant(x) => x.render($svg, $transform, $style)),* + } + }; +} +macro_rules! call_kurbo_path { + ($self:ident.to_kurbo_path($transform:ident, $style:ident) { $($variant:ident),* }) => { + match $self { + $(Self::$variant(x) => x.to_kurbo_path($transform, $style)),* } }; } - impl LayerDataTypes { - pub fn render(&mut self, svg: &mut String) { + pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { call_render! { - self.render(svg) { + self.render(svg, transform, style) { + Folder, + Ellipse, + Rect, + Line, + PolyLine, + Shape + } + } + } + pub fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath { + call_kurbo_path! { + self.to_kurbo_path(transform, style) { Folder, - Circle, Ellipse, Rect, Line, @@ -65,16 +82,20 @@ pub struct Layer { pub visible: bool, pub name: Option, pub data: LayerDataTypes, + pub transform: glam::DAffine2, + pub style: style::PathStyle, pub cache: String, pub cache_dirty: bool, } impl Layer { - pub fn new(data: LayerDataTypes) -> Self { + pub fn new(data: LayerDataTypes, transform: [f64; 6], style: style::PathStyle) -> Self { Self { visible: true, name: None, data, + transform: glam::DAffine2::from_cols_array(&transform), + style: style, cache: String::new(), cache_dirty: true, } @@ -86,9 +107,36 @@ impl Layer { } if self.cache_dirty { self.cache.clear(); - self.data.render(&mut self.cache); + self.data.render(&mut self.cache, self.transform, self.style); self.cache_dirty = false; } self.cache.as_str() } + + pub fn render_on(&mut self, svg: &mut String) { + *svg += self.render(); + } + + pub fn to_kurbo_path(&mut self) -> BezPath { + self.data.to_kurbo_path(self.transform, self.style) + } + pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> { + match &mut self.data { + LayerDataTypes::Folder(f) => Ok(f), + _ => Err(DocumentError::NotAFolder), + } + } + pub fn as_folder(&self) -> Result<&Folder, DocumentError> { + match &self.data { + LayerDataTypes::Folder(f) => Ok(&f), + _ => Err(DocumentError::NotAFolder), + } + } + + pub fn render_as_folder(&mut self, svg: &mut String) { + match &mut self.data { + LayerDataTypes::Folder(f) => f.render(svg, self.transform, self.style), + _ => {} + } + } } diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 38e4364581..4892e0320f 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,48 +1,56 @@ -use super::style; -use super::LayerData; - use std::fmt::Write; +use super::{style, LayerData}; + #[derive(Debug, Clone, PartialEq)] pub struct PolyLine { - points: Vec, - style: style::PathStyle, + points: Vec, } impl PolyLine { - pub fn new(points: Vec>, style: style::PathStyle) -> PolyLine { + pub fn new(points: Vec>) -> PolyLine { PolyLine { points: points.into_iter().map(|it| it.into()).collect(), - style, } } } impl LayerData for PolyLine { - fn render(&mut self, svg: &mut String) { + fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + let mut path = kurbo::BezPath::new(); + self.points + .iter() + .map(|v| transform.transform_point2(*v)) + .map(|v| kurbo::Point { x: v.x, y: v.y }) + .enumerate() + .for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) }); + path + } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { if self.points.is_empty() { return; } let _ = write!(svg, r#""#, self.style.render()); + let _ = write!(svg, r#""{} />"#, style.render()); } } #[cfg(test)] #[test] fn polyline_should_render() { + use super::style::PathStyle; + use glam::DVec2; let mut polyline = PolyLine { - points: vec![kurbo::Point::new(3.0, 4.12354), kurbo::Point::new(1.0, 5.54)], - style: style::PathStyle::new(Some(style::Stroke::new(crate::color::Color::GREEN, 0.4)), None), + points: vec![DVec2::new(3.0, 4.12354), DVec2::new(1.0, 5.54)], }; let mut svg = String::new(); - polyline.render(&mut svg); - assert_eq!(r##""##, svg); + polyline.render(&mut svg, glam::DAffine2::IDENTITY, PathStyle::default()); + assert_eq!(r##""##, svg); } diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index e38bcd1294..503430ddad 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -1,33 +1,34 @@ +use glam::DVec2; +use kurbo::Point; + use super::style; use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Rect { - shape: kurbo::Rect, - style: style::PathStyle, -} +#[derive(Debug, Clone, PartialEq)] +pub struct Rect {} impl Rect { - pub fn new(p0: impl Into, p1: impl Into, style: style::PathStyle) -> Rect { - Rect { - shape: kurbo::Rect::from_points(p0, p1), - style, - } + pub fn new() -> Rect { + Rect {} } } impl LayerData for Rect { - fn render(&mut self, svg: &mut String) { - let _ = write!( - svg, - r#""#, - self.shape.min_x(), - self.shape.min_y(), - self.shape.width(), - self.shape.height(), - self.style.render(), - ); + fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + fn new_point(a: DVec2) -> Point { + Point::new(a.x, a.y) + } + let mut path = kurbo::BezPath::new(); + path.move_to(new_point(transform.translation)); + + // TODO: Use into_iter when new impls get added in rust 2021 + [(1., 0.), (1., 1.), (0., 1.)].iter().for_each(|v| path.line_to(new_point(transform.transform_point2((*v).into())))); + path.close_path(); + path + } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { + let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index c48ad514f4..d00134287a 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -1,38 +1,68 @@ -use crate::shape_points; +use kurbo::BezPath; +use kurbo::Vec2; use super::style; use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Shape { - bounding_rect: kurbo::Rect, - shape: shape_points::ShapePoints, - style: style::PathStyle, + equal_sides: bool, + sides: u8, } impl Shape { - pub fn new(p0: impl Into, p1: impl Into, sides: u8, style: style::PathStyle) -> Shape { - Shape { - bounding_rect: kurbo::Rect::from_points(p0, p1), - shape: shape_points::ShapePoints::new(kurbo::Point::new(0.5, 0.5), kurbo::Vec2::new(0.5, 0.0), sides), - style, - } + pub fn new(equal_sides: bool, sides: u8) -> Shape { + Shape { equal_sides, sides } } } impl LayerData for Shape { - fn render(&mut self, svg: &mut String) { - let _ = write!( - svg, - r#""#, - self.shape, - self.bounding_rect.origin().x, - self.bounding_rect.origin().y, - self.bounding_rect.width(), - self.bounding_rect.height(), - self.style.render(), - ); + fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath { + fn unit_rotation(theta: f64) -> Vec2 { + Vec2::new(-theta.sin(), theta.cos()) + } + let extent = Vec2::new((transform.x_axis.x + transform.x_axis.y) / 2., (transform.y_axis.x + transform.y_axis.y) / 2.); + let translation = transform.translation; + let mut path = kurbo::BezPath::new(); + let apothem_offset_angle = std::f64::consts::PI / (self.sides as f64); + + let relative_points = (0..self.sides) + .map(|i| apothem_offset_angle * ((i * 2 + ((self.sides + 1) % 2)) as f64)) + .map(|radians| unit_rotation(radians)); + + let (mut min_x, mut min_y, mut max_x, mut max_y) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN); + relative_points.clone().for_each(|p| { + min_x = min_x.min(p.x); + min_y = min_y.min(p.y); + max_x = max_x.max(p.x); + max_y = max_y.max(p.y); + }); + + relative_points + .map(|p| { + if self.equal_sides { + p + } else { + Vec2::new((p.x - min_x) / (max_x - min_x) * 2. - 1., (p.y - min_y) / (max_y - min_y) * 2. - 1.) + } + }) + .map(|unit| Vec2::new(-unit.x * extent.x + translation.x + extent.x, -unit.y * extent.y + translation.y + extent.y)) + .map(|pos| (pos).to_point()) + .enumerate() + .for_each(|(i, p)| { + if i == 0 { + path.move_to(p); + } else { + path.line_to(p); + } + }); + + path.close_path(); + path + } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { + let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } } diff --git a/core/document/src/lib.rs b/core/document/src/lib.rs index 171983d053..d933bf42b1 100644 --- a/core/document/src/lib.rs +++ b/core/document/src/lib.rs @@ -3,7 +3,6 @@ pub mod document; pub mod layers; pub mod operation; pub mod response; -mod shape_points; pub use operation::Operation; pub use response::DocumentResponse; @@ -15,4 +14,5 @@ pub enum DocumentError { LayerNotFound, InvalidPath, IndexOutOfBounds, + NotAFolder, } diff --git a/core/document/src/operation.rs b/core/document/src/operation.rs index e63ada01ae..960ce35ce7 100644 --- a/core/document/src/operation.rs +++ b/core/document/src/operation.rs @@ -5,44 +5,27 @@ use serde::{Deserialize, Serialize}; #[repr(C)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum Operation { - AddCircle { - path: Vec, - insert_index: isize, - cx: f64, - cy: f64, - r: f64, - style: style::PathStyle, - }, AddEllipse { path: Vec, insert_index: isize, - cx: f64, - cy: f64, - rx: f64, - ry: f64, - rot: f64, + transform: [f64; 6], style: style::PathStyle, }, AddRect { path: Vec, insert_index: isize, - x0: f64, - y0: f64, - x1: f64, - y1: f64, + transform: [f64; 6], style: style::PathStyle, }, AddLine { path: Vec, insert_index: isize, - x0: f64, - y0: f64, - x1: f64, - y1: f64, + transform: [f64; 6], style: style::PathStyle, }, AddPen { path: Vec, + transform: [f64; 6], insert_index: isize, points: Vec<(f64, f64)>, style: style::PathStyle, @@ -50,10 +33,8 @@ pub enum Operation { AddShape { path: Vec, insert_index: isize, - x0: f64, - y0: f64, - x1: f64, - y1: f64, + transform: [f64; 6], + equal_sides: bool, sides: u8, style: style::PathStyle, }, @@ -69,6 +50,10 @@ pub enum Operation { MountWorkingFolder { path: Vec, }, + TransformLayer { + path: Vec, + transform: [f64; 6], + }, DiscardWorkingFolder, ClearWorkingFolder, CommitTransaction, diff --git a/core/document/src/shape_points.rs b/core/document/src/shape_points.rs deleted file mode 100644 index 4b351bd568..0000000000 --- a/core/document/src/shape_points.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{fmt, ops::Add}; - -use kurbo::{PathEl, Point, Vec2}; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ShapePoints { - center: kurbo::Point, - extent: kurbo::Vec2, - sides: u8, -} - -impl ShapePoints { - /// A new shape from center, a point and the number of points. - #[inline] - pub fn new(center: impl Into, extent: impl Into, sides: u8) -> ShapePoints { - ShapePoints { - center: center.into(), - extent: extent.into(), - sides, - } - } - - // Gets the angle in radians between the longest line from the center and the apothem. - #[inline] - pub fn apothem_offset_angle(&self) -> f64 { - std::f64::consts::PI / (self.sides as f64) - } - - // Gets the apothem (the shortest distance from the center to the edge) - #[inline] - pub fn apothem(&self) -> f64 { - self.apothem_offset_angle().cos() * (self.sides as f64) - } - - // Gets the length of one side - #[inline] - pub fn side_length(&self) -> f64 { - self.apothem_offset_angle().sin() * (self.sides as f64) * 2f64 - } -} - -// TODO: The display impl and iter impl share large amounts of code and should be refactored. (Display should use the Iterator) -// TODO: Once that is done, the trailing space from the display impl should be removed -// Also consider implementing index -impl std::fmt::Display for ShapePoints { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn rotate(v: &Vec2, theta: f64) -> Vec2 { - let cosine = theta.cos(); - let sine = theta.sin(); - Vec2::new(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine) - } - for i in 0..self.sides { - let radians = self.apothem_offset_angle() * ((i * 2 + (self.sides % 2)) as f64); - let offset = rotate(&self.extent, radians); - let point = self.center + offset; - write!(f, "{},{} ", point.x, point.y)?; - } - - Ok(()) - } -} - -#[doc(hidden)] -pub struct ShapePathIter { - shape: ShapePoints, - index: usize, -} - -impl Iterator for ShapePathIter { - type Item = PathEl; - - fn next(&mut self) -> Option { - fn rotate(v: &Vec2, theta: f64) -> Vec2 { - let cosine = theta.cos(); - let sine = theta.sin(); - Vec2::new(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine) - } - self.index += 1; - match self.index { - 1 => Some(PathEl::MoveTo(self.shape.center + self.shape.extent)), - _ => { - let radians = self.shape.apothem_offset_angle() * ((self.index * 2 + (self.shape.sides % 2) as usize) as f64); - let offset = rotate(&self.shape.extent, radians); - let point = self.shape.center + offset; - Some(PathEl::LineTo(point)) - } - } - } -} - -impl Add for ShapePoints { - type Output = ShapePoints; - - #[inline] - fn add(self, movement: Vec2) -> ShapePoints { - ShapePoints { - center: self.center + movement, - extent: self.extent, - sides: self.sides, - } - } -} - -impl kurbo::Shape for ShapePoints { - type PathElementsIter = ShapePathIter; - - fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter { - todo!() - } - - #[inline] - fn area(&self) -> f64 { - self.apothem() * self.perimeter(2.1) - } - - #[inline] - fn perimeter(&self, _accuracy: f64) -> f64 { - self.side_length() * (self.sides as f64) - } - - fn winding(&self, _pt: Point) -> i32 { - todo!() - } - - fn bounding_box(&self) -> kurbo::Rect { - todo!() - } -} diff --git a/core/editor/Cargo.toml b/core/editor/Cargo.toml index fe90d2043d..aa701dc168 100644 --- a/core/editor/Cargo.toml +++ b/core/editor/Cargo.toml @@ -14,6 +14,7 @@ bitflags = "1.2.1" thiserror = "1.0.24" serde = { version = "1.0", features = ["derive"] } graphite-proc-macros = {path = "../proc-macro"} +glam = "0.16" [dependencies.document-core] path = "../document" diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index 644bf93891..7081b17ca0 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -34,7 +34,7 @@ impl Dispatcher { } match message { NoOp => (), - Document(message) => self.document_message_handler.process_action(message, (), &mut self.messages), + Document(message) => self.document_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages), Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages), Tool(message) => self .tool_message_handler diff --git a/core/editor/src/document/document_file.rs b/core/editor/src/document/document_file.rs index d724a91363..fb32e35610 100644 --- a/core/editor/src/document/document_file.rs +++ b/core/editor/src/document/document_file.rs @@ -60,9 +60,10 @@ impl Document { let folder = self.document.document_folder(path)?; let self_layer_data = &mut self.layer_data; let entries = folder + .as_folder()? .layers() .iter() - .zip(folder.layer_ids.iter()) + .zip(folder.as_folder()?.layer_ids.iter()) .rev() .map(|(layer, id)| { let path = [path, &[*id]].concat(); diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 81b642053a..b449ca6509 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -1,5 +1,10 @@ -use crate::message_prelude::*; +use crate::{ + input::{mouse::ViewportPosition, InputPreprocessor}, + message_prelude::*, +}; use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation}; +use glam::{DAffine2, DVec2}; +use log::info; use crate::document::Document; use std::collections::VecDeque; @@ -25,6 +30,9 @@ pub enum DocumentMessage { ExportDocument, RenderDocument, Undo, + MouseMove, + TranslateDown, + TranslateUp, } impl From for DocumentMessage { @@ -42,6 +50,8 @@ impl From for Message { pub struct DocumentMessageHandler { documents: Vec, active_document: usize, + mmb_down: bool, + mouse_pos: ViewportPosition, } impl DocumentMessageHandler { @@ -78,12 +88,14 @@ impl Default for DocumentMessageHandler { Self { documents: vec![Document::default()], active_document: 0, + mmb_down: false, + mouse_pos: ViewportPosition::default(), } } } -impl MessageHandler for DocumentMessageHandler { - fn process_action(&mut self, message: DocumentMessage, _data: (), responses: &mut VecDeque) { +impl MessageHandler for DocumentMessageHandler { + fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessor, responses: &mut VecDeque) { use DocumentMessage::*; match message { DeleteLayer(path) => responses.push_back(DocumentOperation::DeleteLayer { path }.into()), @@ -224,7 +236,7 @@ impl MessageHandler for DocumentMessageHandler { } Undo => { // this is a temporary fix and will be addressed by #123 - if let Some(id) = self.active_document().document.root.list_layers().last() { + if let Some(id) = self.active_document().document.root.as_folder().unwrap().list_layers().last() { responses.push_back(DocumentOperation::DeleteLayer { path: vec![*id] }.into()) } } @@ -259,14 +271,32 @@ impl MessageHandler for DocumentMessageHandler { } .into(), ), + TranslateDown => { + self.mmb_down = true; + self.mouse_pos = ipp.mouse.position; + } + TranslateUp => { + self.mmb_down = false; + } + MouseMove => { + if self.mmb_down { + let delta = DVec2::new(ipp.mouse.position.x as f64 - self.mouse_pos.x as f64, ipp.mouse.position.y as f64 - self.mouse_pos.y as f64); + let operation = DocumentOperation::TransformLayer { + path: vec![], + transform: DAffine2::from_translation(delta).to_cols_array(), + }; + responses.push_back(operation.into()); + self.mouse_pos = ipp.mouse.position; + } + } message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()), } } fn actions(&self) -> ActionList { if self.active_document().layer_data.values().any(|data| data.selected) { - actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument) + actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown) } else { - actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument) + actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown) } } } diff --git a/core/editor/src/frontend/layer_panel.rs b/core/editor/src/frontend/layer_panel.rs index f60f2156e1..01cf24beb9 100644 --- a/core/editor/src/frontend/layer_panel.rs +++ b/core/editor/src/frontend/layer_panel.rs @@ -45,7 +45,6 @@ impl From<&LayerDataTypes> for LayerType { match data { Folder(_) => LayerType::Folder, Shape(_) => LayerType::Shape, - Circle(_) => LayerType::Circle, Rect(_) => LayerType::Rect, Line(_) => LayerType::Line, PolyLine(_) => LayerType::PolyLine, diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 1abb4f406d..91d06e307f 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -168,6 +168,9 @@ impl Default for Mapping { entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]}, + entry! {action=DocumentMessage::MouseMove, message=InputMapperMessage::PointerMove}, + entry! {action=DocumentMessage::TranslateDown, key_down=Mmb}, + entry! {action=DocumentMessage::TranslateUp, key_up=Mmb}, entry! {action=DocumentMessage::NewDocument, key_down=KeyN, modifiers=[KeyShift]}, entry! {action=DocumentMessage::NextDocument, key_down=KeyTab, modifiers=[KeyShift]}, entry! {action=DocumentMessage::CloseActiveDocument, key_down=KeyW, modifiers=[KeyShift]}, diff --git a/core/editor/src/tool/tools/ellipse.rs b/core/editor/src/tool/tools/ellipse.rs index dc16f0eb37..f792710916 100644 --- a/core/editor/src/tool/tools/ellipse.rs +++ b/core/editor/src/tool/tools/ellipse.rs @@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; use document_core::{layers::style, Operation}; +use glam::{DAffine2, DVec2}; #[derive(Default)] pub struct Ellipse { @@ -58,7 +59,8 @@ struct EllipseToolData { impl Fsm for EllipseToolFsmState { type ToolData = EllipseToolData; - fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + let transform = document.root.transform; use EllipseMessage::*; use EllipseToolFsmState::*; if let ToolMessage::Ellipse(event) = event { @@ -73,7 +75,7 @@ impl Fsm for EllipseToolFsmState { data.drag_current = input.mouse.position; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); Dragging } @@ -83,7 +85,7 @@ impl Fsm for EllipseToolFsmState { responses.push_back(Operation::ClearWorkingFolder.into()); // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) if data.drag_start != data.drag_current { - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); responses.push_back(Operation::CommitTransaction.into()); } @@ -97,13 +99,13 @@ impl Fsm for EllipseToolFsmState { } (Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_circle, true, Ready), (Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_circle, false, Ready), - (Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, true, tool_data, data, responses, Dragging), - (Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, false, tool_data, data, responses, Dragging), + (Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, false, tool_data, data, responses, Dragging, transform), (Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready), (Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready), - (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging), - (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging), + (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform), _ => self, } } else { @@ -124,16 +126,17 @@ fn update_state( data: &mut EllipseToolData, responses: &mut VecDeque, new_state: EllipseToolFsmState, + transform: DAffine2, ) -> EllipseToolFsmState { *(state(data)) = value; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(&data, tool_data)); + responses.push_back(make_operation(&data, tool_data, transform)); new_state } -fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Message { +fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message { let x0 = data.drag_start.x as f64; let y0 = data.drag_start.y as f64; let x1 = data.drag_current.x as f64; @@ -147,12 +150,10 @@ fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Messa let (x2, y2) = (x0 + (x1 - x0).signum() * diameter, y0 + (y1 - y0).signum() * diameter); ((x0 + x2) * 0.5, (y0 + y2) * 0.5, diameter * 0.5) }; - Operation::AddCircle { + Operation::AddEllipse { path: vec![], insert_index: -1, - cx, - cy, - r, + transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(r, r), 0., DVec2::new(cx, cy))).to_cols_array(), style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), } } else { @@ -161,11 +162,7 @@ fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Messa Operation::AddEllipse { path: vec![], insert_index: -1, - cx, - cy, - rx, - ry, - rot: 0.0, + transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(rx, ry), 0., DVec2::new(cx, cy))).to_cols_array(), style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), } } diff --git a/core/editor/src/tool/tools/line.rs b/core/editor/src/tool/tools/line.rs index 27d392645e..61383c7672 100644 --- a/core/editor/src/tool/tools/line.rs +++ b/core/editor/src/tool/tools/line.rs @@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; use document_core::{layers::style, Operation}; +use glam::{DAffine2, DVec2}; use std::f64::consts::PI; @@ -63,7 +64,8 @@ struct LineToolData { impl Fsm for LineToolFsmState { type ToolData = LineToolData; - fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + let transform = document.root.transform; use LineMessage::*; use LineToolFsmState::*; if let ToolMessage::Line(event) = event { @@ -80,7 +82,7 @@ impl Fsm for LineToolFsmState { data.drag_current = input.mouse.position; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); Dragging } @@ -90,7 +92,7 @@ impl Fsm for LineToolFsmState { responses.push_back(Operation::ClearWorkingFolder.into()); // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) if data.drag_start != data.drag_current { - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); responses.push_back(Operation::CommitTransaction.into()); } @@ -104,18 +106,18 @@ impl Fsm for LineToolFsmState { } (Ready, LockAngle) => update_state_no_op(&mut data.lock_angle, true, Ready), (Ready, UnlockAngle) => update_state_no_op(&mut data.lock_angle, false, Ready), - (Dragging, LockAngle) => update_state(|data| &mut data.lock_angle, true, tool_data, data, responses, Dragging), - (Dragging, UnlockAngle) => update_state(|data| &mut data.lock_angle, false, tool_data, data, responses, Dragging), + (Dragging, LockAngle) => update_state(|data| &mut data.lock_angle, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnlockAngle) => update_state(|data| &mut data.lock_angle, false, tool_data, data, responses, Dragging, transform), (Ready, SnapToAngle) => update_state_no_op(&mut data.snap_angle, true, Ready), (Ready, UnSnapToAngle) => update_state_no_op(&mut data.snap_angle, false, Ready), - (Dragging, SnapToAngle) => update_state(|data| &mut data.snap_angle, true, tool_data, data, responses, Dragging), - (Dragging, UnSnapToAngle) => update_state(|data| &mut data.snap_angle, false, tool_data, data, responses, Dragging), + (Dragging, SnapToAngle) => update_state(|data| &mut data.snap_angle, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnSnapToAngle) => update_state(|data| &mut data.snap_angle, false, tool_data, data, responses, Dragging, transform), (Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready), (Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready), - (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging), - (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging), + (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform), _ => self, } } else { @@ -136,16 +138,17 @@ fn update_state( data: &mut LineToolData, responses: &mut VecDeque, new_state: LineToolFsmState, + transform: DAffine2, ) -> LineToolFsmState { *(state(data)) = value; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); new_state } -fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData) -> Message { +fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message { let x0 = data.drag_start.x as f64; let y0 = data.drag_start.y as f64; let x1 = data.drag_current.x as f64; @@ -174,10 +177,7 @@ fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData) -> Mess Operation::AddLine { path: vec![], insert_index: -1, - x0, - y0, - x1, - y1, + transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(), style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), None), } .into() diff --git a/core/editor/src/tool/tools/pen.rs b/core/editor/src/tool/tools/pen.rs index eae68b8c94..6a11b8bc78 100644 --- a/core/editor/src/tool/tools/pen.rs +++ b/core/editor/src/tool/tools/pen.rs @@ -1,7 +1,8 @@ -use crate::input::{mouse::ViewportPosition, InputPreprocessor}; +use crate::input::InputPreprocessor; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; use document_core::{layers::style, Operation}; +use glam::{DAffine2, DVec2}; #[derive(Default)] pub struct Pen { @@ -46,14 +47,17 @@ impl Default for PenToolFsmState { } #[derive(Clone, Debug, Default)] struct PenToolData { - points: Vec, - next_point: ViewportPosition, + points: Vec, + next_point: DAffine2, } impl Fsm for PenToolFsmState { type ToolData = PenToolData; - fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + let transform = document.root.transform; + let pos = transform.inverse() * DAffine2::from_translation(DVec2::new(input.mouse.position.x as f64, input.mouse.position.y as f64)); + use PenMessage::*; use PenToolFsmState::*; if let ToolMessage::Pen(event) = event { @@ -61,16 +65,16 @@ impl Fsm for PenToolFsmState { (Ready, DragStart) => { responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into()); - data.points.push(input.mouse.position); - data.next_point = input.mouse.position; + data.points.push(pos); + data.next_point = pos; Dragging } (Dragging, DragStop) => { // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) - if data.points.last() != Some(&input.mouse.position) { - data.points.push(input.mouse.position); - data.next_point = input.mouse.position; + if data.points.last() != Some(&pos) { + data.points.push(pos); + data.next_point = pos; } responses.push_back(Operation::ClearWorkingFolder.into()); @@ -79,7 +83,7 @@ impl Fsm for PenToolFsmState { Dragging } (Dragging, MouseMove) => { - data.next_point = input.mouse.position; + data.next_point = pos; responses.push_back(Operation::ClearWorkingFolder.into()); responses.push_back(make_operation(data, tool_data, true)); @@ -116,13 +120,14 @@ impl Fsm for PenToolFsmState { } fn make_operation(data: &PenToolData, tool_data: &DocumentToolData, show_preview: bool) -> Message { - let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x as f64, p.y as f64)).collect(); + let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.translation.x, p.translation.y)).collect(); if show_preview { - points.push((data.next_point.x as f64, data.next_point.y as f64)) + points.push((data.next_point.translation.x, data.next_point.translation.y)) } Operation::AddPen { path: vec![], insert_index: -1, + transform: DAffine2::IDENTITY.to_cols_array(), points, style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), Some(style::Fill::none())), } diff --git a/core/editor/src/tool/tools/rectangle.rs b/core/editor/src/tool/tools/rectangle.rs index 0b3c1aa3b0..5f9fee452f 100644 --- a/core/editor/src/tool/tools/rectangle.rs +++ b/core/editor/src/tool/tools/rectangle.rs @@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; use document_core::{layers::style, Operation}; +use glam::{DAffine2, DVec2}; #[derive(Default)] pub struct Rectangle { @@ -57,7 +58,8 @@ struct RectangleToolData { impl Fsm for RectangleToolFsmState { type ToolData = RectangleToolData; - fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + let transform = document.root.transform; use RectangleMessage::*; use RectangleToolFsmState::*; if let ToolMessage::Rectangle(event) = event { @@ -72,7 +74,7 @@ impl Fsm for RectangleToolFsmState { data.drag_current = input.mouse.position; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); Dragging } @@ -82,7 +84,7 @@ impl Fsm for RectangleToolFsmState { responses.push_back(Operation::ClearWorkingFolder.into()); // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) if data.drag_start != data.drag_current { - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); responses.push_back(Operation::CommitTransaction.into()); } @@ -96,13 +98,13 @@ impl Fsm for RectangleToolFsmState { } (Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, true, Ready), (Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, false, Ready), - (Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging), - (Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging), + (Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging, transform), (Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready), (Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready), - (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging), - (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging), + (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform), _ => self, } } else { @@ -123,16 +125,17 @@ fn update_state( data: &mut RectangleToolData, responses: &mut VecDeque, new_state: RectangleToolFsmState, + transform: DAffine2, ) -> RectangleToolFsmState { *(state(data)) = value; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); new_state } -fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData) -> Message { +fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message { let x0 = data.drag_start.x as f64; let y0 = data.drag_start.y as f64; let x1 = data.drag_current.x as f64; @@ -161,10 +164,7 @@ fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData) -> Mes Operation::AddRect { path: vec![], insert_index: -1, - x0, - y0, - x1, - y1, + transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(), style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), } .into() diff --git a/core/editor/src/tool/tools/shape.rs b/core/editor/src/tool/tools/shape.rs index 3e929b4901..da34d752e7 100644 --- a/core/editor/src/tool/tools/shape.rs +++ b/core/editor/src/tool/tools/shape.rs @@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; use document_core::{layers::style, Operation}; +use glam::{DAffine2, DVec2}; #[derive(Default)] pub struct Shape { @@ -59,7 +60,8 @@ struct ShapeToolData { impl Fsm for ShapeToolFsmState { type ToolData = ShapeToolData; - fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + let transform = document.root.transform; use ShapeMessage::*; use ShapeToolFsmState::*; if let ToolMessage::Shape(event) = event { @@ -76,7 +78,7 @@ impl Fsm for ShapeToolFsmState { (Dragging, MouseMove) => { data.drag_current = input.mouse.position; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); Dragging } @@ -85,7 +87,7 @@ impl Fsm for ShapeToolFsmState { responses.push_back(Operation::ClearWorkingFolder.into()); // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) if data.drag_start != data.drag_current { - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); responses.push_back(Operation::CommitTransaction.into()); } @@ -99,13 +101,13 @@ impl Fsm for ShapeToolFsmState { (Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, true, Ready), (Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, false, Ready), - (Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging), - (Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging), + (Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging, transform), (Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready), (Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready), - (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging), - (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging), + (Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform), + (Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform), _ => self, } } else { @@ -126,28 +128,30 @@ fn update_state( data: &mut ShapeToolData, responses: &mut VecDeque, new_state: ShapeToolFsmState, + transform: DAffine2, ) -> ShapeToolFsmState { *(state(data)) = value; responses.push_back(Operation::ClearWorkingFolder.into()); - responses.push_back(make_operation(data, tool_data)); + responses.push_back(make_operation(data, tool_data, transform)); new_state } -fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData) -> Message { +fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message { let x0 = data.drag_start.x as f64; let y0 = data.drag_start.y as f64; let x1 = data.drag_current.x as f64; let y1 = data.drag_current.y as f64; - let (x0, y0, x1, y1) = if data.constrain_to_square { + // TODO: Use regular polygon's aspect ration for constraining rather than a square. + let (x0, y0, x1, y1, equal_sides) = if data.constrain_to_square { let (x_dir, y_dir) = ((x1 - x0).signum(), (y1 - y0).signum()); let max_dist = f64::max((x1 - x0).abs(), (y1 - y0).abs()); if data.center_around_cursor { - (x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir) + (x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir, true) } else { - (x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir) + (x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir, true) } } else { let (x0, y0) = if data.center_around_cursor { @@ -158,16 +162,14 @@ fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData) -> Message } else { (x0, y0) }; - (x0, y0, x1, y1) + (x0, y0, x1, y1, false) }; Operation::AddShape { path: vec![], insert_index: -1, - x0, - y0, - x1, - y1, + transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(), + equal_sides, sides: data.sides, style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), } From b42991368a8d6e26adda0b8eb0f73efbb5e51250 Mon Sep 17 00:00:00 2001 From: Chrs Msln Date: Sun, 27 Jun 2021 08:18:47 +0200 Subject: [PATCH 002/365] Refactor frontend input components to use the v-model Vue pattern (#224) * Use v-model for inputs * Add opacity to LayerTree * Fix FloatingMenu typing --- client/web/src/components/panels/Document.vue | 6 ++- .../web/src/components/panels/LayerTree.vue | 3 +- .../widgets/floating-menus/FloatingMenu.vue | 9 ++++- .../widgets/floating-menus/MenuList.vue | 20 +++++----- .../widgets/inputs/DropdownInput.vue | 12 +----- .../components/widgets/inputs/NumberInput.vue | 38 +++++++++++++++++-- .../components/widgets/inputs/RadioInput.vue | 10 ++--- 7 files changed, 65 insertions(+), 33 deletions(-) diff --git a/client/web/src/components/panels/Document.vue b/client/web/src/components/panels/Document.vue index a70631138e..387231136f 100644 --- a/client/web/src/components/panels/Document.vue +++ b/client/web/src/components/panels/Document.vue @@ -52,7 +52,7 @@
- + @@ -236,6 +236,7 @@ export default defineComponent({ select_tool(toolName); }, async viewModeChanged(toolIndex: number) { + console.log(toolIndex); function todo(_: number) { return _; } @@ -269,6 +270,8 @@ export default defineComponent({ window.addEventListener("keyup", (e: KeyboardEvent) => this.keyUp(e)); window.addEventListener("keydown", (e: KeyboardEvent) => this.keyDown(e)); + + this.$watch("viewModeIndex", this.viewModeChanged); }, data() { return { @@ -278,6 +281,7 @@ export default defineComponent({ SeparatorDirection, SeparatorType, modeMenuEntries, + viewModeIndex: 0, }; }, components: { diff --git a/client/web/src/components/panels/LayerTree.vue b/client/web/src/components/panels/LayerTree.vue index b3d09a9de9..d916478479 100644 --- a/client/web/src/components/panels/LayerTree.vue +++ b/client/web/src/components/panels/LayerTree.vue @@ -5,7 +5,7 @@ - + @@ -222,6 +222,7 @@ export default defineComponent({ layers: [] as Array, selectionRangeStartLayer: undefined as LayerPanelEntry | undefined, selectionRangeEndLayer: undefined as LayerPanelEntry | undefined, + opacity: 100, }; }, components: { diff --git a/client/web/src/components/widgets/floating-menus/FloatingMenu.vue b/client/web/src/components/widgets/floating-menus/FloatingMenu.vue index 75b1b29800..d717a187c9 100644 --- a/client/web/src/components/widgets/floating-menus/FloatingMenu.vue +++ b/client/web/src/components/widgets/floating-menus/FloatingMenu.vue @@ -2,7 +2,7 @@
-
+
@@ -350,5 +350,12 @@ export default defineComponent({ } }, }, + computed: { + floatingMenuContentStyle(): Partial { + return { + minWidth: this.minWidth > 0 ? `${this.minWidth}px` : "", + }; + }, + }, }); diff --git a/client/web/src/components/widgets/floating-menus/MenuList.vue b/client/web/src/components/widgets/floating-menus/MenuList.vue index b550370d50..7c93ace193 100644 --- a/client/web/src/components/widgets/floating-menus/MenuList.vue +++ b/client/web/src/components/widgets/floating-menus/MenuList.vue @@ -6,7 +6,7 @@ v-for="(entry, entryIndex) in section" :key="entryIndex" class="row" - :class="{ open: isMenuEntryOpen(entry), active: entry === activeEntry }" + :class="{ open: isMenuEntryOpen(entry), active: entry === currentEntry }" @click="handleEntryClick(entry)" @mouseenter="handleEntryMouseEnter(entry)" @mouseleave="handleEntryMouseLeave(entry)" @@ -22,9 +22,8 @@ v-if="entry.children" :direction="MenuDirection.TopRight" :menuEntries="entry.children" - :activeEntry="activeEntry" + v-model:active-entry="currentEntry" :minWidth="minWidth" - :defaultAction="defaultAction" :drawIcon="drawIcon" :ref="(ref) => setEntryRefs(entry, ref)" /> @@ -146,8 +145,6 @@ const MenuList = defineComponent({ menuEntries: { type: Array as PropType, required: true }, activeEntry: { type: Object as PropType, required: false }, minWidth: { type: Number, default: 0 }, - defaultAction: { type: Function, required: false }, - widthChanged: { type: Function, required: false }, drawIcon: { type: Boolean, default: false }, }, methods: { @@ -157,8 +154,11 @@ const MenuList = defineComponent({ handleEntryClick(menuEntry: MenuListEntry) { (this.$refs.floatingMenu as typeof FloatingMenu).setClosed(); - if (menuEntry.action) menuEntry.action(); - else if (this.defaultAction) this.defaultAction(menuEntry); + if (menuEntry.action) { + menuEntry.action(); + } else { + this.$emit("update:activeEntry", menuEntry); + } }, handleEntryMouseEnter(menuEntry: MenuListEntry) { if (!menuEntry.children || !menuEntry.children.length) return; @@ -193,9 +193,6 @@ const MenuList = defineComponent({ return Boolean(floatingMenu && floatingMenu.isOpen()); }, measureAndReportWidth() { - const { widthChanged } = this; - if (!widthChanged) return; - // API is experimental but supported in all browsers - https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet // eslint-disable-next-line @typescript-eslint/no-explicit-any (document as any).fonts.ready.then(() => { @@ -212,7 +209,7 @@ const MenuList = defineComponent({ // Restore open/closed state if it was forced open for measurement if (!initiallyOpen) floatingMenu.setClosed(); - widthChanged(width); + this.$emit("width-changed", width); }); }); }); @@ -246,6 +243,7 @@ const MenuList = defineComponent({ }, data() { return { + currentEntry: this.activeEntry, SeparatorDirection, SeparatorType, MenuDirection, diff --git a/client/web/src/components/widgets/inputs/DropdownInput.vue b/client/web/src/components/widgets/inputs/DropdownInput.vue index 5e9a7e3ec0..7e5c5352ea 100644 --- a/client/web/src/components/widgets/inputs/DropdownInput.vue +++ b/client/web/src/components/widgets/inputs/DropdownInput.vue @@ -5,15 +5,7 @@ {{ activeEntry.label }}
- +
@@ -101,7 +93,7 @@ export default defineComponent({ setActiveEntry(newActiveEntry: MenuListEntry) { this.activeEntry = newActiveEntry; }, - widthChanged(newWidth: number) { + onWidthChanged(newWidth: number) { this.minWidth = newWidth; }, }, diff --git a/client/web/src/components/widgets/inputs/NumberInput.vue b/client/web/src/components/widgets/inputs/NumberInput.vue index 935cbe2a0c..7bcaad58d0 100644 --- a/client/web/src/components/widgets/inputs/NumberInput.vue +++ b/client/web/src/components/widgets/inputs/NumberInput.vue @@ -1,8 +1,8 @@ @@ -99,7 +99,37 @@ export default defineComponent({ components: {}, props: { value: { type: Number, required: true }, - unit: { type: String, default: "" }, + unit: { type: String, default: "", required: false }, + step: { type: Number, default: 1, required: false }, + min: { type: Number, required: false }, + max: { type: Number, required: false }, + }, + computed: { + displayValue(): string { + if (!this.unit) return this.value.toString(); + return `${this.value}${this.unit}`; + }, + }, + methods: { + onIncrement(direction: number) { + const step = this.step * direction; + const newValue = this.value + step; + this.updateValue(newValue); + }, + + updateValue(newValue: number) { + let value = newValue; + + if (Number.isFinite(this.min) && typeof this.min === "number") { + value = Math.max(value, this.min); + } + + if (Number.isFinite(this.max) && typeof this.max === "number") { + value = Math.min(value, this.max); + } + + this.$emit("update:value", value); + }, }, }); diff --git a/client/web/src/components/widgets/inputs/RadioInput.vue b/client/web/src/components/widgets/inputs/RadioInput.vue index ec3b7318af..1cbe8748f6 100644 --- a/client/web/src/components/widgets/inputs/RadioInput.vue +++ b/client/web/src/components/widgets/inputs/RadioInput.vue @@ -51,12 +51,11 @@ import { defineComponent } from "vue"; export default defineComponent({ components: {}, props: { - initialIndex: { type: Number, required: true }, - setIndex: { type: Function, required: false }, + index: { type: Number, required: true }, }, data() { return { - activeIndex: this.initialIndex, + activeIndex: this.index, }; }, mounted() { @@ -64,8 +63,7 @@ export default defineComponent({ (this.$refs.radioInput as Element).querySelectorAll(".icon-button").forEach((iconButton, index) => { iconButton.addEventListener("click", () => { - this.activeIndex = index; - this.$emit("changed", index); + this.setActive(index); }); }); }, @@ -78,6 +76,8 @@ export default defineComponent({ // This method may be called by the user of this component by setting a `ref="radioInput"` attribute and calling `(this.$refs.viewModePicker as typeof RadioInput).setActive(...)` setActive(index: number) { this.activeIndex = index; + this.$emit("update:index", index); + this.$emit("changed", index); }, updateActiveIconButton() { const iconButtons = (this.$refs.radioInput as Element).querySelectorAll(".icon-button"); From 3ba570fa6840d824092372795f71f74871ded79d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 26 Jun 2021 23:46:39 -0700 Subject: [PATCH 003/365] Fix bug where other panel tabs would switch the document panel tabs --- client/web/src/components/widgets/ShelfItem.vue | 4 ++++ client/web/src/components/workspace/Panel.vue | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/client/web/src/components/widgets/ShelfItem.vue b/client/web/src/components/widgets/ShelfItem.vue index 670adb27c9..2959563380 100644 --- a/client/web/src/components/widgets/ShelfItem.vue +++ b/client/web/src/components/widgets/ShelfItem.vue @@ -17,6 +17,10 @@ background: var(--color-accent); } + .icon-button { + background: unset; + } + svg { width: 24px; height: 24px; diff --git a/client/web/src/components/workspace/Panel.vue b/client/web/src/components/workspace/Panel.vue index 5b59705a3c..237b46bb46 100644 --- a/client/web/src/components/workspace/Panel.vue +++ b/client/web/src/components/workspace/Panel.vue @@ -158,10 +158,14 @@ export default defineComponent({ }, methods: { async handleTabClick(tabIndex: number) { + if (this.panelType !== "Document") return; + const { select_document } = await wasm; select_document(tabIndex); }, async closeTab(tabIndex: number) { + if (this.panelType !== "Document") return; + const { close_document } = await wasm; // eslint-disable-next-line no-alert const result = window.confirm("Closing this document will permanently discard all work. Continue?"); From 69a446091f6bed5b5e1aaf53b251511ed50e6b05 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Tue, 29 Jun 2021 21:06:37 +0100 Subject: [PATCH 004/365] Remove partialeq for layers (#230) --- Cargo.lock | 3 ++- core/document/Cargo.toml | 4 +--- core/document/src/document.rs | 2 +- core/document/src/layers/ellipse.rs | 2 +- core/document/src/layers/folder.rs | 2 +- core/document/src/layers/line.rs | 2 +- core/document/src/layers/mod.rs | 4 ++-- core/document/src/layers/polyline.rs | 2 +- core/document/src/layers/rect.rs | 2 +- core/document/src/layers/shape.rs | 2 +- 10 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74e2147412..6988fd1948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,7 +119,8 @@ dependencies = [ [[package]] name = "kurbo" version = "0.8.1" -source = "git+https://github.com/GraphiteEditor/kurbo.git?branch=bezpath-partial-eq#3f86bda9ba77d3b1279612bc5779e425f2d8714d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e30b1df631d23875f230ed3ddd1a88c231f269a04b2044eb6ca87e763b5f4c42" dependencies = [ "arrayvec", ] diff --git a/core/document/Cargo.toml b/core/document/Cargo.toml index 0f7882eab1..9d8657640e 100644 --- a/core/document/Cargo.toml +++ b/core/document/Cargo.toml @@ -10,8 +10,6 @@ license = "Apache-2.0" [dependencies] log = "0.4" -# TODO: Swich to kurbo release when derive `PartialEq` is available for BezPath -#kurbo = "0.8.0" -kurbo = {git="https://github.com/GraphiteEditor/kurbo.git", branch="bezpath-partial-eq"} +kurbo = "0.8" serde = { version = "1.0", features = ["derive"] } glam = "0.16" diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 5ccbf6f6b9..d376a292db 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -5,7 +5,7 @@ use crate::{ DocumentError, DocumentResponse, LayerId, Operation, }; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Document { pub root: Layer, pub work: Layer, diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index 56a07157c0..25c838c50a 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -5,7 +5,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct Ellipse {} impl Ellipse { diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index bafff20fd2..55fc723830 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -4,7 +4,7 @@ use super::{style, Layer, LayerData, LayerDataTypes}; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Folder { next_assignment_id: LayerId, pub layer_ids: Vec, diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 7595380fce..1419833033 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -6,7 +6,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub struct Line {} impl Line { diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 2ee4ad8c10..7c485bb564 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -26,7 +26,7 @@ pub trait LayerData { fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum LayerDataTypes { Folder(Folder), Ellipse(Ellipse), @@ -77,7 +77,7 @@ impl LayerDataTypes { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Layer { pub visible: bool, pub name: Option, diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 4892e0320f..3eb96ab0bf 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use super::{style, LayerData}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct PolyLine { points: Vec, } diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 503430ddad..2316f13b77 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -6,7 +6,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Rect {} impl Rect { diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index d00134287a..d4cf4b1084 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -6,7 +6,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Shape { equal_sides: bool, sides: u8, From c41605b464d9ab6a640f74e5140bd7fd79a49dd9 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 1 Jul 2021 22:08:23 -0700 Subject: [PATCH 005/365] Add styled scrollbars (#231) --- client/web/src/App.vue | 72 ++++++++++++++++++- .../web/src/components/panels/LayerTree.vue | 4 +- .../widgets/floating-menus/FloatingMenu.vue | 3 +- .../widgets/floating-menus/MenuList.vue | 28 ++++---- .../widgets/inputs/DropdownInput.vue | 11 ++- .../widgets/inputs/MenuBarInput.vue | 10 +-- 6 files changed, 95 insertions(+), 33 deletions(-) diff --git a/client/web/src/App.vue b/client/web/src/App.vue index 179bc74ab7..4cdf5f6596 100644 --- a/client/web/src/App.vue +++ b/client/web/src/App.vue @@ -41,7 +41,7 @@ // TODO: Replace with CSS color() function to calculate alpha when browsers support it // See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color() and https://caniuse.com/css-color-function // E6 = 90% alpha - --floating-menu-opacity-color-2-mildblack: #222222e6; + --floating-menu-opacity-color-2-mildblack: #222222f2; --floating-menu-shadow: rgba(0, 0, 0, 50%); } @@ -69,6 +69,72 @@ img { display: block; } +.scrollable, +.scrollable-x, +.scrollable-y { + // Standard + scrollbar-width: thin; + scrollbar-width: 6px; + scrollbar-gutter: 6px; + scrollbar-color: var(--color-5-dullgray) transparent; + + &:not(:hover) { + scrollbar-width: none; + } + + // WebKit + &::-webkit-scrollbar { + width: calc(2px + 6px + 2px); + } + + &:not(:hover)::-webkit-scrollbar { + width: 0; + } + + &::-webkit-scrollbar-track { + box-shadow: inset 0 0 0 1px var(--color-5-dullgray); + border: 2px solid transparent; + border-radius: 10px; + + &:hover { + box-shadow: inset 0 0 0 1px var(--color-6-lowergray); + } + } + + &::-webkit-scrollbar-thumb { + background-clip: padding-box; + background-color: var(--color-5-dullgray); + border: 2px solid transparent; + border-radius: 10px; + margin: 2px; + + &:hover { + background-color: var(--color-6-lowergray); + } + } +} + +.scrollable { + // Standard + overflow: auto; + // WebKit + overflow: overlay; +} + +.scrollable-x { + // Standard + overflow-x: auto; + // WebKit + overflow-x: overlay; +} + +.scrollable-y { + // Standard + overflow-y: auto; + // WebKit + overflow-y: overlay; +} + // For placeholder messages (remove eventually) .floating-menu { h1, @@ -117,12 +183,12 @@ img { border-radius: 2px; &:hover { - background-color: var(--color-6-lowergray); + background: var(--color-6-lowergray); color: var(--color-f-white); } &:active { - background-color: var(--color-accent-hover); + background: var(--color-accent-hover); color: var(--color-f-white); } } diff --git a/client/web/src/components/panels/LayerTree.vue b/client/web/src/components/panels/LayerTree.vue index d916478479..5052134641 100644 --- a/client/web/src/components/panels/LayerTree.vue +++ b/client/web/src/components/panels/LayerTree.vue @@ -14,7 +14,7 @@

More blend and compositing options will be here

- +
@@ -62,8 +62,6 @@ } .layer-tree { - overflow: auto; - .layer-row { display: flex; height: 36px; diff --git a/client/web/src/components/widgets/floating-menus/FloatingMenu.vue b/client/web/src/components/widgets/floating-menus/FloatingMenu.vue index d717a187c9..b6d1e5e034 100644 --- a/client/web/src/components/widgets/floating-menus/FloatingMenu.vue +++ b/client/web/src/components/widgets/floating-menus/FloatingMenu.vue @@ -2,7 +2,7 @@
-
+
@@ -193,6 +193,7 @@ export default defineComponent({ type: { type: String, required: true }, windowEdgeMargin: { type: Number, default: 8 }, minWidth: { type: Number, default: 0 }, + scrollable: { type: Boolean, default: false }, }, data() { return { diff --git a/client/web/src/components/widgets/floating-menus/MenuList.vue b/client/web/src/components/widgets/floating-menus/MenuList.vue index 7c93ace193..c3e6c86cc4 100644 --- a/client/web/src/components/widgets/floating-menus/MenuList.vue +++ b/client/web/src/components/widgets/floating-menus/MenuList.vue @@ -1,5 +1,5 @@ From 744e3729d72403a1ca0fe7c60e8d538290ecadd5 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 2 Jul 2021 13:50:19 -0700 Subject: [PATCH 006/365] Update wasm-pack dependency and others, fixes #167 --- client/web/package-lock.json | 888 ++++++++++++++++++++--------------- client/web/package.json | 22 +- 2 files changed, 521 insertions(+), 389 deletions(-) diff --git a/client/web/package-lock.json b/client/web/package-lock.json index 4c840a9813..00e49747d2 100644 --- a/client/web/package-lock.json +++ b/client/web/package-lock.json @@ -16,7 +16,8 @@ "@babel/helper-validator-identifier": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true }, "@babel/highlight": { "version": "7.12.13", @@ -30,18 +31,24 @@ } }, "@babel/parser": { - "version": "7.13.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz", - "integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q==" + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==" }, "@babel/types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz", - "integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + } } }, "@hapi/address": { @@ -258,6 +265,12 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/estree": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", + "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", + "dev": true + }, "@types/express": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", @@ -346,7 +359,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "dev": true, + "optional": true }, "@types/q": { "version": "1.5.4", @@ -546,87 +560,6 @@ "tslint": "^5.20.1", "webpack": "^4.0.0", "yorkie": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fork-ts-checker-webpack-plugin-v5": { - "version": "npm:fork-ts-checker-webpack-plugin@5.2.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz", - "integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@vue/cli-plugin-vuex": { @@ -710,6 +643,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "optional": true, "requires": { "color-convert": "^2.0.1" } @@ -719,6 +653,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, + "optional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -729,6 +664,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "optional": true, "requires": { "color-name": "~1.1.4" } @@ -737,7 +673,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "optional": true }, "fs-extra": { "version": "7.0.1", @@ -754,13 +691,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "dev": true, + "optional": true }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, + "optional": true, "requires": { "minimist": "^1.2.5" } @@ -788,6 +727,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "optional": true, "requires": { "has-flag": "^4.0.0" } @@ -824,6 +764,7 @@ "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz", "integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==", "dev": true, + "optional": true, "requires": { "chalk": "^4.1.0", "hash-sum": "^2.0.0", @@ -835,6 +776,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "dev": true, + "optional": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -889,38 +831,39 @@ } }, "@vue/compiler-core": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.11.tgz", - "integrity": "sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.4.tgz", + "integrity": "sha512-TnUz+1z0y74O/A4YKAbzsdUfamyHV73MihrEfvettWpm9bQKVoZd1nEmR1cGN9LsXWlwAvVQBetBlWdOjmQO5Q==", "requires": { "@babel/parser": "^7.12.0", "@babel/types": "^7.12.0", - "@vue/shared": "3.0.11", + "@vue/shared": "3.1.4", "estree-walker": "^2.0.1", "source-map": "^0.6.1" } }, "@vue/compiler-dom": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.11.tgz", - "integrity": "sha512-+3xB50uGeY5Fv9eMKVJs2WSRULfgwaTJsy23OIltKgMrynnIj8hTYY2UL97HCoz78aDw1VDXdrBQ4qepWjnQcw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.4.tgz", + "integrity": "sha512-3tG2ScHkghhUBuFwl9KgyZhrS8CPFZsO7hUDekJgIp5b1OMkROr4AvxHu6rRMl4WkyvYkvidFNBS2VfOnwa6Kw==", "requires": { - "@vue/compiler-core": "3.0.11", - "@vue/shared": "3.0.11" + "@vue/compiler-core": "3.1.4", + "@vue/shared": "3.1.4" } }, "@vue/compiler-sfc": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.0.11.tgz", - "integrity": "sha512-7fNiZuCecRleiyVGUWNa6pn8fB2fnuJU+3AGjbjl7r1P5wBivfl02H4pG+2aJP5gh2u+0wXov1W38tfWOphsXw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.4.tgz", + "integrity": "sha512-4KDQg60Khy3SgnF+V/TB2NZqzmM4TyGRmzsxqG1SebGdMSecCweFDSlI/F1vDYk6dKiCHgmpoT9A1sLxswkJ0A==", "dev": true, "requires": { "@babel/parser": "^7.13.9", "@babel/types": "^7.13.0", - "@vue/compiler-core": "3.0.11", - "@vue/compiler-dom": "3.0.11", - "@vue/compiler-ssr": "3.0.11", - "@vue/shared": "3.0.11", + "@types/estree": "^0.0.48", + "@vue/compiler-core": "3.1.4", + "@vue/compiler-dom": "3.1.4", + "@vue/compiler-ssr": "3.1.4", + "@vue/shared": "3.1.4", "consolidate": "^0.16.0", "estree-walker": "^2.0.1", "hash-sum": "^2.0.0", @@ -933,12 +876,6 @@ "source-map": "^0.6.1" }, "dependencies": { - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, "consolidate": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", @@ -957,12 +894,6 @@ "yallist": "^3.0.2" } }, - "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "dev": true - }, "postcss": { "version": "8.3.5", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", @@ -983,13 +914,13 @@ } }, "@vue/compiler-ssr": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.0.11.tgz", - "integrity": "sha512-66yUGI8SGOpNvOcrQybRIhl2M03PJ+OrDPm78i7tvVln86MHTKhM3ERbALK26F7tXl0RkjX4sZpucCpiKs3MnA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.4.tgz", + "integrity": "sha512-Box8fCuCFPp0FuimIswjDkjwiSDCBkHvt/xVALyFkYCiIMWv2eR53fIjmlsnEHhcBuZ+VgRC+UanCTcKvSA1gA==", "dev": true, "requires": { - "@vue/compiler-dom": "3.0.11", - "@vue/shared": "3.0.11" + "@vue/compiler-dom": "3.1.4", + "@vue/shared": "3.1.4" } }, "@vue/component-compiler-utils": { @@ -1068,36 +999,36 @@ "dev": true }, "@vue/reactivity": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.11.tgz", - "integrity": "sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.4.tgz", + "integrity": "sha512-YDlgii2Cr9yAoKVZFzgY4j0mYlVT73986X3e5SPp6ifqckSEoFSUWXZK2Tb53TB/9qO29BEEbspnKD3m3wAwkA==", "requires": { - "@vue/shared": "3.0.11" + "@vue/shared": "3.1.4" } }, "@vue/runtime-core": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.0.11.tgz", - "integrity": "sha512-87XPNwHfz9JkmOlayBeCCfMh9PT2NBnv795DSbi//C/RaAnc/bGZgECjmkD7oXJ526BZbgk9QZBPdFT8KMxkAg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.1.4.tgz", + "integrity": "sha512-qmVJgJuFxfT7M4qHQ4M6KqhKC66fjuswK+aBivE8dWiZ2rtIGl9gtJGpwqwjQEcKEBTOfvvrtrwBncYArJUO8Q==", "requires": { - "@vue/reactivity": "3.0.11", - "@vue/shared": "3.0.11" + "@vue/reactivity": "3.1.4", + "@vue/shared": "3.1.4" } }, "@vue/runtime-dom": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.0.11.tgz", - "integrity": "sha512-jm3FVQESY3y2hKZ2wlkcmFDDyqaPyU3p1IdAX92zTNeCH7I8zZ37PtlE1b9NlCtzV53WjB4TZAYh9yDCMIEumA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.1.4.tgz", + "integrity": "sha512-vbmwgTxku1BU87Kw7r29adv0OIrDXCW0PslOPQT0O/9R5SqcXgS94Yj6zsztDjvghegenwIAPNLlDR1Auh5s+w==", "requires": { - "@vue/runtime-core": "3.0.11", - "@vue/shared": "3.0.11", + "@vue/runtime-core": "3.1.4", + "@vue/shared": "3.1.4", "csstype": "^2.6.8" } }, "@vue/shared": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz", - "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA==" + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.4.tgz", + "integrity": "sha512-6O45kZAmkLvzGLToBxEz4lR2W6kXohCtebV2UxjH9GXjd8X9AhEn68FN9eNanFtWNzvgw1hqd6HkPRVQalqf7Q==" }, "@vue/web-component-wrapper": { "version": "1.3.0", @@ -1655,7 +1586,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true + "dev": true, + "optional": true }, "atob": { "version": "2.1.2", @@ -1691,38 +1623,12 @@ "dev": true }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "dev": true, "requires": { - "follow-redirects": "1.5.10" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dev": true, - "requires": { - "debug": "=3.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "follow-redirects": "^1.10.0" } }, "babel-code-frame": { @@ -1890,17 +1796,14 @@ "dev": true }, "binary-install": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.0.1.tgz", - "integrity": "sha512-axr6lqB4ec/pkEOb/JMnZpfcroWv1zT48pVz1oQHG7XmGkS77vmdxmP1btuH79lWQdy9e2MVw/uW0D8siopkRg==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.1.1.tgz", + "integrity": "sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==", "dev": true, "requires": { - "axios": "^0.19.0", - "env-paths": "^2.2.0", - "mkdirp": "^0.5.1", - "rimraf": "^3.0.0", - "tar": "^5.0.5", - "universal-url": "^2.0.0" + "axios": "^0.21.1", + "rimraf": "^3.0.2", + "tar": "^6.1.0" }, "dependencies": { "rimraf": { @@ -3019,27 +2922,6 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, - "contains-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-1.0.0.tgz", - "integrity": "sha1-NFizMhhWA+ju0Y9RjUoQiIo6vJE=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1", - "path-starts-with": "^1.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -3189,6 +3071,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, + "optional": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.1.0", @@ -3201,7 +3084,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "dev": true, + "optional": true } } }, @@ -3531,9 +3415,9 @@ } }, "csstype": { - "version": "2.6.16", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz", - "integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q==" + "version": "2.6.17", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", + "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==" }, "cyclist": { "version": "1.0.1", @@ -3613,7 +3497,8 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true + "dev": true, + "optional": true }, "default-gateway": { "version": "5.0.5", @@ -3975,12 +3860,20 @@ "dev": true }, "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", + "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "^2.2.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true + } } }, "domutils": { @@ -4150,12 +4043,6 @@ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, "errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -4530,14 +4417,13 @@ } }, "eslint-plugin-import": { - "version": "2.23.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.1.tgz", - "integrity": "sha512-epW62znqcFCyQeixVrqy26WpdN1Y3LZH5G9XCuiiTCVuksjC4Je+4o1z5mIpa6P1KMyz1n4RT436VSrZoA5+5A==", + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", "dev": true, "requires": { "array-includes": "^3.1.3", "array.prototype.flat": "^1.2.4", - "contains-path": "^1.0.0", "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.4", @@ -4562,6 +4448,30 @@ "ms": "2.0.0" } }, + "es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -4571,6 +4481,12 @@ "locate-path": "^2.0.0" } }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, "is-core-module": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", @@ -4580,6 +4496,16 @@ "has": "^1.0.3" } }, + "is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + } + }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -4596,16 +4522,21 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "object-inspect": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "dev": true + }, "object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "es-abstract": "^1.18.2" } }, "p-limit": { @@ -4637,13 +4568,33 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } } } }, "eslint-plugin-prettier-vue": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier-vue/-/eslint-plugin-prettier-vue-3.0.0.tgz", - "integrity": "sha512-fRjvHSu7aLjS+rJp/Asrualum/6uThr1Swf5ExlieAT8EeY3Z1kegmNinN2t2UTEi3lOtRvhV6b6iRVWjVtpqA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier-vue/-/eslint-plugin-prettier-vue-3.1.0.tgz", + "integrity": "sha512-PxJxmHTAqBv5v6rU/hSg55cyJAp+LhF2K0ffF+MeR3zt0Njy36X5AcYQ75sYLt0KKdgA35XkQHIBrgLF63hTxA==", "dev": true, "requires": { "@vue/compiler-sfc": "^3.0.0", @@ -4704,9 +4655,9 @@ } }, "eslint-plugin-vue": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.9.0.tgz", - "integrity": "sha512-2Q0qQp5+5h+pZvJKCbG1/jCRUYrdgAz5BYKGyTlp2NU8mx09u3Hp7PsH6d5qef6ojuPoCXMnrbbDxeoplihrSw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.12.1.tgz", + "integrity": "sha512-xHf/wCt88qmzqQerjaSteUFGASj7fPreglKD4ijnvoKRkoSJ3/H3kuJE8QFFtc+2wjw6hRDs834HH7vpuTJQzg==", "dev": true, "requires": { "eslint-utils": "^2.1.0", @@ -4716,17 +4667,26 @@ }, "dependencies": { "vue-eslint-parser": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.6.0.tgz", - "integrity": "sha512-QXxqH8ZevBrtiZMZK0LpwaMfevQi9UL7lY6Kcp+ogWHC88AuwUPwwCIzkOUc1LR4XsYAt/F9yHXAB/QoD17QXA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.7.2.tgz", + "integrity": "sha512-zkfxSttpwBW9SQEa+rLR+j6sFHGGhanVH3VuzHQwybCQWJsg/Yi1W619gXOW01U/zekN4D+J4/S4Zufd1sClZg==", "dev": true, "requires": { "debug": "^4.1.1", - "eslint-scope": "^5.0.0", + "eslint-scope": "^5.1.1", "eslint-visitor-keys": "^1.1.0", "espree": "^6.2.1", "esquery": "^1.4.0", - "lodash": "^4.17.15" + "lodash": "^4.17.21", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } } } @@ -5387,6 +5347,95 @@ } } }, + "fork-ts-checker-webpack-plugin-v5": { + "version": "npm:fork-ts-checker-webpack-plugin@5.2.1", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz", + "integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==", + "dev": true, + "optional": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "optional": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -5434,6 +5483,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, + "optional": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -5454,7 +5504,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", - "dev": true + "dev": true, + "optional": true }, "fs-write-stream-atomic": { "version": "1.0.10", @@ -5687,6 +5738,12 @@ } } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5770,12 +5827,6 @@ "minimalistic-assert": "^1.0.1" } }, - "hasurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hasurl/-/hasurl-1.0.0.tgz", - "integrity": "sha512-43ypUd3DbwyCT01UYpA99AEZxZ4aKtRxWGBHEIbjcOsUghd9YUON0C+JF6isNjaiwC/UF5neaUudy6JS9jZPZQ==", - "dev": true - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5928,34 +5979,43 @@ } }, "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "dev": true, "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" }, "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" } } } @@ -6432,6 +6492,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -6441,6 +6507,15 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -6595,6 +6670,12 @@ } } }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -6839,6 +6920,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" @@ -7045,8 +7127,7 @@ }, "glob-parent": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "resolved": "", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -7444,7 +7525,8 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "lodash.camelcase": { "version": "4.3.0", @@ -7470,12 +7552,6 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lodash.transform": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", @@ -7648,6 +7724,7 @@ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.2.tgz", "integrity": "sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q==", "dev": true, + "optional": true, "requires": { "fs-monkey": "1.0.3" } @@ -7939,6 +8016,12 @@ "thenify-all": "^1.0.0" } }, + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -8568,26 +8651,6 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "path-starts-with": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-starts-with/-/path-starts-with-1.0.0.tgz", - "integrity": "sha1-soJDAV6LE43lcmgqxS2kLmRq2E4=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -9075,9 +9138,9 @@ } }, "postcss-modules": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.0.0.tgz", - "integrity": "sha512-ghS/ovDzDqARm4Zj6L2ntadjyQMoyJmi0JkLlYtH2QFLrvNlxH5OAVRPWPeKilB0pY7SbuhO173KOWkPAxRJcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz", + "integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==", "dev": true, "requires": { "generic-names": "^2.0.1", @@ -9449,9 +9512,9 @@ "dev": true }, "prettier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz", - "integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true }, "prettier-linter-helpers": { @@ -9824,16 +9887,16 @@ "dev": true }, "renderkid": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.5.tgz", - "integrity": "sha512-ccqoLg+HLOHq1vdfYNm4TBeaCDIi1FLt3wGojTDSvdewUv65oTmI3cnT2E4hRjl1gzKZIPK+KZrXzlUYKnR+vQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", + "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", "dev": true, "requires": { - "css-select": "^2.0.2", - "dom-converter": "^0.2", - "htmlparser2": "^3.10.1", - "lodash": "^4.17.20", - "strip-ansi": "^3.0.0" + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { @@ -9842,6 +9905,62 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, + "css-select": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", + "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^5.0.0", + "domhandler": "^4.2.0", + "domutils": "^2.6.0", + "nth-check": "^2.0.0" + } + }, + "css-what": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", + "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "dev": true + }, + "dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true + }, + "domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "nth-check": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", + "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -10064,9 +10183,9 @@ "dev": true }, "sass": { - "version": "1.32.13", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz", - "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", + "integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0" @@ -11078,17 +11197,31 @@ "dev": true }, "tar": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.5.tgz", - "integrity": "sha512-MNIgJddrV2TkuwChwcSNds/5E9VijOiw7kAc1y5hTNJoLDSuIyid2QtLYiCYNnICebpuvjhPQZsXwUL0O3l7OQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", "dev": true, "requires": { - "chownr": "^1.1.3", + "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^0.5.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } } }, "terser": { @@ -11306,15 +11439,6 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -11515,9 +11639,9 @@ "dev": true }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "uglify-js": { @@ -11538,6 +11662,26 @@ } } }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + } + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -11580,21 +11724,12 @@ "imurmurhash": "^0.1.4" } }, - "universal-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universal-url/-/universal-url-2.0.0.tgz", - "integrity": "sha512-3DLtXdm/G1LQMCnPj+Aw7uDoleQttNHp2g5FnNQKR6cP6taNWS1b/Ehjjx4PVyvejKi3TJyu8iBraKM4q3JQPg==", - "dev": true, - "requires": { - "hasurl": "^1.0.0", - "whatwg-url": "^7.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "dev": true, + "optional": true }, "unpipe": { "version": "1.0.0", @@ -11853,13 +11988,13 @@ "dev": true }, "vue": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.0.11.tgz", - "integrity": "sha512-3/eUi4InQz8MPzruHYSTQPxtM3LdZ1/S/BvaU021zBnZi0laRUyH6pfuE4wtUeLvI8wmUNwj5wrZFvbHUXL9dw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.1.4.tgz", + "integrity": "sha512-p8dcdyeCgmaAiZsbLyDkmOLcFGZb/jEVdCLW65V68LRCXTNX8jKsgah2F7OZ/v/Ai2V0Fb1MNO0vz/GFqsPVMA==", "requires": { - "@vue/compiler-dom": "3.0.11", - "@vue/runtime-dom": "3.0.11", - "@vue/shared": "3.0.11" + "@vue/compiler-dom": "3.1.4", + "@vue/runtime-dom": "3.1.4", + "@vue/shared": "3.1.4" } }, "vue-class-component": { @@ -11888,9 +12023,9 @@ "dev": true }, "vue-loader": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz", - "integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.0.tgz", + "integrity": "sha512-UDgni/tUVSdwHuQo+vuBmEgamWx88SuSlEb5fgdvHrlJSPB9qMBRF6W7bfPWSqDns425Gt1wxAUif+f+h/rWjg==", "requires": { "chalk": "^4.1.0", "hash-sum": "^2.0.0", @@ -11906,9 +12041,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -12012,9 +12147,9 @@ } }, "vue-template-compiler": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", - "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", "dev": true, "requires": { "de-indent": "^1.0.2", @@ -12028,12 +12163,12 @@ "dev": true }, "wasm-pack": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.9.1.tgz", - "integrity": "sha512-866+5UIdASabHMVU+M1azbn8tN1g6kLDoL5qvzVYep2hCYicKCgD/Y1LD0yOB3xMDdi+OD51WYNNBGH1NNF23g==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.10.0.tgz", + "integrity": "sha512-E+bs6Bh5mmSPT5lqEsyhEsFSVIIGYFEq96oG+SfQqY8gv8Hvn7TB3FGD+gVL5On5TCOgPQb7Sr2dbLkj9cN0QQ==", "dev": true, "requires": { - "binary-install": "0.0.1" + "binary-install": "^0.1.0" } }, "watchpack": { @@ -12158,12 +12293,6 @@ "defaults": "^1.0.3" } }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, "webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -12611,17 +12740,6 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12631,6 +12749,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -12744,7 +12875,8 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "dev": true, + "optional": true }, "yargs": { "version": "16.2.0", diff --git a/client/web/package.json b/client/web/package.json index d0d792a03e..05b60ae016 100644 --- a/client/web/package.json +++ b/client/web/package.json @@ -17,9 +17,9 @@ "license": "Apache-2.0", "homepage": "https://www.graphite.design", "dependencies": { - "vue": "^3.0.11", + "vue": "^3.1.4", "vue-class-component": "^8.0.0-0", - "vue-loader": "^16.2.0" + "vue-loader": "^16.3.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^2.33.0", @@ -27,23 +27,23 @@ "@vue/cli-plugin-eslint": "^4.5.13", "@vue/cli-plugin-typescript": "^4.5.13", "@vue/cli-service": "^4.5.13", - "@vue/compiler-sfc": "^3.0.11", + "@vue/compiler-sfc": "^3.1.4", "@vue/eslint-config-airbnb": "^5.0.2", "@vue/eslint-config-typescript": "^5.0.2", "@wasm-tool/wasm-pack-plugin": "github:wasm-tool/wasm-pack-plugin", "eslint": "^6.8.0", "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.23.1", - "eslint-plugin-prettier-vue": "^3.0.0", - "eslint-plugin-vue": "^7.9.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-prettier-vue": "^3.1.0", + "eslint-plugin-vue": "^7.12.1", "lint-staged": "^9.5.0", - "prettier": "^2.3.0", - "sass": "^1.32.13", + "prettier": "^2.3.2", + "sass": "^1.35.1", "sass-loader": "^8.0.2", - "typescript": "^4.2.0", + "typescript": "^4.3.5", "vue-svg-loader": "^0.17.0-beta.2", - "vue-template-compiler": "^2.6.12", - "wasm-pack": "^0.9.1" + "vue-template-compiler": "^2.6.14", + "wasm-pack": "^0.10.0" }, "gitHooks": { "pre-commit": "lint-staged" From 1e6724ed6e19e9d042cf4161e4519bf92e248620 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 2 Jul 2021 15:02:01 -0700 Subject: [PATCH 007/365] Downgrade `@vue/compiler-sfc` which breaks linting --- client/web/package-lock.json | 278 +++++++++++++++++++++-------------- client/web/package.json | 2 +- 2 files changed, 167 insertions(+), 113 deletions(-) diff --git a/client/web/package-lock.json b/client/web/package-lock.json index 00e49747d2..8e0b4694b9 100644 --- a/client/web/package-lock.json +++ b/client/web/package-lock.json @@ -265,12 +265,6 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, - "@types/estree": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", - "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", - "dev": true - }, "@types/express": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", @@ -560,6 +554,95 @@ "tslint": "^5.20.1", "webpack": "^4.0.0", "yorkie": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "fork-ts-checker-webpack-plugin-v5": { + "version": "npm:fork-ts-checker-webpack-plugin@5.2.1", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz", + "integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==", + "dev": true, + "optional": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "optional": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@vue/cli-plugin-vuex": { @@ -760,9 +843,9 @@ } }, "vue-loader-v16": { - "version": "npm:vue-loader@16.2.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz", - "integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==", + "version": "npm:vue-loader@16.3.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.0.tgz", + "integrity": "sha512-UDgni/tUVSdwHuQo+vuBmEgamWx88SuSlEb5fgdvHrlJSPB9qMBRF6W7bfPWSqDns425Gt1wxAUif+f+h/rWjg==", "dev": true, "optional": true, "requires": { @@ -852,18 +935,17 @@ } }, "@vue/compiler-sfc": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.4.tgz", - "integrity": "sha512-4KDQg60Khy3SgnF+V/TB2NZqzmM4TyGRmzsxqG1SebGdMSecCweFDSlI/F1vDYk6dKiCHgmpoT9A1sLxswkJ0A==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.0.11.tgz", + "integrity": "sha512-7fNiZuCecRleiyVGUWNa6pn8fB2fnuJU+3AGjbjl7r1P5wBivfl02H4pG+2aJP5gh2u+0wXov1W38tfWOphsXw==", "dev": true, "requires": { "@babel/parser": "^7.13.9", "@babel/types": "^7.13.0", - "@types/estree": "^0.0.48", - "@vue/compiler-core": "3.1.4", - "@vue/compiler-dom": "3.1.4", - "@vue/compiler-ssr": "3.1.4", - "@vue/shared": "3.1.4", + "@vue/compiler-core": "3.0.11", + "@vue/compiler-dom": "3.0.11", + "@vue/compiler-ssr": "3.0.11", + "@vue/shared": "3.0.11", "consolidate": "^0.16.0", "estree-walker": "^2.0.1", "hash-sum": "^2.0.0", @@ -876,6 +958,35 @@ "source-map": "^0.6.1" }, "dependencies": { + "@vue/compiler-core": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.11.tgz", + "integrity": "sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw==", + "dev": true, + "requires": { + "@babel/parser": "^7.12.0", + "@babel/types": "^7.12.0", + "@vue/shared": "3.0.11", + "estree-walker": "^2.0.1", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-dom": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.11.tgz", + "integrity": "sha512-+3xB50uGeY5Fv9eMKVJs2WSRULfgwaTJsy23OIltKgMrynnIj8hTYY2UL97HCoz78aDw1VDXdrBQ4qepWjnQcw==", + "dev": true, + "requires": { + "@vue/compiler-core": "3.0.11", + "@vue/shared": "3.0.11" + } + }, + "@vue/shared": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz", + "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA==", + "dev": true + }, "consolidate": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", @@ -914,13 +1025,44 @@ } }, "@vue/compiler-ssr": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.4.tgz", - "integrity": "sha512-Box8fCuCFPp0FuimIswjDkjwiSDCBkHvt/xVALyFkYCiIMWv2eR53fIjmlsnEHhcBuZ+VgRC+UanCTcKvSA1gA==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.0.11.tgz", + "integrity": "sha512-66yUGI8SGOpNvOcrQybRIhl2M03PJ+OrDPm78i7tvVln86MHTKhM3ERbALK26F7tXl0RkjX4sZpucCpiKs3MnA==", "dev": true, "requires": { - "@vue/compiler-dom": "3.1.4", - "@vue/shared": "3.1.4" + "@vue/compiler-dom": "3.0.11", + "@vue/shared": "3.0.11" + }, + "dependencies": { + "@vue/compiler-core": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.11.tgz", + "integrity": "sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw==", + "dev": true, + "requires": { + "@babel/parser": "^7.12.0", + "@babel/types": "^7.12.0", + "@vue/shared": "3.0.11", + "estree-walker": "^2.0.1", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-dom": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.11.tgz", + "integrity": "sha512-+3xB50uGeY5Fv9eMKVJs2WSRULfgwaTJsy23OIltKgMrynnIj8hTYY2UL97HCoz78aDw1VDXdrBQ4qepWjnQcw==", + "dev": true, + "requires": { + "@vue/compiler-core": "3.0.11", + "@vue/shared": "3.0.11" + } + }, + "@vue/shared": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz", + "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA==", + "dev": true + } } }, "@vue/component-compiler-utils": { @@ -5347,95 +5489,6 @@ } } }, - "fork-ts-checker-webpack-plugin-v5": { - "version": "npm:fork-ts-checker-webpack-plugin@5.2.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz", - "integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==", - "dev": true, - "optional": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "optional": true, - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -7127,7 +7180,8 @@ }, "glob-parent": { "version": "5.1.1", - "resolved": "", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { "is-glob": "^4.0.1" diff --git a/client/web/package.json b/client/web/package.json index 05b60ae016..7aa7b200b4 100644 --- a/client/web/package.json +++ b/client/web/package.json @@ -27,7 +27,7 @@ "@vue/cli-plugin-eslint": "^4.5.13", "@vue/cli-plugin-typescript": "^4.5.13", "@vue/cli-service": "^4.5.13", - "@vue/compiler-sfc": "^3.1.4", + "@vue/compiler-sfc": "~3.0.11", "@vue/eslint-config-airbnb": "^5.0.2", "@vue/eslint-config-typescript": "^5.0.2", "@wasm-tool/wasm-pack-plugin": "github:wasm-tool/wasm-pack-plugin", From 34ae4d738718299c031d80e90c0df04753bf70e9 Mon Sep 17 00:00:00 2001 From: kastenbutt Date: Sat, 3 Jul 2021 11:28:52 +0200 Subject: [PATCH 008/365] Auto-select new layer created by Pen Tool. (#235) Signed-off-by: kastenbutt --- core/document/src/document.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index d376a292db..0bd84ef7f7 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -203,8 +203,9 @@ impl Document { } => { let points: Vec = points.iter().map(|&it| it.into()).collect(); let polyline = PolyLine::new(points); - self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline), *transform, *style), *insert_index)?; - Some(vec![DocumentResponse::DocumentChanged]) + let id = self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline), *transform, *style), *insert_index)?; + let path = [path.clone(), vec![id]].concat(); + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } Operation::AddShape { path, From cd52629c80833c79b8125585102cadf09c73c201 Mon Sep 17 00:00:00 2001 From: Paul Kupper <11900073+pkupper@users.noreply.github.com> Date: Sun, 4 Jul 2021 22:29:50 +0200 Subject: [PATCH 009/365] Don't use ESLint formatter for Rust files in VS Code (#228) * Remove default formatter from vscode settings * Only use eslint formatter on js/ts/json/vue files --- .vscode/settings.json | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 343c7b7789..393af3659b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,30 +3,20 @@ "editor.formatOnSave": true, "editor.formatOnPaste": true }, - "[vue]": { - "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, + "[typescript, javascript, json, vue]": { + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, "editor.formatOnSave": true, + "editor.defaultFormatter": "dbaeumer.vscode-eslint", }, - "[javascript]": { - "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, - "editor.formatOnSave": true, - }, - "[json]": { - "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, - "editor.formatOnSave": true, - }, - "[typescript, json]": { - "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, - "editor.formatOnSave": true, - }, - "rust-analyzer.diagnostics.disabled": [ - "missing-unsafe" // Remove when rust-analyzer bug fixes unsafe code on WASM JavaScript https://github.com/rust-analyzer/rust-analyzer/issues/5412 - ], + "rust-analyzer.experimental.procAttrMacros": true, "files.eol": "\n", "html.format.wrapLineLength": 200, - "editor.defaultFormatter": "dbaeumer.vscode-eslint", "eslint.format.enable": true, - "eslint.workingDirectories": ["./client/web"], + "eslint.workingDirectories": [ + "./client/web" + ], "eslint.validate": [ "javascript", "typescript", From 7b65409c589584552eb7462d72322ee1c7c56ae0 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Sun, 4 Jul 2021 22:27:09 +0100 Subject: [PATCH 010/365] Re-add PartialEq removed in #230 (#232) --- core/document/src/document.rs | 2 +- core/document/src/layers/ellipse.rs | 2 +- core/document/src/layers/folder.rs | 2 +- core/document/src/layers/line.rs | 2 +- core/document/src/layers/mod.rs | 4 ++-- core/document/src/layers/polyline.rs | 2 +- core/document/src/layers/rect.rs | 2 +- core/document/src/layers/shape.rs | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 0bd84ef7f7..a7aa3848c2 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -5,7 +5,7 @@ use crate::{ DocumentError, DocumentResponse, LayerId, Operation, }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Document { pub root: Layer, pub work: Layer, diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index 25c838c50a..56a07157c0 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -5,7 +5,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Ellipse {} impl Ellipse { diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index 55fc723830..bafff20fd2 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -4,7 +4,7 @@ use super::{style, Layer, LayerData, LayerDataTypes}; use std::fmt::Write; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Folder { next_assignment_id: LayerId, pub layer_ids: Vec, diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 1419833033..7595380fce 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -6,7 +6,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Line {} impl Line { diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 7c485bb564..2ee4ad8c10 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -26,7 +26,7 @@ pub trait LayerData { fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum LayerDataTypes { Folder(Folder), Ellipse(Ellipse), @@ -77,7 +77,7 @@ impl LayerDataTypes { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Layer { pub visible: bool, pub name: Option, diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 3eb96ab0bf..4892e0320f 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use super::{style, LayerData}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct PolyLine { points: Vec, } diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 2316f13b77..503430ddad 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -6,7 +6,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Rect {} impl Rect { diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index d4cf4b1084..d00134287a 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -6,7 +6,7 @@ use super::LayerData; use std::fmt::Write; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Shape { equal_sides: bool, sides: u8, From 20420c12862885ee2da06ce1395ce8dce4a7dabb Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Mon, 5 Jul 2021 00:34:47 +0200 Subject: [PATCH 011/365] Copy and paste layers MVP (#220) * Initial implementation of copy and paste for layers * Sort layers on copy and add tests * Fix logger init for test * Fix `copy_paste_deleted_layers` test * Readd erroneously removed svg * Make Layer serializable and cleanup * Add test for copy and pasting folders * Cleanup * Rename left_mouseup * Cleanup * Add length check to test * Fix typo * Make mouseup, mousedown more consistent --- Cargo.lock | 142 ++++++++++- core/document/Cargo.toml | 5 +- core/document/src/document.rs | 31 +++ core/document/src/layers/ellipse.rs | 3 +- core/document/src/layers/folder.rs | 3 +- core/document/src/layers/line.rs | 3 +- core/document/src/layers/mod.rs | 17 +- core/document/src/layers/polyline.rs | 3 +- core/document/src/layers/rect.rs | 3 +- core/document/src/layers/shape.rs | 3 +- core/document/src/operation.rs | 9 +- core/editor/Cargo.toml | 3 + core/editor/src/communication/dispatcher.rs | 225 ++++++++++++++++++ .../src/document/document_message_handler.rs | 62 ++++- core/editor/src/input/input_mapper.rs | 4 +- core/editor/src/misc/mod.rs | 1 + core/editor/src/misc/test_utils.rs | 83 +++++++ 17 files changed, 568 insertions(+), 32 deletions(-) create mode 100644 core/editor/src/misc/test_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 6988fd1948..39120197f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,13 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] [[package]] name = "arrayvec" @@ -8,6 +15,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -16,9 +34,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" [[package]] name = "cfg-if" @@ -42,11 +60,27 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "glam" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" +dependencies = [ + "serde", +] [[package]] name = "graphite-cli" @@ -67,6 +101,7 @@ name = "graphite-editor-core" version = "0.1.0" dependencies = [ "bitflags", + "env_logger", "glam", "graphite-document-core", "graphite-proc-macros", @@ -101,6 +136,21 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "itoa" version = "0.4.7" @@ -123,6 +173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e30b1df631d23875f230ed3ddd1a88c231f269a04b2044eb6ca87e763b5f4c42" dependencies = [ "arrayvec", + "serde", ] [[package]] @@ -131,6 +182,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" + [[package]] name = "log" version = "0.4.14" @@ -140,11 +197,17 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -158,6 +221,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "ryu" version = "1.0.5" @@ -203,29 +283,38 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" dependencies = [ "proc-macro2", "quote", @@ -339,3 +428,34 @@ dependencies = [ "js-sys", "wasm-bindgen", ] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/core/document/Cargo.toml b/core/document/Cargo.toml index 9d8657640e..d54a5f2576 100644 --- a/core/document/Cargo.toml +++ b/core/document/Cargo.toml @@ -10,6 +10,7 @@ license = "Apache-2.0" [dependencies] log = "0.4" -kurbo = "0.8" + +kurbo = {version="0.8", features = ["serde"]} serde = { version = "1.0", features = ["derive"] } -glam = "0.16" +glam = { version = "0.16", features = ["serde"] } diff --git a/core/document/src/document.rs b/core/document/src/document.rs index a7aa3848c2..8307cf5dbe 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -133,6 +133,30 @@ impl Document { self.folder(path)?.layer(id).ok_or(DocumentError::LayerNotFound) } + /// Given a path to a layer, returns a vector of the indices in the layer tree + /// These indices can be used to order a list of layers + pub fn indices_for_path(&self, mut path: &[LayerId]) -> Result, DocumentError> { + let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) { + path = &path[self.work_mount_path.len()..]; + &self.work + } else { + &self.root + } + .as_folder()?; + let mut indices = vec![]; + let (path, layer_id) = split_path(path)?; + + for id in path { + let pos = root.layer_ids.iter().position(|x| *x == *id).ok_or(DocumentError::LayerNotFound)?; + indices.push(pos); + root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?; + } + + indices.push(root.layer_ids.iter().position(|x| *x == layer_id).ok_or(DocumentError::LayerNotFound)?); + + Ok(indices) + } + /// Returns a mutable reference to the layer struct at the specified `path`. /// If you manually edit the layer you have to set the cache_dirty flag yourself. pub fn layer_mut(&mut self, path: &[LayerId]) -> Result<&mut Layer, DocumentError> { @@ -227,6 +251,13 @@ impl Document { let (path, _) = split_path(path.as_slice()).unwrap_or_else(|_| (&[], 0)); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.to_vec() }]) } + Operation::PasteLayer { path, layer } => { + let folder = self.folder_mut(path)?; + //FIXME: This clone of layer should be avoided somehow + folder.add_layer(layer.clone(), -1).ok_or(DocumentError::IndexOutOfBounds)?; + + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.clone() }]) + } Operation::DuplicateLayer { path } => { let layer = self.layer(&path)?.clone(); let (folder_path, _) = split_path(path.as_slice()).unwrap_or_else(|_| (&[], 0)); diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index 56a07157c0..65e8b6ea87 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -3,9 +3,10 @@ use kurbo::Shape; use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default, Deserialize, Serialize)] pub struct Ellipse {} impl Ellipse { diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index bafff20fd2..dae9344c86 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -2,9 +2,10 @@ use crate::{DocumentError, LayerId}; use super::{style, Layer, LayerData, LayerDataTypes}; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Folder { next_assignment_id: LayerId, pub layer_ids: Vec, diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 7595380fce..8b36a2e2cb 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -4,9 +4,10 @@ use kurbo::Point; use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct Line {} impl Line { diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 2ee4ad8c10..48a8ebbdcd 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -4,6 +4,7 @@ pub mod ellipse; pub use ellipse::Ellipse; pub mod line; +use glam::{DMat2, DVec2}; use kurbo::BezPath; pub use line::Line; @@ -17,16 +18,16 @@ pub mod shape; pub use shape::Shape; pub mod folder; -pub use folder::Folder; - use crate::DocumentError; +pub use folder::Folder; +use serde::{Deserialize, Serialize}; pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum LayerDataTypes { Folder(Folder), Ellipse(Ellipse), @@ -77,11 +78,19 @@ impl LayerDataTypes { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize)] +#[serde(remote = "glam::DAffine2")] +struct DAffine2Ref { + pub matrix2: DMat2, + pub translation: DVec2, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Layer { pub visible: bool, pub name: Option, pub data: LayerDataTypes, + #[serde(with = "DAffine2Ref")] pub transform: glam::DAffine2, pub style: style::PathStyle, pub cache: String, diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 4892e0320f..c2558fae13 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,8 +1,9 @@ +use serde::{Deserialize, Serialize}; use std::fmt::Write; use super::{style, LayerData}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct PolyLine { points: Vec, } diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 503430ddad..0cd3e7f946 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -4,9 +4,10 @@ use kurbo::Point; use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Rect {} impl Rect { diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index d00134287a..6ff9a71c6e 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -4,9 +4,10 @@ use kurbo::Vec2; use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Shape { equal_sides: bool, sides: u8, diff --git a/core/document/src/operation.rs b/core/document/src/operation.rs index 960ce35ce7..96902ee5c1 100644 --- a/core/document/src/operation.rs +++ b/core/document/src/operation.rs @@ -1,4 +1,7 @@ -use crate::{layers::style, LayerId}; +use crate::{ + layers::{style, Layer}, + LayerId, +}; use serde::{Deserialize, Serialize}; @@ -44,6 +47,10 @@ pub enum Operation { DuplicateLayer { path: Vec, }, + PasteLayer { + layer: Layer, + path: Vec, + }, AddFolder { path: Vec, }, diff --git a/core/editor/Cargo.toml b/core/editor/Cargo.toml index aa701dc168..5f6c36e21b 100644 --- a/core/editor/Cargo.toml +++ b/core/editor/Cargo.toml @@ -19,3 +19,6 @@ glam = "0.16" [dependencies.document-core] path = "../document" package = "graphite-document-core" + +[dev-dependencies] +env_logger = "0.8.4" diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index 7081b17ca0..5e519d7083 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -76,3 +76,228 @@ impl Dispatcher { } } } + +#[cfg(test)] +mod test { + use crate::{ + message_prelude::{DocumentMessage, Message}, + misc::test_utils::EditorTestUtils, + Editor, + }; + use document_core::{color::Color, Operation}; + use log::info; + + fn init_logger() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + /// Create an editor instance with three layers + /// 1. A red rectangle + /// 2. A blue shape + /// 3. A green ellipse + fn create_editor_with_three_layers() -> Editor { + let mut editor = Editor::new(Box::new(|e| { + info!("Got frontend message: {:?}", e); + })); + + editor.select_primary_color(Color::RED); + editor.draw_rect(100, 200, 300, 400); + editor.select_primary_color(Color::BLUE); + editor.draw_shape(10, 1200, 1300, 400); + editor.select_primary_color(Color::GREEN); + editor.draw_ellipse(104, 1200, 1300, 400); + + editor + } + + #[test] + /// - create rect, shape and ellipse + /// - copy + /// - paste + /// - assert that ellipse was copied + fn copy_paste_single_layer() { + init_logger(); + let mut editor = create_editor_with_three_layers(); + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); + + assert_eq!(layers_before_copy.len(), 3); + assert_eq!(layers_after_copy.len(), 4); + + // Existing layers are unaffected + for i in 0..=2 { + assert_eq!(layers_before_copy[i], layers_after_copy[i]); + } + + // The ellipse was copied + assert_eq!(layers_before_copy[2], layers_after_copy[3]); + } + + #[test] + /// - create rect, shape and ellipse + /// - select shape + /// - copy + /// - paste + /// - assert that shape was copied + fn copy_paste_single_layer_from_middle() { + init_logger(); + let mut editor = create_editor_with_three_layers(); + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1]; + + editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![shape_id]]))).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); + + assert_eq!(layers_before_copy.len(), 3); + assert_eq!(layers_after_copy.len(), 4); + + // Existing layers are unaffected + for i in 0..=2 { + assert_eq!(layers_before_copy[i], layers_after_copy[i]); + } + + // The shape was copied + assert_eq!(layers_before_copy[1], layers_after_copy[3]); + } + + #[test] + fn copy_paste_folder() { + init_logger(); + let mut editor = create_editor_with_three_layers(); + + const FOLDER_INDEX: usize = 3; + const ELLIPSE_INDEX: usize = 2; + const SHAPE_INDEX: usize = 1; + const RECT_INDEX: usize = 0; + + const LINE_INDEX: usize = 0; + const PEN_INDEX: usize = 1; + + editor.handle_message(Message::Document(DocumentMessage::AddFolder(vec![]))).unwrap(); + + let document_before_added_shapes = editor.dispatcher.document_message_handler.active_document().document.clone(); + let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX]; + + // TODO: This adding of a Line and Pen should be rewritten using the corresponding functions in EditorTestUtils. + // This has not been done yet as the line and pen tool are not yet able to add layers to the currently selected folder + editor + .handle_message(Message::Document(DocumentMessage::DispatchOperation(Operation::AddLine { + path: vec![folder_id], + insert_index: 0, + transform: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + style: Default::default(), + }))) + .unwrap(); + + editor + .handle_message(Message::Document(DocumentMessage::DispatchOperation(Operation::AddPen { + path: vec![folder_id], + insert_index: 0, + transform: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + style: Default::default(), + points: vec![(10.0, 20.0), (30.0, 40.0)], + }))) + .unwrap(); + + editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![folder_id]]))).unwrap(); + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::DeleteSelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); + + assert_eq!(layers_before_copy.len(), 4); + assert_eq!(layers_after_copy.len(), 5); + + let rect_before_copy = &layers_before_copy[RECT_INDEX]; + let ellipse_before_copy = &layers_before_copy[ELLIPSE_INDEX]; + let shape_before_copy = &layers_before_copy[SHAPE_INDEX]; + let folder_before_copy = &layers_before_copy[FOLDER_INDEX]; + let line_before_copy = folder_before_copy.as_folder().unwrap().layers()[LINE_INDEX].clone(); + let pen_before_copy = folder_before_copy.as_folder().unwrap().layers()[PEN_INDEX].clone(); + + assert_eq!(&layers_after_copy[0], rect_before_copy); + assert_eq!(&layers_after_copy[1], shape_before_copy); + assert_eq!(&layers_after_copy[2], ellipse_before_copy); + assert_eq!(&layers_after_copy[3], folder_before_copy); + assert_eq!(&layers_after_copy[4], folder_before_copy); + + // Check the layers inside the two folders + let first_folder_layers_after_copy = layers_after_copy[3].as_folder().unwrap().layers(); + let second_folder_layers_after_copy = layers_after_copy[4].as_folder().unwrap().layers(); + + assert_eq!(first_folder_layers_after_copy.len(), 2); + assert_eq!(second_folder_layers_after_copy.len(), 2); + + assert_eq!(first_folder_layers_after_copy[0], line_before_copy); + assert_eq!(first_folder_layers_after_copy[1], pen_before_copy); + + assert_eq!(second_folder_layers_after_copy[0], line_before_copy); + assert_eq!(second_folder_layers_after_copy[1], pen_before_copy); + } + + #[test] + /// - create rect, shape and ellipse + /// - select ellipse and rect + /// - copy + /// - delete + /// - create another rect + /// - paste + /// - paste + fn copy_paste_deleted_layers() { + init_logger(); + let mut editor = create_editor_with_three_layers(); + + const ELLIPSE_INDEX: usize = 2; + const SHAPE_INDEX: usize = 1; + const RECT_INDEX: usize = 0; + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + let rect_id = document_before_copy.root.as_folder().unwrap().layer_ids[RECT_INDEX]; + let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX]; + + editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![rect_id], vec![ellipse_id]]))).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::DeleteSelectedLayers)).unwrap(); + editor.draw_rect(0, 800, 12, 200); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); + + assert_eq!(layers_before_copy.len(), 3); + assert_eq!(layers_after_copy.len(), 6); + + let rect_before_copy = &layers_before_copy[RECT_INDEX]; + let ellipse_before_copy = &layers_before_copy[ELLIPSE_INDEX]; + + assert_eq!(layers_after_copy[0], layers_before_copy[SHAPE_INDEX]); + assert_eq!(&layers_after_copy[2], rect_before_copy); + assert_eq!(&layers_after_copy[3], ellipse_before_copy); + assert_eq!(&layers_after_copy[4], rect_before_copy); + assert_eq!(&layers_after_copy[5], ellipse_before_copy); + } +} diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index b449ca6509..a9d2e8872a 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -1,10 +1,9 @@ -use crate::{ - input::{mouse::ViewportPosition, InputPreprocessor}, - message_prelude::*, -}; +use crate::input::{mouse::ViewportPosition, InputPreprocessor}; +use crate::message_prelude::*; +use document_core::layers::Layer; use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation}; use glam::{DAffine2, DVec2}; -use log::info; +use log::warn; use crate::document::Document; use std::collections::VecDeque; @@ -17,6 +16,8 @@ pub enum DocumentMessage { DeleteLayer(Vec), DeleteSelectedLayers, DuplicateSelectedLayers, + CopySelectedLayers, + PasteLayers, AddFolder(Vec), RenameLayer(Vec, String), ToggleLayerVisibility(Vec), @@ -52,6 +53,7 @@ pub struct DocumentMessageHandler { active_document: usize, mmb_down: bool, mouse_pos: ViewportPosition, + copy_buffer: Vec, } impl DocumentMessageHandler { @@ -81,6 +83,32 @@ impl DocumentMessageHandler { // TODO: Add deduplication (!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten() } + + /// Returns the paths to the selected layers in order + fn selected_layers_sorted(&self) -> Vec> { + // Compute the indices for each layer to be able to sort them + let mut layers_with_indices: Vec<(Vec, Vec)> = self + .active_document() + .layer_data + .iter() + .filter_map(|(path, data)| data.selected.then(|| path.clone())) + .filter_map(|path| { + // Currently it is possible that layer_data contains layers that are don't actually exist + // and thus indices_for_path can return an error. We currently skip these layers and log a warning. + // Once this problem is solved this code can be simplified + match self.active_document().document.indices_for_path(&path) { + Err(err) => { + warn!("selected_layers_sorted: Could not get indices for the layer {:?}: {:?}", path, err); + None + } + Ok(indices) => Some((path, indices)), + } + }) + .collect(); + + layers_with_indices.sort_by_key(|(_, indices)| indices.clone()); + return layers_with_indices.into_iter().map(|(path, _)| path).collect(); + } } impl Default for DocumentMessageHandler { @@ -90,6 +118,7 @@ impl Default for DocumentMessageHandler { active_document: 0, mmb_down: false, mouse_pos: ViewportPosition::default(), + copy_buffer: vec![], } } } @@ -228,6 +257,25 @@ impl MessageHandler for DocumentMessageHand responses.push_back(DocumentOperation::DuplicateLayer { path }.into()) } } + CopySelectedLayers => { + let paths: Vec> = self.selected_layers_sorted(); + self.copy_buffer.clear(); + for path in paths { + match self.active_document().document.layer(&path).map(|t| t.clone()) { + Ok(layer) => { + self.copy_buffer.push(layer); + } + Err(e) => warn!("Could not access selected layer {:?}: {:?}", path, e), + } + } + } + PasteLayers => { + for layer in self.copy_buffer.iter() { + //TODO: Should be the path to the current folder instead of root + responses.push_back(DocumentOperation::PasteLayer { layer: layer.clone(), path: vec![] }.into()) + } + } + SelectLayers(paths) => { self.clear_selection(); for path in paths { @@ -294,9 +342,9 @@ impl MessageHandler for DocumentMessageHand } fn actions(&self) -> ActionList { if self.active_document().layer_data.values().any(|data| data.selected) { - actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown) + actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown, CopySelectedLayers, PasteLayers, ) } else { - actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown) + actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown, PasteLayers) } } } diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 91d06e307f..797e2d1b3f 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -103,6 +103,7 @@ macro_rules! mapping { impl Default for Mapping { fn default() -> Self { let (up, down, pointer_move) = mapping![ + entry! {action=DocumentMessage::PasteLayers, key_down=KeyV, modifiers=[KeyControl]}, // Rectangle entry! {action=RectangleMessage::Center, key_down=KeyAlt}, entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt}, @@ -174,11 +175,12 @@ impl Default for Mapping { entry! {action=DocumentMessage::NewDocument, key_down=KeyN, modifiers=[KeyShift]}, entry! {action=DocumentMessage::NextDocument, key_down=KeyTab, modifiers=[KeyShift]}, entry! {action=DocumentMessage::CloseActiveDocument, key_down=KeyW, modifiers=[KeyShift]}, + entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]}, + entry! {action=DocumentMessage::CopySelectedLayers, key_down=KeyC, modifiers=[KeyControl]}, // Global Actions entry! {action=GlobalMessage::LogInfo, key_down=Key1}, entry! {action=GlobalMessage::LogDebug, key_down=Key2}, entry! {action=GlobalMessage::LogTrace, key_down=Key3}, - entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]}, ]; Self { up, down, pointer_move } } diff --git a/core/editor/src/misc/mod.rs b/core/editor/src/misc/mod.rs index ad0b8560a0..963324ed36 100644 --- a/core/editor/src/misc/mod.rs +++ b/core/editor/src/misc/mod.rs @@ -2,6 +2,7 @@ pub mod macros; pub mod derivable_custom_traits; mod error; +pub mod test_utils; pub use error::EditorError; pub use macros::*; diff --git a/core/editor/src/misc/test_utils.rs b/core/editor/src/misc/test_utils.rs new file mode 100644 index 0000000000..fe3b03f421 --- /dev/null +++ b/core/editor/src/misc/test_utils.rs @@ -0,0 +1,83 @@ +use crate::{ + input::{ + mouse::{MouseKeys, MouseState, ViewportPosition}, + InputPreprocessorMessage, + }, + message_prelude::{Message, ToolMessage}, + tool::ToolType, + Editor, +}; +use document_core::color::Color; + +/// A set of utility functions to make the writing of editor test more declarative +pub trait EditorTestUtils { + fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + + /// Select given tool and drag it from (x1, y1) to (x2, y2) + fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32); + fn move_mouse(&mut self, x: u32, y: u32); + fn mousedown(&mut self, state: MouseState); + fn mouseup(&mut self, state: MouseState); + fn lmb_mousedown(&mut self, x: u32, y: u32); + fn input(&mut self, message: InputPreprocessorMessage); + fn select_tool(&mut self, typ: ToolType); + fn select_primary_color(&mut self, color: Color); +} + +impl EditorTestUtils for Editor { + fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Rectangle, x1, y1, x2, y2); + } + + fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Shape, x1, y1, x2, y2); + } + + fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Ellipse, x1, y1, x2, y2); + } + + fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32) { + self.select_tool(typ); + self.move_mouse(x1, y1); + self.lmb_mousedown(x1, y1); + self.move_mouse(x2, y2); + self.mouseup(MouseState { + position: ViewportPosition { x: x2, y: y2 }, + mouse_keys: MouseKeys::empty(), + }); + } + + fn move_mouse(&mut self, x: u32, y: u32) { + self.input(InputPreprocessorMessage::MouseMove(ViewportPosition { x, y })); + } + + fn mousedown(&mut self, state: MouseState) { + self.input(InputPreprocessorMessage::MouseDown(state)); + } + + fn mouseup(&mut self, state: MouseState) { + self.handle_message(InputPreprocessorMessage::MouseUp(state)).unwrap() + } + + fn lmb_mousedown(&mut self, x: u32, y: u32) { + self.mousedown(MouseState { + position: ViewportPosition { x, y }, + mouse_keys: MouseKeys::LEFT, + }) + } + + fn input(&mut self, message: InputPreprocessorMessage) { + self.handle_message(Message::InputPreprocessor(message)).unwrap(); + } + + fn select_tool(&mut self, typ: ToolType) { + self.handle_message(Message::Tool(ToolMessage::SelectTool(typ))).unwrap(); + } + + fn select_primary_color(&mut self, color: Color) { + self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor(color))).unwrap(); + } +} From ad064602a5b412e57abdddcade93e0df1f8bfcd3 Mon Sep 17 00:00:00 2001 From: Paul Kupper <11900073+pkupper@users.noreply.github.com> Date: Mon, 5 Jul 2021 01:05:12 +0200 Subject: [PATCH 012/365] Implement viewport selection (#178) * Begin implementing viewport selection * Implement viewport click and drag selection for ellipse and rectangle * Begin implementing line selection * Remove debug prints * Run cargo format * Use DVec2 instead of kurbo::Point * Line and polyline intersection * Run cargo format * Add fix for missing layer panel update * Replace point selection with box selection * Formatting Co-authored-by: Keavon Chambers --- core/document/src/document.rs | 15 ++- core/document/src/intersection.rs | 57 +++++++++ core/document/src/layers/ellipse.rs | 16 ++- core/document/src/layers/folder.rs | 14 ++- core/document/src/layers/line.rs | 13 +- core/document/src/layers/mod.rs | 46 ++++++- core/document/src/layers/polyline.rs | 11 +- core/document/src/layers/rect.rs | 12 +- core/document/src/layers/shape.rs | 13 +- core/document/src/lib.rs | 1 + .../src/document/document_message_handler.rs | 2 + core/editor/src/input/input_mapper.rs | 6 + core/editor/src/tool/tools/select.rs | 112 +++++++++++++++--- 13 files changed, 293 insertions(+), 25 deletions(-) create mode 100644 core/document/src/intersection.rs diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 8307cf5dbe..260a0fc86f 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -1,4 +1,4 @@ -use glam::DAffine2; +use glam::{DAffine2, DVec2}; use crate::{ layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape}, @@ -64,6 +64,19 @@ impl Document { svg } + /// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`. + pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>) { + self.document_folder(path).unwrap().intersects_quad(quad, path, intersections); + return; + } + + /// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers. + pub fn intersects_quad_root(&self, quad: [DVec2; 4]) -> Vec> { + let mut intersections = Vec::new(); + self.intersects_quad(quad, &mut vec![], &mut intersections); + intersections + } + fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool { path.starts_with(mount_path) && self.work_mounted } diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs new file mode 100644 index 0000000000..9e749e07ff --- /dev/null +++ b/core/document/src/intersection.rs @@ -0,0 +1,57 @@ +use glam::DVec2; +use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2}; + +fn to_point(vec: DVec2) -> Point { + Point::new(vec.x, vec.y) +} + +pub fn intersect_quad_bez_path(quad: [DVec2; 4], shape: &BezPath, closed: bool) -> bool { + let lines = vec![ + Line::new(to_point(quad[0]), to_point(quad[1])), + Line::new(to_point(quad[1]), to_point(quad[2])), + Line::new(to_point(quad[2]), to_point(quad[3])), + Line::new(to_point(quad[3]), to_point(quad[0])), + ]; + // check if outlines intersect + for path_segment in shape.segments() { + for line in &lines { + if !path_segment.intersect_line(*line).is_empty() { + return true; + } + } + } + // check if selection is entirely within the shape + if closed && shape.contains(to_point(quad[0])) { + return true; + } + // check if shape is entirely within the selection + if let Some(shape_point) = get_arbitrary_point_on_path(shape) { + let mut pos = 0; + let mut neg = 0; + for line in lines { + if line.p0 == shape_point { + return true; + }; + let line_vec = Vec2::new(line.p1.x - line.p0.x, line.p1.y - line.p0.y); + let point_vec = Vec2::new(line.p1.x - shape_point.x, line.p1.y - shape_point.y); + let cross = line_vec.cross(point_vec); + if cross > 0.0 { + pos += 1; + } else if cross < 0.0 { + neg += 1; + } + if pos > 0 && neg > 0 { + return false; + } + } + } + true +} + +pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { + path.segments().next().map(|seg| match seg { + PathSeg::Line(line) => line.p0, + PathSeg::Quad(quad) => quad.p0, + PathSeg::Cubic(cubic) => cubic.p0, + }) +} diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index 65e8b6ea87..d884cab41b 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -1,5 +1,10 @@ +use glam::DAffine2; +use glam::DVec2; use kurbo::Shape; +use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; + use super::style; use super::LayerData; @@ -16,10 +21,17 @@ impl Ellipse { } impl LayerData for Ellipse { - fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { - kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.1) + fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.01) } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } + + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) { + intersections.push(path.clone()); + } + } } diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index dae9344c86..983482eafd 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -1,3 +1,5 @@ +use glam::DVec2; + use crate::{DocumentError, LayerId}; use super::{style, Layer, LayerData, LayerDataTypes}; @@ -13,6 +15,10 @@ pub struct Folder { } impl LayerData for Folder { + fn to_kurbo_path(&self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath { + unimplemented!() + } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, _style: style::PathStyle) { let _ = writeln!(svg, r#""); } - fn to_kurbo_path(&mut self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath { - unimplemented!() + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { + for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { + path.push(*layer_id); + layer.intersects_quad(quad, path, intersections); + path.pop(); + } } } diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 8b36a2e2cb..d8502c7f8e 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,6 +1,10 @@ +use glam::DAffine2; use glam::DVec2; use kurbo::Point; +use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; + use super::style; use super::LayerData; @@ -17,7 +21,7 @@ impl Line { } impl LayerData for Line { - fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { fn new_point(a: DVec2) -> Point { Point::new(a.x, a.y) } @@ -26,10 +30,17 @@ impl LayerData for Line { path.line_to(new_point(transform.transform_point2(DVec2::ONE))); path } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { let [x1, y1] = transform.translation.to_array(); let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array(); let _ = write!(svg, r#""#, x1, y1, x2, y2, style.render(),); } + + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) { + intersections.push(path.clone()); + } + } } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 48a8ebbdcd..dca61b3237 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -19,12 +19,16 @@ pub use shape::Shape; pub mod folder; use crate::DocumentError; +use crate::LayerId; pub use folder::Folder; use serde::{Deserialize, Serialize}; +pub const SELECTION_TOLERANCE: f64 = 5.0; + pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); - fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; + fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -51,6 +55,15 @@ macro_rules! call_kurbo_path { } }; } + +macro_rules! call_intersects_quad { + ($self:ident.intersects_quad($quad:ident, $path:ident, $intersections:ident, $style:ident) { $($variant:ident),* }) => { + match $self { + $(Self::$variant(x) => x.intersects_quad($quad, $path, $intersections, $style)),* + } + }; +} + impl LayerDataTypes { pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { call_render! { @@ -64,7 +77,7 @@ impl LayerDataTypes { } } } - pub fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath { + pub fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath { call_kurbo_path! { self.to_kurbo_path(transform, style) { Folder, @@ -76,6 +89,19 @@ impl LayerDataTypes { } } } + + pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + call_intersects_quad! { + self.intersects_quad(quad, path, intersections, style) { + Folder, + Ellipse, + Rect, + Line, + PolyLine, + Shape + } + } + } } #[derive(Serialize, Deserialize)] @@ -122,6 +148,20 @@ impl Layer { self.cache.as_str() } + pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>) { + let inv_transform = self.transform.inverse(); + let transformed_quad = [ + inv_transform.transform_point2(quad[0]), + inv_transform.transform_point2(quad[1]), + inv_transform.transform_point2(quad[2]), + inv_transform.transform_point2(quad[3]), + ]; + if !self.visible { + return; + } + self.data.intersects_quad(transformed_quad, path, intersections, self.style) + } + pub fn render_on(&mut self, svg: &mut String) { *svg += self.render(); } @@ -129,12 +169,14 @@ impl Layer { pub fn to_kurbo_path(&mut self) -> BezPath { self.data.to_kurbo_path(self.transform, self.style) } + pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> { match &mut self.data { LayerDataTypes::Folder(f) => Ok(f), _ => Err(DocumentError::NotAFolder), } } + pub fn as_folder(&self) -> Result<&Folder, DocumentError> { match &self.data { LayerDataTypes::Folder(f) => Ok(&f), diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index c2558fae13..068b7585ab 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,3 +1,5 @@ +use crate::{intersection::intersect_quad_bez_path, LayerId}; +use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; use std::fmt::Write; @@ -17,7 +19,7 @@ impl PolyLine { } impl LayerData for PolyLine { - fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { let mut path = kurbo::BezPath::new(); self.points .iter() @@ -27,6 +29,7 @@ impl LayerData for PolyLine { .for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) }); path } + fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { if self.points.is_empty() { return; @@ -40,6 +43,12 @@ impl LayerData for PolyLine { } let _ = write!(svg, r#""{} />"#, style.render()); } + + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) { + intersections.push(path.clone()); + } + } } #[cfg(test)] diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 0cd3e7f946..22c0befeb0 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -1,6 +1,10 @@ +use glam::DAffine2; use glam::DVec2; use kurbo::Point; +use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; + use super::style; use super::LayerData; @@ -17,7 +21,7 @@ impl Rect { } impl LayerData for Rect { - fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { + fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { fn new_point(a: DVec2) -> Point { Point::new(a.x, a.y) } @@ -32,4 +36,10 @@ impl LayerData for Rect { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } + + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) { + intersections.push(path.clone()); + } + } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index 6ff9a71c6e..b81257fe60 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -1,3 +1,8 @@ +use glam::DAffine2; +use glam::DVec2; + +use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; use kurbo::BezPath; use kurbo::Vec2; @@ -20,7 +25,7 @@ impl Shape { } impl LayerData for Shape { - fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath { + fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath { fn unit_rotation(theta: f64) -> Vec2 { Vec2::new(-theta.sin(), theta.cos()) } @@ -66,4 +71,10 @@ impl LayerData for Shape { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } + + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) { + intersections.push(path.clone()); + } + } } diff --git a/core/document/src/lib.rs b/core/document/src/lib.rs index d933bf42b1..9e419d6a32 100644 --- a/core/document/src/lib.rs +++ b/core/document/src/lib.rs @@ -1,5 +1,6 @@ pub mod color; pub mod document; +pub mod intersection; pub mod layers; pub mod operation; pub mod response; diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index a9d2e8872a..106a506c9b 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -281,6 +281,8 @@ impl MessageHandler for DocumentMessageHand for path in paths { responses.extend(self.select_layer(&path)); } + // TODO: Correctly update layer panel in clear_selection instead of here + responses.extend(self.handle_folder_changed(Vec::new())); } Undo => { // this is a temporary fix and will be addressed by #123 diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 797e2d1b3f..d8fb62afb1 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -104,6 +104,12 @@ impl Default for Mapping { fn default() -> Self { let (up, down, pointer_move) = mapping![ entry! {action=DocumentMessage::PasteLayers, key_down=KeyV, modifiers=[KeyControl]}, + // Select + entry! {action=SelectMessage::MouseMove, message=InputMapperMessage::PointerMove}, + entry! {action=SelectMessage::DragStart, key_down=Lmb}, + entry! {action=SelectMessage::DragStop, key_up=Lmb}, + entry! {action=SelectMessage::Abort, key_down=Rmb}, + entry! {action=SelectMessage::Abort, key_down=KeyEscape}, // Rectangle entry! {action=RectangleMessage::Center, key_down=KeyAlt}, entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt}, diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index 1df1166c72..832824c8cd 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -1,4 +1,11 @@ -use crate::input::InputPreprocessor; +use document_core::color::Color; +use document_core::layers::style::Fill; +use document_core::layers::style::Stroke; +use document_core::layers::{style, SELECTION_TOLERANCE}; +use document_core::Operation; +use glam::{DAffine2, DVec2}; + +use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; @@ -11,19 +18,29 @@ pub struct Select { #[impl_message(Message, ToolMessage, Select)] #[derive(PartialEq, Clone, Debug)] pub enum SelectMessage { + DragStart, + DragStop, MouseMove, + Abort, } impl<'a> MessageHandler> for Select { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses); } - advertise_actions!(); + fn actions(&self) -> ActionList { + use SelectToolFsmState::*; + match self.fsm_state { + Ready => actions!(SelectMessageDiscriminant; DragStart), + Dragging => actions!(SelectMessageDiscriminant; DragStop, MouseMove, Abort), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum SelectToolFsmState { Ready, + Dragging, } impl Default for SelectToolFsmState { @@ -32,29 +49,96 @@ impl Default for SelectToolFsmState { } } -#[derive(Default)] -struct SelectToolData; +#[derive(Clone, Debug, Default)] +struct SelectToolData { + drag_start: ViewportPosition, + drag_current: ViewportPosition, +} impl Fsm for SelectToolFsmState { type ToolData = SelectToolData; - fn transition( - self, - event: ToolMessage, - _document: &SvgDocument, - _tool_data: &DocumentToolData, - _data: &mut Self::ToolData, - _input: &InputPreprocessor, - _responses: &mut VecDeque, - ) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { + let transform = document.root.transform; use SelectMessage::*; use SelectToolFsmState::*; if let ToolMessage::Select(event) = event { match (self, event) { - (Ready, MouseMove) => self, + (Ready, DragStart) => { + data.drag_start = input.mouse.position; + data.drag_current = input.mouse.position; + responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into()); + Dragging + } + (Dragging, MouseMove) => { + data.drag_current = input.mouse.position; + + responses.push_back(Operation::ClearWorkingFolder.into()); + responses.push_back(make_operation(data, tool_data, transform)); + + Dragging + } + (Dragging, DragStop) => { + data.drag_current = input.mouse.position; + + responses.push_back(Operation::ClearWorkingFolder.into()); + + let (point_1, point_2) = if data.drag_start == data.drag_current { + let (x, y) = (data.drag_current.x as f64, data.drag_current.y as f64); + ( + DVec2::new(x - SELECTION_TOLERANCE, y - SELECTION_TOLERANCE), + DVec2::new(x + SELECTION_TOLERANCE, y + SELECTION_TOLERANCE), + ) + } else { + ( + DVec2::new(data.drag_start.x as f64, data.drag_start.y as f64), + DVec2::new(data.drag_current.x as f64, data.drag_current.y as f64), + ) + }; + + let quad = [ + DVec2::new(point_1.x, point_1.y), + DVec2::new(point_2.x, point_1.y), + DVec2::new(point_2.x, point_2.y), + DVec2::new(point_1.x, point_2.y), + ]; + + if data.drag_start == data.drag_current { + if let Some(intersection) = document.intersects_quad_root(quad).last() { + responses.push_back(DocumentMessage::SelectLayers(vec![intersection.clone()]).into()); + } else { + responses.push_back(DocumentMessage::SelectLayers(vec![]).into()); + } + } else { + responses.push_back(DocumentMessage::SelectLayers(document.intersects_quad_root(quad)).into()); + } + + Ready + } + (Dragging, Abort) => { + responses.push_back(Operation::DiscardWorkingFolder.into()); + + Ready + } + _ => self, } } else { self } } } + +fn make_operation(data: &SelectToolData, _tool_data: &DocumentToolData, transform: DAffine2) -> Message { + let x0 = data.drag_start.x as f64; + let y0 = data.drag_start.y as f64; + let x1 = data.drag_current.x as f64; + let y1 = data.drag_current.y as f64; + + Operation::AddRect { + path: vec![], + insert_index: -1, + transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(), + style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x31, 0x94, 0xD6), 2.0)), Some(Fill::none())), + } + .into() +} From a12e14c2823751a065b08dca1dc808d5fa3e2e9f Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 5 Jul 2021 00:38:18 -0700 Subject: [PATCH 013/365] Fix horizontal scrollbar due to status bar negative margin --- client/web/src/components/window/MainWindow.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/web/src/components/window/MainWindow.vue b/client/web/src/components/window/MainWindow.vue index 23a45d7777..98829bfc0c 100644 --- a/client/web/src/components/window/MainWindow.vue +++ b/client/web/src/components/window/MainWindow.vue @@ -29,6 +29,8 @@ .status-bar-row { flex: 0 0 auto; + // Prevents the creation of a scrollbar due to the child's negative margin + overflow: hidden; } From e46889dc592a36ef01ec88db17c0ddae8193409f Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 8 Jul 2021 14:10:30 -0700 Subject: [PATCH 014/365] Add the checkbox input widget (#204) * Add the checkbox input widget * Add OptionalInput widget --- .vscode/vuecomp.code-snippets | 4 +- client/web/assets/12px-solid/checkmark.svg | 3 + client/web/assets/12px-solid/grid.svg | 10 +++ client/web/assets/12px-solid/link.svg | 4 + client/web/assets/12px-solid/overlays.svg | 10 +++ client/web/assets/12px-solid/snapping.svg | 6 ++ .../16px-solid/align-horizontal-right.svg | 4 +- .../16px-solid/align-vertical-bottom.svg | 4 +- ...sibility-eye-hidden.svg => eye-hidden.svg} | 0 ...bility-eye-visible.svg => eye-visible.svg} | 0 client/web/src/components/panels/Document.vue | 37 ++++++++- .../src/components/widgets/WorkingColors.vue | 4 +- .../widgets/buttons/PopoverButton.vue | 9 +++ .../widgets/inputs/CheckboxInput.vue | 74 +++++++++++++++++ .../widgets/inputs/OptionalInput.vue | 80 +++++++++++++++++++ .../src/components/widgets/labels/Icon.vue | 64 ++++++++------- 16 files changed, 274 insertions(+), 39 deletions(-) create mode 100644 client/web/assets/12px-solid/checkmark.svg create mode 100644 client/web/assets/12px-solid/grid.svg create mode 100644 client/web/assets/12px-solid/link.svg create mode 100644 client/web/assets/12px-solid/overlays.svg create mode 100644 client/web/assets/12px-solid/snapping.svg rename client/web/assets/16px-solid/{visibility-eye-hidden.svg => eye-hidden.svg} (100%) rename client/web/assets/16px-solid/{visibility-eye-visible.svg => eye-visible.svg} (100%) create mode 100644 client/web/src/components/widgets/inputs/CheckboxInput.vue create mode 100644 client/web/src/components/widgets/inputs/OptionalInput.vue diff --git a/.vscode/vuecomp.code-snippets b/.vscode/vuecomp.code-snippets index 0eb1fd698a..737f769673 100644 --- a/.vscode/vuecomp.code-snippets +++ b/.vscode/vuecomp.code-snippets @@ -22,10 +22,10 @@ "import { defineComponent } from \"vue\";", "", "export default defineComponent({", - "\tcomponents: {", - "\t},", "\tprops: {", "\t},", + "\tcomponents: {", + "\t},", "});", "", "", diff --git a/client/web/assets/12px-solid/checkmark.svg b/client/web/assets/12px-solid/checkmark.svg new file mode 100644 index 0000000000..83f0568ce7 --- /dev/null +++ b/client/web/assets/12px-solid/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/web/assets/12px-solid/grid.svg b/client/web/assets/12px-solid/grid.svg new file mode 100644 index 0000000000..f0a474d51b --- /dev/null +++ b/client/web/assets/12px-solid/grid.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/web/assets/12px-solid/link.svg b/client/web/assets/12px-solid/link.svg new file mode 100644 index 0000000000..a29d1cccc1 --- /dev/null +++ b/client/web/assets/12px-solid/link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/web/assets/12px-solid/overlays.svg b/client/web/assets/12px-solid/overlays.svg new file mode 100644 index 0000000000..848b2c268f --- /dev/null +++ b/client/web/assets/12px-solid/overlays.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/web/assets/12px-solid/snapping.svg b/client/web/assets/12px-solid/snapping.svg new file mode 100644 index 0000000000..2a84016dc8 --- /dev/null +++ b/client/web/assets/12px-solid/snapping.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/web/assets/16px-solid/align-horizontal-right.svg b/client/web/assets/16px-solid/align-horizontal-right.svg index 29dd918ff8..4f86e83140 100644 --- a/client/web/assets/16px-solid/align-horizontal-right.svg +++ b/client/web/assets/16px-solid/align-horizontal-right.svg @@ -1,5 +1,5 @@ - - + + diff --git a/client/web/assets/16px-solid/align-vertical-bottom.svg b/client/web/assets/16px-solid/align-vertical-bottom.svg index d481401b4c..8b60abb680 100644 --- a/client/web/assets/16px-solid/align-vertical-bottom.svg +++ b/client/web/assets/16px-solid/align-vertical-bottom.svg @@ -1,5 +1,5 @@ - - + + diff --git a/client/web/assets/16px-solid/visibility-eye-hidden.svg b/client/web/assets/16px-solid/eye-hidden.svg similarity index 100% rename from client/web/assets/16px-solid/visibility-eye-hidden.svg rename to client/web/assets/16px-solid/eye-hidden.svg diff --git a/client/web/assets/16px-solid/visibility-eye-visible.svg b/client/web/assets/16px-solid/eye-visible.svg similarity index 100% rename from client/web/assets/16px-solid/visibility-eye-visible.svg rename to client/web/assets/16px-solid/eye-visible.svg diff --git a/client/web/src/components/panels/Document.vue b/client/web/src/components/panels/Document.vue index 387231136f..2c1d3cd882 100644 --- a/client/web/src/components/panels/Document.vue +++ b/client/web/src/components/panels/Document.vue @@ -52,15 +52,39 @@
+ + +

Snapping

+

More snapping options will be here

+
+ + + + + +

Grid

+

More grid options will be here

+
+ + + + + +

Overlays

+

More overlays options will be here

+
+ + + - -

Display Mode

-

More display mode options will be here

-
+ +

Display Mode

+

More display mode options will be here

+
@@ -176,6 +200,7 @@ import PopoverButton from "../widgets/buttons/PopoverButton.vue"; import RadioInput from "../widgets/inputs/RadioInput.vue"; import NumberInput from "../widgets/inputs/NumberInput.vue"; import DropdownInput from "../widgets/inputs/DropdownInput.vue"; +import OptionalInput from "../widgets/inputs/OptionalInput.vue"; import { SectionsOfMenuListEntries } from "../widgets/floating-menus/MenuList.vue"; const modeMenuEntries: SectionsOfMenuListEntries = [ @@ -282,6 +307,9 @@ export default defineComponent({ SeparatorType, modeMenuEntries, viewModeIndex: 0, + snappingEnabled: true, + gridEnabled: true, + overlaysEnabled: true, }; }, components: { @@ -295,6 +323,7 @@ export default defineComponent({ RadioInput, NumberInput, DropdownInput, + OptionalInput, }, }); diff --git a/client/web/src/components/widgets/WorkingColors.vue b/client/web/src/components/widgets/WorkingColors.vue index 07939a19f2..025830ec08 100644 --- a/client/web/src/components/widgets/WorkingColors.vue +++ b/client/web/src/components/widgets/WorkingColors.vue @@ -2,8 +2,8 @@
- - + +
diff --git a/client/web/src/components/widgets/buttons/PopoverButton.vue b/client/web/src/components/widgets/buttons/PopoverButton.vue index d1d5bf1379..bc07a3cd58 100644 --- a/client/web/src/components/widgets/buttons/PopoverButton.vue +++ b/client/web/src/components/widgets/buttons/PopoverButton.vue @@ -35,6 +35,15 @@ fill: var(--color-f-white); } } + + // TODO: Refactor this and other complicated cases dealing with joined widget margins and border-radius by adding a single standard set of classes: joined-first, joined-inner, and joined-last + div[class*="-input"] + & { + margin-left: 1px; + + .icon-button { + border-radius: 0 2px 2px 0; + } + } } diff --git a/client/web/src/components/widgets/inputs/CheckboxInput.vue b/client/web/src/components/widgets/inputs/CheckboxInput.vue new file mode 100644 index 0000000000..4816cebd7e --- /dev/null +++ b/client/web/src/components/widgets/inputs/CheckboxInput.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/client/web/src/components/widgets/inputs/OptionalInput.vue b/client/web/src/components/widgets/inputs/OptionalInput.vue new file mode 100644 index 0000000000..f40d4007d9 --- /dev/null +++ b/client/web/src/components/widgets/inputs/OptionalInput.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/client/web/src/components/widgets/labels/Icon.vue b/client/web/src/components/widgets/labels/Icon.vue index 1cf0416f4f..9729d4442c 100644 --- a/client/web/src/components/widgets/labels/Icon.vue +++ b/client/web/src/components/widgets/labels/Icon.vue @@ -30,27 +30,27 @@ diff --git a/client/web/src/components/window/title-bar/WindowButtonsMac.vue b/client/web/src/components/window/title-bar/WindowButtonsMac.vue index a41b719c6e..c9dcac660a 100644 --- a/client/web/src/components/window/title-bar/WindowButtonsMac.vue +++ b/client/web/src/components/window/title-bar/WindowButtonsMac.vue @@ -20,15 +20,15 @@ border-radius: 50%; &.close { - background: #ff5f57; + background: #ff5a52; } &.minimize { - background: #ffbd2f; + background: #e6c029; } &.zoom { - background: #29c93f; + background: #54c22b; } } } diff --git a/client/web/src/components/window/title-bar/WindowButtonsWeb.vue b/client/web/src/components/window/title-bar/WindowButtonsWeb.vue new file mode 100644 index 0000000000..e102cc4154 --- /dev/null +++ b/client/web/src/components/window/title-bar/WindowButtonsWeb.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/client/web/src/main.ts b/client/web/src/main.ts index c1cb0f2b32..4b83aaef72 100644 --- a/client/web/src/main.ts +++ b/client/web/src/main.ts @@ -1,9 +1,11 @@ import { createApp } from "vue"; +import { fullscreenModeChanged } from "@/utilities/fullscreen"; import { handleKeyUp, handleKeyDown } from "@/utilities/input"; import App from "./App.vue"; // Bind global browser events document.addEventListener("contextmenu", (e) => e.preventDefault()); +document.addEventListener("fullscreenchange", () => fullscreenModeChanged()); window.addEventListener("keyup", (e: KeyboardEvent) => handleKeyUp(e)); window.addEventListener("keydown", (e: KeyboardEvent) => handleKeyDown(e)); diff --git a/client/web/src/utilities/fullscreen.ts b/client/web/src/utilities/fullscreen.ts new file mode 100644 index 0000000000..744e0a7be7 --- /dev/null +++ b/client/web/src/utilities/fullscreen.ts @@ -0,0 +1,37 @@ +import { reactive, readonly } from "vue"; + +const state = reactive({ + windowFullscreen: false, + keyboardLocked: false, +}); + +export function fullscreenModeChanged() { + state.windowFullscreen = Boolean(document.fullscreenElement); + if (!state.windowFullscreen) state.keyboardLocked = false; +} + +export function keyboardLockApiSupported(): boolean { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return "keyboard" in navigator && "lock" in (navigator as any).keyboard; +} + +export async function enterFullscreen() { + await document.documentElement.requestFullscreen(); + + if (keyboardLockApiSupported()) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (navigator as any).keyboard.lock(["ControlLeft", "ControlRight"]); + state.keyboardLocked = true; + } +} + +export async function exitFullscreen() { + await document.exitFullscreen(); +} + +export async function toggleFullscreen() { + if (state.windowFullscreen) await exitFullscreen(); + else await enterFullscreen(); +} + +export default readonly(state); diff --git a/client/web/src/utilities/input.ts b/client/web/src/utilities/input.ts index b695498523..eacb61897d 100644 --- a/client/web/src/utilities/input.ts +++ b/client/web/src/utilities/input.ts @@ -1,3 +1,5 @@ +import { toggleFullscreen } from "@/utilities/fullscreen"; + const wasm = import("@/../wasm/pkg"); export function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean { @@ -6,7 +8,11 @@ export function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean if (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable) return false; // Don't redirect a fullscreen request - if (e.key.toLowerCase() === "f11") return false; + if (e.key.toLowerCase() === "f11" && e.type === "keydown" && !e.repeat) { + e.preventDefault(); + toggleFullscreen(); + return false; + } // Don't redirect a reload request if (e.key.toLowerCase() === "f5") return false; From c76bb608d302d3a6f788630983b678a7fb85e174 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 14 Jul 2021 20:11:51 -0700 Subject: [PATCH 034/365] Cleanup of variable naming --- client/web/src/components/panels/Document.vue | 6 +- client/web/src/components/workspace/Panel.vue | 12 ++-- .../src/components/workspace/Workspace.vue | 1 - client/web/src/utilities/response-handler.ts | 34 +++++----- client/web/wasm/src/document.rs | 2 +- core/editor/src/communication/dispatcher.rs | 2 +- .../src/document/document_message_handler.rs | 68 ++++++++++++------- .../src/frontend/frontend_message_handler.rs | 8 +-- 8 files changed, 75 insertions(+), 58 deletions(-) diff --git a/client/web/src/components/panels/Document.vue b/client/web/src/components/panels/Document.vue index 6ac091a882..2e71f025dd 100644 --- a/client/web/src/components/panels/Document.vue +++ b/client/web/src/components/panels/Document.vue @@ -193,7 +193,7 @@ diff --git a/client/web/src/components/widgets/floating-menus/MenuList.vue b/client/web/src/components/widgets/floating-menus/MenuList.vue index 2dd5cc23a3..05d4ad281f 100644 --- a/client/web/src/components/widgets/floating-menus/MenuList.vue +++ b/client/web/src/components/widgets/floating-menus/MenuList.vue @@ -12,10 +12,10 @@ @mouseleave="handleEntryMouseLeave(entry)" :data-hover-menu-spawner-extend="entry.children && []" > - +
- +
@@ -53,7 +53,7 @@ flex: 0 0 auto; } - .icon svg { + .icon-label svg { fill: var(--color-e-nearwhite); } @@ -66,7 +66,7 @@ margin-left: 8px; } - .icon, + .icon-label, .no-icon { margin: 0 4px; @@ -125,7 +125,7 @@ import { defineComponent, PropType } from "vue"; import { keyboardLockApiSupported } from "@/utilities/fullscreen"; import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue"; import Separator, { SeparatorDirection, SeparatorType } from "@/components/widgets/separators/Separator.vue"; -import Icon from "@/components/widgets/labels/Icon.vue"; +import IconLabel from "@/components/widgets/labels/IconLabel.vue"; import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue"; export type MenuListEntries = Array; @@ -259,7 +259,7 @@ const MenuList = defineComponent({ components: { FloatingMenu, Separator, - Icon, + IconLabel, UserInputLabel, }, }); diff --git a/client/web/src/components/widgets/inputs/CheckboxInput.vue b/client/web/src/components/widgets/inputs/CheckboxInput.vue index 775eab99c9..3e7e2c872e 100644 --- a/client/web/src/components/widgets/inputs/CheckboxInput.vue +++ b/client/web/src/components/widgets/inputs/CheckboxInput.vue @@ -3,7 +3,7 @@
@@ -26,7 +26,7 @@ padding: 2px; border-radius: 2px; - .icon { + .icon-label { fill: var(--color-2-mildblack); } } @@ -40,7 +40,7 @@ .checkbox-box { background: var(--color-accent); - .icon { + .icon-label { fill: var(--color-f-white); } } @@ -54,7 +54,7 @@ diff --git a/client/web/src/components/widgets/inputs/DropdownInput.vue b/client/web/src/components/widgets/inputs/DropdownInput.vue index 30b74da763..b6d146dc8f 100644 --- a/client/web/src/components/widgets/inputs/DropdownInput.vue +++ b/client/web/src/components/widgets/inputs/DropdownInput.vue @@ -1,9 +1,9 @@