Restructure project directories (#333)

`/client/web` -> `/frontend`
`/client/cli` -> *delete for now*
`/client/native` -> *delete for now*
`/core/editor` -> `/editor`
`/core/document` -> `/graphene`
`/core/renderer` -> `/charcoal`
`/core/proc-macro` -> `/proc-macros` *(now plural)*
This commit is contained in:
Keavon Chambers 2021-08-07 05:17:18 -07:00
parent 434695d578
commit 53ad105f57
239 changed files with 197 additions and 224 deletions

1
frontend/wasm/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
pkg/

43
frontend/wasm/Cargo.toml Normal file
View file

@ -0,0 +1,43 @@
[package]
name = "graphite-wasm"
version = "0.1.0"
authors = ["Graphite Authors <contact@graphite.design>"]
edition = "2018"
readme = "../../README.md"
homepage = "https://www.graphite.design"
repository = "https://github.com/GraphiteEditor/Graphite"
license = "Apache-2.0"
publish = false
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
console_error_panic_hook = { version = "0.1.6", optional = true }
editor = { path = "../../editor", package = "graphite-editor" }
graphene = { path = "../../graphene", package = "graphite-graphene" }
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "0.2.73", features = ["serde-serialize"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.22"
[package.metadata.wasm-pack.profile.dev]
wasm-opt = false
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]
debug-js-glue = true
demangle-name-section = true
dwarf-debug-info = true
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Os"]
[package.metadata.wasm-pack.profile.release.wasm-bindgen]
debug-js-glue = false
demangle-name-section = false
dwarf-debug-info = false

View file

@ -0,0 +1,313 @@
use crate::shims::Error;
use crate::wrappers::{translate_key, translate_tool, Color};
use crate::EDITOR_STATE;
use editor::input::input_preprocessor::ModifierKeys;
use editor::input::mouse::ScrollDelta;
use editor::message_prelude::*;
use editor::misc::EditorError;
use editor::tool::{tool_options::ToolOptions, tools, ToolType};
use editor::{input::mouse::MouseState, LayerId};
use graphene::layers::BlendMode;
use wasm_bindgen::prelude::*;
fn convert_error(err: editor::EditorError) -> JsValue {
Error::new(&err.to_string()).into()
}
/// Modify the currently selected tool in the document state store
#[wasm_bindgen]
pub fn select_tool(tool: String) -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| match translate_tool(&tool) {
Some(tool) => editor.borrow_mut().handle_message(ToolMessage::SelectTool(tool)).map_err(convert_error),
None => Err(Error::new(&format!("Couldn't select {} because it was not recognized as a valid tool", tool)).into()),
})
}
/// Update the options for a given tool
#[wasm_bindgen]
pub fn set_tool_options(tool: String, options: &JsValue) -> Result<(), JsValue> {
match options.into_serde::<ToolOptions>() {
Ok(options) => EDITOR_STATE.with(|editor| match translate_tool(&tool) {
Some(tool) => editor.borrow_mut().handle_message(ToolMessage::SetToolOptions(tool, options)).map_err(convert_error),
None => Err(Error::new(&format!("Couldn't set options for {} because it was not recognized as a valid tool", tool)).into()),
}),
Err(err) => Err(Error::new(&format!("Invalid JSON for ToolOptions: {}", err)).into()),
}
}
/// Send a message to a given tool
#[wasm_bindgen]
pub fn send_tool_message(tool: String, message: &JsValue) -> Result<(), JsValue> {
let tool_message = match translate_tool(&tool) {
Some(tool) => match tool {
ToolType::Select => match message.into_serde::<tools::select::SelectMessage>() {
Ok(select_message) => Ok(ToolMessage::Select(select_message)),
Err(err) => Err(Error::new(&format!("Invalid message for {}: {}", tool, err)).into()),
},
_ => Err(Error::new(&format!("Tool message sending not implemented for {}", tool)).into()),
},
None => Err(Error::new(&format!("Couldn't send message for {} because it was not recognized as a valid tool", tool)).into()),
};
EDITOR_STATE.with(|editor| match tool_message {
Ok(tool_message) => editor.borrow_mut().handle_message(tool_message).map_err(convert_error),
Err(err) => Err(err),
})
}
#[wasm_bindgen]
pub fn select_document(document: usize) -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::SelectDocument(document)).map_err(convert_error))
}
#[wasm_bindgen]
pub fn get_open_documents_list() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::GetOpenDocumentsList).map_err(convert_error))
}
#[wasm_bindgen]
pub fn new_document() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::NewDocument).map_err(convert_error))
}
#[wasm_bindgen]
pub fn close_document(document: usize) -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseDocument(document)).map_err(convert_error))
}
#[wasm_bindgen]
pub fn close_all_documents() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseAllDocuments).map_err(convert_error))
}
#[wasm_bindgen]
pub fn close_active_document_with_confirmation() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseActiveDocumentWithConfirmation).map_err(convert_error))
}
#[wasm_bindgen]
pub fn close_all_documents_with_confirmation() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseAllDocumentsWithConfirmation).map_err(convert_error))
}
// TODO: Call event when the panels are resized
/// Viewport resized
#[wasm_bindgen]
pub fn viewport_resize(new_width: u32, new_height: u32) -> Result<(), JsValue> {
let ev = InputPreprocessorMessage::ViewportResize((new_width, new_height).into());
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
// TODO: When a mouse button is down that started in the viewport, this should trigger even when the mouse is outside the viewport (or even the browser window if the browser supports it)
/// Mouse movement within the screenspace bounds of the viewport
#[wasm_bindgen]
pub fn on_mouse_move(x: u32, y: u32, modifiers: u8) -> Result<(), JsValue> {
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
// TODO: Convert these screenspace viewport coordinates to canvas coordinates based on the current zoom and pan
let ev = InputPreprocessorMessage::MouseMove((x, y).into(), mods);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// Mouse scrolling within the screenspace bounds of the viewport
#[wasm_bindgen]
pub fn on_mouse_scroll(delta_x: i32, delta_y: i32, delta_z: i32, modifiers: u8) -> Result<(), JsValue> {
// TODO: Convert these screenspace viewport coordinates to canvas coordinates based on the current zoom and pan
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
let ev = InputPreprocessorMessage::MouseScroll(ScrollDelta::new(delta_x, delta_y, delta_z), mods);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// A mouse button depressed within screenspace the bounds of the viewport
#[wasm_bindgen]
pub fn on_mouse_down(x: u32, y: u32, mouse_keys: u8, modifiers: u8) -> Result<(), JsValue> {
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
let ev = InputPreprocessorMessage::MouseDown(MouseState::from_u8_pos(mouse_keys, (x, y).into()), mods);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// A mouse button released
#[wasm_bindgen]
pub fn on_mouse_up(x: u32, y: u32, mouse_keys: u8, modifiers: u8) -> Result<(), JsValue> {
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
let ev = InputPreprocessorMessage::MouseUp(MouseState::from_u8_pos(mouse_keys, (x, y).into()), mods);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// A keyboard button depressed within screenspace the bounds of the viewport
#[wasm_bindgen]
pub fn on_key_down(name: String, modifiers: u8) -> Result<(), JsValue> {
let key = translate_key(&name);
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
log::trace!("key down {:?}, name: {}, modifiers: {:?}", key, name, mods);
let ev = InputPreprocessorMessage::KeyDown(key, mods);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// A keyboard button released
#[wasm_bindgen]
pub fn on_key_up(name: String, modifiers: u8) -> Result<(), JsValue> {
let key = translate_key(&name);
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
log::trace!("key up {:?}, name: {}, modifiers: {:?}", key, name, mods);
let ev = InputPreprocessorMessage::KeyUp(key, mods);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// Update primary color
#[wasm_bindgen]
pub fn update_primary_color(primary_color: Color) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(ToolMessage::SelectPrimaryColor(primary_color.inner())))
.map_err(convert_error)
}
/// Update secondary color
#[wasm_bindgen]
pub fn update_secondary_color(secondary_color: Color) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(ToolMessage::SelectSecondaryColor(secondary_color.inner())))
.map_err(convert_error)
}
/// Swap primary and secondary color
#[wasm_bindgen]
pub fn swap_colors() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ToolMessage::SwapColors)).map_err(convert_error)
}
/// Reset primary and secondary colors to their defaults
#[wasm_bindgen]
pub fn reset_colors() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ToolMessage::ResetColors)).map_err(convert_error)
}
/// Undo history one step
#[wasm_bindgen]
pub fn undo() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::Undo)).map_err(convert_error)
}
/// Select all layers
#[wasm_bindgen]
pub fn select_all_layers() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectAllLayers)).map_err(convert_error)
}
/// Deselect all layers
#[wasm_bindgen]
pub fn deselect_all_layers() -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::DeselectAllLayers))
.map_err(convert_error)
}
/// Reorder selected layer
#[wasm_bindgen]
pub fn reorder_selected_layers(delta: i32) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ReorderSelectedLayers(delta)))
.map_err(convert_error)
}
/// Set the blend mode for the selected layers
#[wasm_bindgen]
pub fn set_blend_mode_for_selected_layers(blend_mode_svg_style_name: String) -> Result<(), JsValue> {
let blend_mode = match blend_mode_svg_style_name.as_str() {
"normal" => BlendMode::Normal,
"multiply" => BlendMode::Multiply,
"darken" => BlendMode::Darken,
"color-burn" => BlendMode::ColorBurn,
"screen" => BlendMode::Screen,
"lighten" => BlendMode::Lighten,
"color-dodge" => BlendMode::ColorDodge,
"overlay" => BlendMode::Overlay,
"soft-light" => BlendMode::SoftLight,
"hard-light" => BlendMode::HardLight,
"difference" => BlendMode::Difference,
"exclusion" => BlendMode::Exclusion,
"hue" => BlendMode::Hue,
"saturation" => BlendMode::Saturation,
"color" => BlendMode::Color,
"luminosity" => BlendMode::Luminosity,
_ => return Err(convert_error(EditorError::Misc("UnknownBlendMode".to_string()))),
};
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SetBlendModeForSelectedLayers(blend_mode)).map_err(convert_error))
}
/// Set the opacity for the selected layers
#[wasm_bindgen]
pub fn set_opacity_for_selected_layers(opacity_percent: f64) -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| {
editor
.borrow_mut()
.handle_message(DocumentMessage::SetOpacityForSelectedLayers(opacity_percent / 100.))
.map_err(convert_error)
})
}
/// Export the document
#[wasm_bindgen]
pub fn export_document() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ExportDocument)).map_err(convert_error)
}
/// Sets the zoom to the value
#[wasm_bindgen]
pub fn set_zoom(new_zoom: f64) -> Result<(), JsValue> {
let ev = MovementMessage::SetCanvasZoom(new_zoom);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// Sets the rotation to the new value (in radians)
#[wasm_bindgen]
pub fn set_rotation(new_radians: f64) -> Result<(), JsValue> {
let ev = MovementMessage::SetCanvasRotation(new_radians);
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
}
/// Update the list of selected layers. The layer paths have to be stored in one array and are separated by LayerId::MAX
#[wasm_bindgen]
pub fn select_layers(paths: Vec<LayerId>) -> Result<(), JsValue> {
let paths = paths.split(|id| *id == LayerId::MAX).map(|path| path.to_vec()).collect();
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectLayers(paths)))
.map_err(convert_error)
}
/// Toggle visibility of a layer from the layer list
#[wasm_bindgen]
pub fn toggle_layer_visibility(path: Vec<LayerId>) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ToggleLayerVisibility(path)))
.map_err(convert_error)
}
/// Toggle expansions state of a layer from the layer list
#[wasm_bindgen]
pub fn toggle_layer_expansion(path: Vec<LayerId>) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ToggleLayerExpansion(path)))
.map_err(convert_error)
}
/// Renames a layer from the layer list
#[wasm_bindgen]
pub fn rename_layer(path: Vec<LayerId>, new_name: String) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::RenameLayer(path, new_name)))
.map_err(convert_error)
}
/// Deletes a layer from the layer list
#[wasm_bindgen]
pub fn delete_layer(path: Vec<LayerId>) -> Result<(), JsValue> {
EDITOR_STATE
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::DeleteLayer(path)))
.map_err(convert_error)
}
/// Requests the backend to add a layer to the layer list
#[wasm_bindgen]
pub fn add_folder(path: Vec<LayerId>) -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::AddFolder(path))).map_err(convert_error)
}

