From d0ba6312c2e6651ad1dbfe744c2eecff66e98b15 Mon Sep 17 00:00:00 2001 From: Kai Vogelgesang Date: Tue, 9 Nov 2021 11:25:24 +0100 Subject: [PATCH] Update --- boilerbloat/src/main/main.ts | 64 ++---- boilerbloat/src/main/preload.js | 16 +- boilerbloat/src/patterns/chaser.ts | 33 +++ boilerbloat/src/patterns/proto.ts | 10 + boilerbloat/src/renderer/App.css | 62 ------ boilerbloat/src/renderer/App.tsx | 18 +- boilerbloat/src/renderer/index.ejs | 10 +- rust_native_module/index.d.ts | 61 +++--- .../src/beat_tracking/metronome.rs | 47 +++++ rust_native_module/src/beat_tracking/mod.rs | 67 ++++++ rust_native_module/src/lib.rs | 198 +----------------- rust_native_module/src/output/mod.rs | 196 ++++++++++++++++- 12 files changed, 422 insertions(+), 360 deletions(-) create mode 100644 boilerbloat/src/patterns/chaser.ts create mode 100644 boilerbloat/src/patterns/proto.ts create mode 100644 rust_native_module/src/beat_tracking/metronome.rs create mode 100644 rust_native_module/src/beat_tracking/mod.rs diff --git a/boilerbloat/src/main/main.ts b/boilerbloat/src/main/main.ts index 9803c48..dc7dbb7 100644 --- a/boilerbloat/src/main/main.ts +++ b/boilerbloat/src/main/main.ts @@ -12,57 +12,28 @@ import 'core-js/stable'; import 'regenerator-runtime/runtime'; import path from 'path'; import { app, BrowserWindow, shell, ipcMain } from 'electron'; -import { autoUpdater } from 'electron-updater'; -import log from 'electron-log'; -import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; -export default class AppUpdater { - constructor() { - log.transports.file.level = 'info'; - autoUpdater.logger = log; - autoUpdater.checkForUpdatesAndNotify(); - } -} +import rust from 'rust_native_module'; let mainWindow: BrowserWindow | null = null; -ipcMain.on('ipc-example', async (event, arg) => { - const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; - console.log(msgTemplate(arg)); - event.reply('ipc-example', msgTemplate('pong')); -}); +let beat_tracker = rust.getBeatTracker(); -if (process.env.NODE_ENV === 'production') { - const sourceMapSupport = require('source-map-support'); - sourceMapSupport.install(); +// TODO @thamma why does this not infer that beat_tracker has tap? +if (beat_tracker.type === 'success') { + + console.log('Beat Tracker started.'); + + ipcMain.on('beat-tracking', async (_, arg) => { + if (arg === 'tap') { + // see here + (beat_tracker as any).tap(); + } + }); } -const isDevelopment = - process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; - -if (isDevelopment) { - require('electron-debug')(); -} - -const installExtensions = async () => { - const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS']; - - return installer - .default( - extensions.map((name) => installer[name]), - forceDownload - ) - .catch(console.log); -}; - const createWindow = async () => { - if (isDevelopment) { - await installExtensions(); - } - const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') : path.join(__dirname, '../../assets'); @@ -81,6 +52,8 @@ const createWindow = async () => { }, }); + mainWindow.removeMenu(); + mainWindow.loadURL(resolveHtmlPath('index.html')); mainWindow.on('ready-to-show', () => { @@ -98,18 +71,11 @@ const createWindow = async () => { mainWindow = null; }); - const menuBuilder = new MenuBuilder(mainWindow); - menuBuilder.buildMenu(); - // Open urls in the user's browser mainWindow.webContents.on('new-window', (event, url) => { event.preventDefault(); shell.openExternal(url); }); - - // Remove this if your app does not use auto updates - // eslint-disable-next-line - new AppUpdater(); }; /** diff --git a/boilerbloat/src/main/preload.js b/boilerbloat/src/main/preload.js index d9fbb91..925f2b0 100644 --- a/boilerbloat/src/main/preload.js +++ b/boilerbloat/src/main/preload.js @@ -3,23 +3,19 @@ const rust = require('rust_native_module'); contextBridge.exposeInMainWorld('electron', { ipcRenderer: { - myPing() { - ipcRenderer.send('ipc-example', 'ping'); + send(channel, data) { + const validChannels = ['beat-tracking']; + if (validChannels.includes(channel)) { + ipcRenderer.send(channel, data); + } }, on(channel, func) { - const validChannels = ['ipc-example']; + const validChannels = ['tick', 'beat-tracking']; if (validChannels.includes(channel)) { // Deliberately strip event as it includes `sender` ipcRenderer.on(channel, (event, ...args) => func(...args)); } }, - once(channel, func) { - const validChannels = ['ipc-example']; - if (validChannels.includes(channel)) { - // Deliberately strip event as it includes `sender` - ipcRenderer.once(channel, (event, ...args) => func(...args)); - } - }, }, rustding: rust }); diff --git a/boilerbloat/src/patterns/chaser.ts b/boilerbloat/src/patterns/chaser.ts new file mode 100644 index 0000000..02cb22b --- /dev/null +++ b/boilerbloat/src/patterns/chaser.ts @@ -0,0 +1,33 @@ +import { MovingHeadState } from 'rust_native_module'; +import { Pattern, Time } from './proto'; + +export class ChaserPattern implements Pattern { + render(time: Time): Array { + let head_number = Math.ceil(time.beat_relative) % 4; + + let template: MovingHeadState = { + startAddress: 0, + pan: 0, + tilt: 0, + brightness: { + type: 'dimmer', + value: 0.2, + }, + rgbw: [255, 0, 0, 0], + speed: 0 + } + + let result = []; + + for (let [i, startAddress] of [1, 15, 29, 43].entries()) { + result[i] = template; + result[i].startAddress = startAddress; + + if (i === head_number) { + result[i].brightness = { type: 'dimmer', value: 1 }; + } + } + + return result; + } +} diff --git a/boilerbloat/src/patterns/proto.ts b/boilerbloat/src/patterns/proto.ts new file mode 100644 index 0000000..5b39f9f --- /dev/null +++ b/boilerbloat/src/patterns/proto.ts @@ -0,0 +1,10 @@ +import { MovingHeadState } from "rust_native_module"; + +export type Time = { + absolute: number, + beat_relative: number, +}; + +export interface Pattern { + render(time: Time): Array; +} diff --git a/boilerbloat/src/renderer/App.css b/boilerbloat/src/renderer/App.css index 616d9a4..e69de29 100644 --- a/boilerbloat/src/renderer/App.css +++ b/boilerbloat/src/renderer/App.css @@ -1,62 +0,0 @@ -/* - * @NOTE: Prepend a `~` to css file paths that are in your node_modules - * See https://github.com/webpack-contrib/sass-loader#imports - */ -body { - position: relative; - color: white; - height: 100vh; - background: linear-gradient( - 200.96deg, - #fedc2a -29.09%, - #dd5789 51.77%, - #7a2c9e 129.35% - ); - font-family: sans-serif; - overflow-y: hidden; - display: flex; - justify-content: center; - align-items: center; -} - -button { - background-color: white; - padding: 10px 20px; - border-radius: 10px; - border: none; - appearance: none; - font-size: 1.3rem; - box-shadow: 0px 8px 28px -6px rgba(24, 39, 75, 0.12), - 0px 18px 88px -4px rgba(24, 39, 75, 0.14); - transition: all ease-in 0.1s; - cursor: pointer; - opacity: 0.9; -} - -button:hover { - transform: scale(1.05); - opacity: 1; -} - -li { - list-style: none; -} - -a { - text-decoration: none; - height: fit-content; - width: fit-content; - margin: 10px; -} - -a:hover { - opacity: 1; - text-decoration: none; -} - -.Hello { - display: flex; - justify-content: center; - align-items: center; - margin: 20px 0; -} diff --git a/boilerbloat/src/renderer/App.tsx b/boilerbloat/src/renderer/App.tsx index ee0778d..a5dc85b 100644 --- a/boilerbloat/src/renderer/App.tsx +++ b/boilerbloat/src/renderer/App.tsx @@ -1,23 +1,19 @@ import { MemoryRouter as Router, Switch, Route } from 'react-router-dom'; -import icon from '../../assets/icon.svg'; +import { IpcRenderer } from 'electron/renderer'; import './App.css'; -import rust from 'rust_native_module'; -const deinelib = ((window as any).electron.rustding) as (typeof rust); +const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer; + +function tap() { + ipcRenderer.send("beat-tracking", "tap"); +} const Hello = () => { - const {hello} = deinelib; return (
-
- icon -
-

electron-react-boilerplate

-
-

Rust says {hello()}

-
+
); }; diff --git a/boilerbloat/src/renderer/index.ejs b/boilerbloat/src/renderer/index.ejs index 67422f3..224834b 100644 --- a/boilerbloat/src/renderer/index.ejs +++ b/boilerbloat/src/renderer/index.ejs @@ -2,17 +2,9 @@ - Hello Electron React! + Abnormal krass episch wylde Lightshow
- diff --git a/rust_native_module/index.d.ts b/rust_native_module/index.d.ts index 3dcc203..6f6d029 100644 --- a/rust_native_module/index.d.ts +++ b/rust_native_module/index.d.ts @@ -1,31 +1,42 @@ -type Result = - { type: "success" } & T - | { type: "error", message: string }; - -type Brightness = - { type: "off" } - | { type: "switch" } - | { type: "dimmer", value: number } - | { type: "strobe", value: number }; - -type MovingHeadState = { - start_address: number, // [0, 512] - - pan: number, // [-3pi/2, 3pi/2] - tilt: number, // [-pi/2, pi/2] - brightness: Brightness, - rgbw: [number, number, number, number], // RGBW, [0, 255] - speed: number, // [255, 0] -} - -type OutputHandle = { - set: (heads: [MovingHeadState, MovingHeadState, MovingHeadState, MovingHeadState]) => Result<{}>, - close: () => Result<{}>, -} - declare module rust_native_module { + type Result = + { type: "success" } & T + | { type: "error", message: string }; + + type Option = + { type: "some", value: T } + | { type: "none" } + + type Brightness = + { type: "off" } + | { type: "switch" } + | { type: "dimmer", value: number } + | { type: "strobe", value: number }; + + type MovingHeadState = { + startAddress: number, // [0, 512] + + pan: number, // [-3pi/2, 3pi/2] + tilt: number, // [-pi/2, pi/2] + brightness: Brightness, + rgbw: [number, number, number, number], // RGBW, [0, 255] + speed: number, // [255, 0] + } + + type OutputHandle = { + set: (heads: Array) => Result<{}>, + close: () => Result<{}>, + } + + type BeatTrackerHandle = { + tap: () => void, + getProgress: () => Option, + } + function listPorts(): Array; function openOutput(): Result; + + function getBeatTracker(): Result; } export = rust_native_module; \ No newline at end of file diff --git a/rust_native_module/src/beat_tracking/metronome.rs b/rust_native_module/src/beat_tracking/metronome.rs new file mode 100644 index 0000000..f60e3d8 --- /dev/null +++ b/rust_native_module/src/beat_tracking/metronome.rs @@ -0,0 +1,47 @@ +use std::time::{Duration, Instant}; + +pub struct Metronome { + taps: Vec, + beat_interval: Option, + timeout: Duration, +} + +impl Metronome { + pub fn new(timeout: Duration) -> Self { + Self { + taps: Vec::new(), + beat_interval: None, + timeout, + } + } + + pub fn tap(&mut self) { + let now = Instant::now(); + if let Some(t) = self.taps.last() { + if now - *t > self.timeout { + self.taps.clear(); + self.beat_interval = None; + } + } + + self.taps.push(now); + + let n = self.taps.len(); + if n >= 2 { + let dt = *self.taps.last().unwrap() - *self.taps.first().unwrap(); + let interval_millis = dt.as_millis() / (n - 1) as u128; + self.beat_interval = Some(interval_millis); + } + } + + pub fn current_beat_progress(&self) -> Option { + if self.beat_interval.is_none() { + return None; + } + + let now = Instant::now(); + let relative_millis = (now - *self.taps.last().unwrap()).as_millis(); + + Some(relative_millis as f64 / self.beat_interval.unwrap() as f64) + } +} diff --git a/rust_native_module/src/beat_tracking/mod.rs b/rust_native_module/src/beat_tracking/mod.rs new file mode 100644 index 0000000..5d4260a --- /dev/null +++ b/rust_native_module/src/beat_tracking/mod.rs @@ -0,0 +1,67 @@ +mod metronome; + +use std::{cell::RefCell, time::Duration}; + +use metronome::Metronome; + +use neon::prelude::*; + +type BoxedTracker = JsBox>; +impl Finalize for Metronome {} + +pub fn get_beat_tracker(mut cx: FunctionContext) -> JsResult { + let obj = cx.empty_object(); + + let success_string = cx.string("success".to_string()); + obj.set(&mut cx, "type", success_string)?; + + let boxed_tracker = cx.boxed(RefCell::new(Metronome::new(Duration::from_secs(2)))); + obj.set(&mut cx, "_rust_ptr", boxed_tracker)?; + + let tap_function = JsFunction::new(&mut cx, tap)?; + obj.set(&mut cx, "tap", tap_function)?; + + let get_progress_function = JsFunction::new(&mut cx, get_progress)?; + obj.set(&mut cx, "getProgress", get_progress_function)?; + + Ok(obj) +} + +pub fn tap(mut cx: FunctionContext) -> JsResult { + let this = cx.this(); + + let boxed_tracker = this + .get(&mut cx, "_rust_ptr")? + .downcast_or_throw::(&mut cx)?; + + boxed_tracker.borrow_mut().tap(); + + Ok(JsUndefined::new(&mut cx).upcast()) +} + +pub fn get_progress(mut cx: FunctionContext) -> JsResult { + let this = cx.this(); + + let boxed_tracker = this + .get(&mut cx, "_rust_ptr")? + .downcast_or_throw::(&mut cx)?; + + let obj = cx.empty_object(); + + let type_string; + match boxed_tracker.borrow().current_beat_progress() { + Some(progress) => { + type_string = cx.string("some".to_string()); + + let progress = cx.number(progress); + obj.set(&mut cx, "value", progress)?; + } + None => { + type_string = cx.string("none".to_string()); + } + } + + obj.set(&mut cx, "type", type_string)?; + + Ok(obj) +} diff --git a/rust_native_module/src/lib.rs b/rust_native_module/src/lib.rs index 8f4a668..d562e4d 100644 --- a/rust_native_module/src/lib.rs +++ b/rust_native_module/src/lib.rs @@ -1,199 +1,13 @@ -pub mod output; -use std::cell::RefCell; - -use output::controller::Controller; -use output::fixtures::{Brightness, DMXFixture, MovingHead}; - -type BoxedController = JsBox>; -impl Finalize for Controller {} +mod beat_tracking; +mod output; use neon::prelude::*; -fn list_ports(mut cx: FunctionContext) -> JsResult { - let array = cx.empty_array(); - - if let Ok(ports) = serialport::available_ports() { - for (i, port) in ports.iter().enumerate() { - let port_name = cx.string(&port.port_name); - array.set(&mut cx, i as u32, port_name)?; - } - } - - Ok(array) -} - -fn open_output(mut cx: FunctionContext) -> JsResult { - let path = cx.argument::(0)?; - let path = path.value(&mut cx); - - let obj = cx.empty_object(); - let success_string; - - match Controller::new(path) { - Ok(controller) => { - success_string = cx.string("success"); - - let boxed_controller = cx.boxed(RefCell::new(controller)); - obj.set(&mut cx, "_rust_ptr", boxed_controller)?; - - let set_output_function = JsFunction::new(&mut cx, set_output)?; - obj.set(&mut cx, "set", set_output_function)?; - - let close_function = JsFunction::new(&mut cx, close_output)?; - obj.set(&mut cx, "close", close_function)?; - } - Err(e) => { - success_string = cx.string("error"); - - let error_message = cx.string(e.to_string()); - obj.set(&mut cx, "message", error_message)?; - } - } - - obj.set(&mut cx, "type", success_string)?; - - Ok(obj) -} - -fn set_output(mut cx: FunctionContext) -> JsResult { - let arg = cx.argument::(0)?; - - let mut moving_heads = Vec::with_capacity(4); - - for i in 0..4 { - // why isn't neon-serde maintained anymore? T_T - - let head = arg - .get(&mut cx, i)? - .downcast_or_throw::(&mut cx)?; - - moving_heads.push(MovingHead { - start_address: head - .get(&mut cx, "startAddress")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx) as usize, - - pan: head - .get(&mut cx, "pan")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx), - - tilt: head - .get(&mut cx, "tilt")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx), - - brightness: { - let brightness = head - .get(&mut cx, "brightness")? - .downcast_or_throw::(&mut cx)?; - - match brightness - .get(&mut cx, "type")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx) - .as_str() - { - "off" => Brightness::Off, - "dimmer" => Brightness::Dimmer( - brightness - .get(&mut cx, "value")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx), - ), - "strobe" => Brightness::Strobe( - brightness - .get(&mut cx, "value")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx), - ), - "switch" => Brightness::Switch, - s => { - let err = cx.string(format!("Invalid brightness type: {}", s)); - return cx.throw(err); - } - } - }, - - rgbw: { - let rgbw = head - .get(&mut cx, "rgbw")? - .downcast_or_throw::(&mut cx)?; - - let mut vec = Vec::new(); - - for i in 0..4 { - let v = rgbw - .get(&mut cx, i)? - .downcast_or_throw::(&mut cx)? - .value(&mut cx) as u8; - - vec.push(v); - } - - (vec[0], vec[1], vec[2], vec[3]) - }, - - speed: head - .get(&mut cx, "speed")? - .downcast_or_throw::(&mut cx)? - .value(&mut cx) as u8, - }) - } - - let this = cx.this(); - let boxed_controller = this - .get(&mut cx, "_rust_ptr")? - .downcast_or_throw::(&mut cx)?; - - let boxed_controller = boxed_controller.borrow_mut(); - { - let mut state = boxed_controller.state.lock().unwrap(); - - for head in moving_heads { - head.render(&mut state.data); - } - } - - let obj = cx.empty_object(); - - let success_text = cx.string("success".to_string()); - obj.set(&mut cx, "type", success_text)?; - - Ok(obj) -} - -fn close_output(mut cx: FunctionContext) -> JsResult { - let this = cx.this(); - - let boxed_controller = this - .get(&mut cx, "_rust_ptr")? - .downcast_or_throw::(&mut cx)?; - - let obj = cx.empty_object(); - - let success_string; - - match boxed_controller.borrow_mut().stop() { - Ok(()) => { - success_string = cx.string("success"); - } - Err(e) => { - success_string = cx.string("error"); - - let error_message = cx.string(e.to_string()); - obj.set(&mut cx, "message", error_message)?; - } - } - - obj.set(&mut cx, "type", success_string)?; - - Ok(obj) -} - #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { - cx.export_function("listPorts", list_ports)?; - cx.export_function("openOutput", open_output)?; + cx.export_function("listPorts", output::list_ports)?; + cx.export_function("openOutput", output::open_output)?; + + cx.export_function("getBeatTracker", beat_tracking::get_beat_tracker)?; Ok(()) } diff --git a/rust_native_module/src/output/mod.rs b/rust_native_module/src/output/mod.rs index 501ac5e..20f1a98 100644 --- a/rust_native_module/src/output/mod.rs +++ b/rust_native_module/src/output/mod.rs @@ -1,2 +1,194 @@ -pub mod fixtures; -pub mod controller; \ No newline at end of file +mod controller; +mod fixtures; + +use std::cell::RefCell; + +use controller::Controller; +use fixtures::{Brightness, DMXFixture, MovingHead}; + +type BoxedController = JsBox>; +impl Finalize for Controller {} + +use neon::prelude::*; + +pub fn list_ports(mut cx: FunctionContext) -> JsResult { + let array = cx.empty_array(); + + if let Ok(ports) = serialport::available_ports() { + for (i, port) in ports.iter().enumerate() { + let port_name = cx.string(&port.port_name); + array.set(&mut cx, i as u32, port_name)?; + } + } + + Ok(array) +} + +pub fn open_output(mut cx: FunctionContext) -> JsResult { + let path = cx.argument::(0)?; + let path = path.value(&mut cx); + + let obj = cx.empty_object(); + let success_string; + + match Controller::new(path) { + Ok(controller) => { + success_string = cx.string("success"); + + let boxed_controller = cx.boxed(RefCell::new(controller)); + obj.set(&mut cx, "_rust_ptr", boxed_controller)?; + + let set_output_function = JsFunction::new(&mut cx, set_output)?; + obj.set(&mut cx, "set", set_output_function)?; + + let close_function = JsFunction::new(&mut cx, close_output)?; + obj.set(&mut cx, "close", close_function)?; + } + Err(e) => { + success_string = cx.string("error"); + + let error_message = cx.string(e.to_string()); + obj.set(&mut cx, "message", error_message)?; + } + } + + obj.set(&mut cx, "type", success_string)?; + + Ok(obj) +} + +fn set_output(mut cx: FunctionContext) -> JsResult { + let arg = cx.argument::(0)?; + + let mut moving_heads = Vec::with_capacity(4); + + for i in 0..arg.len(&mut cx) { + // why isn't neon-serde maintained anymore? T_T + + let head = arg + .get(&mut cx, i)? + .downcast_or_throw::(&mut cx)?; + + moving_heads.push(MovingHead { + start_address: head + .get(&mut cx, "startAddress")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx) as usize, + + pan: head + .get(&mut cx, "pan")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx), + + tilt: head + .get(&mut cx, "tilt")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx), + + brightness: { + let brightness = head + .get(&mut cx, "brightness")? + .downcast_or_throw::(&mut cx)?; + + match brightness + .get(&mut cx, "type")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx) + .as_str() + { + "off" => Brightness::Off, + "dimmer" => Brightness::Dimmer( + brightness + .get(&mut cx, "value")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx), + ), + "strobe" => Brightness::Strobe( + brightness + .get(&mut cx, "value")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx), + ), + "switch" => Brightness::Switch, + s => { + let err = cx.string(format!("Invalid brightness type: {}", s)); + return cx.throw(err); + } + } + }, + + rgbw: { + let rgbw = head + .get(&mut cx, "rgbw")? + .downcast_or_throw::(&mut cx)?; + + let mut vec = Vec::new(); + + for i in 0..4 { + let v = rgbw + .get(&mut cx, i)? + .downcast_or_throw::(&mut cx)? + .value(&mut cx) as u8; + + vec.push(v); + } + + (vec[0], vec[1], vec[2], vec[3]) + }, + + speed: head + .get(&mut cx, "speed")? + .downcast_or_throw::(&mut cx)? + .value(&mut cx) as u8, + }) + } + + let this = cx.this(); + let boxed_controller = this + .get(&mut cx, "_rust_ptr")? + .downcast_or_throw::(&mut cx)?; + + let boxed_controller = boxed_controller.borrow_mut(); + { + let mut state = boxed_controller.state.lock().unwrap(); + + for head in moving_heads { + head.render(&mut state.data); + } + } + + let obj = cx.empty_object(); + + let success_text = cx.string("success".to_string()); + obj.set(&mut cx, "type", success_text)?; + + Ok(obj) +} + +fn close_output(mut cx: FunctionContext) -> JsResult { + let this = cx.this(); + + let boxed_controller = this + .get(&mut cx, "_rust_ptr")? + .downcast_or_throw::(&mut cx)?; + + let obj = cx.empty_object(); + + let success_string; + + match boxed_controller.borrow_mut().stop() { + Ok(()) => { + success_string = cx.string("success"); + } + Err(e) => { + success_string = cx.string("error"); + + let error_message = cx.string(e.to_string()); + obj.set(&mut cx, "message", error_message)?; + } + } + + obj.set(&mut cx, "type", success_string)?; + + Ok(obj) +}