37
frontend/wasm/src/lib.rs Normal file
View file

@ -0,0 +1,37 @@
pub mod document;
mod shims;
pub mod utils;
pub mod window;
pub mod wrappers;
use editor::{message_prelude::*, Editor};
use std::cell::RefCell;
use utils::WasmLog;
use wasm_bindgen::prelude::*;
// the thread_local macro provides a way to initialize static variables with non-constant functions
thread_local! { pub static EDITOR_STATE: RefCell<Editor> = RefCell::new(Editor::new(Box::new(handle_response))) }
static LOGGER: WasmLog = WasmLog;
#[wasm_bindgen(start)]
pub fn init() {
utils::set_panic_hook();
log::set_logger(&LOGGER).expect("Failed to set logger");
log::set_max_level(log::LevelFilter::Debug);
}
#[wasm_bindgen(module = "/../src/utilities/response-handler-binding.ts")]
extern "C" {
#[wasm_bindgen(catch)]
fn handleResponse(responseType: String, responseData: JsValue) -> Result<(), JsValue>;
}
fn handle_response(response: FrontendMessage) {
let response_type = response.to_discriminant().local_name();
send_response(response_type, response);
}
fn send_response(response_type: String, response_data: FrontendMessage) {
let response_data = JsValue::from_serde(&response_data).expect("Failed to serialize response");
let _ = handleResponse(response_type, response_data).map_err(|error| log::error!("javascript threw an error: {:?}", error));
}

View file

@ -0,0 +1,10 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[derive(Clone, Debug)]
pub type Error;
#[wasm_bindgen(constructor)]
pub fn new(msg: &str) -> Error;
}

View file

@ -0,0 +1,45 @@
use wasm_bindgen::prelude::*;
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn info(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn warn(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(msg: &str, format: &str);
}
#[derive(Default)]
pub struct WasmLog;
impl log::Log for WasmLog {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
let (log, name, color): (fn(&str, &str), &str, &str) = match record.level() {
log::Level::Trace => (log, "trace", "color:plum"),
log::Level::Debug => (log, "debug", "color:cyan"),
log::Level::Warn => (warn, "warn", "color:goldenrod"),
log::Level::Info => (info, "info", "color:mediumseagreen"),
log::Level::Error => (error, "error", "color:red"),
};
let msg = &format!("%c{}\t{}", name, record.args());
log(msg, color)
}
fn flush(&self) {}
}

View file

@ -0,0 +1,43 @@
use wasm_bindgen::prelude::*;
type DocumentId = u32;
/// Modify the active Document in the editor state store
#[wasm_bindgen]
pub fn set_active_document(document_id: DocumentId) {
todo!("set_active_document {}", document_id)
}
/// Query the name of a specific document
#[wasm_bindgen]
pub fn get_document_name(document_id: DocumentId) -> String {
todo!("get_document_name {}", document_id)
}
/// Query the id of the most recently interacted with document
#[wasm_bindgen]
pub fn get_active_document() -> DocumentId {
todo!("get_active_document")
}
type PanelId = u32;
/// Notify the editor that the mouse hovers above a panel
#[wasm_bindgen]
pub fn panel_hover_enter(panel_id: PanelId) {
todo!("panel_hover_enter {}", panel_id)
}
/// Query a list of currently available operations
#[wasm_bindgen]
pub fn get_available_operations() -> Vec<JsValue> {
todo!("get_available_operations")
// vec!["example1", "example2"].into_iter().map(JsValue::from).collect()
}
/*
/// Load a new .gdd file into the editor
/// Returns a unique document identifier
#[wasm_bindgen]
pub fn load_document(raw_data: &[u8]) -> DocumentId {
todo!()
}*/

View file

@ -0,0 +1,140 @@
use crate::shims::Error;
use editor::input::keyboard::Key;
use editor::tool::{SelectAppendMode, ToolType};
use editor::Color as InnerColor;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Color(InnerColor);
#[wasm_bindgen]
impl Color {
#[wasm_bindgen(constructor)]
pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Result<Color, JsValue> {
match InnerColor::from_rgbaf32(red, green, blue, alpha) {
Some(v) => Ok(Self(v)),
None => Err(Error::new("invalid color").into()),
}
}
}
impl Color {
pub fn inner(&self) -> InnerColor {
self.0
}
}
macro_rules! match_string_to_enum {
(match ($e:expr) {$($var:ident),* $(,)?}) => {
match $e {
$(
stringify!($var) => Some($var),
)*
_ => None
}
};
}
pub fn translate_tool(name: &str) -> Option<ToolType> {
use ToolType::*;
match_string_to_enum!(match (name) {
Select,
Crop,
Navigate,
Eyedropper,
Text,
Fill,
Gradient,
Brush,
Heal,
Clone,
Patch,
BlurSharpen,
Relight,
Path,
Pen,
Freehand,
Spline,
Line,
Rectangle,
Ellipse,
Shape
})
}
pub fn translate_append_mode(name: &str) -> Option<SelectAppendMode> {
use SelectAppendMode::*;
match_string_to_enum!(match (name) {
New,
Add,
Subtract,
Intersect
})
}
pub fn translate_key(name: &str) -> Key {
log::trace!("pressed key: {}", name);
use Key::*;
match name.to_lowercase().as_str() {
"a" => KeyA,
"b" => KeyB,
"c" => KeyC,
"d" => KeyD,
"e" => KeyE,
"f" => KeyF,
"g" => KeyG,
"h" => KeyH,
"i" => KeyI,
"j" => KeyJ,
"k" => KeyK,
"l" => KeyL,
"m" => KeyM,
"n" => KeyN,
"o" => KeyO,
"p" => KeyP,
"q" => KeyQ,
"r" => KeyR,
"s" => KeyS,
"t" => KeyT,
"u" => KeyU,
"v" => KeyV,
"w" => KeyW,
"x" => KeyX,
"y" => KeyY,
"z" => KeyZ,
"0" => Key0,
"1" => Key1,
"2" => Key2,
"3" => Key3,
"4" => Key4,
"5" => Key5,
"6" => Key6,
"7" => Key7,
"8" => Key8,
"9" => Key9,
"enter" => KeyEnter,
"=" => KeyEquals,
"+" => KeyPlus,
"-" => KeyMinus,
"shift" => KeyShift,
// When using linux + chrome + the neo keyboard layout, the shift key is recognized as caps
"capslock" => KeyShift,
"control" => KeyControl,
"delete" => KeyDelete,
"backspace" => KeyBackspace,
"alt" => KeyAlt,
"escape" => KeyEscape,
"tab" => KeyTab,
"arrowup" => KeyArrowUp,
"arrowdown" => KeyArrowDown,
"arrowleft" => KeyArrowLeft,
"arrowright" => KeyArrowRight,
"[" => KeyLeftBracket,
"]" => KeyRightBracket,
"{" => KeyLeftCurlyBracket,
"}" => KeyRightCurlyBracket,
_ => UnknownKey,
}
}

View file

@ -0,0 +1,10 @@
#![cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn pass() {
assert_eq!(1 + 1, 2);
}