From 9908dee105bd96a12a25f35e38b0fc802ed6d719 Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 30 Jul 2017 11:54:54 -0700 Subject: [PATCH 01/99] Add more options to server --- src/bin/server.rs | 74 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index 3030ae7..3d1d77f 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -56,14 +56,16 @@ pub struct ClientHandler { } impl ClientHandler { - pub fn new(out:Sender, files:&Vec<&str>, persist:Option<&str>) -> ClientHandler { + pub fn new(out:Sender, files:&Vec<&str>, persist:Option<&str>, clean: bool) -> ClientHandler { let mut runner = ProgramRunner::new(); let outgoing = runner.program.outgoing.clone(); - runner.program.attach("system/timer", Box::new(SystemTimerWatcher::new(outgoing.clone()))); - runner.program.attach("eve/compiler", Box::new(CompilerWatcher::new(outgoing))); - runner.program.attach("client/websocket", Box::new(WebsocketClientWatcher::new(out.clone()))); - + if !clean { + runner.program.attach("system/timer", Box::new(SystemTimerWatcher::new(outgoing.clone()))); + runner.program.attach("eve/compiler", Box::new(CompilerWatcher::new(outgoing))); + runner.program.attach("client/websocket", Box::new(WebsocketClientWatcher::new(out.clone()))); + } + if let Some(persist_file) = persist { let mut persister = Persister::new(persist_file); persister.load(persist_file); @@ -75,6 +77,7 @@ impl ClientHandler { } let running = runner.run(); + ClientHandler {out, running} } } @@ -155,7 +158,7 @@ impl AfterMiddleware for Custom404 { } } -fn http_server(port:String) { +fn http_server(address: String) -> std::thread::JoinHandle<()> { thread::spawn(move || { let mut mount = Mount::new(); mount.mount("/", Static::new(Path::new("assets/index.html"))); @@ -165,19 +168,23 @@ fn http_server(port:String) { let mut chain = Chain::new(mount); chain.link_after(Custom404); - let address = format!("127.0.0.1:{}", port); - println!("{} HTTP Server at {}... ", BrightGreen.paint("Started:"), address); - println!(""); - Iron::new(chain).http(&address).unwrap(); - }); + println!("{} HTTP Server at {}... ", BrightGreen.paint("Starting:"), address); + match Iron::new(chain).http(&address) { + Ok(_) => {}, + Err(why) => println!("{} Failed to start HTTP Server: {}", BrightRed.paint("Error:"), why), + }; + + }) } -fn websocket_server(port:&str, files:&Vec<&str>, persist:Option<&str>) { - let address = format!("127.0.0.1:{}", port); - println!("{} Websocket Server at {}... ", BrightGreen.paint("Started:"), address); - listen(address, |out| { - ClientHandler::new(out, files, persist) - }).unwrap() +fn websocket_server(address: String, files:&Vec<&str>, persist:Option<&str>, clean: bool) { + println!("{} Websocket Server at {}... ", BrightGreen.paint("Starting:"), address); + match listen(address, |out| { + ClientHandler::new(out, files, persist, clean) + }) { + Ok(_) => {}, + Err(why) => println!("{} Failed to start Websocket Server: {}", BrightRed.paint("Error:"), why), + }; } //------------------------------------------------------------------------- @@ -188,8 +195,9 @@ fn main() { let matches = App::new("Eve") .version("0.4") .author("Kodowa Inc.") - .about("Creates an instance of the Eve server") + .about("Creates an instance of the Eve server. Default values for options are in parentheses.") .arg(Arg::with_name("persist") + .short("s") .long("persist") .value_name("FILE") .help("Sets the name for the database to load from and write to") @@ -200,19 +208,41 @@ fn main() { .multiple(true)) .arg(Arg::with_name("port") .short("p") + .long("port") + .value_name("PORT") + .help("Sets the port for the Eve server (3012)") + .takes_value(true)) + .arg(Arg::with_name("http-port") + .short("t") + .long("http-port") .value_name("PORT") - .help("Sets the port for the server") + .help("Sets the port for the HTTP server (8081)") + .takes_value(true)) + .arg(Arg::with_name("address") + .short("a") + .long("address") + .value_name("ADDRESS") + .help("Sets the address of the server (127.0.0.1)") .takes_value(true)) + .arg(Arg::with_name("clean") + .short("C") + .long("Clean") + .help("Starts Eve with a clean database and no watchers (false)")) .get_matches(); println!(""); - let port = matches.value_of("port").unwrap_or("3012"); + let wport = matches.value_of("port").unwrap_or("3012"); + let hport = matches.value_of("http-port").unwrap_or("8081"); + let address = matches.value_of("address").unwrap_or("127.0.0.1"); + let http_address = format!("{}:{}",address,hport); + let websocket_address = format!("{}:{}",address,wport); let files = match matches.values_of("EVE_FILES") { Some(fs) => fs.collect(), None => vec![] }; let persist = matches.value_of("persist"); + let clean = matches.is_present("clean"); - http_server("8081".to_owned()); - websocket_server(port, &files, persist); + http_server(http_address); + websocket_server(websocket_address, &files, persist, clean); } From d561cd48c472e20a66786b8860bc628db0e0e899 Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 30 Jul 2017 12:37:23 -0700 Subject: [PATCH 02/99] Get rid of old watcher file --- src/watcher.rs | 199 ------------------------------------------------- 1 file changed, 199 deletions(-) delete mode 100644 src/watcher.rs diff --git a/src/watcher.rs b/src/watcher.rs deleted file mode 100644 index 469fce3..0000000 --- a/src/watcher.rs +++ /dev/null @@ -1,199 +0,0 @@ -extern crate tokio_timer; -extern crate tokio_core; -extern crate futures; -extern crate time; - -use std::time::*; -use indexes::{WatchDiff}; -use ops::{make_scan, Constraint, Interned, Internable, Interner, Field, RawChange, RunLoopMessage}; -use compiler::{Compilation, compilation_to_blocks}; -use std::sync::mpsc::{self, Sender}; -use std::thread::{self}; -use std::collections::{HashMap}; -use std::collections::hash_map::{Entry}; - -pub trait Watcher { - fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff); -} - -//------------------------------------------------------------------------- -// System Watcher -//------------------------------------------------------------------------- - -pub struct SystemTimerWatcher { - outgoing: Sender, - timers: HashMap)> -} - -impl SystemTimerWatcher { - pub fn new(outgoing: Sender) -> SystemTimerWatcher { - SystemTimerWatcher { outgoing, timers: HashMap::new() } - } -} - -impl Watcher for SystemTimerWatcher { - fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - for remove in diff.removes { - if let Entry::Occupied(mut entry) = self.timers.entry(remove[1]) { - let should_remove = { - let pair = entry.get_mut(); - let ref mut count = pair.0; - if *count > 1 { - *count -= 1; - false - } else { - pair.1.send(()).unwrap(); - true - } - }; - if should_remove { - entry.remove_entry(); - } - } - } - - for add in diff.adds { - if let Entry::Occupied(mut entry) = self.timers.entry(add[1]) { - let ref mut count = entry.get_mut().0; - *count += 1; - continue; - } - - println!("timer: {:?}", add.iter().map(|v| interner.get_value(*v).print()).collect::>()); - let internable_resolution = interner.get_value(add[1]).clone(); - let resolution = Internable::to_number(&internable_resolution) as u64; - let id = Internable::String(format!("system/timer/change/{}", add[0])); - - let duration = Duration::from_millis(resolution); - let (sender, receiver) = mpsc::channel(); - let outgoing = self.outgoing.clone(); - self.timers.insert(add[1], (1, sender)); - - thread::spawn(move || { - let mut tick = 0; - loop { - thread::sleep(duration); - if receiver.try_recv().is_ok() { - break; - } - let cur_time = time::now(); - // println!("It's time! {:?}", cur_time); - let changes = vec![ - RawChange {e: id.clone(), a: Internable::String("tag".to_string()), v: Internable::String("system/timer/change".to_string()), n: Internable::String("System/timer".to_string()), count: 1}, - RawChange {e: id.clone(), a: Internable::String("resolution".to_string()), v: internable_resolution.clone(), n: Internable::String("System/timer".to_string()), count: 1}, - RawChange {e: id.clone(), a: Internable::String("hour".to_string()), v: Internable::from_number(cur_time.tm_hour as f32), n: Internable::String("System/timer".to_string()), count: 1}, - RawChange {e: id.clone(), a: Internable::String("minute".to_string()), v: Internable::from_number(cur_time.tm_min as f32), n: Internable::String("System/timer".to_string()), count: 1}, - RawChange {e: id.clone(), a: Internable::String("second".to_string()), v: Internable::from_number(cur_time.tm_sec as f32), n: Internable::String("System/timer".to_string()), count: 1}, - RawChange {e: id.clone(), a: Internable::String("tick".to_string()), v: Internable::from_number(tick as f32), n: Internable::String("System/timer".to_string()), count: 1}, - ]; - tick += 1; - match outgoing.send(RunLoopMessage::Transaction(changes)) { - Err(_) => break, - _ => {} - } - } - }); - } - } -} - -pub struct PrintDiffWatcher { } - -impl Watcher for PrintDiffWatcher { - fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - for remove in diff.removes { - println!("Printer: - {:?}", remove.iter().map(|v| interner.get_value(*v).print()).collect::>()); - } - for add in diff.adds { - println!("Printer: + {:?}", add.iter().map(|v| interner.get_value(*v).print()).collect::>()); - } - } -} - -// pub enum RawConstraint { -// Scan{e:Internable, a:Internable, v:Internable}, -// Output{e:Internable, a:Internable, v:Internable} -// } - -//------------------------------------------------------------------------- -// Compiler Watcher -//------------------------------------------------------------------------- - -pub struct CompilerWatcher { - outgoing: Sender, - variable_ix: usize, - variables: HashMap, - block_types: HashMap, - blocks_to_constraints: HashMap>, -} - -impl CompilerWatcher { - pub fn new(outgoing: Sender) -> CompilerWatcher { - CompilerWatcher{outgoing, - variable_ix: 0, - variables: HashMap::new(), - block_types: HashMap::new(), - blocks_to_constraints: HashMap::new()} - } - - pub fn get_field(&self, value:Interned) -> Field { - self.variables.get(&value).cloned().unwrap_or_else(|| Field::Value(value)) - } -} - -impl Watcher for CompilerWatcher { - fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - for _ in diff.removes { - println!("WARNING: Compile watcher ignoring removals for now"); - } - - let interned_variable = interner.string_id("variable"); - for v in diff.adds.iter().filter(|v| interned_variable == v[0]) { - match self.variables.entry(v[1]) { - Entry::Occupied(_) => {}, - Entry::Vacant(entry) => { - let ix = self.variable_ix; - self.variable_ix += 1; - entry.insert(Field::Register(ix)); - } - }; - } - - for add in diff.adds { - if let &Internable::String(ref kind) = interner.get_value(add[0]) { - match (kind.as_ref(), &add[1..]) { - ("block", &[block, kind]) => { - match self.block_types.entry(block) { - Entry::Occupied(_) => panic!("Cannot compile block with multiple types."), - Entry::Vacant(entry) => { entry.insert(kind); } - } - }, - ("scan", &[block, e, a, v]) => { - let scan = make_scan(self.get_field(e), self.get_field(a), self.get_field(v)); - let constraints = self.blocks_to_constraints.entry(block).or_insert_with(|| vec![]); - constraints.push(scan); - }, - ("output", &[block, e, a, v]) => { - let output = Constraint::Insert{e: self.get_field(e), a: self.get_field(a), v: self.get_field(v), commit: false}; - let constraints = self.blocks_to_constraints.entry(block).or_insert_with(|| vec![]); - constraints.push(output); - }, - ("variable", _) => {}, - _ => println!("Found other '{:?}'", add) - } - } - } - - let mut added_blocks = vec![]; - for (block, _) in self.block_types.iter() { - let mut comp = Compilation::new(format!("made up block's nice string (it's for him) {}", block)); - let constraints = self.blocks_to_constraints.get(block).unwrap(); - comp.constraints.extend(constraints.iter().cloned()); - comp.finalize(); - added_blocks.extend(compilation_to_blocks(comp, "compiler_watcher", "")); - } - println!("got some blocks? {:?}", added_blocks); - self.outgoing.send(RunLoopMessage::CodeTransaction(added_blocks, vec![])).unwrap(); - } -} - From 0a0ae0ac3e39be673a35597e96d1cd64954cc576 Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 30 Jul 2017 12:38:16 -0700 Subject: [PATCH 03/99] Watchers self-report their name This way you don't have to set it when attaching the watcher --- src/ops.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ops.rs b/src/ops.rs index 1dd0012..1c4ed40 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -3002,8 +3002,8 @@ impl Program { self.register_block(block); } - pub fn attach(&mut self, name:&str, watcher:Box) { - self.watchers.insert(name.to_string(), watcher); + pub fn attach(&mut self, watcher:Box) { + self.watchers.insert(watcher.get_name(), watcher); } pub fn get_pipes<'a>(&self, block_info:&'a BlockInfo, input: &Change, pipes: &mut Vec<&'a Vec>) { From 28436ad8ef970ffa5d42e3baee9e9ac5ad54fe47 Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 30 Jul 2017 12:38:33 -0700 Subject: [PATCH 04/99] make watchers self-report their name --- src/bin/server.rs | 9 ++++++--- src/watchers/compiler.rs | 3 +++ src/watchers/console.rs | 6 ++++++ src/watchers/file.rs | 3 +++ src/watchers/mod.rs | 1 + src/watchers/system.rs | 3 +++ 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index 3d1d77f..b09c5d0 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -61,9 +61,9 @@ impl ClientHandler { let mut runner = ProgramRunner::new(); let outgoing = runner.program.outgoing.clone(); if !clean { - runner.program.attach("system/timer", Box::new(SystemTimerWatcher::new(outgoing.clone()))); - runner.program.attach("eve/compiler", Box::new(CompilerWatcher::new(outgoing))); - runner.program.attach("client/websocket", Box::new(WebsocketClientWatcher::new(out.clone()))); + runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); + runner.program.attach(Box::new(CompilerWatcher::new(outgoing))); + runner.program.attach(Box::new(WebsocketClientWatcher::new(out.clone()))); } if let Some(persist_file) = persist { @@ -134,6 +134,9 @@ impl WebsocketClientWatcher { } impl Watcher for WebsocketClientWatcher { + fn get_name(& self) -> String { + "client/websocket".to_string() + } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { let adds:Vec> = diff.adds.iter().map(|row| { row.iter().map(|v| interner.get_value(*v).into()).collect() diff --git a/src/watchers/compiler.rs b/src/watchers/compiler.rs index 9974ba6..407a4d5 100644 --- a/src/watchers/compiler.rs +++ b/src/watchers/compiler.rs @@ -33,6 +33,9 @@ impl CompilerWatcher { } impl Watcher for CompilerWatcher { + fn get_name(& self) -> String { + "eve/compiler".to_string() + } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for _ in diff.removes { println!("WARNING: Compile watcher ignoring removals for now"); diff --git a/src/watchers/console.rs b/src/watchers/console.rs index 63a2d4d..bdb7e26 100644 --- a/src/watchers/console.rs +++ b/src/watchers/console.rs @@ -13,6 +13,9 @@ use self::term_painter::Color::*; pub struct ConsoleWatcher {} impl Watcher for ConsoleWatcher { + fn get_name(& self) -> String { + "console".to_string() + } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); @@ -34,6 +37,9 @@ impl Watcher for ConsoleWatcher { pub struct PrintDiffWatcher { } impl Watcher for PrintDiffWatcher { + fn get_name(& self) -> String { + "console/print-diff".to_string() + } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for remove in diff.removes { println!("Printer: - {:?}", remove.iter().map(|v| interner.get_value(*v).print()).collect::>()); diff --git a/src/watchers/file.rs b/src/watchers/file.rs index bc83fc2..9f5599e 100644 --- a/src/watchers/file.rs +++ b/src/watchers/file.rs @@ -25,6 +25,9 @@ fn file_error(changes: &mut Vec, id: String, why: Error) { } impl Watcher for FileWatcher { + fn get_name(& self) -> String { + "file".to_string() + } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 1e10da8..bf0efae 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -2,6 +2,7 @@ use indexes::{WatchDiff}; use ops::{Interner}; pub trait Watcher { + fn get_name(& self) -> String; fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff); } diff --git a/src/watchers/system.rs b/src/watchers/system.rs index 8d5be5b..73ff07a 100644 --- a/src/watchers/system.rs +++ b/src/watchers/system.rs @@ -25,6 +25,9 @@ impl SystemTimerWatcher { } impl Watcher for SystemTimerWatcher { + fn get_name(& self) -> String { + "system/timer".to_string() + } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for remove in diff.removes { if let Entry::Occupied(mut entry) = self.timers.entry(remove[1]) { From 97fa1ded173bbd252b015c153295e86abf869f45 Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 30 Jul 2017 12:38:43 -0700 Subject: [PATCH 05/99] new options for main exe --- src/bin/main.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index e540b2e..93abf32 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -31,6 +31,10 @@ fn main() { .help("The eve files and folders to load") .required(true) .multiple(true)) + .arg(Arg::with_name("clean") + .short("C") + .long("Clean") + .help("Starts Eve with a clean database and no watchers (false)")) .get_matches(); let files = match matches.values_of("EVE_FILES") { @@ -38,13 +42,16 @@ fn main() { None => vec![] }; let persist = matches.value_of("persist"); + let clean = matches.is_present("clean"); let mut runner = ProgramRunner::new(); let outgoing = runner.program.outgoing.clone(); - runner.program.attach("system/timer", Box::new(SystemTimerWatcher::new(outgoing.clone()))); - runner.program.attach("file", Box::new(FileWatcher::new(outgoing.clone()))); - runner.program.attach("console", Box::new(ConsoleWatcher{})); - runner.program.attach("system/print-diff", Box::new(PrintDiffWatcher{})); + if !clean { + runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); + runner.program.attach(Box::new(FileWatcher::new(outgoing.clone()))); + runner.program.attach(Box::new(ConsoleWatcher{})); + runner.program.attach(Box::new(PrintDiffWatcher{})); + } if let Some(persist_file) = persist { let mut persister = Persister::new(persist_file); From b2b8749e62d17229118f5f16c9392dc4be3ed93d Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 30 Jul 2017 13:52:55 -0700 Subject: [PATCH 06/99] Print a message when a watcher is attached --- src/ops.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ops.rs b/src/ops.rs index 1c4ed40..7778707 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -5,6 +5,7 @@ extern crate time; extern crate serde_json; extern crate bincode; +extern crate term_painter; use unicode_segmentation::UnicodeSegmentation; @@ -33,6 +34,9 @@ use std::fs::{OpenOptions, File}; use std::f32::consts::{PI}; use std::mem; use rand::{Rng, SeedableRng, XorShiftRng}; +use self::term_painter::ToStyle; +use self::term_painter::Color::*; + //------------------------------------------------------------------------- // Interned value @@ -3003,7 +3007,9 @@ impl Program { } pub fn attach(&mut self, watcher:Box) { - self.watchers.insert(watcher.get_name(), watcher); + let name = watcher.get_name(); + println!("{} {}", BrightCyan.paint("Loaded Watcher:"), name); + self.watchers.insert(name, watcher); } pub fn get_pipes<'a>(&self, block_info:&'a BlockInfo, input: &Change, pipes: &mut Vec<&'a Vec>) { From f65b0cdec9807acfb1ab96d314cce3ad2cbcb0c2 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 31 Jul 2017 12:25:03 -0700 Subject: [PATCH 07/99] add ability to set name on watcher --- src/bin/server.rs | 10 +++++++--- src/watchers/compiler.rs | 9 +++++++-- src/watchers/console.rs | 31 +++++++++++++++++++++++++++---- src/watchers/file.rs | 8 ++++++-- src/watchers/mod.rs | 1 + src/watchers/system.rs | 8 ++++++-- 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index b09c5d0..71e64fc 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -57,7 +57,7 @@ pub struct ClientHandler { impl ClientHandler { pub fn new(out:Sender, files:&Vec<&str>, persist:Option<&str>, clean: bool) -> ClientHandler { - + println!("Creating a new guy!"); let mut runner = ProgramRunner::new(); let outgoing = runner.program.outgoing.clone(); if !clean { @@ -124,18 +124,22 @@ impl Handler for ClientHandler { //------------------------------------------------------------------------- pub struct WebsocketClientWatcher { + name: String, outgoing: Sender, } impl WebsocketClientWatcher { pub fn new(outgoing: Sender) -> WebsocketClientWatcher { - WebsocketClientWatcher { outgoing } + WebsocketClientWatcher { name: "client/weboscket".to_string(), outgoing } } } impl Watcher for WebsocketClientWatcher { fn get_name(& self) -> String { - "client/websocket".to_string() + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { let adds:Vec> = diff.adds.iter().map(|row| { diff --git a/src/watchers/compiler.rs b/src/watchers/compiler.rs index 407a4d5..a435922 100644 --- a/src/watchers/compiler.rs +++ b/src/watchers/compiler.rs @@ -11,6 +11,7 @@ use std::collections::hash_map::{Entry}; //------------------------------------------------------------------------- pub struct CompilerWatcher { + name: String, outgoing: Sender, variable_ix: usize, variables: HashMap, @@ -20,7 +21,8 @@ pub struct CompilerWatcher { impl CompilerWatcher { pub fn new(outgoing: Sender) -> CompilerWatcher { - CompilerWatcher{outgoing, + CompilerWatcher{name: "eve/compiler".to_string(), + outgoing, variable_ix: 0, variables: HashMap::new(), block_types: HashMap::new(), @@ -34,7 +36,10 @@ impl CompilerWatcher { impl Watcher for CompilerWatcher { fn get_name(& self) -> String { - "eve/compiler".to_string() + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for _ in diff.removes { diff --git a/src/watchers/console.rs b/src/watchers/console.rs index bdb7e26..0bb1c38 100644 --- a/src/watchers/console.rs +++ b/src/watchers/console.rs @@ -10,11 +10,23 @@ use self::term_painter::Color::*; // Console Watcher //------------------------------------------------------------------------- -pub struct ConsoleWatcher {} +pub struct ConsoleWatcher { + name: String, +} + +impl ConsoleWatcher { + pub fn new() -> ConsoleWatcher { + ConsoleWatcher{name: "console".to_string()} + } +} + impl Watcher for ConsoleWatcher { fn get_name(& self) -> String { - "console".to_string() + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for add in diff.adds { @@ -34,11 +46,22 @@ impl Watcher for ConsoleWatcher { // Print Diff Watcher //------------------------------------------------------------------------- -pub struct PrintDiffWatcher { } +pub struct PrintDiffWatcher { + name: String, +} + +impl PrintDiffWatcher { + pub fn new() -> PrintDiffWatcher { + PrintDiffWatcher{name: "console/print-diff".to_string()} + } +} impl Watcher for PrintDiffWatcher { fn get_name(& self) -> String { - "console/print-diff".to_string() + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for remove in diff.removes { diff --git a/src/watchers/file.rs b/src/watchers/file.rs index 9f5599e..23c2932 100644 --- a/src/watchers/file.rs +++ b/src/watchers/file.rs @@ -8,12 +8,13 @@ use std::path::Path; use super::Watcher; pub struct FileWatcher { + name: String, outgoing: Sender, } impl FileWatcher { pub fn new(outgoing: Sender) -> FileWatcher { - FileWatcher { outgoing } + FileWatcher { name: "file".to_string(), outgoing } } } @@ -26,7 +27,10 @@ fn file_error(changes: &mut Vec, id: String, why: Error) { impl Watcher for FileWatcher { fn get_name(& self) -> String { - "file".to_string() + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for add in diff.adds { diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index bf0efae..dc0c1c6 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -3,6 +3,7 @@ use ops::{Interner}; pub trait Watcher { fn get_name(& self) -> String; + fn set_name(&mut self, &str); fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff); } diff --git a/src/watchers/system.rs b/src/watchers/system.rs index 73ff07a..ac1e719 100644 --- a/src/watchers/system.rs +++ b/src/watchers/system.rs @@ -14,19 +14,23 @@ use super::Watcher; //------------------------------------------------------------------------- pub struct SystemTimerWatcher { + name: String, outgoing: Sender, timers: HashMap)> } impl SystemTimerWatcher { pub fn new(outgoing: Sender) -> SystemTimerWatcher { - SystemTimerWatcher { outgoing, timers: HashMap::new() } + SystemTimerWatcher { name: "system/timer".to_string(), outgoing, timers: HashMap::new() } } } impl Watcher for SystemTimerWatcher { fn get_name(& self) -> String { - "system/timer".to_string() + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for remove in diff.removes { From ac9e3a5754fec3dc44fd0d5e973e11bbf6888a9e Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 31 Jul 2017 12:25:18 -0700 Subject: [PATCH 08/99] add watcher skeleton --- src/watchers/json.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/watchers/json.rs diff --git a/src/watchers/json.rs b/src/watchers/json.rs new file mode 100644 index 0000000..3ef57c4 --- /dev/null +++ b/src/watchers/json.rs @@ -0,0 +1,43 @@ +use super::super::indexes::{WatchDiff}; +use super::super::ops::{Internable, Interner}; +use super::Watcher; + +extern crate term_painter; +use self::term_painter::ToStyle; +use self::term_painter::Color::*; + +pub struct JsonWatcher { + name: String, + outgoing: Sender, +} + +impl JsonWatcher { + pub fn new(outgoing: Sender) -> JsonWatcher { + JsonWatcher { name: "json", outgoing } + } +} + +impl Watcher for JsonWatcher { + fn get_name(& self) -> String { + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); + } + fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { + for add in diff.adds { + let kind = Internable::to_string(interner.get_value(add[0])); + match (&kind[..], text) { + ("parse", text) => { + println!("Parsing JSON into EAVs") + }, + ("enocde", text) => { + + } + println!("{} {}", BrightYellow.paint("Warn:"), text), + ("error", text) => println!("{} {}", BrightRed.paint("Error:"), text), + _ => {}, + } + } + } +} \ No newline at end of file From 1f61855d1817b42e6e35f76a1fcf56d083332cbe Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 31 Jul 2017 13:58:30 -0700 Subject: [PATCH 09/99] Fix loading watcher in main --- src/bin/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 93abf32..6f855c4 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -49,8 +49,8 @@ fn main() { if !clean { runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(FileWatcher::new(outgoing.clone()))); - runner.program.attach(Box::new(ConsoleWatcher{})); - runner.program.attach(Box::new(PrintDiffWatcher{})); + runner.program.attach(Box::new(ConsoleWatcher::new())); + runner.program.attach(Box::new(PrintDiffWatcher::new())); } if let Some(persist_file) = persist { From 9a4690a909fe144daa871f941d994b28565a1bbd Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 31 Jul 2017 13:58:43 -0700 Subject: [PATCH 10/99] rename print diff to match TS version --- src/watchers/console.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/watchers/console.rs b/src/watchers/console.rs index 0bb1c38..e51ad39 100644 --- a/src/watchers/console.rs +++ b/src/watchers/console.rs @@ -52,7 +52,7 @@ pub struct PrintDiffWatcher { impl PrintDiffWatcher { pub fn new() -> PrintDiffWatcher { - PrintDiffWatcher{name: "console/print-diff".to_string()} + PrintDiffWatcher{name: "console/diff".to_string()} } } @@ -65,10 +65,10 @@ impl Watcher for PrintDiffWatcher { } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for remove in diff.removes { - println!("Printer: - {:?}", remove.iter().map(|v| interner.get_value(*v).print()).collect::>()); + println!("- {:?}", remove.iter().map(|v| interner.get_value(*v).print()).collect::>()); } for add in diff.adds { - println!("Printer: + {:?}", add.iter().map(|v| interner.get_value(*v).print()).collect::>()); + println!("+ {:?}", add.iter().map(|v| interner.get_value(*v).print()).collect::>()); } } } From d90b930c21243ac294705f36894a95d279741461 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 31 Jul 2017 13:59:09 -0700 Subject: [PATCH 11/99] fix spelling, remove println --- src/bin/server.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index 71e64fc..c81d35b 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -57,7 +57,6 @@ pub struct ClientHandler { impl ClientHandler { pub fn new(out:Sender, files:&Vec<&str>, persist:Option<&str>, clean: bool) -> ClientHandler { - println!("Creating a new guy!"); let mut runner = ProgramRunner::new(); let outgoing = runner.program.outgoing.clone(); if !clean { @@ -130,7 +129,7 @@ pub struct WebsocketClientWatcher { impl WebsocketClientWatcher { pub fn new(outgoing: Sender) -> WebsocketClientWatcher { - WebsocketClientWatcher { name: "client/weboscket".to_string(), outgoing } + WebsocketClientWatcher { name: "client/websocket".to_string(), outgoing } } } From 8081023fca5c27369bd026db32a33e693c0b4ed7 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 31 Jul 2017 23:34:55 -0700 Subject: [PATCH 12/99] Json deserialize --- libraries/json/json.eve | 19 +++++++++++ src/bin/main.rs | 2 ++ src/watchers/json.rs | 70 +++++++++++++++++++++++++++++++++-------- src/watchers/mod.rs | 1 + 4 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 libraries/json/json.eve diff --git a/libraries/json/json.eve b/libraries/json/json.eve new file mode 100644 index 0000000..b607bfe --- /dev/null +++ b/libraries/json/json.eve @@ -0,0 +1,19 @@ +# JSON + +A library for encoding and decoding Eve records into and from JSON + +## Encoding + +search + j = [#json/encode record] +watch json + ("encode", j, record) +end + +## Decoding + +search + j = [#json/decode json] +watch json + ("decode", j, json) +end \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs index 6f855c4..0ad6c57 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -12,6 +12,7 @@ use eve::ops::{ProgramRunner, Persister}; use eve::watchers::system::{SystemTimerWatcher}; use eve::watchers::console::{ConsoleWatcher, PrintDiffWatcher}; use eve::watchers::file::FileWatcher; +use eve::watchers::json::JsonWatcher; //------------------------------------------------------------------------- // Main @@ -51,6 +52,7 @@ fn main() { runner.program.attach(Box::new(FileWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(ConsoleWatcher::new())); runner.program.attach(Box::new(PrintDiffWatcher::new())); + runner.program.attach(Box::new(JsonWatcher::new(outgoing.clone()))); } if let Some(persist_file) = persist { diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 3ef57c4..5af467b 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -1,10 +1,12 @@ use super::super::indexes::{WatchDiff}; -use super::super::ops::{Internable, Interner}; +use super::super::ops::{Interned, Internable, Interner, RawChange, RunLoopMessage, JSONInternable}; +use std::sync::mpsc::{self, Sender}; +use std::collections::hash_map::{Entry}; use super::Watcher; -extern crate term_painter; -use self::term_painter::ToStyle; -use self::term_painter::Color::*; +extern crate serde_json; +extern crate serde; +use self::serde_json::{Map, Value, Error}; pub struct JsonWatcher { name: String, @@ -13,7 +15,7 @@ pub struct JsonWatcher { impl JsonWatcher { pub fn new(outgoing: Sender) -> JsonWatcher { - JsonWatcher { name: "json", outgoing } + JsonWatcher { name: "json".to_string(), outgoing } } } @@ -27,17 +29,59 @@ impl Watcher for JsonWatcher { fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); - match (&kind[..], text) { - ("parse", text) => { - println!("Parsing JSON into EAVs") + let record_id = Internable::to_string(interner.get_value(add[1])); + let j_arg = Internable::to_string(interner.get_value(add[2])); + let mut changes = vec![]; + match (&kind[..], j_arg) { + ("decode", j_arg) => { + let v: Value = serde_json::from_str(&j_arg).unwrap(); + print_value(v, &mut changes, &mut "ID".to_string(), "object"); }, - ("enocde", text) => { - + ("enocde", j_arg) => { + println!("encoding:\n{:?}",j_arg); } - println!("{} {}", BrightYellow.paint("Warn:"), text), - ("error", text) => println!("{} {}", BrightRed.paint("Error:"), text), _ => {}, } + match self.outgoing.send(RunLoopMessage::Transaction(changes)) { + Err(_) => break, + _ => (), + } } } -} \ No newline at end of file +} + +//changes.push(RawChange {e: id.clone(), a: Internable::String("tag".to_string()), v: Internable::String("file/read/change".to_string()), n: Internable::String("file/read".to_string()), count: 1}); + +fn print_value(value: Value, changes: &mut Vec, id: &mut String, attribute: &str) { + match value { + Value::Number(n) => { + if n.is_u64() { + let v = Internable::Number(n.as_u64().unwrap() as u32); + changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v, n: Internable::String("json/decode".to_string()), count: 1}); + } else if n.is_f64() { + let v = Internable::from_number(n.as_f64().unwrap() as f32); + changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v, n: Internable::String("json/decode".to_string()), count: 1}); + }; + }, + Value::String(ref n) => { + changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v: Internable::String(n.clone()), n: Internable::String("json/decode".to_string()), count: 1}); + }, + Value::Bool(ref n) => println!("Bool: {}",n), + Value::Array(ref n) => { + for v in n { + print_value(v.clone(), changes, id, attribute); + } + }, + Value::Object(ref n) => { + let idq = format!("{:?}",n); + changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v: Internable::String(idq.clone()), n: Internable::String("json/decode".to_string()), count: 1}); + changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String("tag".to_string()), v: Internable::String("json-object".to_string()), n: Internable::String("json/decode".to_string()), count: 1}); + for key in n.keys() { + //let mut idq = id.clone(); + //idq.push_str("|Object|"); + print_value(n[key].clone(), changes, &mut idq.clone(), key); + } + }, + _ => {}, + } +} \ No newline at end of file diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index dc0c1c6..2bf444b 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -11,3 +11,4 @@ pub mod file; pub mod console; pub mod system; pub mod compiler; +pub mod json; From d2cbada1695bd28524c8b850574ed559e82c71f5 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 1 Aug 2017 13:33:41 -0700 Subject: [PATCH 13/99] update json decoding --- src/watchers/json.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 5af467b..334607a 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -35,13 +35,13 @@ impl Watcher for JsonWatcher { match (&kind[..], j_arg) { ("decode", j_arg) => { let v: Value = serde_json::from_str(&j_arg).unwrap(); - print_value(v, &mut changes, &mut "ID".to_string(), "object"); + value_to_changes(v, &mut changes, &mut record_id.to_string(), "json-object"); }, ("enocde", j_arg) => { println!("encoding:\n{:?}",j_arg); } _ => {}, - } + } match self.outgoing.send(RunLoopMessage::Transaction(changes)) { Err(_) => break, _ => (), @@ -50,9 +50,7 @@ impl Watcher for JsonWatcher { } } -//changes.push(RawChange {e: id.clone(), a: Internable::String("tag".to_string()), v: Internable::String("file/read/change".to_string()), n: Internable::String("file/read".to_string()), count: 1}); - -fn print_value(value: Value, changes: &mut Vec, id: &mut String, attribute: &str) { +fn value_to_changes(value: Value, changes: &mut Vec, id: &mut String, attribute: &str) { match value { Value::Number(n) => { if n.is_u64() { @@ -69,7 +67,7 @@ fn print_value(value: Value, changes: &mut Vec, id: &mut String, attr Value::Bool(ref n) => println!("Bool: {}",n), Value::Array(ref n) => { for v in n { - print_value(v.clone(), changes, id, attribute); + value_to_changes(v.clone(), changes, id, attribute); } }, Value::Object(ref n) => { @@ -77,9 +75,7 @@ fn print_value(value: Value, changes: &mut Vec, id: &mut String, attr changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v: Internable::String(idq.clone()), n: Internable::String("json/decode".to_string()), count: 1}); changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String("tag".to_string()), v: Internable::String("json-object".to_string()), n: Internable::String("json/decode".to_string()), count: 1}); for key in n.keys() { - //let mut idq = id.clone(); - //idq.push_str("|Object|"); - print_value(n[key].clone(), changes, &mut idq.clone(), key); + value_to_changes(n[key].clone(), changes, &mut idq.clone(), key); } }, _ => {}, From 2f14f6fb477a543886ba28b70f1eecc9a35af18a Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 1 Aug 2017 22:32:05 -0700 Subject: [PATCH 14/99] Serialize records --- libraries/json/json.eve | 5 +++-- src/watchers/json.rs | 23 +++++++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/libraries/json/json.eve b/libraries/json/json.eve index b607bfe..ce18b86 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -6,8 +6,9 @@ A library for encoding and decoding Eve records into and from JSON search j = [#json/encode record] + lookup[entity: record, attribute, value] watch json - ("encode", j, record) + ("encode", j, record, attribute, value) end ## Decoding @@ -16,4 +17,4 @@ search j = [#json/decode json] watch json ("decode", j, json) -end \ No newline at end of file +end diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 334607a..2ed86bc 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -3,6 +3,9 @@ use super::super::ops::{Interned, Internable, Interner, RawChange, RunLoopMessag use std::sync::mpsc::{self, Sender}; use std::collections::hash_map::{Entry}; use super::Watcher; +use std::ops::{Neg, AddAssign, MulAssign}; +use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess, + MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; extern crate serde_json; extern crate serde; @@ -27,18 +30,27 @@ impl Watcher for JsonWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { + println!("Making changes"); + let mut mappy: Map = Map::new(); + let mut id = "".to_string(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); + id = record_id.clone(); let j_arg = Internable::to_string(interner.get_value(add[2])); let mut changes = vec![]; match (&kind[..], j_arg) { ("decode", j_arg) => { let v: Value = serde_json::from_str(&j_arg).unwrap(); + //println!("{:?}",v); value_to_changes(v, &mut changes, &mut record_id.to_string(), "json-object"); }, - ("enocde", j_arg) => { - println!("encoding:\n{:?}",j_arg); + ("encode", j_arg) => { + let e = j_arg; + let a = Internable::to_string(interner.get_value(add[3])); + let v = Internable::to_string(interner.get_value(add[4])); + println!("encoding:\n[e: {:?} a: {:?} v: {:?}]",e,a,v); + mappy.insert(a,Value::String(v)); } _ => {}, } @@ -47,6 +59,13 @@ impl Watcher for JsonWatcher { _ => (), } } + let json = serde_json::to_string(&mappy).unwrap(); + let mut chchanges = vec![]; + chchanges.push(RawChange {e: Internable::String(id.clone().to_string()), a: Internable::String("json-string".to_string()), v: Internable::String(json.clone()), n: Internable::String("json/encode".to_string()), count: 1}); + match self.outgoing.send(RunLoopMessage::Transaction(chchanges)) { + Err(_) => (), + _ => (), + } } } From 6b959ae7bfb0d9034d8c35bc94cd74bf70b7aab8 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 3 Aug 2017 12:52:33 -0700 Subject: [PATCH 15/99] Try nested records again --- src/watchers/json.rs | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 5a9c588..6d2ca57 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -2,6 +2,7 @@ use super::super::indexes::{WatchDiff}; use super::super::ops::{Interned, Internable, Interner, RawChange, RunLoopMessage, JSONInternable}; use std::sync::mpsc::{self, Sender}; use std::collections::hash_map::{Entry}; +use std::collections::HashMap; use super::Watcher; use std::ops::{Neg, AddAssign, MulAssign}; use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess, @@ -31,13 +32,13 @@ impl Watcher for JsonWatcher { } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { println!("Making changes"); - let mut record_map: Map = Map::new(); + let mut record_map = Map::new(); let mut id = "".to_string(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); - id = record_id.clone(); let j_arg = Internable::to_string(interner.get_value(add[2])); + id = j_arg.clone(); let mut changes = vec![]; match (&kind[..], j_arg) { ("decode", j_arg) => { @@ -49,30 +50,16 @@ impl Watcher for JsonWatcher { let e = j_arg; let a = Internable::to_string(interner.get_value(add[3])); let v = Internable::to_string(interner.get_value(add[4])); + println!("[e: {:?} a: {:?} v: {:?}]",e,a,v); if record_map.contains_key(&e) { - - - let foo = record_map.get(&e).unwrap(); - //foo.bar - - //match record_map.get(&e).unwrap() { - // &Value::Object(ref mut map) => Some(map), - // _ => None, - //}; - - - //let mut v = json!({ "a": { "nested": true } }); - //v["a"].as_object_mut().unwrap().clear(); - - //let record: Map = record_map.get(&e).as_object_mut().unwrap(); - //record.insert(a, Value::String(v)); + let mut record = record_map.get_mut(&e).unwrap(); + let r_map = record.as_object_mut().unwrap(); + r_map.insert(a, Value::String(v)); } else { - let mut m = Value::Object(Map::new()); - let q = m.as_object_mut(); - q.bar - //record_map.insert(e, m); + let mut new_record = Map::new(); + new_record.insert(a, Value::String(v)); + record_map.insert(e, Value::Object(new_record)); } - //println!("[e: {:?} a: {:?} v: {:?}]",e,a,v); } _ => {}, } @@ -81,9 +68,12 @@ impl Watcher for JsonWatcher { _ => (), } } - let json = serde_json::to_string(&record_map).unwrap(); - println!("{}",json); - //let mut chchanges = vec![]; + //let json = serde_json::to_string(&record_map).unwrap(); + + let target_record = record_map.get_mut(&id); + + + println!("{:?}",target_record); //chchanges.push(RawChange {e: Internable::String(id.clone().to_string()), a: Internable::String("json-string".to_string()), v: Internable::String(json.clone()), n: Internable::String("json/encode".to_string()), count: 1}); //match self.outgoing.send(RunLoopMessage::Transaction(chchanges)) { // Err(_) => (), From dd094c356e5ebaeea6517501c3960add54fb6e86 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 3 Aug 2017 12:52:41 -0700 Subject: [PATCH 16/99] commit tests --- examples/test.eve | 17 +++++++++++++---- person.json | 31 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index 2bed816..7fd7ec7 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,6 +1,15 @@ +commit + [#file/read #sudoku path: "person.json"] +end + +search + [#sudoku contents] +commit + [#json/decode json: contents] +end + search - (text, ix) = string/split[text:"f o o" by:" "] - ix = 1 -bind - [#ui/text text] + [#json/decode json-object] +commit + [#console/log text: json-object.Time.Total] end diff --git a/person.json b/person.json index 1c5fb41..e33e13e 100644 --- a/person.json +++ b/person.json @@ -1 +1,30 @@ -{"age":"30","name":"Corey Montella","tag":"person"} \ No newline at end of file +{ + "Solver": "clingo version 4.5.4", + "Input": [ + "puzzle_easy.lp","sudoku.lp" + ], + "Call": [ + { + "Witnesses": [ + { + "Value": [ + "paint(1,1,7)", "paint(1,3,3)", "paint(1,5,8)", "paint(1,8,1)", "paint(2,5,6)", "paint(3,1,6)", "paint(3,3,8)", "paint(3,5,5)", "paint(3,6,7)", "paint(3,7,4)", "paint(3,9,9)", "paint(4,4,8)", "paint(4,5,9)", "paint(4,7,1)", "paint(4,8,2)", "paint(4,9,5)", "paint(5,3,2)", "paint(5,7,6)", "paint(6,1,8)", "paint(6,2,1)", "paint(6,3,6)", "paint(6,5,2)", "paint(6,6,5)", "paint(7,1,3)", "paint(7,3,5)", "paint(7,4,2)", "paint(7,5,4)", "paint(7,7,9)", "paint(7,9,1)", "paint(8,5,7)", "paint(9,2,7)", "paint(9,5,1)", "paint(9,7,5)", "paint(9,9,2)", "paint(1,2,5)", "paint(1,4,9)", "paint(1,6,4)", "paint(1,7,2)", "paint(1,9,6)", "paint(2,1,1)", "paint(2,2,4)", "paint(2,3,9)", "paint(2,4,3)", "paint(2,6,2)", "paint(2,7,8)", "paint(2,8,5)", "paint(2,9,7)", "paint(3,2,2)", "paint(3,4,1)", "paint(3,8,3)", "paint(4,2,3)", "paint(4,1,4)", "paint(4,6,6)", "paint(4,3,7)", "paint(5,4,7)", "paint(5,5,3)", "paint(5,8,4)", "paint(5,6,1)", "paint(6,4,4)", "paint(6,9,3)", "paint(6,7,7)", "paint(7,2,6)", "paint(7,8,7)", "paint(8,1,2)", "paint(5,1,5)", "paint(5,9,8)", "paint(5,2,9)", "paint(6,8,9)", "paint(7,6,8)", "paint(8,3,1)", "paint(8,7,3)", "paint(8,8,6)", "paint(8,9,4)", "paint(8,4,5)", "paint(8,2,8)", "paint(8,6,9)", "paint(9,4,6)", "paint(9,6,3)", "paint(9,3,4)", "paint(9,8,8)", "paint(9,1,9)" + ] + } + ] + } + ], + "Result": "SATISFIABLE", + "Models": { + "Number": 1, + "More": "no" + }, + "Calls": 1, + "Time": { + "Total": 0.084, + "Solve": 0.000, + "Model": 0.000, + "Unsat": 0.000, + "CPU": 0.080 + } +} \ No newline at end of file From 274f4de757bf8e418c5842b37018f46741ade682 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 7 Aug 2017 11:32:08 -0700 Subject: [PATCH 17/99] add hyper dep --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 2185aab..3e181f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,4 @@ unicode-segmentation = "1.1.0" iron = "0.5" staticfile = "*" mount = "*" +hyper = "0.11.2" From ff41684a5cee4b32e46e08d35ad0ec61be492318 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 7 Aug 2017 11:32:17 -0700 Subject: [PATCH 18/99] test http send --- examples/test.eve | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index 7fd7ec7..beec2ed 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,15 +1,3 @@ commit - [#file/read #sudoku path: "person.json"] -end - -search - [#sudoku contents] -commit - [#json/decode json: contents] -end - -search - [#json/decode json-object] -commit - [#console/log text: json-object.Time.Total] + [#http/send address: "http://jsonplaceholder.typicode.com/posts/1/comments"] end From 6a4c84598033058f0fcbee1ace1ce3dd2d93d92f Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 7 Aug 2017 11:32:31 -0700 Subject: [PATCH 19/99] add http watcher --- libraries/http/http.eve | 9 +++++ src/bin/server.rs | 6 +++- src/watchers/http.rs | 79 +++++++++++++++++++++++++++++++++++++++++ src/watchers/mod.rs | 1 + 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 libraries/http/http.eve create mode 100644 src/watchers/http.rs diff --git a/libraries/http/http.eve b/libraries/http/http.eve new file mode 100644 index 0000000..ef4e550 --- /dev/null +++ b/libraries/http/http.eve @@ -0,0 +1,9 @@ +# HTTP + +## Send an HTTP Request + +search + h = [#http/send address] +watch http + ("send", h, address) +end \ No newline at end of file diff --git a/src/bin/server.rs b/src/bin/server.rs index c81d35b..50c0602 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -23,6 +23,8 @@ use eve::indexes::{WatchDiff}; use eve::watchers::{Watcher}; use eve::watchers::system::{SystemTimerWatcher}; use eve::watchers::compiler::{CompilerWatcher}; +use eve::watchers::http::{HttpWatcher}; +use eve::watchers::json::JsonWatcher; extern crate iron; extern crate staticfile; @@ -61,8 +63,10 @@ impl ClientHandler { let outgoing = runner.program.outgoing.clone(); if !clean { runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); - runner.program.attach(Box::new(CompilerWatcher::new(outgoing))); + runner.program.attach(Box::new(CompilerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(WebsocketClientWatcher::new(out.clone()))); + runner.program.attach(Box::new(JsonWatcher::new(outgoing.clone()))); + runner.program.attach(Box::new(HttpWatcher::new(outgoing.clone()))); } if let Some(persist_file) = persist { diff --git a/src/watchers/http.rs b/src/watchers/http.rs new file mode 100644 index 0000000..52353b3 --- /dev/null +++ b/src/watchers/http.rs @@ -0,0 +1,79 @@ +use super::super::indexes::{WatchDiff}; +use super::super::ops::{Interned, Internable, Interner, RawChange, RunLoopMessage, JSONInternable}; +use std::sync::mpsc::{self, Sender}; +use std::collections::hash_map::{Entry}; +use std::collections::HashMap; +use super::Watcher; +use std::ops::{Neg, AddAssign, MulAssign}; + +extern crate futures; +extern crate hyper; +extern crate tokio_core; + +use std::io::{self, Write}; +use futures::{Future, Stream}; +use self::hyper::Client; +use self::tokio_core::reactor::Core; + +pub struct HttpWatcher { + name: String, + outgoing: Sender, +} + +impl HttpWatcher { + pub fn new(outgoing: Sender) -> HttpWatcher { + HttpWatcher { name: "http".to_string(), outgoing } + } +} + +impl Watcher for HttpWatcher { + fn get_name(& self) -> String { + self.name.clone() + } + fn set_name(&mut self, name: &str) { + self.name = name.to_string(); + } + fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { + for add in diff.adds { + let kind = Internable::to_string(interner.get_value(add[0])); + let address = Internable::to_string(interner.get_value(add[2])); + + //let record_id = Internable::to_string(interner.get_value(add[1])); + //let j_arg = Internable::to_string(interner.get_value(add[2])); + //id = j_arg.clone(); + let mut changes = vec![]; + match &kind[..] { + "send" => { + + send_http_request(address); + + + + }, + _ => {}, + } + match self.outgoing.send(RunLoopMessage::Transaction(changes)) { + Err(_) => break, + _ => (), + } + } + } +} + + +fn send_http_request(address: String) { + let mut core = tokio_core::reactor::Core::new().unwrap(); + let handle = core.handle(); + let client = Client::new(&handle); + let url = address.parse::().unwrap(); + let work = client.get(url).and_then(|res| { + println!("Response: {}", res.status()); + println!("Headers: \n{}", res.headers()); + res.body().for_each(|chunk| { + io::stdout().write_all(&chunk).map_err(From::from) + }) + }).map(|_| { + println!("\n\nDone."); + }); + core.run(work).unwrap(); +} \ No newline at end of file diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 2bf444b..def7c73 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -12,3 +12,4 @@ pub mod console; pub mod system; pub mod compiler; pub mod json; +pub mod http; From 63e4cd9b67aacf76befc7e6050d55c0779bc737e Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 9 Aug 2017 02:13:13 -0700 Subject: [PATCH 20/99] fix watcher import --- src/bin/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index 62db3d9..2e783ff 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -65,7 +65,7 @@ impl ClientHandler { if !clean { runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(CompilerWatcher::new(outgoing.clone()))); - runner.program.attach(Box::new(RawTextCompilerWatcher::new(outgoing))); + runner.program.attach(Box::new(RawTextCompilerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(WebsocketClientWatcher::new(out.clone()))); runner.program.attach(Box::new(JsonWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(HttpWatcher::new(outgoing.clone()))); From bb5a0211e44d4326a275e37a339fe61ca7a95992 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 9 Aug 2017 02:13:31 -0700 Subject: [PATCH 21/99] inject response into eve --- Cargo.toml | 2 ++ libraries/http/http.eve | 2 +- src/watchers/http.rs | 59 +++++++++++++++++++++++------------------ src/watchers/json.rs | 2 +- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b62005b..10caee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,5 @@ iron = "0.5" staticfile = "*" mount = "*" hyper = "0.11.2" +futures = "0.1.14" +tokio-core = "0.1.9" \ No newline at end of file diff --git a/libraries/http/http.eve b/libraries/http/http.eve index ef4e550..4017434 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -3,7 +3,7 @@ ## Send an HTTP Request search - h = [#http/send address] + h = [#http/request address] watch http ("send", h, address) end \ No newline at end of file diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 52353b3..e002990 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -10,11 +10,17 @@ extern crate futures; extern crate hyper; extern crate tokio_core; -use std::io::{self, Write}; -use futures::{Future, Stream}; +extern crate serde_json; +extern crate serde; +use self::serde_json::{Map, Value, Error}; + +use std::io::{self, Write, BufReader}; +use self::futures::{Future, Stream}; use self::hyper::Client; use self::tokio_core::reactor::Core; +use watchers::json::{value_to_changes}; + pub struct HttpWatcher { name: String, outgoing: Sender, @@ -36,19 +42,13 @@ impl Watcher for HttpWatcher { fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); + let id = Internable::to_string(interner.get_value(add[1])); let address = Internable::to_string(interner.get_value(add[2])); - - //let record_id = Internable::to_string(interner.get_value(add[1])); - //let j_arg = Internable::to_string(interner.get_value(add[2])); - //id = j_arg.clone(); let mut changes = vec![]; match &kind[..] { "send" => { - - send_http_request(address); - - - + send_http_request(address, id, &mut changes); + println!("{:?}", &changes); }, _ => {}, } @@ -60,20 +60,27 @@ impl Watcher for HttpWatcher { } } +fn send_http_request(address: String, id: String, changes: &mut Vec) { + let mut core = tokio_core::reactor::Core::new().unwrap(); + let handle = core.handle(); + let client = Client::new(&handle); + let url = address.parse::().unwrap(); + //let mut vec = Vec::new(); + let work = client.get(url).and_then(|res| { + //println!("Response: {}", res.status()); + //println!("Headers: \n{}", res.headers()); -fn send_http_request(address: String) { - let mut core = tokio_core::reactor::Core::new().unwrap(); - let handle = core.handle(); - let client = Client::new(&handle); - let url = address.parse::().unwrap(); - let work = client.get(url).and_then(|res| { - println!("Response: {}", res.status()); - println!("Headers: \n{}", res.headers()); - res.body().for_each(|chunk| { - io::stdout().write_all(&chunk).map_err(From::from) - }) - }).map(|_| { - println!("\n\nDone."); - }); - core.run(work).unwrap(); + res.body().concat2().and_then(move |body| { + let v: Value = serde_json::from_slice(&body).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + e + ) + })?; + value_to_changes(v, changes, &id, "http-response"); + Ok(()) + }) + }); + //println!("{:?}",vec); + core.run(work).unwrap(); } \ No newline at end of file diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 6d2ca57..d03dc02 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -82,7 +82,7 @@ impl Watcher for JsonWatcher { } } -fn value_to_changes(value: Value, changes: &mut Vec, id: &mut String, attribute: &str) { +pub fn value_to_changes(value: Value, changes: &mut Vec, id: &String, attribute: &str) { match value { Value::Number(n) => { if n.is_u64() { From 15d155bfcbdc882dec4da214b6908b31efa782c6 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 9 Aug 2017 02:13:40 -0700 Subject: [PATCH 22/99] test HTTP request --- examples/test.eve | 41 ++++++----------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index 8a1260e..8d1b44e 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,39 +1,10 @@ -<<<<<<< HEAD commit - [#http/send address: "http://jsonplaceholder.typicode.com/posts/1/comments"] + [#http/request #req address: "http://echo.jsontest.com/key/value/one/two"] end -======= - search - foo = [#foo value] - gather/bottom[for:(value), limit:2] - bind - [#max foo] - end - commit - [#foo value: 4] - [#foo value: 5] - [#foo value: 6] - end - search - [#foo value] - value <= 4 - value > 1 - bind - [#foo value: value - 1] - end - - search - foo = [#foo value:4] - [#foo value:1] - commit - foo := none - end - - search - [#max foo: [value]] - bind - [#ui/text text: "max: {{value}}"] - end ->>>>>>> refs/remotes/origin/master +search + [#req http-response: [one key]] +commit + [#ui/text text: "{{one}} {{key}}"] +end \ No newline at end of file From d85ab5b3328a758d190d08d60671392a143bfaac Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 9 Aug 2017 22:23:54 -0700 Subject: [PATCH 23/99] Fix up deser --- src/watchers/json.rs | 114 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 23 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 6d2ca57..31f153c 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -5,12 +5,17 @@ use std::collections::hash_map::{Entry}; use std::collections::HashMap; use super::Watcher; use std::ops::{Neg, AddAssign, MulAssign}; -use serde::de::{self, Deserialize, DeserializeSeed, Visitor, SeqAccess, +use serde::de::{self, Deserialize, Deserializer, DeserializeSeed, Visitor, SeqAccess, MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; +use serde::ser::{Serialize, Serializer}; extern crate serde_json; extern crate serde; -use self::serde_json::{Map, Value, Error}; +use self::serde_json::{Map, Value}; +use std::fmt; +use std::error::Error; +use std::mem::transmute; +use std::marker::PhantomData; pub struct JsonWatcher { name: String, @@ -39,13 +44,8 @@ impl Watcher for JsonWatcher { let record_id = Internable::to_string(interner.get_value(add[1])); let j_arg = Internable::to_string(interner.get_value(add[2])); id = j_arg.clone(); - let mut changes = vec![]; + let mut changes: Vec = vec![]; match (&kind[..], j_arg) { - ("decode", j_arg) => { - let v: Value = serde_json::from_str(&j_arg).unwrap(); - //println!("{:?}",v); - value_to_changes(v, &mut changes, &mut record_id.to_string(), "json-object"); - }, ("encode", j_arg) => { let e = j_arg; let a = Internable::to_string(interner.get_value(add[3])); @@ -60,7 +60,11 @@ impl Watcher for JsonWatcher { new_record.insert(a, Value::String(v)); record_map.insert(e, Value::Object(new_record)); } - } + }, + ("decode", j_arg) => { + let v: Value = serde_json::from_str(&j_arg).unwrap(); + value_to_changes(&mut record_id.to_string(), "json-object", v, &mut changes); + }, _ => {}, } match self.outgoing.send(RunLoopMessage::Transaction(changes)) { @@ -70,10 +74,9 @@ impl Watcher for JsonWatcher { } //let json = serde_json::to_string(&record_map).unwrap(); - let target_record = record_map.get_mut(&id); + //let target_record = record_map.get_mut(&id); - - println!("{:?}",target_record); + //println!("{:?}",target_record); //chchanges.push(RawChange {e: Internable::String(id.clone().to_string()), a: Internable::String("json-string".to_string()), v: Internable::String(json.clone()), n: Internable::String("json/encode".to_string()), count: 1}); //match self.outgoing.send(RunLoopMessage::Transaction(chchanges)) { // Err(_) => (), @@ -82,34 +85,99 @@ impl Watcher for JsonWatcher { } } -fn value_to_changes(value: Value, changes: &mut Vec, id: &mut String, attribute: &str) { +fn newChange(e: &str, a: &str, v: Internable, n: &str) -> RawChange { + RawChange {e: Internable::String(e.to_string()), a: Internable::String(a.to_string()), v: v.clone(), n: Internable::String(n.to_string()), count: 1} +} + +fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec) { + let node = "json/decode"; match value { - Value::Number(n) => { + Value::Number(n) => { if n.is_u64() { let v = Internable::Number(n.as_u64().unwrap() as u32); - changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v, n: Internable::String("json/decode".to_string()), count: 1}); + changes.push(newChange(id,attribute,v,node)); + } else if n.is_i64() { + let v = Internable::Number(n.as_i64().unwrap() as u32); + changes.push(newChange(id,attribute,v,node)); } else if n.is_f64() { let v = Internable::from_number(n.as_f64().unwrap() as f32); - changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v, n: Internable::String("json/decode".to_string()), count: 1}); + changes.push(newChange(id,attribute,v,node)); }; }, Value::String(ref n) => { - changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v: Internable::String(n.clone()), n: Internable::String("json/decode".to_string()), count: 1}); + changes.push(newChange(id,attribute,Internable::String(n.clone()),node)); }, Value::Bool(ref n) => println!("Bool: {}",n), Value::Array(ref n) => { - for v in n { - value_to_changes(v.clone(), changes, id, attribute); + for (ix, value) in n.iter().enumerate() { + let ix = ix + 1; + let array_id = format!("array|{:?}|{:?}",ix,value); + let array_id = &array_id[..]; + changes.push(newChange(id,attribute,Internable::String(array_id.to_string()),node)); + changes.push(newChange(array_id,"tag",Internable::String("array".to_string()),node)); + changes.push(newChange(array_id,"index",Internable::String(ix.to_string()),node)); + value_to_changes(array_id, "value", value.clone(), changes); } }, Value::Object(ref n) => { let idq = format!("{:?}",n); - changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String(attribute.to_string()), v: Internable::String(idq.clone()), n: Internable::String("json/decode".to_string()), count: 1}); - changes.push(RawChange {e: Internable::String(id.clone()), a: Internable::String("tag".to_string()), v: Internable::String("json-object".to_string()), n: Internable::String("json/decode".to_string()), count: 1}); + changes.push(newChange(id,attribute,Internable::String(idq.clone()),node)); + changes.push(newChange(id,"tag",Internable::String("json-object".to_string()),node)); for key in n.keys() { - value_to_changes(n[key].clone(), changes, &mut idq.clone(), key); + value_to_changes(&mut idq.clone(), key, n[key].clone(), changes); } }, _ => {}, } -} \ No newline at end of file +} + +/* +#[derive(Debug)] +pub enum ChangeVec { + Changes(Vec) +} + +impl ChangeVec { + pub fn new() -> ChangeVec { + ChangeVec::Changes(Vec::new()) + } +} + +impl<'de> Deserialize<'de> for ChangeVec { + + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + struct ChangeVecVisitor { + marker: PhantomData + } + + impl ChangeVecVisitor { + fn new() -> Self { + ChangeVecVisitor { + marker: PhantomData + } + } + } + + impl<'de> Visitor<'de> for ChangeVecVisitor { + type Value = ChangeVec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("expecting a thing") + } + + fn visit_map(self, mut access: M) -> Result + where M: MapAccess<'de> + { + let mut vec = Vec::new(); + while let Some(kv) = try!(access.next_entry()) { + vec.push(kv); + } + Ok(ChangeVec::new()) + } + } + + deserializer.deserialize_any(ChangeVecVisitor::new()) + } +}*/ \ No newline at end of file From 371bd8f3078ec8fcd77bac1646ea5ad4be977744 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 03:54:00 -0700 Subject: [PATCH 24/99] json encoding --- libraries/json/json.eve | 56 ++++++++++++++++++--- src/watchers/json.rs | 108 ++++++++++++++++++++++++---------------- 2 files changed, 114 insertions(+), 50 deletions(-) diff --git a/libraries/json/json.eve b/libraries/json/json.eve index e2e746b..ad513bb 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -4,11 +4,55 @@ A library for encoding and decoding Eve records into and from JSON ## Encoding -//search -// j = [#json/encode record attribute value] -//watch json -// ("encode", j, record, attribute, value) -//end +Encoding starts with a `#json/encode` record. + +search + j = [#json/encode record] + lookup[entity: record attribute value] +commit + [#json/encode/target record] + [#json/encode/flatten record] +end + +Target records are specified for encoding + +search + j = [#json/encode/target record] +watch json + ("encode/target", j, record) +end + +Flattening is done recursively. We start with the target record and look up all +a/v pairs. Any v that is aslo a record (satisfies `lookup[entity]`) we will also +flatten. + +search + [#json/encode/flatten record] + lookup[entity: record attribute value] + sub-record = if lookup[entity: value] then value + else "" +commit + [#json/encode/flatten record: sub-record] + [#json/encode/eav record attribute value] +end + +Raw EAVs are encoded as JSON + +search + j = [#json/encode/eav record attribute value] +watch json + ("encode/eav", j, record, attribute, value) +end + +We get back a `json-string` as a change. Apply it as an attribute to the +original `json/encode` + +search + [#json/encode/change record json-string] + r = [#json/encode record] +commit + r.json-string := json-string +end ## Decoding @@ -16,4 +60,4 @@ search j = [#json/decode json] watch json ("decode", j, json) -end +end \ No newline at end of file diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 31f153c..88d02d1 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -1,21 +1,14 @@ use super::super::indexes::{WatchDiff}; -use super::super::ops::{Interned, Internable, Interner, RawChange, RunLoopMessage, JSONInternable}; -use std::sync::mpsc::{self, Sender}; -use std::collections::hash_map::{Entry}; -use std::collections::HashMap; +use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; +use std::sync::mpsc::{Sender}; use super::Watcher; -use std::ops::{Neg, AddAssign, MulAssign}; -use serde::de::{self, Deserialize, Deserializer, DeserializeSeed, Visitor, SeqAccess, - MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; -use serde::ser::{Serialize, Serializer}; +//use serde::de::{self, Deserialize, Deserializer, DeserializeSeed, Visitor, SeqAccess, +// MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; +//use serde::ser::{Serialize, Serializer}; extern crate serde_json; extern crate serde; -use self::serde_json::{Map, Value}; -use std::fmt; -use std::error::Error; -use std::mem::transmute; -use std::marker::PhantomData; +use self::serde_json::{Map, Value, Number}; pub struct JsonWatcher { name: String, @@ -36,34 +29,38 @@ impl Watcher for JsonWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - println!("Making changes"); let mut record_map = Map::new(); let mut id = "".to_string(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); let j_arg = Internable::to_string(interner.get_value(add[2])); - id = j_arg.clone(); let mut changes: Vec = vec![]; match (&kind[..], j_arg) { - ("encode", j_arg) => { + ("encode/target", _) => { + id = Internable::to_string(interner.get_value(add[2])); + }, + ("encode/eav", j_arg) => { let e = j_arg; let a = Internable::to_string(interner.get_value(add[3])); - let v = Internable::to_string(interner.get_value(add[4])); - println!("[e: {:?} a: {:?} v: {:?}]",e,a,v); + let v: Value = match interner.get_value(add[4]) { + &Internable::Number(ref n) => Value::Number(Number::from(n.clone())), + &Internable::String(ref n) => Value::String(String::from(n.clone())), + _ => Value::Null, + }; if record_map.contains_key(&e) { let mut record = record_map.get_mut(&e).unwrap(); - let r_map = record.as_object_mut().unwrap(); - r_map.insert(a, Value::String(v)); + let sub_record = record.as_object_mut().unwrap(); + sub_record.insert(a, v); } else { let mut new_record = Map::new(); - new_record.insert(a, Value::String(v)); + new_record.insert(a, v); record_map.insert(e, Value::Object(new_record)); } }, - ("decode", j_arg) => { - let v: Value = serde_json::from_str(&j_arg).unwrap(); - value_to_changes(&mut record_id.to_string(), "json-object", v, &mut changes); + ("decode", value) => { + let v: Value = serde_json::from_str(&value).unwrap(); + value_to_changes(&record_id.to_string(), "json-object", v, &mut changes); }, _ => {}, } @@ -72,20 +69,43 @@ impl Watcher for JsonWatcher { _ => (), } } - //let json = serde_json::to_string(&record_map).unwrap(); - - //let target_record = record_map.get_mut(&id); - - //println!("{:?}",target_record); - //chchanges.push(RawChange {e: Internable::String(id.clone().to_string()), a: Internable::String("json-string".to_string()), v: Internable::String(json.clone()), n: Internable::String("json/encode".to_string()), count: 1}); - //match self.outgoing.send(RunLoopMessage::Transaction(chchanges)) { - // Err(_) => (), - // _ => (), - // } + let target_record = record_map.get(&id).unwrap(); + let inner_map = target_record.as_object().unwrap(); + let qqq = dereference(inner_map, &record_map); + let json = serde_json::to_string(&qqq).unwrap(); + let mut new_changes = Vec::new(); + let change_id = format!("json/encode/change|{:?}",id); + new_changes.push(new_change(&change_id, "tag", Internable::String("json/encode/change".to_string()), "json/encode")); + new_changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); + new_changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); + match self.outgoing.send(RunLoopMessage::Transaction(new_changes)) { + Err(_) => (), + _ => (), + } } } -fn newChange(e: &str, a: &str, v: Internable, n: &str) -> RawChange { +// Resolves all the object links in the flat map +fn dereference(target: &Map, flatmap: &Map) -> Map { + let mut dereferenced = Map::new(); + for key in target.keys() { + let value = target.get(key).unwrap(); + match value.as_str() { + Some(v) => { + if flatmap.contains_key(v) { + let value = flatmap.get(v).unwrap().as_object().unwrap(); + dereferenced.insert(key.to_string(),Value::Object(dereference(value, flatmap))); + } else { + dereferenced.insert(key.to_string(),value.clone()); + } + }, + None => (), + }; + } + dereferenced +} + +fn new_change(e: &str, a: &str, v: Internable, n: &str) -> RawChange { RawChange {e: Internable::String(e.to_string()), a: Internable::String(a.to_string()), v: v.clone(), n: Internable::String(n.to_string()), count: 1} } @@ -95,17 +115,17 @@ fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec { if n.is_u64() { let v = Internable::Number(n.as_u64().unwrap() as u32); - changes.push(newChange(id,attribute,v,node)); + changes.push(new_change(id,attribute,v,node)); } else if n.is_i64() { let v = Internable::Number(n.as_i64().unwrap() as u32); - changes.push(newChange(id,attribute,v,node)); + changes.push(new_change(id,attribute,v,node)); } else if n.is_f64() { let v = Internable::from_number(n.as_f64().unwrap() as f32); - changes.push(newChange(id,attribute,v,node)); + changes.push(new_change(id,attribute,v,node)); }; }, Value::String(ref n) => { - changes.push(newChange(id,attribute,Internable::String(n.clone()),node)); + changes.push(new_change(id,attribute,Internable::String(n.clone()),node)); }, Value::Bool(ref n) => println!("Bool: {}",n), Value::Array(ref n) => { @@ -113,16 +133,16 @@ fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec { let idq = format!("{:?}",n); - changes.push(newChange(id,attribute,Internable::String(idq.clone()),node)); - changes.push(newChange(id,"tag",Internable::String("json-object".to_string()),node)); + changes.push(new_change(id,attribute,Internable::String(idq.clone()),node)); + changes.push(new_change(id,"tag",Internable::String("json-object".to_string()),node)); for key in n.keys() { value_to_changes(&mut idq.clone(), key, n[key].clone(), changes); } From 548e44a82ce31cf4d350d12d3172bc3a5ac7a370 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 03:54:15 -0700 Subject: [PATCH 25/99] tests --- examples/test.eve | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index 7fd7ec7..3f84507 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,15 +1,40 @@ +# JSON Testing + +## Test Encoding + +commit + rachel = [#person name: "Rachel" age: 28.0] + [#person name: "Corey" age: 30.0 wife: rachel] +end + +search + p = [#person name: "Corey"] commit - [#file/read #sudoku path: "person.json"] + [#json/encode record: p] end search - [#sudoku contents] + [#json/encode json-string] commit + [#console/log text: "Converted some JSON in Eve: {{json-string}}"] +end + +## Test Decode + +commit + [#file/read #file path: "person.json"] +end + +search + [#file contents] +commit + [#console/log text: contents] [#json/decode json: contents] end search - [#json/decode json-object] + [#json/decode json-object: [Value: [#array index value: [#array index: ix value]]]] commit - [#console/log text: json-object.Time.Total] + [#console/log text: "Got a thing: {{index}} {{value}}"] end + From 14432b952139556900ae16e4aa24d8731210cd23 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 10:41:05 -0700 Subject: [PATCH 26/99] test if we are in the process of decoding --- src/watchers/json.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 88d02d1..e022e61 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -69,18 +69,20 @@ impl Watcher for JsonWatcher { _ => (), } } - let target_record = record_map.get(&id).unwrap(); - let inner_map = target_record.as_object().unwrap(); - let qqq = dereference(inner_map, &record_map); - let json = serde_json::to_string(&qqq).unwrap(); - let mut new_changes = Vec::new(); - let change_id = format!("json/encode/change|{:?}",id); - new_changes.push(new_change(&change_id, "tag", Internable::String("json/encode/change".to_string()), "json/encode")); - new_changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); - new_changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); - match self.outgoing.send(RunLoopMessage::Transaction(new_changes)) { - Err(_) => (), - _ => (), + + if let Some(target_record) = record_map.get(&id) { + let inner_map = target_record.as_object().unwrap(); + let qqq = dereference(inner_map, &record_map); + let json = serde_json::to_string(&qqq).unwrap(); + let mut new_changes = Vec::new(); + let change_id = format!("json/encode/change|{:?}",id); + new_changes.push(new_change(&change_id, "tag", Internable::String("json/encode/change".to_string()), "json/encode")); + new_changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); + new_changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); + match self.outgoing.send(RunLoopMessage::Transaction(new_changes)) { + Err(_) => (), + _ => (), + } } } } From 523a0e8460d4028d3e4aae89bb9f0ab6913fd737 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 12:45:28 -0700 Subject: [PATCH 27/99] add http to main --- src/bin/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bin/main.rs b/src/bin/main.rs index 9356889..cd6ae93 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -11,6 +11,7 @@ use eve::watchers::system::{SystemTimerWatcher}; use eve::watchers::console::{ConsoleWatcher, PrintDiffWatcher}; use eve::watchers::file::FileWatcher; use eve::watchers::json::JsonWatcher; +use eve::watchers::http::HttpWatcher; //------------------------------------------------------------------------- // Main @@ -51,6 +52,7 @@ fn main() { runner.program.attach(Box::new(ConsoleWatcher::new())); runner.program.attach(Box::new(PrintDiffWatcher::new())); runner.program.attach(Box::new(JsonWatcher::new(outgoing.clone()))); + runner.program.attach(Box::new(HttpWatcher::new(outgoing.clone()))); } if let Some(persist_file) = persist { From 0f217404bc1f664b1a1277209128c67e0ef11ef8 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 12:45:38 -0700 Subject: [PATCH 28/99] add from_str method for internables --- src/ops.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ops.rs b/src/ops.rs index 1add990..d4cec31 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -616,6 +616,10 @@ impl Internable { Internable::Number(value) } + pub fn from_str(s: &str) -> Internable { + Internable::String(s.to_string()) + } + pub fn print(&self) -> String { match self { &Internable::String(ref s) => { From 20d08bd38303c579747e16606bfb2929742a411b Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 12:45:53 -0700 Subject: [PATCH 29/99] rename some things in JSON watcher --- src/watchers/json.rs | 47 ++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index e022e61..5cee6f3 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -30,12 +30,13 @@ impl Watcher for JsonWatcher { } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { let mut record_map = Map::new(); + let mut changes: Vec = vec![]; let mut id = "".to_string(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); let j_arg = Internable::to_string(interner.get_value(add[2])); - let mut changes: Vec = vec![]; + match (&kind[..], j_arg) { ("encode/target", _) => { id = Internable::to_string(interner.get_value(add[2])); @@ -60,30 +61,25 @@ impl Watcher for JsonWatcher { }, ("decode", value) => { let v: Value = serde_json::from_str(&value).unwrap(); - value_to_changes(&record_id.to_string(), "json-object", v, &mut changes); + value_to_changes(&record_id.to_string(), "json-object", v, "json/decode", &mut changes); }, _ => {}, - } - match self.outgoing.send(RunLoopMessage::Transaction(changes)) { - Err(_) => break, - _ => (), } } if let Some(target_record) = record_map.get(&id) { let inner_map = target_record.as_object().unwrap(); - let qqq = dereference(inner_map, &record_map); - let json = serde_json::to_string(&qqq).unwrap(); - let mut new_changes = Vec::new(); + let dereferenced_target = dereference(inner_map, &record_map); + let json = serde_json::to_string(&dereferenced_target).unwrap(); let change_id = format!("json/encode/change|{:?}",id); - new_changes.push(new_change(&change_id, "tag", Internable::String("json/encode/change".to_string()), "json/encode")); - new_changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); - new_changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); - match self.outgoing.send(RunLoopMessage::Transaction(new_changes)) { - Err(_) => (), - _ => (), - } + changes.push(new_change(&change_id, "tag", Internable::from_str("json/encode/change"), "json/encode")); + changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); + changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); } + match self.outgoing.send(RunLoopMessage::Transaction(changes)) { + Err(_) => (), + _ => (), + } } } @@ -107,12 +103,11 @@ fn dereference(target: &Map, flatmap: &Map) -> Map RawChange { +pub fn new_change(e: &str, a: &str, v: Internable, n: &str) -> RawChange { RawChange {e: Internable::String(e.to_string()), a: Internable::String(a.to_string()), v: v.clone(), n: Internable::String(n.to_string()), count: 1} } -fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec) { - let node = "json/decode"; +pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, changes: &mut Vec) { match value { Value::Number(n) => { if n.is_u64() { @@ -135,18 +130,18 @@ fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec { - let idq = format!("{:?}",n); - changes.push(new_change(id,attribute,Internable::String(idq.clone()),node)); - changes.push(new_change(id,"tag",Internable::String("json-object".to_string()),node)); + let object_id = format!("{:?}",n); + changes.push(new_change(id,attribute,Internable::String(object_id.clone()),node)); + changes.push(new_change(id,"tag",Internable::from_str("json-object"),node)); for key in n.keys() { - value_to_changes(&mut idq.clone(), key, n[key].clone(), changes); + value_to_changes(&mut object_id.clone(), key, n[key].clone(), node, changes); } }, _ => {}, From a568b6b9797e3addc75f1ad57d675aa2ecc51f16 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 12:46:09 -0700 Subject: [PATCH 30/99] update http requests --- libraries/http/http.eve | 17 ++++++++++++++++- src/watchers/http.rs | 37 +++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 4017434..4ddbad2 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -5,5 +5,20 @@ search h = [#http/request address] watch http - ("send", h, address) + ("request", h, address) +end + +search + response = [#http/response request] + request = [#http/request] +commit + request.response := response +end + +## Process HTTP Requests + +search + [#http/serve address] +watch HTTP + ("server", h, address) end \ No newline at end of file diff --git a/src/watchers/http.rs b/src/watchers/http.rs index e002990..6e57d81 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -1,25 +1,17 @@ use super::super::indexes::{WatchDiff}; -use super::super::ops::{Interned, Internable, Interner, RawChange, RunLoopMessage, JSONInternable}; -use std::sync::mpsc::{self, Sender}; -use std::collections::hash_map::{Entry}; -use std::collections::HashMap; +use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; +use std::sync::mpsc::{Sender}; use super::Watcher; -use std::ops::{Neg, AddAssign, MulAssign}; - extern crate futures; extern crate hyper; extern crate tokio_core; - extern crate serde_json; extern crate serde; -use self::serde_json::{Map, Value, Error}; - -use std::io::{self, Write, BufReader}; +use self::serde_json::{Value}; +use std::io::{self}; use self::futures::{Future, Stream}; use self::hyper::Client; -use self::tokio_core::reactor::Core; - -use watchers::json::{value_to_changes}; +use watchers::json::{value_to_changes, new_change}; pub struct HttpWatcher { name: String, @@ -46,9 +38,11 @@ impl Watcher for HttpWatcher { let address = Internable::to_string(interner.get_value(add[2])); let mut changes = vec![]; match &kind[..] { - "send" => { + "request" => { send_http_request(address, id, &mut changes); - println!("{:?}", &changes); + }, + "server" => { + }, _ => {}, } @@ -67,9 +61,9 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) let url = address.parse::().unwrap(); //let mut vec = Vec::new(); let work = client.get(url).and_then(|res| { - //println!("Response: {}", res.status()); - //println!("Headers: \n{}", res.headers()); - + let status = res.status().as_u16(); + // TODO Ship headers back to Eve + //println!("Headers: \n{:?}", res.headers()); res.body().concat2().and_then(move |body| { let v: Value = serde_json::from_slice(&body).map_err(|e| { io::Error::new( @@ -77,10 +71,13 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) e ) })?; - value_to_changes(v, changes, &id, "http-response"); + let response_id = format!("http/response|{:?}",id); + changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); + changes.push(new_change(&response_id, "request", Internable::String(id), "http/request")); + changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), "http/request")); + value_to_changes(&response_id, "body", v, "http/request", changes); Ok(()) }) }); - //println!("{:?}",vec); core.run(work).unwrap(); } \ No newline at end of file From 39a448c41403afc0650e2c16b92208be91ab3874 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 12:46:19 -0700 Subject: [PATCH 31/99] update example --- examples/test.eve | 6 +++--- src/watchers/mod.rs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index 2f462f0..8404761 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,10 +1,10 @@ commit - [#http/request #req address: "http://echo.jsontest.com/key/value/one/two"] + [#http/request #req address: "http://echo.jsontest.com/last/shenberger/first/manifold"] end search - [#req http-response: [one key]] + [#req response: [#http/response status body]] commit - [#ui/text text: "{{one}} {{key}}"] + [#console/log text: "{{status}} {{body.first}} {{body.last}}"] end diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 449af5e..508e58f 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -11,6 +11,7 @@ pub mod file; pub mod console; pub mod system; pub mod compiler; +pub mod compiler2; pub mod json; pub mod http; -pub mod compiler2; + From 0e246e7df64878ad9e80fb59a5f226df65a4c361 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 14:32:56 -0700 Subject: [PATCH 32/99] stub in http server --- libraries/http/http.eve | 4 ++-- src/watchers/http.rs | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 4ddbad2..b57f217 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -18,7 +18,7 @@ end ## Process HTTP Requests search - [#http/serve address] -watch HTTP + h = [#http/server address] +watch http ("server", h, address) end \ No newline at end of file diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 6e57d81..5a190ce 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -1,17 +1,21 @@ use super::super::indexes::{WatchDiff}; use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; use std::sync::mpsc::{Sender}; +use std::io::{self}; +use watchers::json::{value_to_changes, new_change}; use super::Watcher; + extern crate futures; extern crate hyper; extern crate tokio_core; extern crate serde_json; extern crate serde; use self::serde_json::{Value}; -use std::io::{self}; use self::futures::{Future, Stream}; use self::hyper::Client; -use watchers::json::{value_to_changes, new_change}; +//use self::hyper::header::ContentLength; +//use self::hyper::server::{Http, Request, Response, Service}; +//use std::thread; pub struct HttpWatcher { name: String, @@ -42,7 +46,9 @@ impl Watcher for HttpWatcher { send_http_request(address, id, &mut changes); }, "server" => { - + println!("Starting HTTP Server"); + //http_server(address); + println!("HTTP Server started"); }, _ => {}, } @@ -54,6 +60,15 @@ impl Watcher for HttpWatcher { } } +/*fn http_server(address: String, service: str) -> thread::JoinHandle<()> { + thread::spawn(move || { + println!("Starting HTTP Server at {}... ", address); + let addr = address.parse().unwrap(); + let server = Http::new().bind(&addr, || Ok(service)).unwrap(); + server.run().unwrap(); + }) +}*/ + fn send_http_request(address: String, id: String, changes: &mut Vec) { let mut core = tokio_core::reactor::Core::new().unwrap(); let handle = core.handle(); From c0c921f1a2e469d573590cb7b67cf500a52afc27 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 16:11:13 -0700 Subject: [PATCH 33/99] Stub in http error --- src/watchers/http.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 5a190ce..3c121d9 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -70,10 +70,13 @@ impl Watcher for HttpWatcher { }*/ fn send_http_request(address: String, id: String, changes: &mut Vec) { + println!("HERE0"); let mut core = tokio_core::reactor::Core::new().unwrap(); let handle = core.handle(); let client = Client::new(&handle); + println!("HERE1"); let url = address.parse::().unwrap(); + println!("HERE2"); //let mut vec = Vec::new(); let work = client.get(url).and_then(|res| { let status = res.status().as_u16(); @@ -94,5 +97,16 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) Ok(()) }) }); - core.run(work).unwrap(); + match core.run(work) { + Ok(_) => println!("OK"), + Err(e) => { + // Form an HTTP Error + //let error_id = format!("http/request/error|{:?}|{:?}",&id,address); + //changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), "http/request")); + //changes.push(new_change(&error_id, "request", Internable::String(id), "http/request")); + //changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), "http/request")); + println!("Not OK {:?}",e) + }, + } + println!("HERE3"); } \ No newline at end of file From aefe11bd1c39ebd33085ccc5ed199c9f887dd199 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 18:14:51 -0700 Subject: [PATCH 34/99] Add https support --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 10caee5..e664e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,6 @@ iron = "0.5" staticfile = "*" mount = "*" hyper = "0.11.2" +hyper-tls = "0.1.2" futures = "0.1.14" tokio-core = "0.1.9" \ No newline at end of file From fffa21290d317be061efb6043db0d8d3e840c59a Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 18:15:11 -0700 Subject: [PATCH 35/99] Decode into a string, implement https on sender --- src/watchers/http.rs | 52 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 3c121d9..bbdc0b4 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -1,21 +1,26 @@ use super::super::indexes::{WatchDiff}; use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; use std::sync::mpsc::{Sender}; -use std::io::{self}; use watchers::json::{value_to_changes, new_change}; use super::Watcher; extern crate futures; extern crate hyper; +extern crate hyper_tls; extern crate tokio_core; extern crate serde_json; extern crate serde; use self::serde_json::{Value}; use self::futures::{Future, Stream}; use self::hyper::Client; -//use self::hyper::header::ContentLength; -//use self::hyper::server::{Http, Request, Response, Service}; -//use std::thread; +use self::hyper_tls::HttpsConnector; +use self::tokio_core::reactor::Core; +use self::hyper::header::ContentLength; +use self::hyper::{Method, Request}; +use self::hyper::server::{Http, Response, Service}; +use std::thread; +use std::io::{self, Write}; +use std::fs::File; pub struct HttpWatcher { name: String, @@ -70,15 +75,20 @@ impl Watcher for HttpWatcher { }*/ fn send_http_request(address: String, id: String, changes: &mut Vec) { - println!("HERE0"); - let mut core = tokio_core::reactor::Core::new().unwrap(); + let mut core = Core::new().unwrap(); let handle = core.handle(); - let client = Client::new(&handle); - println!("HERE1"); + let client = Client::configure() + .connector(HttpsConnector::new(4,&handle).unwrap()) + .build(&handle); let url = address.parse::().unwrap(); - println!("HERE2"); + println!("{:?}",url); //let mut vec = Vec::new(); - let work = client.get(url).and_then(|res| { + let req = Request::new(Method::Get, url); + + /* + let work = client.request(req) + + .and_then(|res| { let status = res.status().as_u16(); // TODO Ship headers back to Eve //println!("Headers: \n{:?}", res.headers()); @@ -96,7 +106,28 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) value_to_changes(&response_id, "body", v, "http/request", changes); Ok(()) }) + });*/ + + + let work = client.request(req).and_then(|res| { + let status = res.status().as_u16(); + let response_id = format!("http/response|{:?}",id); + changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); + changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), "http/request")); + changes.push(new_change(&response_id, "request", Internable::String(id.clone()), "http/request")); + println!("Response: {}", res.status()); + res.body().for_each(|chunk| { + let response_id = format!("http/response|{:?}",id); + let mut vector: Vec = Vec::new(); + vector.write_all(&chunk); + let body_string = String::from_utf8(vector).unwrap(); + changes.push(new_change(&response_id, "body", Internable::String(body_string), "http/request")); + Ok(()) + }) }); + + + match core.run(work) { Ok(_) => println!("OK"), Err(e) => { @@ -108,5 +139,4 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) println!("Not OK {:?}",e) }, } - println!("HERE3"); } \ No newline at end of file From 2d63257f32ba8c1ec1e55c849f70b1a298fcd358 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 10 Aug 2017 18:18:33 -0700 Subject: [PATCH 36/99] do some cleanup --- src/watchers/http.rs | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/src/watchers/http.rs b/src/watchers/http.rs index bbdc0b4..09e8ab9 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -1,7 +1,7 @@ use super::super::indexes::{WatchDiff}; use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; use std::sync::mpsc::{Sender}; -use watchers::json::{value_to_changes, new_change}; +use watchers::json::{new_change}; use super::Watcher; extern crate futures; @@ -10,17 +10,13 @@ extern crate hyper_tls; extern crate tokio_core; extern crate serde_json; extern crate serde; -use self::serde_json::{Value}; use self::futures::{Future, Stream}; use self::hyper::Client; use self::hyper_tls::HttpsConnector; use self::tokio_core::reactor::Core; -use self::hyper::header::ContentLength; use self::hyper::{Method, Request}; -use self::hyper::server::{Http, Response, Service}; -use std::thread; -use std::io::{self, Write}; -use std::fs::File; +//use std::thread; +use std::io::{Write}; pub struct HttpWatcher { name: String, @@ -81,34 +77,7 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) .connector(HttpsConnector::new(4,&handle).unwrap()) .build(&handle); let url = address.parse::().unwrap(); - println!("{:?}",url); - //let mut vec = Vec::new(); let req = Request::new(Method::Get, url); - - /* - let work = client.request(req) - - .and_then(|res| { - let status = res.status().as_u16(); - // TODO Ship headers back to Eve - //println!("Headers: \n{:?}", res.headers()); - res.body().concat2().and_then(move |body| { - let v: Value = serde_json::from_slice(&body).map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - e - ) - })?; - let response_id = format!("http/response|{:?}",id); - changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); - changes.push(new_change(&response_id, "request", Internable::String(id), "http/request")); - changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), "http/request")); - value_to_changes(&response_id, "body", v, "http/request", changes); - Ok(()) - }) - });*/ - - let work = client.request(req).and_then(|res| { let status = res.status().as_u16(); let response_id = format!("http/response|{:?}",id); @@ -119,15 +88,13 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) res.body().for_each(|chunk| { let response_id = format!("http/response|{:?}",id); let mut vector: Vec = Vec::new(); - vector.write_all(&chunk); + vector.write_all(&chunk).unwrap(); let body_string = String::from_utf8(vector).unwrap(); changes.push(new_change(&response_id, "body", Internable::String(body_string), "http/request")); Ok(()) }) }); - - match core.run(work) { Ok(_) => println!("OK"), Err(e) => { From 4691364966471c8ece5b0adfc78ef3263ad59e1d Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 11 Aug 2017 07:51:58 -0700 Subject: [PATCH 37/99] remove println --- src/watchers/http.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 09e8ab9..597d084 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -96,13 +96,16 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) }); match core.run(work) { - Ok(_) => println!("OK"), + Ok(_) => (), Err(e) => { // Form an HTTP Error - //let error_id = format!("http/request/error|{:?}|{:?}",&id,address); - //changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), "http/request")); - //changes.push(new_change(&error_id, "request", Internable::String(id), "http/request")); - //changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), "http/request")); + /* + let error_id = format!("http/request/error|{:?}|{:?}",&id,address); + let mut changes = vec![]; + changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), "http/request")); + changes.push(new_change(&error_id, "request", Internable::String(id.clone()), "http/request")); + changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), "http/request")); + */ println!("Not OK {:?}",e) }, } From 9ea340fd55618bbfc4a04d750e5eb89c5ba75b4d Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 11 Aug 2017 07:52:04 -0700 Subject: [PATCH 38/99] update test --- examples/test.eve | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index 8404761..f9206d5 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,10 +1,39 @@ +search + client_id = "3354b6a98e3c4fb3b804c5a9e064a3df" + response_type = "code" + redirect_uri = "google.com" + //address = "https://accounts.spotify.com/authorize/?client_id={{client_id}}&response_type=code&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=user-read-private%20user-read-email&state=34fFs29kd09" + //address = "https://jsonplaceholder.typicode.com/comments" + address = "http://echo.jsontest.com/key/shenberger/one/manifold" commit - [#http/request #req address: "http://echo.jsontest.com/last/shenberger/first/manifold"] + //[#foo bar: 10] + [#http/request #req address] end - search [#req response: [#http/response status body]] commit - [#console/log text: "{{status}} {{body.first}} {{body.last}}"] + [#json/decode #qq json: body] +end + +search + [#qq json-object: [one key]] +commit + [#console/log text: "{{one}} {{key}}"] end + + + +disabled +commit + [#http/server address: "127.0.0.1:8080"] +end + +Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 +Accept-Encoding:gzip, deflate, br +Accept-Language:en-US,en;q=0.8 +Connection:keep-alive +Cookie:remember=cmontella%40live.com; sp_dc=AQD2mtOZxGK5uVMFom_yG9Nx6CPebAlDZWqfJF-G0sbHXVI-5cjSxQoSyD5VW-IHgLhNIobci3nTrIUq2cryJCKQ; BOOTSTRAP_CONFIG=%7B%22FB_APP_ID%22%3A%22174829003346%22%2C%22GOOGLE_ANALYTICS_ID%22%3A%22UA-5784146-31%22%2C%22TINDER_ADJUST_TRACKER_ID%22%3A%22szcgk1_dn83gr%22%2C%22TINDER_CLIENT_ID%22%3A%22b06a803d686e4612bdc074e786e94062%22%2C%22client%22%3A%7B%22name%22%3A%22Spotify%20Developer%22%7D%2C%22country%22%3A%22US%22%2C%22locales%22%3A%5B%22en_US%22%2C%22en%22%5D%2C%22BON%22%3A%5B%220%22%2C%220%22%2C-1744276953%5D%2C%22user%22%3A%7B%22displayName%22%3A%22cmontella%22%7D%2C%22redirect%22%3Anull%7D; sp_ac=AQB_BTskkac5q-RpyZq7_nnS1-_aYgwlfwYBaKpmxVCevYGZHNF_1JUlrlGBg3gzB93sVVRSvwl4T5LMul5V0mKNaEcthgazQIhNPICjC8wsBvz10snCrorB_ZwW_DI-j1nAOdmmoP51jlyjJsh0KiB9mFoaQ9uMPFim4Q66lni0kzsAW8V0zXUcq0lA-XL-RR9Auycu5fo; csrf_token=AQBEr6KFRYrRb-PliLvnoJduz3nsImku4nRSkEhsDyrXj-Mwj8XcqVXqMd1eNruxEw2p8cw1UOxOzWrz; _ga=GA1.2.389876630.1502400017; _gid=GA1.2.745352553.1502400017; _gat=1 +Host:accounts.spotify.com +Upgrade-Insecure-Requests:1 +User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 \ No newline at end of file From 87a629d05fe85b6ad39347d4436ddebfa55e1d11 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 11 Aug 2017 19:08:32 -0700 Subject: [PATCH 39/99] Start an http server in Eve --- src/watchers/http.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 597d084..aee8427 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -14,9 +14,12 @@ use self::futures::{Future, Stream}; use self::hyper::Client; use self::hyper_tls::HttpsConnector; use self::tokio_core::reactor::Core; -use self::hyper::{Method, Request}; -//use std::thread; +use self::hyper::{Method}; +use std::thread; use std::io::{Write}; +extern crate iron; +use self::iron::prelude::*; +use self::iron::status; pub struct HttpWatcher { name: String, @@ -47,8 +50,8 @@ impl Watcher for HttpWatcher { send_http_request(address, id, &mut changes); }, "server" => { - println!("Starting HTTP Server"); - //http_server(address); + println!("Starting HTTP Server at {:?}",address); + http_server(address); println!("HTTP Server started"); }, _ => {}, @@ -61,14 +64,15 @@ impl Watcher for HttpWatcher { } } -/*fn http_server(address: String, service: str) -> thread::JoinHandle<()> { +fn hello_world(_: &mut Request) -> IronResult { + Ok(Response::with((status::Ok, "Hello World!"))) +} + +fn http_server(address: String) -> thread::JoinHandle<()> { thread::spawn(move || { - println!("Starting HTTP Server at {}... ", address); - let addr = address.parse().unwrap(); - let server = Http::new().bind(&addr, || Ok(service)).unwrap(); - server.run().unwrap(); + let server = Iron::new(hello_world).http(address).unwrap(); }) -}*/ +} fn send_http_request(address: String, id: String, changes: &mut Vec) { let mut core = Core::new().unwrap(); @@ -77,7 +81,7 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) .connector(HttpsConnector::new(4,&handle).unwrap()) .build(&handle); let url = address.parse::().unwrap(); - let req = Request::new(Method::Get, url); + let req = hyper::Request::new(Method::Get, url); let work = client.request(req).and_then(|res| { let status = res.status().as_u16(); let response_id = format!("http/response|{:?}",id); From 62d3d3772dd1e9d895ec3726916153755c06df8f Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 15 Aug 2017 15:13:45 -0700 Subject: [PATCH 40/99] add redirect --- examples/test.eve | 22 +++++++++++++++++----- libraries/html/html.eve | 9 +++++++++ libraries/html/html.ts | 5 +++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index f9206d5..118e965 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,21 +1,25 @@ search client_id = "3354b6a98e3c4fb3b804c5a9e064a3df" response_type = "code" - redirect_uri = "google.com" - //address = "https://accounts.spotify.com/authorize/?client_id={{client_id}}&response_type=code&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=user-read-private%20user-read-email&state=34fFs29kd09" + redirect_uri = "http://google.com" + address = "https://accounts.spotify.com/authorize/?client_id={{client_id}}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost:8080%2Fcallback&scope=user-read-private%20user-read-email&state=34fFs29kd09" //address = "https://jsonplaceholder.typicode.com/comments" - address = "http://echo.jsontest.com/key/shenberger/one/manifold" + //address = "http://echo.jsontest.com/key/shenberger/one/manifold" commit //[#foo bar: 10] - [#http/request #req address] + [#html/redirect #req url: address] end + +disabled search [#req response: [#http/response status body]] commit [#json/decode #qq json: body] end + +disabled search [#qq json-object: [one key]] commit @@ -36,4 +40,12 @@ Connection:keep-alive Cookie:remember=cmontella%40live.com; sp_dc=AQD2mtOZxGK5uVMFom_yG9Nx6CPebAlDZWqfJF-G0sbHXVI-5cjSxQoSyD5VW-IHgLhNIobci3nTrIUq2cryJCKQ; BOOTSTRAP_CONFIG=%7B%22FB_APP_ID%22%3A%22174829003346%22%2C%22GOOGLE_ANALYTICS_ID%22%3A%22UA-5784146-31%22%2C%22TINDER_ADJUST_TRACKER_ID%22%3A%22szcgk1_dn83gr%22%2C%22TINDER_CLIENT_ID%22%3A%22b06a803d686e4612bdc074e786e94062%22%2C%22client%22%3A%7B%22name%22%3A%22Spotify%20Developer%22%7D%2C%22country%22%3A%22US%22%2C%22locales%22%3A%5B%22en_US%22%2C%22en%22%5D%2C%22BON%22%3A%5B%220%22%2C%220%22%2C-1744276953%5D%2C%22user%22%3A%7B%22displayName%22%3A%22cmontella%22%7D%2C%22redirect%22%3Anull%7D; sp_ac=AQB_BTskkac5q-RpyZq7_nnS1-_aYgwlfwYBaKpmxVCevYGZHNF_1JUlrlGBg3gzB93sVVRSvwl4T5LMul5V0mKNaEcthgazQIhNPICjC8wsBvz10snCrorB_ZwW_DI-j1nAOdmmoP51jlyjJsh0KiB9mFoaQ9uMPFim4Q66lni0kzsAW8V0zXUcq0lA-XL-RR9Auycu5fo; csrf_token=AQBEr6KFRYrRb-PliLvnoJduz3nsImku4nRSkEhsDyrXj-Mwj8XcqVXqMd1eNruxEw2p8cw1UOxOzWrz; _ga=GA1.2.389876630.1502400017; _gid=GA1.2.745352553.1502400017; _gat=1 Host:accounts.spotify.com Upgrade-Insecure-Requests:1 -User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 \ No newline at end of file +User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 + + + +commit + //[#html/redirect url: "http://bing.com"] + [#console/log text: "This is a test"] + [#ui/text text: "This is also a test"] +end \ No newline at end of file diff --git a/libraries/html/html.eve b/libraries/html/html.eve index 24db3c7..b477acf 100644 --- a/libraries/html/html.eve +++ b/libraries/html/html.eve @@ -239,3 +239,12 @@ watch client/websocket ("html/export listeners" instance listener) end ~~~ + +Redirect to a url. +~~~ eve +search + [#html/redirect url] +watch client/websocket + ("html/redirect" url) +end +~~~ diff --git a/libraries/html/html.ts b/libraries/html/html.ts index f81dd46..bd67a19 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -340,6 +340,11 @@ export class HTML extends Library { if(!instance.listeners) instance.listeners = {[listener]: true}; else instance.listeners[listener] = true; } + }), + "redirect": handleTuples(({adds, removes}) => { + for(let [url] of adds || EMPTY) { + window.location.replace(`${url}`); + } }) }; From ed9a760efae8c2af19d2865c1b5cd89cf773dbd2 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 15 Aug 2017 17:50:50 -0700 Subject: [PATCH 41/99] Add url-change event --- libraries/html/html.ts | 40 +++++++++++++++++++++++++++++++++++----- package.json | 3 ++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/libraries/html/html.ts b/libraries/html/html.ts index bd67a19..ac9d877 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -1,6 +1,7 @@ import md5 from "md5"; import "setimmediate"; import {Program, Library, createId, RawValue, RawEAV, RawMap, handleTuples} from "../../ts"; +import url from "url"; const EMPTY:never[] = []; @@ -92,22 +93,26 @@ export class HTML extends Library { this._container.appendChild(this._syntheticStyleContainer); this._dummy = document.createElement("div"); + // Mouse events window.addEventListener("click", this._mouseEventHandler("click")); window.addEventListener("dblclick", this._mouseEventHandler("double-click")); window.addEventListener("mousedown", this._mouseEventHandler("mouse-down")); window.addEventListener("mouseup", this._mouseEventHandler("mouse-up")); window.addEventListener("contextmenu", this._captureContextMenuHandler()); + document.body.addEventListener("mouseenter", this._hoverEventHandler("hover-in"), true); + document.body.addEventListener("mouseleave", this._hoverEventHandler("hover-out"), true); + // Form events window.addEventListener("input", this._inputEventHandler("change")); - window.addEventListener("keydown", this._keyEventHandler("key-down")); - window.addEventListener("keyup", this._keyEventHandler("key-up")); window.addEventListener("focus", this._focusEventHandler("focus"), true); window.addEventListener("blur", this._focusEventHandler("blur"), true); - document.body.addEventListener("mouseenter", this._hoverEventHandler("hover-in"), true); - document.body.addEventListener("mouseleave", this._hoverEventHandler("hover-out"), true); + // Keyboard events + window.addEventListener("keydown", this._keyEventHandler("key-down")); + window.addEventListener("keyup", this._keyEventHandler("key-up")); - // window.addEventListener("hashchange", this._hashChangeHandler("url-change")); + // Frame events + window.addEventListener("hashchange", this._hashChangeHandler("url-change")); } protected decorate(elem:Element, elemId:RawValue): Instance { @@ -512,6 +517,31 @@ export class HTML extends Library { if(eavs.length) this._sendEvent(eavs); }; } + + _hashChangeHandler(tagname:string) { + return (event: HashChangeEvent) => { + if (event.newURL !== null) { + let eavs:RawEAV[] = []; + let eventId = createId(); + let {hash, host, hostname, href, path, pathname, port, protocol} = url.parse(event.newURL); + eavs.push( + [eventId, "tag", "html/event"], + [eventId, "tag", `html/event/${tagname}`], + [eventId, "host", `${host}`], + [eventId, "hash", `${hash}`], + [eventId, "hostname", `${hostname}`], + [eventId, "href", `${href}`], + [eventId, "path", `${path}`], + [eventId, "pathname", `${pathname}`], + [eventId, "port", `${port}`], + [eventId, "protocol", `${protocol}`], + ); + this._sendEvent(eavs); + } + } + } + + } Library.register(HTML.id, HTML); diff --git a/package.json b/package.json index 12b9316..3fc3fca 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "md5": "^2.2.1", "postinstall-build": "^5.0.1", "setimmediate": "^1.0.5", - "uuid": "^3.1.0" + "uuid": "^3.1.0", + "url": "^0.11.0" }, "devDependencies": { "@types/md5": "^2.1.32", From 4dd0834b6b956c1032736803fc57eac0bd351449 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 15 Aug 2017 18:11:32 -0700 Subject: [PATCH 42/99] Add page-show event --- libraries/html/html.ts | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/libraries/html/html.ts b/libraries/html/html.ts index ac9d877..095c0f4 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -113,6 +113,7 @@ export class HTML extends Library { // Frame events window.addEventListener("hashchange", this._hashChangeHandler("url-change")); + window.addEventListener("pageshow", this._pageShowHandler("page-show")); } protected decorate(elem:Element, elemId:RawValue): Instance { @@ -523,25 +524,52 @@ export class HTML extends Library { if (event.newURL !== null) { let eavs:RawEAV[] = []; let eventId = createId(); + let urlId = createId(); let {hash, host, hostname, href, path, pathname, port, protocol} = url.parse(event.newURL); eavs.push( [eventId, "tag", "html/event"], [eventId, "tag", `html/event/${tagname}`], - [eventId, "host", `${host}`], - [eventId, "hash", `${hash}`], - [eventId, "hostname", `${hostname}`], - [eventId, "href", `${href}`], - [eventId, "path", `${path}`], - [eventId, "pathname", `${pathname}`], - [eventId, "port", `${port}`], - [eventId, "protocol", `${protocol}`], + [eventId, "url", `${urlId}`], + [urlId, "tag", "html/url"], + [urlId, "host", `${host}`], + [urlId, "hash", `${hash}`], + [urlId, "hostname", `${hostname}`], + [urlId, "href", `${href}`], + [urlId, "path", `${path}`], + [urlId, "pathname", `${pathname}`], + [urlId, "port", `${port}`], + [urlId, "protocol", `${protocol}`], ); this._sendEvent(eavs); } } } - + _pageShowHandler(tagname:string) { + return (event: any) => { + if (event.srcElement !== null) { + let {hash, host, hostname, href, path, pathname, port, protocol} = url.parse(event.target.URL); + let eavs:RawEAV[] = []; + let eventId = createId(); + let urlId = createId(); + eavs.push( + [eventId, "tag", "html/event"], + [eventId, "tag", `html/event/${tagname}`], + [eventId, "url", `${urlId}`], + [urlId, "tag", "html/url"], + [urlId, "host", `${host}`], + [urlId, "hash", `${hash}`], + [urlId, "hostname", `${hostname}`], + [urlId, "href", `${href}`], + [urlId, "path", `${path}`], + [urlId, "pathname", `${pathname}`], + [urlId, "port", `${port}`], + [urlId, "protocol", `${protocol}`], + ); + this._sendEvent(eavs); + } + } + } } Library.register(HTML.id, HTML); From 90408575c684e7a86f8ed7aca0fb3ff24de0be0c Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 15 Aug 2017 18:40:04 -0700 Subject: [PATCH 43/99] decode URL queries --- libraries/html/html.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/libraries/html/html.ts b/libraries/html/html.ts index 095c0f4..225ae7e 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -525,7 +525,7 @@ export class HTML extends Library { let eavs:RawEAV[] = []; let eventId = createId(); let urlId = createId(); - let {hash, host, hostname, href, path, pathname, port, protocol} = url.parse(event.newURL); + let {hash, host, hostname, href, path, pathname, port, protocol, query} = url.parse(event.newURL, true); eavs.push( [eventId, "tag", "html/event"], [eventId, "tag", `html/event/${tagname}`], @@ -535,20 +535,31 @@ export class HTML extends Library { [urlId, "hash", `${hash}`], [urlId, "hostname", `${hostname}`], [urlId, "href", `${href}`], - [urlId, "path", `${path}`], [urlId, "pathname", `${pathname}`], [urlId, "port", `${port}`], [urlId, "protocol", `${protocol}`], ); + let ix = 1; + for (var key in query) { + let value = query[key]; + let queryId = createId(); + eavs.push( + [urlId, "query", `${queryId}`], + [queryId, "index", `${ix}`], + [queryId, "key", key], + [queryId, "value", value], + ) + ix++; + } this._sendEvent(eavs); } } } _pageShowHandler(tagname:string) { - return (event: any) => { + return (event: any) => { if (event.srcElement !== null) { - let {hash, host, hostname, href, path, pathname, port, protocol} = url.parse(event.target.URL); + let {hash, host, hostname, href, path, pathname, port, protocol, query} = url.parse(event.target.URL, true); let eavs:RawEAV[] = []; let eventId = createId(); let urlId = createId(); @@ -561,11 +572,22 @@ export class HTML extends Library { [urlId, "hash", `${hash}`], [urlId, "hostname", `${hostname}`], [urlId, "href", `${href}`], - [urlId, "path", `${path}`], [urlId, "pathname", `${pathname}`], [urlId, "port", `${port}`], [urlId, "protocol", `${protocol}`], ); + let ix = 1; + for (var key in query) { + let value = query[key]; + let queryId = createId(); + eavs.push( + [urlId, "query", `${queryId}`], + [queryId, "index", `${ix}`], + [queryId, "key", key], + [queryId, "value", value], + ) + ix++; + } this._sendEvent(eavs); } } From a414907db04bf501576692b9a21a297f5975043e Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 17 Aug 2017 14:52:10 -0700 Subject: [PATCH 44/99] handle multiple reuquests at once --- Cargo.toml | 3 +- examples/test.eve | 61 ++++++++++++++++++++++++++++--------- libraries/http/http.eve | 43 +++++++++++++++++++++++--- src/watchers/http.rs | 67 ++++++++++++++++++++++++++++++++--------- 4 files changed, 139 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e664e17..7c64b97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,5 @@ mount = "*" hyper = "0.11.2" hyper-tls = "0.1.2" futures = "0.1.14" -tokio-core = "0.1.9" \ No newline at end of file +tokio-core = "0.1.9" +data-encoding = "1.2.0" \ No newline at end of file diff --git a/examples/test.eve b/examples/test.eve index 118e965..dcd1fec 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,15 +1,54 @@ +Get the auth + +disabled search - client_id = "3354b6a98e3c4fb3b804c5a9e064a3df" - response_type = "code" - redirect_uri = "http://google.com" - address = "https://accounts.spotify.com/authorize/?client_id={{client_id}}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost:8080%2Fcallback&scope=user-read-private%20user-read-email&state=34fFs29kd09" - //address = "https://jsonplaceholder.typicode.com/comments" - //address = "http://echo.jsontest.com/key/shenberger/one/manifold" + [#html/event/click element: [#login]] + client-id = "3354b6a98e3c4fb3b804c5a9e064a3df" + response-type = "code" + redirect-uri = "http%3A%2F%2Flocalhost%3A8081%2F%23spotify-callback" + address = "https://accounts.spotify.com/authorize/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{redirect-uri}}&scope=user-read-private%20user-read-email&state=34fFs29kd09" commit - //[#foo bar: 10] [#html/redirect #req url: address] end +Display a button to login + +disabled +search + not([#logged-in]) +bind + [#ui/button #login text: "Login to Spotify"] +end + +When a code is received, the user is logged in + +disabled +search + [#html/event/page-show url: [#html/url query: [key: "code" value index]]] +commit + [#ui/text text: "Logged in"] + [#logged-in] +end + +//search + //address = "https://accounts.spotify.com/api/token?grant_type={{grant-type}}&code={{code}}&redirec_uri={{redirect-uri}}" + //grant-type = [] +commit + //[#http/request #goog address: "http://echo.jsontest.com/key/shenberger/one/manifold" method: "GET"] + [#http/request address: "http://echo.jsontest.com/key/value" method: "GET" headers: [#http/header key: "foo" value: "bar"] [#http/header key: "baz" value: "bin"]] + [#http/request address: "http://test2.com/" method: "PUT" body: "Goodbye" headers: [#http/header key: "a" value: "b"] [#http/header key: "c" value: "d"] [#http/header key: "e" value: "f"]] + [#http/request address: "foo.com" headers: [#http/header key: "Authorization" value: "Basic 3354b6a98e3c4fb3b804c5a9e064a3df:b431c961f8d1457a91e0b90857bc8fa0"]] +end + +search + [#http/request response: [#http/response status]] +commit + [#ui/text text: status] +end + + + + disabled search @@ -41,11 +80,3 @@ Cookie:remember=cmontella%40live.com; sp_dc=AQD2mtOZxGK5uVMFom_yG9Nx6CPebAlDZWqf Host:accounts.spotify.com Upgrade-Insecure-Requests:1 User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 - - - -commit - //[#html/redirect url: "http://bing.com"] - [#console/log text: "This is a test"] - [#ui/text text: "This is also a test"] -end \ No newline at end of file diff --git a/libraries/http/http.eve b/libraries/http/http.eve index b57f217..40d2fc2 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -2,12 +2,47 @@ ## Send an HTTP Request +Watch for HTTP requests. requests with multiple headers will be processed +several times, but the request is not send until the diff is completely +resolved to avoid duplicate requests. + search - h = [#http/request address] + request = [#http/request address method body headers: [#http/header key value]] watch http - ("request", h, address) + ("request", request, address, method, key, value) +end + +Default method + +search + request = [#http/request] + not(request.method) +commit + request.method := "GET" +end + +Default empty body + +search + request = [#http/request] + not(request.body) +commit + request.body := "" end +Default empty header + +search + request = [#http/request] + not(request.headers) +commit + request.headers := [#http/header key: "" value: ""] +end + + + +Associate response with the request + search response = [#http/response request] request = [#http/request] @@ -18,7 +53,7 @@ end ## Process HTTP Requests search - h = [#http/server address] + server = [#http/server address] watch http - ("server", h, address) + ("server", server, address) end \ No newline at end of file diff --git a/src/watchers/http.rs b/src/watchers/http.rs index aee8427..467cd47 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -4,6 +4,7 @@ use std::sync::mpsc::{Sender}; use watchers::json::{new_change}; use super::Watcher; +extern crate data_encoding; extern crate futures; extern crate hyper; extern crate hyper_tls; @@ -20,6 +21,8 @@ use std::io::{Write}; extern crate iron; use self::iron::prelude::*; use self::iron::status; +use self::data_encoding::base64; +use std::collections::HashMap; pub struct HttpWatcher { name: String, @@ -28,6 +31,7 @@ pub struct HttpWatcher { impl HttpWatcher { pub fn new(outgoing: Sender) -> HttpWatcher { + println!("{:?}",base64::encode(b"Hello world")); HttpWatcher { name: "http".to_string(), outgoing } } } @@ -39,28 +43,63 @@ impl Watcher for HttpWatcher { fn set_name(&mut self, name: &str) { self.name = name.to_string(); } - fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { + fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { + println!("DIFF"); + let mut requests: HashMap = HashMap::new(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let id = Internable::to_string(interner.get_value(add[1])); - let address = Internable::to_string(interner.get_value(add[2])); - let mut changes = vec![]; + let address = Internable::to_string(interner.get_value(add[2])); match &kind[..] { "request" => { - send_http_request(address, id, &mut changes); + let body = Internable::to_string(interner.get_value(add[3])); + let key = Internable::to_string(interner.get_value(add[4])); + let value = Internable::to_string(interner.get_value(add[5])); + if !requests.contains_key(&id) { + let url = address.parse::().unwrap(); + let method = Internable::to_string(interner.get_value(add[3])); + let rmethod: Method = match &method.to_lowercase()[..] { + "get" => Method::Get, + "put" => Method::Put, + "post" => Method::Post, + "delete" => Method::Delete, + "head" => Method::Head, + "trace" => Method::Trace, + "connect" => Method::Connect, + "patch" => Method::Patch, + _ => Method::Get + }; + let mut req = hyper::Request::new(rmethod, url); + requests.insert(id.clone(),req); + } + let req = requests.get_mut(&id).unwrap(); + if key != "" { + req.headers_mut().set_raw(key, vec![value.into_bytes().to_vec()]); + } + if body != "" { + req.set_body(body); + } }, "server" => { - println!("Starting HTTP Server at {:?}",address); + println!("Starting HTTP Server at {:?}", address); http_server(address); println!("HTTP Server started"); }, _ => {}, - } - match self.outgoing.send(RunLoopMessage::Transaction(changes)) { - Err(_) => break, - _ => (), - } + } + } + // Send the HTTP request and package response in the changevec + let mut changes: Vec = vec![]; + for (id, request) in requests.drain() { + send_http_request(&id,request,&mut changes); } + println!("{:?}",changes); + + + match self.outgoing.send(RunLoopMessage::Transaction(changes)) { + Err(_) => (), + _ => (), + }; } } @@ -74,15 +113,13 @@ fn http_server(address: String) -> thread::JoinHandle<()> { }) } -fn send_http_request(address: String, id: String, changes: &mut Vec) { +fn send_http_request(id: &String, request: hyper::Request, changes: &mut Vec) { let mut core = Core::new().unwrap(); let handle = core.handle(); let client = Client::configure() .connector(HttpsConnector::new(4,&handle).unwrap()) .build(&handle); - let url = address.parse::().unwrap(); - let req = hyper::Request::new(Method::Get, url); - let work = client.request(req).and_then(|res| { + let work = client.request(request).and_then(|res| { let status = res.status().as_u16(); let response_id = format!("http/response|{:?}",id); changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); @@ -93,12 +130,12 @@ fn send_http_request(address: String, id: String, changes: &mut Vec) let response_id = format!("http/response|{:?}",id); let mut vector: Vec = Vec::new(); vector.write_all(&chunk).unwrap(); + // Something is going wrong here let body_string = String::from_utf8(vector).unwrap(); changes.push(new_change(&response_id, "body", Internable::String(body_string), "http/request")); Ok(()) }) }); - match core.run(work) { Ok(_) => (), Err(e) => { From bcb61913a945861e4baccb66582c1d69d8d36415 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 17 Aug 2017 16:26:44 -0700 Subject: [PATCH 45/99] Plumb in errors --- examples/test.eve | 11 +++++++++-- src/watchers/http.rs | 29 ++++++++++++++++------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index dcd1fec..bdcbefc 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -41,9 +41,16 @@ commit end search - [#http/request response: [#http/response status]] + [#http/request response: [#http/response body]] commit - [#ui/text text: status] + [#html/div text: body] +end + + +search + [#http/request/error error] +commit + [#html/div text: error] end diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 467cd47..fc39bcb 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -91,7 +91,7 @@ impl Watcher for HttpWatcher { // Send the HTTP request and package response in the changevec let mut changes: Vec = vec![]; for (id, request) in requests.drain() { - send_http_request(&id,request,&mut changes); + send_http_request(&id,request,&self.outgoing); } println!("{:?}",changes); @@ -113,40 +113,43 @@ fn http_server(address: String) -> thread::JoinHandle<()> { }) } -fn send_http_request(id: &String, request: hyper::Request, changes: &mut Vec) { +fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender) { let mut core = Core::new().unwrap(); let handle = core.handle(); let client = Client::configure() .connector(HttpsConnector::new(4,&handle).unwrap()) .build(&handle); let work = client.request(request).and_then(|res| { + let mut response_changes: Vec = vec![]; let status = res.status().as_u16(); let response_id = format!("http/response|{:?}",id); - changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); - changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), "http/request")); - changes.push(new_change(&response_id, "request", Internable::String(id.clone()), "http/request")); + response_changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); + response_changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), "http/request")); + response_changes.push(new_change(&response_id, "request", Internable::String(id.clone()), "http/request")); println!("Response: {}", res.status()); + outgoing.send(RunLoopMessage::Transaction(response_changes)).unwrap(); res.body().for_each(|chunk| { let response_id = format!("http/response|{:?}",id); let mut vector: Vec = Vec::new(); vector.write_all(&chunk).unwrap(); // Something is going wrong here let body_string = String::from_utf8(vector).unwrap(); - changes.push(new_change(&response_id, "body", Internable::String(body_string), "http/request")); + println!("{:?}",body_string); + outgoing.send(RunLoopMessage::Transaction(vec![new_change(&response_id, "body", Internable::String(body_string), "http/request")])).unwrap(); Ok(()) }) + }); match core.run(work) { Ok(_) => (), Err(e) => { // Form an HTTP Error - /* - let error_id = format!("http/request/error|{:?}|{:?}",&id,address); - let mut changes = vec![]; - changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), "http/request")); - changes.push(new_change(&error_id, "request", Internable::String(id.clone()), "http/request")); - changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), "http/request")); - */ + let error_id = format!("http/request/error|{:?}",&id); + let mut error_changes: Vec = vec![]; + error_changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), "http/request")); + error_changes.push(new_change(&error_id, "request", Internable::String(id.clone()), "http/request")); + error_changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), "http/request")); + outgoing.send(RunLoopMessage::Transaction(error_changes)).unwrap(); println!("Not OK {:?}",e) }, } From 5f3a4232c0fb077def2d082db189956cfeb7b032 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 17 Aug 2017 16:59:54 -0700 Subject: [PATCH 46/99] clean up some things --- examples/test.eve | 18 ++++-------------- libraries/http/http.eve | 19 +++++++++++++------ src/watchers/http.rs | 37 ++++++++++--------------------------- src/watchers/json.rs | 2 +- 4 files changed, 28 insertions(+), 48 deletions(-) diff --git a/examples/test.eve b/examples/test.eve index bdcbefc..04afbc3 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -37,26 +37,16 @@ commit //[#http/request #goog address: "http://echo.jsontest.com/key/shenberger/one/manifold" method: "GET"] [#http/request address: "http://echo.jsontest.com/key/value" method: "GET" headers: [#http/header key: "foo" value: "bar"] [#http/header key: "baz" value: "bin"]] [#http/request address: "http://test2.com/" method: "PUT" body: "Goodbye" headers: [#http/header key: "a" value: "b"] [#http/header key: "c" value: "d"] [#http/header key: "e" value: "f"]] - [#http/request address: "foo.com" headers: [#http/header key: "Authorization" value: "Basic 3354b6a98e3c4fb3b804c5a9e064a3df:b431c961f8d1457a91e0b90857bc8fa0"]] + [#http/request address: "https://accounts.spotify.com/authorize/" headers: [#http/header key: "Authorization" value: "Basic 3354b6a98e3c4fb3b804c5a9e064a3df:b431c961f8d1457a91e0b90857bc8fa0"]] end search - [#http/request response: [#http/response body]] + (address, text) = if [#http/request address response] then (address, response.body) + else if [#http/request address error] then (address, error.error) commit - [#html/div text: body] + [#html/div text: "{{address}} {{text}}"] end - -search - [#http/request/error error] -commit - [#html/div text: error] -end - - - - - disabled search [#req response: [#http/response status body]] diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 40d2fc2..2ccf925 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -9,7 +9,7 @@ resolved to avoid duplicate requests. search request = [#http/request address method body headers: [#http/header key value]] watch http - ("request", request, address, method, key, value) + ("request", request, address, method, body, key, value) end Default method @@ -39,9 +39,7 @@ commit request.headers := [#http/header key: "" value: ""] end - - -Associate response with the request +Associate response with its request search response = [#http/response request] @@ -50,10 +48,19 @@ commit request.response := response end +Associate an error with its request + +search + error = [#http/request/error request] + request = [#http/request] +commit + request.error := error +end + ## Process HTTP Requests search - server = [#http/server address] + server = [#http/server address body] watch http - ("server", server, address) + ("server", server, address, body) end \ No newline at end of file diff --git a/src/watchers/http.rs b/src/watchers/http.rs index fc39bcb..037b150 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -31,7 +31,6 @@ pub struct HttpWatcher { impl HttpWatcher { pub fn new(outgoing: Sender) -> HttpWatcher { - println!("{:?}",base64::encode(b"Hello world")); HttpWatcher { name: "http".to_string(), outgoing } } } @@ -44,7 +43,6 @@ impl Watcher for HttpWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - println!("DIFF"); let mut requests: HashMap = HashMap::new(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); @@ -52,9 +50,9 @@ impl Watcher for HttpWatcher { let address = Internable::to_string(interner.get_value(add[2])); match &kind[..] { "request" => { - let body = Internable::to_string(interner.get_value(add[3])); - let key = Internable::to_string(interner.get_value(add[4])); - let value = Internable::to_string(interner.get_value(add[5])); + let body = Internable::to_string(interner.get_value(add[4])); + let key = Internable::to_string(interner.get_value(add[5])); + let value = Internable::to_string(interner.get_value(add[6])); if !requests.contains_key(&id) { let url = address.parse::().unwrap(); let method = Internable::to_string(interner.get_value(add[3])); @@ -69,7 +67,7 @@ impl Watcher for HttpWatcher { "patch" => Method::Patch, _ => Method::Get }; - let mut req = hyper::Request::new(rmethod, url); + let req = hyper::Request::new(rmethod, url); requests.insert(id.clone(),req); } let req = requests.get_mut(&id).unwrap(); @@ -81,35 +79,24 @@ impl Watcher for HttpWatcher { } }, "server" => { - println!("Starting HTTP Server at {:?}", address); - http_server(address); - println!("HTTP Server started"); + let body = Internable::to_string(interner.get_value(add[3])); + http_server(address, body); }, _ => {}, } } // Send the HTTP request and package response in the changevec - let mut changes: Vec = vec![]; for (id, request) in requests.drain() { send_http_request(&id,request,&self.outgoing); } - println!("{:?}",changes); - - - match self.outgoing.send(RunLoopMessage::Transaction(changes)) { - Err(_) => (), - _ => (), - }; } } -fn hello_world(_: &mut Request) -> IronResult { - Ok(Response::with((status::Ok, "Hello World!"))) -} - -fn http_server(address: String) -> thread::JoinHandle<()> { +fn http_server(address: String, body: String) -> thread::JoinHandle<()> { thread::spawn(move || { - let server = Iron::new(hello_world).http(address).unwrap(); + Iron::new(|req: &mut Request| { + Ok(Response::with((status::Ok, "Hello"))) + }).http(address).unwrap(); }) } @@ -126,15 +113,12 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = Vec::new(); vector.write_all(&chunk).unwrap(); - // Something is going wrong here let body_string = String::from_utf8(vector).unwrap(); - println!("{:?}",body_string); outgoing.send(RunLoopMessage::Transaction(vec![new_change(&response_id, "body", Internable::String(body_string), "http/request")])).unwrap(); Ok(()) }) @@ -150,7 +134,6 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender Value::Null, }; if record_map.contains_key(&e) { - let mut record = record_map.get_mut(&e).unwrap(); + let record = record_map.get_mut(&e).unwrap(); let sub_record = record.as_object_mut().unwrap(); sub_record.insert(a, v); } else { From b209b4cbab882f0e62165e7a6ac51fb0eb076672 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 17 Aug 2017 19:10:58 -0700 Subject: [PATCH 47/99] Add string/url-encode and string/encode --- Cargo.toml | 3 ++- examples/test.eve | 24 +++++++++++------------- src/compiler.rs | 2 ++ src/ops.rs | 21 +++++++++++++++++++++ src/watchers/http.rs | 2 -- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c64b97..cb30a8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,5 @@ hyper = "0.11.2" hyper-tls = "0.1.2" futures = "0.1.14" tokio-core = "0.1.9" -data-encoding = "1.2.0" \ No newline at end of file +data-encoding = "1.2.0" +urlencoding = "1.0.0" \ No newline at end of file diff --git a/examples/test.eve b/examples/test.eve index 04afbc3..c24cff4 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -5,7 +5,7 @@ search [#html/event/click element: [#login]] client-id = "3354b6a98e3c4fb3b804c5a9e064a3df" response-type = "code" - redirect-uri = "http%3A%2F%2Flocalhost%3A8081%2F%23spotify-callback" + redirect-uri = string/url-encode[text: "http://localhost:8081/#spotify-callback"] address = "https://accounts.spotify.com/authorize/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{redirect-uri}}&scope=user-read-private%20user-read-email&state=34fFs29kd09" commit [#html/redirect #req url: address] @@ -30,14 +30,13 @@ commit [#logged-in] end -//search +search //address = "https://accounts.spotify.com/api/token?grant_type={{grant-type}}&code={{code}}&redirec_uri={{redirect-uri}}" + //encoded-authorization = string/encode[text: "3354b6a98e3c4fb3b804c5a9e064a3df:b431c961f8d1457a91e0b90857bc8fa0"] //grant-type = [] commit //[#http/request #goog address: "http://echo.jsontest.com/key/shenberger/one/manifold" method: "GET"] - [#http/request address: "http://echo.jsontest.com/key/value" method: "GET" headers: [#http/header key: "foo" value: "bar"] [#http/header key: "baz" value: "bin"]] - [#http/request address: "http://test2.com/" method: "PUT" body: "Goodbye" headers: [#http/header key: "a" value: "b"] [#http/header key: "c" value: "d"] [#http/header key: "e" value: "f"]] - [#http/request address: "https://accounts.spotify.com/authorize/" headers: [#http/header key: "Authorization" value: "Basic 3354b6a98e3c4fb3b804c5a9e064a3df:b431c961f8d1457a91e0b90857bc8fa0"]] + //[#http/request address: "https://accounts.spotify.com/authorize/" headers: [#http/header key: "Authorization" value: "Basic {{encoded-authorization}}"]] end search @@ -69,11 +68,10 @@ commit [#http/server address: "127.0.0.1:8080"] end -Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 -Accept-Encoding:gzip, deflate, br -Accept-Language:en-US,en;q=0.8 -Connection:keep-alive -Cookie:remember=cmontella%40live.com; sp_dc=AQD2mtOZxGK5uVMFom_yG9Nx6CPebAlDZWqfJF-G0sbHXVI-5cjSxQoSyD5VW-IHgLhNIobci3nTrIUq2cryJCKQ; BOOTSTRAP_CONFIG=%7B%22FB_APP_ID%22%3A%22174829003346%22%2C%22GOOGLE_ANALYTICS_ID%22%3A%22UA-5784146-31%22%2C%22TINDER_ADJUST_TRACKER_ID%22%3A%22szcgk1_dn83gr%22%2C%22TINDER_CLIENT_ID%22%3A%22b06a803d686e4612bdc074e786e94062%22%2C%22client%22%3A%7B%22name%22%3A%22Spotify%20Developer%22%7D%2C%22country%22%3A%22US%22%2C%22locales%22%3A%5B%22en_US%22%2C%22en%22%5D%2C%22BON%22%3A%5B%220%22%2C%220%22%2C-1744276953%5D%2C%22user%22%3A%7B%22displayName%22%3A%22cmontella%22%7D%2C%22redirect%22%3Anull%7D; sp_ac=AQB_BTskkac5q-RpyZq7_nnS1-_aYgwlfwYBaKpmxVCevYGZHNF_1JUlrlGBg3gzB93sVVRSvwl4T5LMul5V0mKNaEcthgazQIhNPICjC8wsBvz10snCrorB_ZwW_DI-j1nAOdmmoP51jlyjJsh0KiB9mFoaQ9uMPFim4Q66lni0kzsAW8V0zXUcq0lA-XL-RR9Auycu5fo; csrf_token=AQBEr6KFRYrRb-PliLvnoJduz3nsImku4nRSkEhsDyrXj-Mwj8XcqVXqMd1eNruxEw2p8cw1UOxOzWrz; _ga=GA1.2.389876630.1502400017; _gid=GA1.2.745352553.1502400017; _gat=1 -Host:accounts.spotify.com -Upgrade-Insecure-Requests:1 -User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 + + +search + encoded-string = string/url-encode[text: "http://localhost:8081/#spotify-callback"] +commit + [#ui/text text: encoded-string] +end \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 0d31c03..c2cc421 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -107,6 +107,8 @@ lazy_static! { m.insert("string/replace".to_string(), FunctionInfo::new(vec!["text", "replace", "with"])); m.insert("string/contains".to_string(), FunctionInfo::new(vec!["text", "substring"])); m.insert("string/lowercase".to_string(), FunctionInfo::new(vec!["text"])); + m.insert("string/encode".to_string(), FunctionInfo::new(vec!["text"])); + m.insert("string/url-encode".to_string(), FunctionInfo::new(vec!["text"])); m.insert("string/uppercase".to_string(), FunctionInfo::new(vec!["text"])); m.insert("string/length".to_string(), FunctionInfo::new(vec!["text"])); m.insert("string/substring".to_string(), FunctionInfo::new(vec!["text", "from", "to"])); diff --git a/src/ops.rs b/src/ops.rs index 527321f..d532630 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -6,6 +6,9 @@ extern crate time; extern crate serde_json; extern crate bincode; extern crate term_painter; +extern crate data_encoding; +extern crate urlencoding; + use unicode_segmentation::UnicodeSegmentation; @@ -33,6 +36,7 @@ use std::f32::consts::{PI}; use std::mem; use std::usize; use rand::{Rng, SeedableRng, XorShiftRng}; +use self::data_encoding::base64; use self::term_painter::ToStyle; use self::term_painter::Color::*; @@ -1227,6 +1231,8 @@ pub fn make_function(op: &str, params: Vec, output: Field) -> Constraint "string/uppercase" => string_uppercase, "string/substring" => string_substring, "string/length" => string_length, + "string/encode" => string_encode, + "string/url-encode" => string_urlencode, "concat" => concat, "gen_id" => gen_id, _ => panic!("Unknown function: {:?}", op) @@ -1516,6 +1522,21 @@ pub fn string_length(params: Vec<&Internable>) -> Option { } } +pub fn string_encode(params: Vec<&Internable>) -> Option { + match params.as_slice() { + &[&Internable::String(ref text)] => Some(Internable::String(base64::encode(text.as_bytes()))), + _ => None + } +} + +pub fn string_urlencode(params: Vec<&Internable>) -> Option { + match params.as_slice() { + &[&Internable::String(ref text)] => Some(Internable::String(urlencoding::encode(text))), + _ => None + } +} + + pub fn string_substring(params: Vec<&Internable>) -> Option { let params_slice = params.as_slice(); match params_slice { diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 037b150..102a3a6 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -4,7 +4,6 @@ use std::sync::mpsc::{Sender}; use watchers::json::{new_change}; use super::Watcher; -extern crate data_encoding; extern crate futures; extern crate hyper; extern crate hyper_tls; @@ -21,7 +20,6 @@ use std::io::{Write}; extern crate iron; use self::iron::prelude::*; use self::iron::status; -use self::data_encoding::base64; use std::collections::HashMap; pub struct HttpWatcher { From 8db57fae1ab9f4d5dbb949aa8f5af69ce7b65edd Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 18 Aug 2017 16:27:13 -0700 Subject: [PATCH 48/99] Property intern ints --- src/watchers/json.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index acc3803..014cd39 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -2,9 +2,6 @@ use super::super::indexes::{WatchDiff}; use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; use std::sync::mpsc::{Sender}; use super::Watcher; -//use serde::de::{self, Deserialize, Deserializer, DeserializeSeed, Visitor, SeqAccess, -// MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; -//use serde::ser::{Serialize, Serializer}; extern crate serde_json; extern crate serde; @@ -111,10 +108,10 @@ pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, cha match value { Value::Number(n) => { if n.is_u64() { - let v = Internable::Number(n.as_u64().unwrap() as u32); + let v = Internable::from_number(n.as_u64().unwrap() as f32); changes.push(new_change(id,attribute,v,node)); } else if n.is_i64() { - let v = Internable::Number(n.as_i64().unwrap() as u32); + let v = Internable::from_number(n.as_i64().unwrap() as f32); changes.push(new_change(id,attribute,v,node)); } else if n.is_f64() { let v = Internable::from_number(n.as_f64().unwrap() as f32); From d219c37f8ce44b544435b35ee41f7f0687c5836f Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 18 Aug 2017 16:27:26 -0700 Subject: [PATCH 49/99] default body --- libraries/http/http.eve | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 2ccf925..17845ca 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -60,7 +60,9 @@ end ## Process HTTP Requests search - server = [#http/server address body] + server = [#http/server address] + body = if server.body then server.body + else "" watch http ("server", server, address, body) end \ No newline at end of file From acd6d556169afdf3199ee99869b4ba562632cf9d Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 18 Aug 2017 16:27:37 -0700 Subject: [PATCH 50/99] add tag to url query --- libraries/html/html.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/html/html.ts b/libraries/html/html.ts index e369177..04c647c 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -645,6 +645,7 @@ export class HTML extends Library { let queryId = createId(); eavs.push( [urlId, "query", `${queryId}`], + [queryId, "tag", `html/url/query`], [queryId, "index", `${ix}`], [queryId, "key", key], [queryId, "value", value], From b8c78095bbcc14b5edfc6721b7fe384e9effef0e Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 18 Aug 2017 16:28:06 -0700 Subject: [PATCH 51/99] ignore vscode file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index aac2347..9d89521 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ data/* node_modules/ build/ dist/ +.vscode/tasks.json From 06399790eb7deab00833f0b183f672761912f52c Mon Sep 17 00:00:00 2001 From: cmontella Date: Sun, 20 Aug 2017 15:57:28 -0700 Subject: [PATCH 52/99] Parse kvs in hash string --- examples/setlist-aggregator.eve | 190 ++++++++++++++++++++++++++++++++ libraries/html/html.ts | 19 ++++ 2 files changed, 209 insertions(+) create mode 100644 examples/setlist-aggregator.eve diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve new file mode 100644 index 0000000..983a863 --- /dev/null +++ b/examples/setlist-aggregator.eve @@ -0,0 +1,190 @@ +# Setlist Aggregator + +This app takes a set of concerts, and accesses the setlist.fm api to retrieve +the setlist that was played at that concert. This is cross-referenced with +the MS Groove API to create playlists for those concerts. + +The app configuration + +commit + [#setlist-app + groove-client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" + groove-client-secret: "UskJMTfCRNffOLbuBnGSBb2" + setlist-api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275"] +end + +## Logging in to setlist + +Logging in is kicked off by clicking the login button. + +search + not([#setlist-app user]) +bind + [#ui/button #groove-login text: "Login to Groove"] +end + +Clicking this button redirects to a setlist login portal, where the user is +prompted to give the app access to his or her account. + +search + [#html/event/click element: [#groove-login]] + [#setlist-app groove-client-id] + response-type = "token" + scopes = string/url-encode[text: "MicrosoftMediaServices.GrooveApiAccess offline_access"] + redirect-uri = string/url-encode[text: "http://localhost:8081"] + address = "https://login.live.com/oauth20_authorize.srf/?client_id={{groove-client-id}}&response_type={{response-type}}&redirect_uri={{redirect-uri}}&scope={{scopes}}" +commit + [#html/redirect #req url: address] +end + +A successful login will return to the app a code. This is exchanged for an +access token with a POST request. + +search + [#html/event/page-show url: [#html/url query: [#html/url/query key: "code" value: code]]] + [#setlist-app groove-client-id groove-client-secret] + redirect-uri = string/url-encode[text: "http://localhost:8081/#setlist-callback"] + auth-token = string/encode[text: "{{groove-client-id}}:{{groove-client-secret}}"] + body = "grant_type=authorization_code&code={{code}}&redirect_uri={{redirect-uri}}" + content-length = string/length[text: body] +commit + [#http/request #setlist-token address: "https://accounts.setlist.com/api/token" method: "POST" body headers: + [#http/header key: "Authorization" value: "Basic {{auth-token}}"] + [#http/header key: "Content-Type" value: "application/x-www-form-urlencoded"] + [#http/header key: "Content-Length" value: "{{content-length}}"]] +end + +Parse the response body, which contains the access token + +search + [#setlist-token response: [body]] +commit + [#json/decode #setlist-auth-token json: body] +end + +Place the access token in the app record, so we can use it to fetch data from +the user's account. + +search + [#setlist-auth-token json-object: auth] + setlist = [#setlist-app] +commit + setlist.access-token := auth.access_token + setlist.refresh-token := auth.refresh_token +end + +## Getting User Data + +With an access token in hand, we can use that to get user-specific information +like username and email address. + +search + [#setlist-app access-token] +commit + [#http/request #profile-content address: "https://api.setlist.com/v1/me" method: "GET" headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#profile-content response: [body]] +commit + [#json/decode #setlist-profile json: body] +end + +Save this info in the setlist app + +search + [#setlist-profile json-object: profile] + setlist = [#setlist-app] +commit + setlist.user := [#setlist/user name: profile.id e-mail: profile.email] +end + +## The User Interface + +search + [#setlist-app user: [name e-mail]] +bind + [#html/div text: "Welcome {{name}}!"] +end + +commit + [#ui/input #artist-name] + [#ui/button #search-artist text: "Search"] +end + + +## Get Data From setlist + + +search + [#setlist-app access-token] + address = "https://api.setlist.com/v1/search?q=incubus+glitterbomb+8&type=track" +commit + [#http/request #search-setlist address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"] + [#http/header key: "Accept" value: "application/json"]] +end + +search + [#search-setlist response: [body]] +commit + [#json/decode #search-setlist json: body] +end + +search + [#search-setlist json-object: spotsearch] + track-id = spotsearch.tracks.items.value.id +commit + [#html/div text: track-id] +end + + + +## Get Data From Setlist.fm + +search + [#setlist-app setlist-api-key] + [#html/event/click element: [#search-artist]] + [#artist-name value] + artist = string/replace[text: value replace: " " with: "+"] + //address = "https://api.setlist.fm/rest/1.0/search/artists?artistName={{artist}}" + address = "https://api.setlist.fm/rest/1.0/search/setlists?artistName=Incubus&date=16-08-2017&p=1" +commit + [#http/request #setlist-fm artist: value, address headers: + [#http/header key: "x-api-key" value: setlist-api-key] + [#http/header key: "Accept" value: "application/json"]] +end + +search + [#setlist-fm response: [body]] +commit + [#json/decode #setlist-fm json: body] +end + +search + [#setlist-fm json-object: setlistsearch] + //setlistsearch.artist.value.name = artist + //artist = setlistsearch.artist.value +commit + [#html/div text: "{{setlistsearch.setlist.value.sets.set.value.song.index}} - {{setlistsearch.setlist.value.sets.set.value.song.value.name}}"] +end + +e3e0abcd-7671-4482-a9d8-462f5acc9be5 + + +## Misc Diagnostics + +disabled +search + [#http/request/error error] +commit + [#html/div text: error] +end + +disabled +search + [#http/response body] +commit + [#html/div text: body] +end \ No newline at end of file diff --git a/libraries/html/html.ts b/libraries/html/html.ts index 04c647c..c5d9892 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -639,6 +639,7 @@ export class HTML extends Library { [urlId, "port", `${port}`], [urlId, "protocol", `${protocol}`], ); + // Parse Query String let ix = 1; for (var key in query) { let value = query[key]; @@ -652,6 +653,24 @@ export class HTML extends Library { ) ix++; } + // Parse Hash String + if (hash !== undefined && hash !== null) { + let pairs = hash.substring(1).split('&'); + for (var pair of pairs) { + let queryId = createId(); + let parts = pair.split('='); + if (parts.length == 2) { + eavs.push( + [urlId, "query", `${queryId}`], + [queryId, "tag", `html/url/query`], + [queryId, "index", `${ix}`], + [queryId, "key", parts[0]], + [queryId, "value", parts[1]], + ) + } + ix++; + } + } this._sendEvent(eavs); } } From 089d7d036427b59c4200b396dcfec9af54abb80f Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 22 Aug 2017 15:33:40 -0700 Subject: [PATCH 53/99] parse bools in json --- src/watchers/json.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 014cd39..193f104 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -121,7 +121,10 @@ pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, cha Value::String(ref n) => { changes.push(new_change(id,attribute,Internable::String(n.clone()),node)); }, - Value::Bool(ref n) => println!("Bool: {}",n), + Value::Bool(ref n) => { + let b = format!("{:?}", n); + changes.push(new_change(id,attribute,Internable::String(b),node)); + }, Value::Array(ref n) => { for (ix, value) in n.iter().enumerate() { let ix = ix + 1; From 7a64f61386c8ea3991c79df71610a42dafd01413 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 22 Aug 2017 15:34:29 -0700 Subject: [PATCH 54/99] implement a stream watcher --- libraries/html/html.eve | 3 +++ libraries/html/stream.eve | 20 +++++++++++++++ libraries/html/stream.ts | 52 +++++++++++++++++++++++++++++++++++++++ libraries/index.ts | 1 + package.json | 2 ++ ts/main.ts | 1 + 6 files changed, 79 insertions(+) create mode 100644 libraries/html/stream.eve create mode 100644 libraries/html/stream.ts diff --git a/libraries/html/html.eve b/libraries/html/html.eve index 4dc175b..5bc8635 100644 --- a/libraries/html/html.eve +++ b/libraries/html/html.eve @@ -13,6 +13,9 @@ commit "img" "a" "style" + "audio" + "source" + "video" )] end ~~~ diff --git a/libraries/html/stream.eve b/libraries/html/stream.eve new file mode 100644 index 0000000..e0913d7 --- /dev/null +++ b/libraries/html/stream.eve @@ -0,0 +1,20 @@ +# Stream Element + +search + stream = [#html/stream source] +watch client/websocket + ("stream/create", stream, source) +end + +search + stream = [#html/stream] +bind + stream <- [#html/video id: stream] +end + +search + stream = [#html/stream play] +watch client/websocket + ("stream/play", stream, play) +end + diff --git a/libraries/html/stream.ts b/libraries/html/stream.ts new file mode 100644 index 0000000..39e8f2b --- /dev/null +++ b/libraries/html/stream.ts @@ -0,0 +1,52 @@ +import {Library, RawValue, RawEAV, handleTuples, libraries} from "../../ts"; +import Hls from "hls.js" + +const EMPTY:never[] = []; + +export class Stream extends Library { + static id = "stream"; + streams: any = {}; + + html:libraries.HTML; + + setup() { + this.html = this.program.attach("html") as libraries.HTML; + } + + handlers = { + "create": handleTuples(({adds}) => { + for(let [streamID, source] of adds || EMPTY) { + if(Hls.isSupported()) { + let video: any = document.getElementById(`${streamID}`); + var hls = new Hls(); + hls.loadSource(`${source}`); + hls.attachMedia(video); + hls.on(Hls.Events.MANIFEST_PARSED,function() { + console.log("Stream Ready"); + }); + video.onplay = function () { + console.log("PLAYING"); + }; + video.onpause = function () { + console.log("Paused"); + }; + this.program.inputEAVs([[streamID, "tag", "ready"]]); + window.addEventListener("pageshow", video.onplay()); + this.streams[streamID] = video; + } + } + }), + "play": handleTuples(({adds}) => { + for(let [streamID, play] of adds || EMPTY) { + let video = this.streams[streamID]; + if (play === "true") { + video.play(); + } else { + video.pause(); + } + } + }) + } +} + +Library.register(Stream.id, Stream); \ No newline at end of file diff --git a/libraries/index.ts b/libraries/index.ts index ce1307f..4d5c01c 100644 --- a/libraries/index.ts +++ b/libraries/index.ts @@ -1,3 +1,4 @@ export {HTML} from "./html/html"; export {Canvas} from "./canvas/canvas"; export {Console} from "./console/console"; +export {Stream} from "./html/stream"; diff --git a/package.json b/package.json index 7b27c5a..b6eb89b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "author": "Eve Team", "license": "UNLICENSED", "dependencies": { + "hls.js": "^0.7.11", "md5": "^2.2.1", "postinstall-build": "^5.0.1", "setimmediate": "^1.0.5", @@ -25,6 +26,7 @@ "devDependencies": { "@types/md5": "^2.1.32", "@types/uuid": "^3.4.0", + "@types/hls.js": "^0.7.5", "autoprefixer": "^7.1.2", "postcss-color-function": "^4.0.0", "postcss-custom-properties": "^6.1.0", diff --git a/ts/main.ts b/ts/main.ts index c1813a4..adaa323 100644 --- a/ts/main.ts +++ b/ts/main.ts @@ -33,6 +33,7 @@ let program = new RemoteProgram(`ws://${location.hostname}:3012`); program.attach("html"); program.attach("canvas"); program.attach("console"); +program.attach("stream"); // program.inputEAVs([ // [1, "tag", "html/element"], // [1, "tagname", "div"], From 20e92026a79fe135b4f2a8d83ecdb025e23971cf Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 23 Aug 2017 13:22:17 -0700 Subject: [PATCH 55/99] handle multiple chunks --- libraries/http/http.eve | 12 +++++++++ src/watchers/http.rs | 57 ++++++++++++++++++++++++++++++++++++++--- src/watchers/json.rs | 2 +- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 17845ca..44d71b3 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -1,5 +1,7 @@ # HTTP + + ## Send an HTTP Request Watch for HTTP requests. requests with multiple headers will be processed @@ -57,6 +59,16 @@ commit request.error := error end +Reconstruct a body from chunks + +search + [#http/body-chunk request chunk index] + [#http/request/done request] +watch http + ("body", request, chunk, index) +end + + ## Process HTTP Requests search diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 102a3a6..d70e515 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -16,6 +16,7 @@ use self::hyper_tls::HttpsConnector; use self::tokio_core::reactor::Core; use self::hyper::{Method}; use std::thread; +use std::io; use std::io::{Write}; extern crate iron; use self::iron::prelude::*; @@ -24,12 +25,13 @@ use std::collections::HashMap; pub struct HttpWatcher { name: String, + responses: HashMap>, outgoing: Sender, } impl HttpWatcher { pub fn new(outgoing: Sender) -> HttpWatcher { - HttpWatcher { name: "http".to_string(), outgoing } + HttpWatcher { name: "http".to_string(), responses: HashMap::new(), outgoing } } } @@ -41,6 +43,7 @@ impl Watcher for HttpWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { + println!("DIFF"); let mut requests: HashMap = HashMap::new(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); @@ -80,13 +83,46 @@ impl Watcher for HttpWatcher { let body = Internable::to_string(interner.get_value(add[3])); http_server(address, body); }, + "body" => { + let response_id = Internable::to_string(interner.get_value(add[1])); + let chunk = Internable::to_string(interner.get_value(add[2])); + let index = Internable::to_string(interner.get_value(add[3])); + //println!("A CHUNKIE CHUNK: {:?}",chunk); + //println!("A resposneID CHUNK: {:?}",response_id); + println!("A index CHUNK: {:?}",index); + + let v = self.responses.entry(response_id).or_insert(vec![(index.clone(),chunk.clone())]); + v.push((index,chunk)); + + } _ => {}, } } // Send the HTTP request and package response in the changevec for (id, request) in requests.drain() { send_http_request(&id,request,&self.outgoing); - } + println!("Done sending HTTP Request"); + }; + //println!("RESPONSES: {:?}",self.responses.len()); + + + for (response_id, mut chunk_vec) in self.responses.drain() { + chunk_vec.sort(); + println!("{:?}",chunk_vec); + let body: String = chunk_vec.iter().fold("".to_string(), |acc, ref x| { + let &&(ref ix, ref chunk) = x; + println!("{:?} ----------------\n {:?}",ix, chunk); + acc + chunk + }); + let response_id = format!("http/full-body|{:?}",response_id); + self.outgoing.send(RunLoopMessage::Transaction(vec![ + new_change(&response_id, "tag", Internable::from_str("http/fully-body"), "http/request"), + new_change(&response_id, "body", Internable::String(body), "http/request"), + ])).unwrap(); + }; + + + } } @@ -104,6 +140,7 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = vec![]; let status = res.status().as_u16(); @@ -114,13 +151,20 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = Vec::new(); vector.write_all(&chunk).unwrap(); let body_string = String::from_utf8(vector).unwrap(); - outgoing.send(RunLoopMessage::Transaction(vec![new_change(&response_id, "body", Internable::String(body_string), "http/request")])).unwrap(); + outgoing.send(RunLoopMessage::Transaction(vec![ + new_change(&chunk_id, "tag", Internable::from_str("http/body-chunk"), "http/request"), + new_change(&chunk_id, "request", Internable::from_str(id), "http/request"), + new_change(&chunk_id, "chunk", Internable::String(body_string), "http/request"), + new_change(&chunk_id, "index", Internable::String(ix.to_string()), "http/request") + ])).unwrap(); + println!("Chunk #{:?}",ix); + ix = ix + 1; Ok(()) }) - }); match core.run(work) { Ok(_) => (), @@ -134,4 +178,9 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = vec![]; + changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/done"), "http/request")); + changes.push(new_change(&error_id, "request", Internable::from_str(id), "http/request")); + outgoing.send(RunLoopMessage::Transaction(changes)).unwrap(); } \ No newline at end of file diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 193f104..0627d74 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -101,7 +101,7 @@ fn dereference(target: &Map, flatmap: &Map) -> Map RawChange { - RawChange {e: Internable::String(e.to_string()), a: Internable::String(a.to_string()), v: v.clone(), n: Internable::String(n.to_string()), count: 1} + RawChange {e: Internable::from_str(e), a: Internable::from_str(a), v: v.clone(), n: Internable::from_str(n), count: 1} } pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, changes: &mut Vec) { From 48b80ee2a6515bf2b01c4e172a04f850b7fe8e51 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 23 Aug 2017 13:22:27 -0700 Subject: [PATCH 56/99] more edits to setlist app --- examples/setlist-aggregator.eve | 189 +++++++++++++++++++++----------- 1 file changed, 128 insertions(+), 61 deletions(-) diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve index 983a863..87f8851 100644 --- a/examples/setlist-aggregator.eve +++ b/examples/setlist-aggregator.eve @@ -1,5 +1,7 @@ # Setlist Aggregator + + This app takes a set of concerts, and accesses the setlist.fm api to retrieve the setlist that was played at that concert. This is cross-referenced with the MS Groove API to create playlists for those concerts. @@ -10,10 +12,14 @@ commit [#setlist-app groove-client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" groove-client-secret: "UskJMTfCRNffOLbuBnGSBb2" - setlist-api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275"] + groove-endpoint: "https://music.xboxlive.com" + setlist-api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" + setlist-endpoint: "https://api.setlist.fm/rest"] end -## Logging in to setlist + + +## Logging-in to MS Groove Logging in is kicked off by clicking the login button. @@ -37,93 +43,127 @@ commit [#html/redirect #req url: address] end -A successful login will return to the app a code. This is exchanged for an -access token with a POST request. +A successful login will return to the app an access token search - [#html/event/page-show url: [#html/url query: [#html/url/query key: "code" value: code]]] - [#setlist-app groove-client-id groove-client-secret] - redirect-uri = string/url-encode[text: "http://localhost:8081/#setlist-callback"] - auth-token = string/encode[text: "{{groove-client-id}}:{{groove-client-secret}}"] - body = "grant_type=authorization_code&code={{code}}&redirect_uri={{redirect-uri}}" - content-length = string/length[text: body] + [#html/event/page-show url: [#html/url query: [#html/url/query key: "access_token" value: access-token]]] + app = [#setlist-app groove-client-id groove-client-secret] + //redirect-uri = string/url-encode[text: "http://localhost:8081/#setlist-callback"] + //auth-token = string/encode[text: "{{groove-client-id}}:{{groove-client-secret}}"] + //body = "grant_type=authorization_code&code={{code}}&redirect_uri={{redirect-uri}}" + //content-length = string/length[text: body] commit - [#http/request #setlist-token address: "https://accounts.setlist.com/api/token" method: "POST" body headers: - [#http/header key: "Authorization" value: "Basic {{auth-token}}"] - [#http/header key: "Content-Type" value: "application/x-www-form-urlencoded"] - [#http/header key: "Content-Length" value: "{{content-length}}"]] + app.groove-access-token := access-token + //[#http/request #setlist-token address: "https://accounts.setlist.com/api/token" method: "POST" body headers: + //[#http/header key: "Authorization" value: "Basic {{auth-token}}"] + //[#http/header key: "Content-Type" value: "application/x-www-form-urlencoded"] + //[#http/header key: "Content-Length" value: "{{content-length}}"]] end -Parse the response body, which contains the access token + + +## Getting Groove User Data + +With an access token in hand, we can use that to get user-specific information. + +search + [#setlist-app groove-access-token] +commit + [#http/request #groove-profile address: "https://music.xboxlive.com/1/user/music/profile" method: "GET" headers: + [#http/header key: "Authorization" value: "Bearer {{groove-access-token}}"]] +end search - [#setlist-token response: [body]] + [#groove-profile response] + setlist = [#setlist-app] commit - [#json/decode #setlist-auth-token json: body] + setlist.user := [#setlist/user] + //[#json/decode #groove-profile json: response.body] end -Place the access token in the app record, so we can use it to fetch data from -the user's account. +Create a user record on the app search - [#setlist-auth-token json-object: auth] + [#groove-profile json-object: profile] setlist = [#setlist-app] commit - setlist.access-token := auth.access_token - setlist.refresh-token := auth.refresh_token + setlist.user := [#setlist/user] end -## Getting User Data -With an access token in hand, we can use that to get user-specific information -like username and email address. + +## The User Interface search - [#setlist-app access-token] + [#setlist-app user: [name e-mail]] +bind + [#html/div text: "Welcome {{name}}!"] +end + +commit + [#html/div children: + [#ui/input #artist-name] + [#ui/button #search-artist text: "Search"]] +end + commit - [#http/request #profile-content address: "https://api.setlist.com/v1/me" method: "GET" headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] + [#ui/button #get-song text: "Get a song"] end search - [#profile-content response: [body]] + [#html/event/click element: [#get-song]] + [#setlist-app groove-access-token groove-endpoint] + track-id = "music.8D6KGWZQ8TPR" + address = "{{groove-endpoint}}/1/content/{{track-id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" commit - [#json/decode #setlist-profile json: body] + [#http/request #get-song-req address headers: + [#http/header key: "Authorization" value: "Bearer {{groove-access-token}}"]] end -Save this info in the setlist app +search + [#get-song-req response: [body]] +commit + [#json/decode #get-song-req json: body] +end search - [#setlist-profile json-object: profile] - setlist = [#setlist-app] + [#get-song-req json-object: song] commit - setlist.user := [#setlist/user name: profile.id e-mail: profile.email] + [#html/div children: + [#html/stream #song style: [display: "none"] source: song.Url type: song.ContentType]] end -## The User Interface search - [#setlist-app user: [name e-mail]] -bind - [#html/div text: "Welcome {{name}}!"] + [#song #ready] +commit + [#html/button #play-song text: "Play"] end +search + [#html/event/click element] + element = [#play-song] + song = [#song] + (play, text) = if song = [play: "true"] then ("false","Play") + if song = [play: "false"] then ("true","Pause") + else ("true","Pause") commit - [#ui/input #artist-name] - [#ui/button #search-artist text: "Search"] + song.play := play + element.text := text end -## Get Data From setlist +## Get Data From Setlist.fm +disabled search - [#setlist-app access-token] - address = "https://api.setlist.com/v1/search?q=incubus+glitterbomb+8&type=track" + //[#setlist-app access-token setlist-endpoint] + //address = "{{setlist-endpoint}}/v1/search?q=incubus+glitterbomb+8&type=track" commit - [#http/request #search-setlist address headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"] - [#http/header key: "Accept" value: "application/json"]] + //[#http/request #search-setlist address headers: + //[#http/header key: "Authorization" value: "Bearer {{access-token}}"] + //[#http/header key: "Accept" value: "application/json"]] end search @@ -141,50 +181,77 @@ end -## Get Data From Setlist.fm +## Search Groove search - [#setlist-app setlist-api-key] + [#setlist-app groove-access-token groove-endpoint] [#html/event/click element: [#search-artist]] [#artist-name value] artist = string/replace[text: value replace: " " with: "+"] - //address = "https://api.setlist.fm/rest/1.0/search/artists?artistName={{artist}}" - address = "https://api.setlist.fm/rest/1.0/search/setlists?artistName=Incubus&date=16-08-2017&p=1" + //address = "{{setlist-endpoint}}/1.0/search/artists?artistName={{artist}}" + //address = "{{setlist-endpoint}}/1.0/search/setlists?artistName={{artist}}&date=16-08-2017&p=1" + address = "{{groove-endpoint}}/1/content/music/search?q={{artist}}" commit - [#http/request #setlist-fm artist: value, address headers: - [#http/header key: "x-api-key" value: setlist-api-key] - [#http/header key: "Accept" value: "application/json"]] + [#http/request #groove-search address headers: + [#http/header key: "Authorization" value: "Bearer {{groove-access-token}}"]] + //[#http/request #setlist-fm artist: value, address headers: + //[#http/header key: "x-api-key" value: setlist-api-key] + //[#http/header key: "Accept" value: "application/json"]] end search - [#setlist-fm response: [body]] + [#groove-search #foo response: [body]] commit - [#json/decode #setlist-fm json: body] + [#json/decode #groove-search json: body] end search - [#setlist-fm json-object: setlistsearch] + [#groove-search json-object: setlistsearch] //setlistsearch.artist.value.name = artist //artist = setlistsearch.artist.value commit - [#html/div text: "{{setlistsearch.setlist.value.sets.set.value.song.index}} - {{setlistsearch.setlist.value.sets.set.value.song.value.name}}"] + [#html/div text: "{{setlistsearch.tracks}}"] end -e3e0abcd-7671-4482-a9d8-462f5acc9be5 ## Misc Diagnostics -disabled search + [#disable] [#http/request/error error] commit [#html/div text: error] end -disabled search + [#disable] [#http/response body] commit - [#html/div text: body] + [#html/div text: "THIS IS PART OF HTE BODY {{body}}"] +end + +search + [#disable] + q = [#http/body-chunk chunk request index] +commit + [#html/div request children: + [#html/div request text: index] + [#html/div request text: chunk]] +end + +search + [#disable] + [#http/request/done request] +commit + [#html/div request text: "DONE!!@#!@#!@"] +end + +search + [#disable] + [#http/fully-body body] +commit + [#html/div children: + [#html/div text: "FULL TEXT"] + [#html/div text: body]] end \ No newline at end of file From e0639d30cfd6d662d4b33a213d35a1254034f40f Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 23 Aug 2017 13:25:08 -0700 Subject: [PATCH 57/99] update json from http watcher --- .gitignore | 1 + src/watchers/json.rs | 63 ++++++++++++++++++++------------------------ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index aac2347..9d89521 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ data/* node_modules/ build/ dist/ +.vscode/tasks.json diff --git a/src/watchers/json.rs b/src/watchers/json.rs index e022e61..0627d74 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -2,9 +2,6 @@ use super::super::indexes::{WatchDiff}; use super::super::ops::{Internable, Interner, RawChange, RunLoopMessage}; use std::sync::mpsc::{Sender}; use super::Watcher; -//use serde::de::{self, Deserialize, Deserializer, DeserializeSeed, Visitor, SeqAccess, -// MapAccess, EnumAccess, VariantAccess, IntoDeserializer}; -//use serde::ser::{Serialize, Serializer}; extern crate serde_json; extern crate serde; @@ -30,12 +27,13 @@ impl Watcher for JsonWatcher { } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { let mut record_map = Map::new(); + let mut changes: Vec = vec![]; let mut id = "".to_string(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); let j_arg = Internable::to_string(interner.get_value(add[2])); - let mut changes: Vec = vec![]; + match (&kind[..], j_arg) { ("encode/target", _) => { id = Internable::to_string(interner.get_value(add[2])); @@ -49,7 +47,7 @@ impl Watcher for JsonWatcher { _ => Value::Null, }; if record_map.contains_key(&e) { - let mut record = record_map.get_mut(&e).unwrap(); + let record = record_map.get_mut(&e).unwrap(); let sub_record = record.as_object_mut().unwrap(); sub_record.insert(a, v); } else { @@ -60,30 +58,25 @@ impl Watcher for JsonWatcher { }, ("decode", value) => { let v: Value = serde_json::from_str(&value).unwrap(); - value_to_changes(&record_id.to_string(), "json-object", v, &mut changes); + value_to_changes(&record_id.to_string(), "json-object", v, "json/decode", &mut changes); }, _ => {}, - } - match self.outgoing.send(RunLoopMessage::Transaction(changes)) { - Err(_) => break, - _ => (), } } if let Some(target_record) = record_map.get(&id) { let inner_map = target_record.as_object().unwrap(); - let qqq = dereference(inner_map, &record_map); - let json = serde_json::to_string(&qqq).unwrap(); - let mut new_changes = Vec::new(); + let dereferenced_target = dereference(inner_map, &record_map); + let json = serde_json::to_string(&dereferenced_target).unwrap(); let change_id = format!("json/encode/change|{:?}",id); - new_changes.push(new_change(&change_id, "tag", Internable::String("json/encode/change".to_string()), "json/encode")); - new_changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); - new_changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); - match self.outgoing.send(RunLoopMessage::Transaction(new_changes)) { - Err(_) => (), - _ => (), - } + changes.push(new_change(&change_id, "tag", Internable::from_str("json/encode/change"), "json/encode")); + changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); + changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); } + match self.outgoing.send(RunLoopMessage::Transaction(changes)) { + Err(_) => (), + _ => (), + } } } @@ -107,19 +100,18 @@ fn dereference(target: &Map, flatmap: &Map) -> Map RawChange { - RawChange {e: Internable::String(e.to_string()), a: Internable::String(a.to_string()), v: v.clone(), n: Internable::String(n.to_string()), count: 1} +pub fn new_change(e: &str, a: &str, v: Internable, n: &str) -> RawChange { + RawChange {e: Internable::from_str(e), a: Internable::from_str(a), v: v.clone(), n: Internable::from_str(n), count: 1} } -fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec) { - let node = "json/decode"; +pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, changes: &mut Vec) { match value { Value::Number(n) => { if n.is_u64() { - let v = Internable::Number(n.as_u64().unwrap() as u32); + let v = Internable::from_number(n.as_u64().unwrap() as f32); changes.push(new_change(id,attribute,v,node)); } else if n.is_i64() { - let v = Internable::Number(n.as_i64().unwrap() as u32); + let v = Internable::from_number(n.as_i64().unwrap() as f32); changes.push(new_change(id,attribute,v,node)); } else if n.is_f64() { let v = Internable::from_number(n.as_f64().unwrap() as f32); @@ -129,24 +121,27 @@ fn value_to_changes(id: &str, attribute: &str, value: Value, changes: &mut Vec { changes.push(new_change(id,attribute,Internable::String(n.clone()),node)); }, - Value::Bool(ref n) => println!("Bool: {}",n), + Value::Bool(ref n) => { + let b = format!("{:?}", n); + changes.push(new_change(id,attribute,Internable::String(b),node)); + }, Value::Array(ref n) => { for (ix, value) in n.iter().enumerate() { let ix = ix + 1; let array_id = format!("array|{:?}|{:?}",ix,value); let array_id = &array_id[..]; - changes.push(new_change(id,attribute,Internable::String(array_id.to_string()),node)); - changes.push(new_change(array_id,"tag",Internable::String("array".to_string()),node)); + changes.push(new_change(id,attribute,Internable::from_str(array_id),node)); + changes.push(new_change(array_id,"tag",Internable::from_str("array"),node)); changes.push(new_change(array_id,"index",Internable::String(ix.to_string()),node)); - value_to_changes(array_id, "value", value.clone(), changes); + value_to_changes(array_id, "value", value.clone(), node, changes); } }, Value::Object(ref n) => { - let idq = format!("{:?}",n); - changes.push(new_change(id,attribute,Internable::String(idq.clone()),node)); - changes.push(new_change(id,"tag",Internable::String("json-object".to_string()),node)); + let object_id = format!("{:?}",n); + changes.push(new_change(id,attribute,Internable::String(object_id.clone()),node)); + changes.push(new_change(id,"tag",Internable::from_str("json-object"),node)); for key in n.keys() { - value_to_changes(&mut idq.clone(), key, n[key].clone(), changes); + value_to_changes(&mut object_id.clone(), key, n[key].clone(), node, changes); } }, _ => {}, From dc532a5a928bd129f3292af02a199a66e1e5e57e Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 23 Aug 2017 13:28:27 -0700 Subject: [PATCH 58/99] don't include json file --- person.json | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 person.json diff --git a/person.json b/person.json deleted file mode 100644 index e33e13e..0000000 --- a/person.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Solver": "clingo version 4.5.4", - "Input": [ - "puzzle_easy.lp","sudoku.lp" - ], - "Call": [ - { - "Witnesses": [ - { - "Value": [ - "paint(1,1,7)", "paint(1,3,3)", "paint(1,5,8)", "paint(1,8,1)", "paint(2,5,6)", "paint(3,1,6)", "paint(3,3,8)", "paint(3,5,5)", "paint(3,6,7)", "paint(3,7,4)", "paint(3,9,9)", "paint(4,4,8)", "paint(4,5,9)", "paint(4,7,1)", "paint(4,8,2)", "paint(4,9,5)", "paint(5,3,2)", "paint(5,7,6)", "paint(6,1,8)", "paint(6,2,1)", "paint(6,3,6)", "paint(6,5,2)", "paint(6,6,5)", "paint(7,1,3)", "paint(7,3,5)", "paint(7,4,2)", "paint(7,5,4)", "paint(7,7,9)", "paint(7,9,1)", "paint(8,5,7)", "paint(9,2,7)", "paint(9,5,1)", "paint(9,7,5)", "paint(9,9,2)", "paint(1,2,5)", "paint(1,4,9)", "paint(1,6,4)", "paint(1,7,2)", "paint(1,9,6)", "paint(2,1,1)", "paint(2,2,4)", "paint(2,3,9)", "paint(2,4,3)", "paint(2,6,2)", "paint(2,7,8)", "paint(2,8,5)", "paint(2,9,7)", "paint(3,2,2)", "paint(3,4,1)", "paint(3,8,3)", "paint(4,2,3)", "paint(4,1,4)", "paint(4,6,6)", "paint(4,3,7)", "paint(5,4,7)", "paint(5,5,3)", "paint(5,8,4)", "paint(5,6,1)", "paint(6,4,4)", "paint(6,9,3)", "paint(6,7,7)", "paint(7,2,6)", "paint(7,8,7)", "paint(8,1,2)", "paint(5,1,5)", "paint(5,9,8)", "paint(5,2,9)", "paint(6,8,9)", "paint(7,6,8)", "paint(8,3,1)", "paint(8,7,3)", "paint(8,8,6)", "paint(8,9,4)", "paint(8,4,5)", "paint(8,2,8)", "paint(8,6,9)", "paint(9,4,6)", "paint(9,6,3)", "paint(9,3,4)", "paint(9,8,8)", "paint(9,1,9)" - ] - } - ] - } - ], - "Result": "SATISFIABLE", - "Models": { - "Number": 1, - "More": "no" - }, - "Calls": 1, - "Time": { - "Total": 0.084, - "Solve": 0.000, - "Model": 0.000, - "Unsat": 0.000, - "CPU": 0.080 - } -} \ No newline at end of file From aca663ebdf41370e01d6e2a1d03a140d54c69611 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 23 Aug 2017 20:12:12 -0700 Subject: [PATCH 59/99] fix merge conflicts --- src/bin/server.rs | 6 ------ src/ops.rs | 4 ---- src/watchers/mod.rs | 3 --- 3 files changed, 13 deletions(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index c3f34fd..93534fe 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -75,17 +75,11 @@ impl ClientHandler { router.lock().expect("ERROR: Failed to lock router: Cannot register new client.").register(&client_name, outgoing.clone()); if !clean { runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); -<<<<<<< HEAD - runner.program.attach(Box::new(CompilerWatcher::new(outgoing.clone()))); - runner.program.attach(Box::new(RawTextCompilerWatcher::new(outgoing.clone()))); - runner.program.attach(Box::new(WebsocketClientWatcher::new(out.clone()))); runner.program.attach(Box::new(JsonWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(HttpWatcher::new(outgoing.clone()))); -======= runner.program.attach(Box::new(CompilerWatcher::new(outgoing.clone(), false))); runner.program.attach(Box::new(RawTextCompilerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(WebsocketClientWatcher::new(out.clone(), client_name))); ->>>>>>> master runner.program.attach(Box::new(ConsoleWatcher::new())); runner.program.attach(Box::new(PanicWatcher::new())); runner.program.attach(Box::new(RemoteWatcher::new(client_name, &router.lock().expect("ERROR: Failed to lock router: Cannot init RemoteWatcher.").deref()))); diff --git a/src/ops.rs b/src/ops.rs index 915cbb0..04b38a9 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -6,13 +6,9 @@ extern crate time; extern crate serde_json; extern crate bincode; extern crate term_painter; -<<<<<<< HEAD extern crate data_encoding; extern crate urlencoding; - -======= extern crate natord; ->>>>>>> master use unicode_segmentation::UnicodeSegmentation; diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 90f48ba..0d0cca0 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -11,12 +11,9 @@ pub mod file; pub mod console; pub mod system; pub mod compiler; -<<<<<<< HEAD pub mod json; pub mod http; -======= pub mod textcompiler; pub mod editor; pub mod remote; pub mod websocket; ->>>>>>> master From b8871436997314d3b500dbfadea66819ac90336b Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 24 Aug 2017 16:42:05 -0700 Subject: [PATCH 60/99] properly sort chunks --- libraries/html/html.ts | 90 ++++++++++++++++++++---------------------- src/watchers/http.rs | 60 ++++++++++++---------------- 2 files changed, 68 insertions(+), 82 deletions(-) diff --git a/libraries/html/html.ts b/libraries/html/html.ts index 5657dbe..3418942 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -120,7 +120,8 @@ export class HTML extends Library { // Frame events window.addEventListener("hashchange", this._hashChangeHandler("url-change")); - window.addEventListener("pageshow", this._pageShowHandler("page-show")); + //window.addEventListener("pageshow", this._pageShowHandler("page-show")); + this.getURL(window.location); } protected decorate(elem:Element, elemId:RawValue): Instance { @@ -616,6 +617,7 @@ export class HTML extends Library { } _hashChangeHandler(tagname:string) { + console.log("Hash Change"); return (event: HashChangeEvent) => { if (event.newURL !== null) { let eavs:RawEAV[] = []; @@ -652,61 +654,53 @@ export class HTML extends Library { } } - _pageShowHandler(tagname:string) { - return (event: any) => { - if (event.srcElement !== null) { - let {hash, host, hostname, href, path, pathname, port, protocol, query} = url.parse(event.target.URL, true); - let eavs:RawEAV[] = []; - let eventId = createId(); - let urlId = createId(); - eavs.push( - [eventId, "tag", "html/event"], - [eventId, "tag", `html/event/${tagname}`], - [eventId, "url", `${urlId}`], - [urlId, "tag", "html/url"], - [urlId, "host", `${host}`], - [urlId, "hash", `${hash}`], - [urlId, "hostname", `${hostname}`], - [urlId, "href", `${href}`], - [urlId, "pathname", `${pathname}`], - [urlId, "port", `${port}`], - [urlId, "protocol", `${protocol}`], - ); - // Parse Query String - let ix = 1; - for (var key in query) { - let value = query[key]; - let queryId = createId(); + getURL(location: Location) { + let {hash, host, hostname, href, path, pathname, port, protocol, query} = url.parse(location.href, true); + let eavs:RawEAV[] = []; + let urlId = createId(); + eavs.push( + [urlId, "tag", "html/url"], + [urlId, "host", `${host}`], + [urlId, "hash", `${hash}`], + [urlId, "hostname", `${hostname}`], + [urlId, "href", `${href}`], + [urlId, "pathname", `${pathname}`], + [urlId, "port", `${port}`], + [urlId, "protocol", `${protocol}`], + ); + // Parse Query String + let ix = 1; + for (var key in query) { + let value = query[key]; + let queryId = createId(); + eavs.push( + [urlId, "query", `${queryId}`], + [queryId, "tag", `html/url/query`], + [queryId, "index", `${ix}`], + [queryId, "key", key], + [queryId, "value", value], + ) + ix++; + } + // Parse Hash String + if (hash !== undefined && hash !== null) { + let pairs = hash.substring(1).split('&'); + for (var pair of pairs) { + let queryId = createId(); + let parts = pair.split('='); + if (parts.length == 2) { eavs.push( [urlId, "query", `${queryId}`], [queryId, "tag", `html/url/query`], [queryId, "index", `${ix}`], - [queryId, "key", key], - [queryId, "value", value], + [queryId, "key", parts[0]], + [queryId, "value", parts[1]], ) - ix++; } - // Parse Hash String - if (hash !== undefined && hash !== null) { - let pairs = hash.substring(1).split('&'); - for (var pair of pairs) { - let queryId = createId(); - let parts = pair.split('='); - if (parts.length == 2) { - eavs.push( - [urlId, "query", `${queryId}`], - [queryId, "tag", `html/url/query`], - [queryId, "index", `${ix}`], - [queryId, "key", parts[0]], - [queryId, "value", parts[1]], - ) - } - ix++; - } - } - this._sendEvent(eavs); + ix++; } } + this._sendEvent(eavs); } } diff --git a/src/watchers/http.rs b/src/watchers/http.rs index d70e515..1b37f28 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -25,7 +25,7 @@ use std::collections::HashMap; pub struct HttpWatcher { name: String, - responses: HashMap>, + responses: HashMap>, outgoing: Sender, } @@ -43,7 +43,6 @@ impl Watcher for HttpWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - println!("DIFF"); let mut requests: HashMap = HashMap::new(); for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); @@ -86,32 +85,28 @@ impl Watcher for HttpWatcher { "body" => { let response_id = Internable::to_string(interner.get_value(add[1])); let chunk = Internable::to_string(interner.get_value(add[2])); - let index = Internable::to_string(interner.get_value(add[3])); - //println!("A CHUNKIE CHUNK: {:?}",chunk); - //println!("A resposneID CHUNK: {:?}",response_id); - println!("A index CHUNK: {:?}",index); - - let v = self.responses.entry(response_id).or_insert(vec![(index.clone(),chunk.clone())]); - v.push((index,chunk)); - + let index = Internable::to_number(interner.get_value(add[3])) as u32; + if self.responses.contains_key(&response_id) { + match self.responses.get_mut(&response_id) { + Some(v) => v.push((index,chunk)), + _ => (), + } + } else { + self.responses.insert(response_id,vec![(index.clone(),chunk.clone())]); + } } _ => {}, } } - // Send the HTTP request and package response in the changevec + // Send the HTTP request for (id, request) in requests.drain() { send_http_request(&id,request,&self.outgoing); - println!("Done sending HTTP Request"); }; - //println!("RESPONSES: {:?}",self.responses.len()); - for (response_id, mut chunk_vec) in self.responses.drain() { chunk_vec.sort(); - println!("{:?}",chunk_vec); let body: String = chunk_vec.iter().fold("".to_string(), |acc, ref x| { let &&(ref ix, ref chunk) = x; - println!("{:?} ----------------\n {:?}",ix, chunk); acc + chunk }); let response_id = format!("http/full-body|{:?}",response_id); @@ -120,9 +115,6 @@ impl Watcher for HttpWatcher { new_change(&response_id, "body", Internable::String(body), "http/request"), ])).unwrap(); }; - - - } } @@ -135,19 +127,20 @@ fn http_server(address: String, body: String) -> thread::JoinHandle<()> { } fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender) { + let node = "http/request"; let mut core = Core::new().unwrap(); let handle = core.handle(); let client = Client::configure() .connector(HttpsConnector::new(4,&handle).unwrap()) .build(&handle); - let mut ix = 1; + let mut ix: f32 = 1.0; let work = client.request(request).and_then(|res| { let mut response_changes: Vec = vec![]; let status = res.status().as_u16(); let response_id = format!("http/response|{:?}",id); - response_changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), "http/request")); - response_changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), "http/request")); - response_changes.push(new_change(&response_id, "request", Internable::String(id.clone()), "http/request")); + response_changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), node)); + response_changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), node)); + response_changes.push(new_change(&response_id, "request", Internable::String(id.clone()), node)); outgoing.send(RunLoopMessage::Transaction(response_changes)).unwrap(); res.body().for_each(|chunk| { let response_id = format!("http/response|{:?}",id); @@ -156,13 +149,12 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = vec![]; - error_changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), "http/request")); - error_changes.push(new_change(&error_id, "request", Internable::String(id.clone()), "http/request")); - error_changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), "http/request")); + error_changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/error"), node)); + error_changes.push(new_change(&error_id, "request", Internable::String(id.clone()), node)); + error_changes.push(new_change(&error_id, "error", Internable::String(format!("{:?}",e)), node)); outgoing.send(RunLoopMessage::Transaction(error_changes)).unwrap(); }, } let error_id = format!("http/request/error|123456"); let mut changes: Vec = vec![]; - changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/done"), "http/request")); - changes.push(new_change(&error_id, "request", Internable::from_str(id), "http/request")); + changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/done"), node)); + changes.push(new_change(&error_id, "request", Internable::from_str(id), node)); outgoing.send(RunLoopMessage::Transaction(changes)).unwrap(); } \ No newline at end of file From 74ffb12808d850c410ea64b82010f45149ca6b51 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 24 Aug 2017 18:04:03 -0700 Subject: [PATCH 61/99] Lay groundwork for putting HTTP requests into Eve --- src/watchers/http.rs | 51 ++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 1b37f28..416b1f7 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -16,11 +16,10 @@ use self::hyper_tls::HttpsConnector; use self::tokio_core::reactor::Core; use self::hyper::{Method}; use std::thread; -use std::io; use std::io::{Write}; extern crate iron; use self::iron::prelude::*; -use self::iron::status; +use self::iron::{status, url}; use std::collections::HashMap; pub struct HttpWatcher { @@ -68,7 +67,7 @@ impl Watcher for HttpWatcher { _ => Method::Get }; let req = hyper::Request::new(rmethod, url); - requests.insert(id.clone(),req); + requests.insert(id.clone(), req); } let req = requests.get_mut(&id).unwrap(); if key != "" { @@ -79,8 +78,7 @@ impl Watcher for HttpWatcher { } }, "server" => { - let body = Internable::to_string(interner.get_value(add[3])); - http_server(address, body); + http_server(address, &self.outgoing); }, "body" => { let response_id = Internable::to_string(interner.get_value(add[1])); @@ -92,7 +90,7 @@ impl Watcher for HttpWatcher { _ => (), } } else { - self.responses.insert(response_id,vec![(index.clone(),chunk.clone())]); + self.responses.insert(response_id, vec![(index.clone(), chunk.clone())]); } } _ => {}, @@ -100,7 +98,7 @@ impl Watcher for HttpWatcher { } // Send the HTTP request for (id, request) in requests.drain() { - send_http_request(&id,request,&self.outgoing); + send_http_request(&id, request, &self.outgoing); }; for (response_id, mut chunk_vec) in self.responses.drain() { @@ -118,10 +116,35 @@ impl Watcher for HttpWatcher { } } -fn http_server(address: String, body: String) -> thread::JoinHandle<()> { +fn http_server(address: String, outgoing: &Sender) -> thread::JoinHandle<()> { thread::spawn(move || { Iron::new(|req: &mut Request| { - Ok(Response::with((status::Ok, "Hello"))) + println!("STARTED SERVER"); + let node = "http/server"; + let hostname: String = match req.url.host() { + url::Host::Domain(s) => s.to_string(), + url::Host::Ipv4(s) => s.to_string(), + url::Host::Ipv6(s) => s.to_string(), + }; + let request_id = format!("http/request|{:?}",req.url); + let url_id = format!("http/url|{:?}",request_id); + let mut request_changes: Vec = vec![]; + request_changes.push(new_change(&request_id, "tag", Internable::from_str("http/request"), node)); + request_changes.push(new_change(&request_id, "url", Internable::String(url_id.clone()), node)); + request_changes.push(new_change(&url_id, "tag", Internable::from_str("http/url"), node)); + request_changes.push(new_change(&url_id, "hostname", Internable::String(hostname), node)); + request_changes.push(new_change(&url_id, "port", Internable::String(req.url.port().to_string()), node)); + request_changes.push(new_change(&url_id, "protocol", Internable::from_str(req.url.scheme()), node)); + match req.url.fragment() { + Some(s) => request_changes.push(new_change(&url_id, "hash", Internable::from_str(s), node)), + _ => (), + }; + match req.url.query() { + Some(s) => request_changes.push(new_change(&url_id, "query", Internable::from_str(s), node)), + _ => (), + }; + //outgoing.send(RunLoopMessage::Transaction(request_changes)); + Ok(Response::with((status::Ok, ""))) }).http(address).unwrap(); }) } @@ -170,9 +193,9 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = vec![]; - changes.push(new_change(&error_id, "tag", Internable::from_str("http/request/done"), node)); - changes.push(new_change(&error_id, "request", Internable::from_str(id), node)); - outgoing.send(RunLoopMessage::Transaction(changes)).unwrap(); + let finished_id = format!("http/request/finished|{:?}",id); + outgoing.send(RunLoopMessage::Transaction(vec![ + new_change(&finished_id, "tag", Internable::from_str("http/request/finished"), node), + new_change(&finished_id, "request", Internable::from_str(id), node), + ])).unwrap(); } \ No newline at end of file From 416fb33408c0144cf639dc93fbdca8afd3362ffc Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 24 Aug 2017 19:44:33 -0700 Subject: [PATCH 62/99] put body in response, mark requests as finished --- libraries/http/http.eve | 37 ++++++++++++++++++++++++++++++------- src/watchers/http.rs | 11 ++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 44d71b3..8c18375 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -59,22 +59,45 @@ commit request.error := error end -Reconstruct a body from chunks +Tag a finished request as such search - [#http/body-chunk request chunk index] - [#http/request/done request] + [#http/request/finished request] +commit + request += #finished +end + +Reconstruct a body from chunks, only after the request is`#finished` + +search + [#http/body-chunk response chunk index] + response = [#http/response request] + request = [#http/request #finished] watch http - ("body", request, chunk, index) + ("body", response, chunk, index) end +When the full body is reconstructed, attach it to the response + +search + q = [#http/full-body body response] +commit + response.body := body +end + +Clean up body chunks once the body is reconstructed + +search + chunk = [#http/body-chunk response] + response = [#http/response body] +commit + chunk := none +end ## Process HTTP Requests search server = [#http/server address] - body = if server.body then server.body - else "" watch http - ("server", server, address, body) + ("server", server, address) end \ No newline at end of file diff --git a/src/watchers/http.rs b/src/watchers/http.rs index 416b1f7..c209eae 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -100,17 +100,18 @@ impl Watcher for HttpWatcher { for (id, request) in requests.drain() { send_http_request(&id, request, &self.outgoing); }; - + // Reconstruct the body from chunks for (response_id, mut chunk_vec) in self.responses.drain() { chunk_vec.sort(); let body: String = chunk_vec.iter().fold("".to_string(), |acc, ref x| { let &&(ref ix, ref chunk) = x; acc + chunk }); - let response_id = format!("http/full-body|{:?}",response_id); + let full_body_id = format!("http/full-body|{:?}",response_id); self.outgoing.send(RunLoopMessage::Transaction(vec![ - new_change(&response_id, "tag", Internable::from_str("http/fully-body"), "http/request"), - new_change(&response_id, "body", Internable::String(body), "http/request"), + new_change(&full_body_id, "tag", Internable::from_str("http/full-body"), "http/request"), + new_change(&full_body_id, "body", Internable::String(body), "http/request"), + new_change(&full_body_id, "response", Internable::String(response_id), "http/request"), ])).unwrap(); }; } @@ -173,7 +174,7 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender Date: Fri, 25 Aug 2017 17:53:50 -0700 Subject: [PATCH 63/99] save progress on setlist app --- examples/setlist-aggregator.eve | 256 ++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 110 deletions(-) diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve index 87f8851..37cf9a6 100644 --- a/examples/setlist-aggregator.eve +++ b/examples/setlist-aggregator.eve @@ -1,7 +1,6 @@ # Setlist Aggregator - This app takes a set of concerts, and accesses the setlist.fm api to retrieve the setlist that was played at that concert. This is cross-referenced with the MS Groove API to create playlists for those concerts. @@ -9,147 +8,209 @@ the MS Groove API to create playlists for those concerts. The app configuration commit - [#setlist-app - groove-client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" - groove-client-secret: "UskJMTfCRNffOLbuBnGSBb2" - groove-endpoint: "https://music.xboxlive.com" - setlist-api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" - setlist-endpoint: "https://api.setlist.fm/rest"] + [#setlist-app groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] + setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"]] end +# Groove API -## Logging-in to MS Groove +The groove API is configured with a record tagged `#groove` with the following shape: -Logging in is kicked off by clicking the login button. +`[#groove client-id client-secret endpoint redirect-uri]` -search - not([#setlist-app user]) -bind - [#ui/button #groove-login text: "Login to Groove"] -end -Clicking this button redirects to a setlist login portal, where the user is -prompted to give the app access to his or her account. +## Logging Into Groove + +Commit a `#groove/login` to initiate the login process. This redirects to a +login portal, which asks the user to authorize access to Eve. Once the user +grants access, the browser is redirect back to the supplied `redirect-uri`. search - [#html/event/click element: [#groove-login]] - [#setlist-app groove-client-id] + [#groove/login] + [#groove client-id client-secret redirect-uri] response-type = "token" scopes = string/url-encode[text: "MicrosoftMediaServices.GrooveApiAccess offline_access"] - redirect-uri = string/url-encode[text: "http://localhost:8081"] - address = "https://login.live.com/oauth20_authorize.srf/?client_id={{groove-client-id}}&response_type={{response-type}}&redirect_uri={{redirect-uri}}&scope={{scopes}}" + encoded-redirect-uri = string/url-encode[text: redirect-uri] + address = "https://login.live.com/oauth20_authorize.srf/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{encoded-redirect-uri}}&scope={{scopes}}" commit - [#html/redirect #req url: address] + [#html/redirect url: address] end A successful login will return to the app an access token search - [#html/event/page-show url: [#html/url query: [#html/url/query key: "access_token" value: access-token]]] - app = [#setlist-app groove-client-id groove-client-secret] - //redirect-uri = string/url-encode[text: "http://localhost:8081/#setlist-callback"] - //auth-token = string/encode[text: "{{groove-client-id}}:{{groove-client-secret}}"] - //body = "grant_type=authorization_code&code={{code}}&redirect_uri={{redirect-uri}}" - //content-length = string/length[text: body] + [#html/url query: [#html/url/query key: "access_token" value: access-token]] + groove = [#groove] commit - app.groove-access-token := access-token - //[#http/request #setlist-token address: "https://accounts.setlist.com/api/token" method: "POST" body headers: - //[#http/header key: "Authorization" value: "Basic {{auth-token}}"] - //[#http/header key: "Content-Type" value: "application/x-www-form-urlencoded"] - //[#http/header key: "Content-Length" value: "{{content-length}}"]] + groove.access-token := access-token end - ## Getting Groove User Data With an access token in hand, we can use that to get user-specific information. search - [#setlist-app groove-access-token] + [#groove/get-user] + [#groove access-token endpoint] commit - [#http/request #groove-profile address: "https://music.xboxlive.com/1/user/music/profile" method: "GET" headers: - [#http/header key: "Authorization" value: "Bearer {{groove-access-token}}"]] + [#http/request #groove-profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] end search [#groove-profile response] - setlist = [#setlist-app] commit - setlist.user := [#setlist/user] - //[#json/decode #groove-profile json: response.body] + [#json/decode #groove-profile json: response.body] end -Create a user record on the app +Create a Groove user from the response search [#groove-profile json-object: profile] - setlist = [#setlist-app] commit - setlist.user := [#setlist/user] + [#groove/user region: profile.Culture subscription: profile.HasSubscription] end +## Search Groove -## The User Interface +search + [#groove/search-track query] + [#groove access-token endpoint] + artist = string/replace[text: query replace: " " with: "+"] + address = "{{endpoint}}/1/content/music/search?q={{query}}&filters=tracks" +commit + [#http/request #groove-search address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end search - [#setlist-app user: [name e-mail]] -bind - [#html/div text: "Welcome {{name}}!"] + [#groove-search response: [body]] +commit + [#json/decode #groove-search json: body] end +search + [#groove-search json-object] + track = [Id Name ReleaseDate Duration] commit - [#html/div children: - [#ui/input #artist-name] - [#ui/button #search-artist text: "Search"]] + [#track name: Name groove-id: Id duration: Duration image: track.ImageUrl] end + +## Get a full song strem + +search + [#groove/full-stream track-id] + [#groove access-token endpoint] + address = "{{endpoint}}/1/content/{{track-id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" +commit + [#http/request #groove-get-song track-id address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-get-song track-id response: [body]] commit - [#ui/button #get-song text: "Get a song"] + [#json/decode #groove-get-song track-id json: body] end search - [#html/event/click element: [#get-song]] - [#setlist-app groove-access-token groove-endpoint] - track-id = "music.8D6KGWZQ8TPR" - address = "{{groove-endpoint}}/1/content/{{track-id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" + [#groove-get-song track-id json-object] + groove-stream = [#groove/full-stream track-id] commit - [#http/request #get-song-req address headers: - [#http/header key: "Authorization" value: "Bearer {{groove-access-token}}"]] + groove-stream <- [stream: json-object.Url content-type: json-object.ContentType] end + + +# The User Interface + +Logging in is kicked off by clicking the login button. + search - [#get-song-req response: [body]] + not([#groove/user]) +bind + [#ui/button #groove-login text: "Login to Groove"] +end + +search + [#html/event/click element: [#groove-login]] commit - [#json/decode #get-song-req json: body] + [#groove/login] end +When we've gotten an access token, we get get the user profile. + search - [#get-song-req json-object: song] + [#groove access-token] +commit + [#groove/get-user] +end + +## Searching for tracks + commit [#html/div children: - [#html/stream #song style: [display: "none"] source: song.Url type: song.ContentType]] + [#ui/input #track-search] + [#ui/button #track-search/button text: "Search"]] +end + +search + [#track-search value] + [#html/event/click element: [#track-search/button]] +commit + [#groove/search-track query: value] end +search + track = [#track name image duration] +bind + [#html/div track children: + [#html/img track src: image style: [width: "100px" height: "100px"]] + [#html/div track text: "{{name}} - {{duration}}"] + [#html/div #stream-control track] + [#html/button #groove/stream-player/playback-control track text: "Play"]] +end search - [#song #ready] + [#html/event/click element: [#play-track track]] + track = [#track groove-id] + app = [#setlist-app] commit - [#html/button #play-song text: "Play"] + app.playing := track + [#groove/full-stream track-id: groove-id] end search - [#html/event/click element] - element = [#play-song] - song = [#song] - (play, text) = if song = [play: "true"] then ("false","Play") - if song = [play: "false"] then ("true","Pause") + [#groove/full-stream track-id stream content-type] + track = [#track groove-id: track-id] +commit + track.stream := stream + track.content-type := content-type +end + +search + stream-div = [#stream-control track] + [#setlist-app playing] + track = playing +commit + [#html/div text: "Hello World"] + //stream-div.children += [#html/stream #groove/stream-player track play: "true" controls: "true" source: track.stream type: track.content-type] +end + +search + [#html/event/click element: control] + control = [#groove/stream-player/playback-control track] + player = [#groove/stream-player track] + (play, text) = if player = [play: "true"] then ("false","Play") + if player = [play: "false"] then ("true","Pause") else ("true","Pause") commit - song.play := play - element.text := text + player.play := play + control.text := text end @@ -179,46 +240,24 @@ commit [#html/div text: track-id] end +## seasrch setlist.fm - -## Search Groove - -search - [#setlist-app groove-access-token groove-endpoint] - [#html/event/click element: [#search-artist]] - [#artist-name value] - artist = string/replace[text: value replace: " " with: "+"] +//search + [#disable] //address = "{{setlist-endpoint}}/1.0/search/artists?artistName={{artist}}" //address = "{{setlist-endpoint}}/1.0/search/setlists?artistName={{artist}}&date=16-08-2017&p=1" - address = "{{groove-endpoint}}/1/content/music/search?q={{artist}}" -commit - [#http/request #groove-search address headers: - [#http/header key: "Authorization" value: "Bearer {{groove-access-token}}"]] +//commit //[#http/request #setlist-fm artist: value, address headers: //[#http/header key: "x-api-key" value: setlist-api-key] //[#http/header key: "Accept" value: "application/json"]] -end +//end -search - [#groove-search #foo response: [body]] -commit - [#json/decode #groove-search json: body] -end - -search - [#groove-search json-object: setlistsearch] - //setlistsearch.artist.value.name = artist - //artist = setlistsearch.artist.value -commit - [#html/div text: "{{setlistsearch.tracks}}"] -end ## Misc Diagnostics search - [#disable] [#http/request/error error] commit [#html/div text: error] @@ -226,32 +265,29 @@ end search [#disable] - [#http/response body] + [#http/request/finished request] commit - [#html/div text: "THIS IS PART OF HTE BODY {{body}}"] + [#html/div request text: "***Finished*** {{request}}"] end search [#disable] - q = [#http/body-chunk chunk request index] + q = [#http/response body] commit - [#html/div request children: - [#html/div request text: index] - [#html/div request text: chunk]] + [#html/div text: "THIS IS THE BODY: {{body}}"] + //[#json/decode json: body] end search [#disable] - [#http/request/done request] + [#json/decode json-object] commit - [#html/div request text: "DONE!!@#!@#!@"] + [#html/div text: "{{json-object.Tracks.Items.value.Name}} {{json-object.Tracks.Items.value.Album.Name}} - {{json-object.Tracks.Items.value.Id}}"] end search - [#disable] - [#http/fully-body body] -commit - [#html/div children: - [#html/div text: "FULL TEXT"] - [#html/div text: body]] + request = [#http/request] + not(request = [#finished]) +bind + [#html/div text: "Processing request..."] end \ No newline at end of file From d75235eb27030d92ecb87fb883f365e0a2c85700 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 29 Aug 2017 16:39:42 -0700 Subject: [PATCH 64/99] updates to setlist app --- examples/setlist-aggregator.eve | 484 +++++++++++++++++++++++--------- libraries/groove/groove.css | 7 + libraries/groove/groove.eve | 210 ++++++++++++++ libraries/html/html.eve | 3 + libraries/html/stream.eve | 22 ++ libraries/html/stream.ts | 27 +- 6 files changed, 616 insertions(+), 137 deletions(-) create mode 100644 libraries/groove/groove.css create mode 100644 libraries/groove/groove.eve diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve index 37cf9a6..a3e340f 100644 --- a/examples/setlist-aggregator.eve +++ b/examples/setlist-aggregator.eve @@ -5,254 +5,419 @@ This app takes a set of concerts, and accesses the setlist.fm api to retrieve the setlist that was played at that concert. This is cross-referenced with the MS Groove API to create playlists for those concerts. -The app configuration +## App Configuration commit - [#setlist-app groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] - setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"]] + playing = [#app/page name: "Now Playing" icon: "ios-musical-notes" sort: 0] + showlist = [#app/page name: "Show List" icon: "ios-list-outline" sort: 1] + collection = [#app/page name: "Collection" icon: "ios-albums-outline" sort: 2] + map = [#app/page name: "Map" icon: "map" sort: 3] + settings = [#app/page name: "Settings" icon: "gear-a"] + [#app/configuration groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] + setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"] + pages: (showlist, collection, map, playing)] + [#app/interface page: showlist] end -# Groove API +## Layout -The groove API is configured with a record tagged `#groove` with the following shape: +### Skeleton Layout -`[#groove client-id client-secret endpoint redirect-uri]` - - -## Logging Into Groove +search + interface = [#app/interface] +commit + interface <- [|children: + [#ui/column style: [width: "1600px" height: "900px"] | children: + [#header] + [#content] + [#global-playback] + ]] +end -Commit a `#groove/login` to initiate the login process. This redirects to a -login portal, which asks the user to authorize access to Eve. Once the user -grants access, the browser is redirect back to the supplied `redirect-uri`. +### Header search - [#groove/login] - [#groove client-id client-secret redirect-uri] - response-type = "token" - scopes = string/url-encode[text: "MicrosoftMediaServices.GrooveApiAccess offline_access"] - encoded-redirect-uri = string/url-encode[text: redirect-uri] - address = "https://login.live.com/oauth20_authorize.srf/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{encoded-redirect-uri}}&scope={{scopes}}" -commit - [#html/redirect url: address] + header = [#header] +bind + header += #ui/row + header <- [|children: + [#html/div #header/top-left sort: 1] + [#ui/spacer sort: 2] + [#html/div sort: 3 text: "Setlist Aggregator"] + [#ui/spacer sort: 4] + [#html/div #header/top-right sort: 5] + ] end -A successful login will return to the app an access token +### Navigation search - [#html/url query: [#html/url/query key: "access_token" value: access-token]] - groove = [#groove] -commit - groove.access-token := access-token + nav = [#navigation] + [#app/configuration pages: page] + page = [#app/page name icon sort] + settings = [#app/page name: "Settings"] +bind + nav <- [style: [width: "200px" height: "100%"]] + nav <- [#ui/column | children: + [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: + [#navigation/button #ui/button icon page text: name sort]] + [#ui/spacer sort: 99] + [#navigation/button #ui/button page: settings icon: settings.icon text: settings.name sort: 100] + ] end +### Content -## Getting Groove User Data +search + content = [#content] +bind + content <- [#ui/row style: [height: "100%"] |children: + [#navigation sort: 1] + [#page-container sort: 2] + [#ui/spacer sort: 3] + ] +end -With an access token in hand, we can use that to get user-specific information. +### Footer search - [#groove/get-user] - [#groove access-token endpoint] -commit - [#http/request #groove-profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] + playback = [#global-playback] + +bind + playback += #ui/row + playback <- [|children: + [] + ] end +### Page Container + search - [#groove-profile response] + page-container = [#page-container] + [#app/interface page] commit - [#json/decode #groove-profile json: response.body] + page-container += #html/div + page-container <- [|children: page] end -Create a Groove user from the response +### Navigate Between Pages search - [#groove-profile json-object: profile] + [#html/event/click element: [#navigation/button page]] + interface = [#app/interface] commit - [#groove/user region: profile.Culture subscription: profile.HasSubscription] + interface.page := page end -## Search Groove +## Header search - [#groove/search-track query] - [#groove access-token endpoint] - artist = string/replace[text: query replace: " " with: "+"] - address = "{{endpoint}}/1/content/music/search?q={{query}}&filters=tracks" -commit - [#http/request #groove-search address headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] + top-right = [#header/top-right] + not([#groove/user]) +bind + top-right <- [|children: + [#ui/button #groove-login text: "Log in to Groove"] + ] end +Logging in is kicked off by clicking the login button. + search - [#groove-search response: [body]] + [#html/event/click element: [#groove-login]] commit - [#json/decode #groove-search json: body] + [#groove/login] end +When we've gotten an access token, we get get the user profile. + search - [#groove-search json-object] - track = [Id Name ReleaseDate Duration] + [#groove access-token] commit - [#track name: Name groove-id: Id duration: Duration image: track.ImageUrl] + [#groove/get-user] end -## Get a full song strem +## Pages + +### Now Playing search - [#groove/full-stream track-id] - [#groove access-token endpoint] - address = "{{endpoint}}/1/content/{{track-id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" -commit - [#http/request #groove-get-song track-id address headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] + page = [#app/page name: "Now Playing" stream] + [#app/interface page] + //stream.track = [#groove/track image name image duration artist album] + [#html/test ] +bind + page <- [#html/div text: stream] end +Clicking on a playback control sets the stream attached to "Now Playing" + search - [#groove-get-song track-id response: [body]] + [#html/event/click element: playback-control] + playback-control = [#groove/stream-player/playback-control stream] + now-playing = [#app/page name: "Now Playing"] commit - [#json/decode #groove-get-song track-id json: body] + now-playing.stream := stream end +### Collection + search - [#groove-get-song track-id json-object] - groove-stream = [#groove/full-stream track-id] -commit - groove-stream <- [stream: json-object.Url content-type: json-object.ContentType] + page.name = "Collection" + [#app/interface page] + track = [#groove/track image name image duration artist album] +bind + page <- [#html/div #scrollable | children: + [#html/table #tracks | children: + [#html/tr track | children: + [#html/td sort: 0 children: + [#html/img src: image style: [width: "25px" height: "25px"]]] + [#html/td sort: 1 text: name] + [#html/td sort: 2 text: artist.Name] + [#html/td sort: 3 text: album.Name] + [#html/td sort: 4 text: duration]]]] end +### Show List +search + page.name = "Show List" + [#app/interface page] +bind + page <- [#ui/row | style: [width: "100%"] children: + [#show-list/shows] + [#show-list/show-detail] + [#show-list/search-pane] + ] +end -# The User Interface +#### List all the unmatched shows + +search + show-list = [#show-list/shows] + show = [#show artist date] + not(show = [#matched]) +bind + show-list += #ui/column + show-list <- [|children: + [#html/div #ui/header text: "Unmatched Shows"] + [#show-list/unmatched-shows #ui/list #ui/selectable #ui/single-selectable | item: + [#html/div #show-list/unmatched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "] + ]] +end -Logging in is kicked off by clicking the login button. search - not([#groove/user]) + [#ui/list item] bind - [#ui/button #groove-login text: "Login to Groove"] + item += #ui/list/item end + + +Clicking on an unmatched show searches setlist.fm + search - [#html/event/click element: [#groove-login]] + show-detail = [#show-list/show-detail] + [#html/event/click element: [#show-list/unmatched-show show]] commit - [#groove/login] + show-detail.show := show end -When we've gotten an access token, we get get the user profile. +Get the setlist details from the Setlist.fm API search - [#groove access-token] + show-detail = [#show-list/show-detail show] + not(show = [#matched]) commit - [#groove/get-user] + [#setlist-fm/search/setlists artist: show.artist date: show.date] end -## Searching for tracks +Commit the setlist to the show +search + [#show-list/show-detail show] + [#setlist-fm/search/setlists artist: show.artist date: show.date setlist] commit - [#html/div children: - [#ui/input #track-search] - [#ui/button #track-search/button text: "Search"]] + show.setlist += setlist end +Display search results + search - [#track-search value] - [#html/event/click element: [#track-search/button]] -commit - [#groove/search-track query: value] + show-detail = [#show-list/show-detail show] + setlist = show.setlist + not(show = [#matched]) +bind + show-detail <- [#ui/column | children: + [#html/div #tour sort: -2 text: setlist.tour] + [#html/div #artist sort: -1 text: setlist.artist.name] + [#html/div #date sort: 0 text: setlist.date] + [#html/div #venue sort: 0 text: setlist.venue.name] + [#html/div #setlists | children: + [#ui/list #ui/selectable #ui/single-selectable #setlist + set: setlist.sets sort: setlist.sets.number | item: + [#ui/row #song song: setlist.sets.songs sort: setlist.sets.songs.number | children: + [#ui/text #song-number text: setlist.sets.songs.number] + [#ui/text #song-name text: setlist.sets.songs.name] + [#ui/spacer]]]]] end +Display a loading screen while results are being fetched + search - track = [#track name image duration] + show-detail = [#show-list/show-detail show] + find-setlist = [#setlist-fm/search/setlists artist: show.artist date: show.date] + not(find-setlist = [#finished]) bind - [#html/div track children: - [#html/img track src: image style: [width: "100px" height: "100px"]] - [#html/div track text: "{{name}} - {{duration}}"] - [#html/div #stream-control track] - [#html/button #groove/stream-player/playback-control track text: "Play"]] + show-detail <- [#html/div | children: + [#html/div text: "Loading..."] + ] end +#### Get search results for unmatched songs + +Clicking on an unmatched song kicks off a groove search for that track + search - [#html/event/click element: [#play-track track]] - track = [#track groove-id] - app = [#setlist-app] + [#html/event/click element: [#song song]] + not(song = [#matched]) + search-pane = [#show-list/search-pane] commit - app.playing := track - [#groove/full-stream track-id: groove-id] + search-pane.song := song + search-pane.query := "{{song.artist}} {{song.name}}" end +Create a request for a query + search - [#groove/full-stream track-id stream content-type] - track = [#track groove-id: track-id] + [#show-list/search-pane query] commit - track.stream := stream - track.content-type := content-type + [#groove/search-track query] end + search - stream-div = [#stream-control track] - [#setlist-app playing] - track = playing + + search-pane = [#show-list/search-pane song query] + track = [#groove/track query name image duration artist album] + song.artist = artist.Name +bind + search-pane <- [#ui/column | children: + [#html/div #track-search children: + [#ui/input value: query]] + [#html/div #results-list | children: + [#ui/list | item: + [#ui/row track #album-result children: + [#html/div #album-image children: + [#html/img track src: image style: [width: "100px" height: "100px"]]] + [#ui/column #track-info track | children: + [#html/div #track-name sort: 1 track text: name] + [#html/div sort: 2 track text: artist.Name] + [#html/div sort: 3 track text: album.Name] + [#ui/spacer sort: 4] + [#html/div sort: 5 track text: duration] + ] + [#ui/spacer sort: 97] + [#ui/column track sort: 98 | children: + [#ui/spacer sort: 1] + [#groove/stream-player #display sort: 2 track] + [#ui/spacer sort: 3] + ] + [#ui/spacer sort: 99] + [#ui/button #link-track track icon: "link" sort: 100]]]]] +end + +search + [#html/event/click element: [#link-track track]] + search-pane = [#show-list/search-pane song] commit - [#html/div text: "Hello World"] - //stream-div.children += [#html/stream #groove/stream-player track play: "true" controls: "true" source: track.stream type: track.content-type] + search-pane.query := none + song += #matched + song.track := track end +Clicking on a matched song will play it + search - [#html/event/click element: control] - control = [#groove/stream-player/playback-control track] - player = [#groove/stream-player track] - (play, text) = if player = [play: "true"] then ("false","Play") - if player = [play: "false"] then ("true","Pause") - else ("true","Pause") + matched-song = [#song song: [#matched track]] +bind + matched-song.children += [#groove/stream-player #display track] +end + + + + + + + + +## App Data + commit - player.play := play - control.text := text + [#show artist: "Incubus" date: "16-08-2017"] + [#show artist: "Jimmy Eat World" date: "16-08-2017"] +end + + +### Settings + +search + page.name = "Settings" + [#app/interface page] +bind + page <- [#html/div text: "Settings"] end + + + + ## Get Data From Setlist.fm -disabled search - //[#setlist-app access-token setlist-endpoint] - //address = "{{setlist-endpoint}}/v1/search?q=incubus+glitterbomb+8&type=track" + find-setlist = [#setlist-fm/search/setlists artist date] + [#setlist-fm api-key endpoint] + encoded-artist = string/url-encode[text: artist] + address = "{{endpoint}}/1.0/search/setlists?artistName={{encoded-artist}}&date={{date}}&p=1" commit - //[#http/request #search-setlist address headers: - //[#http/header key: "Authorization" value: "Bearer {{access-token}}"] - //[#http/header key: "Accept" value: "application/json"]] + [#http/request #find-setlist find-setlist address headers: + [#http/header key: "x-api-key" value: api-key] + [#http/header key: "Accept" value: "application/json"]] end search - [#search-setlist response: [body]] + [#find-setlist find-setlist response: [body]] commit - [#json/decode #search-setlist json: body] + [#json/decode #find-setlist find-setlist json: body] end search - [#search-setlist json-object: spotsearch] - track-id = spotsearch.tracks.items.value.id + result = [#find-setlist find-setlist json-object] + not(result = [#finished]) + json-object = [setlist: [value: [artist id eventDate tour venue sets: [set]]]] + set = [index: set-number value: [song: [index: song-number value: [name: song-name]]]] commit - [#html/div text: track-id] + result += #finished + find-setlist += #finished + songs = [#setlist-fm/song artist: artist.name number: song-number, name: song-name] + sets = [#setlist-fm/set id number: set-number | songs] + find-setlist.setlist += [#setlist-fm/setlist id artist venue date: eventDate, tour: tour.name | sets] end -## seasrch setlist.fm - -//search - [#disable] - //address = "{{setlist-endpoint}}/1.0/search/artists?artistName={{artist}}" - //address = "{{setlist-endpoint}}/1.0/search/setlists?artistName={{artist}}&date=16-08-2017&p=1" -//commit - //[#http/request #setlist-fm artist: value, address headers: - //[#http/header key: "x-api-key" value: setlist-api-key] - //[#http/header key: "Accept" value: "application/json"]] -//end - +Clean up records +search + complete = [#find-setlist #finished json-object] + find-setlist = [#find-setlist] +commit + find-setlist := none + complete := none +end ## Misc Diagnostics @@ -290,4 +455,61 @@ search not(request = [#finished]) bind [#html/div text: "Processing request..."] +end + +## Styles + +commit + [#html/style text: " + body { background-color: rgb(24,24,24); height: 40px; color: rgb(200,200,200); } + div { user-select: none; cursor: default;} + + .ui-button { background-color: rgb(40,40,40); color: rgb(200,200,200); padding: 10px; border: 1px solid rgb(60,60,60); margin-bottom: 10px; } + .ui-button:hover { background-color: rgb(50,50,50); color: rgb(200,200,200); border: 1px solid rgb(60,60,60);} + .header { background-color: rgb(18,18,18); padding: 10px; } + .global-playback { background-color: rgb(40,40,40); height: 100px; color: rgb(200,200,200); } + .ui-list-item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px solid rgb(80,80,80); } + .ui-list-item:hover { background-color: rgb(40,40,40); } + .show-list-shows { width: 250px; } + .show-list-show-detail {padding-left: 20px; padding-right: 20px; background-color: rgb(22,22,22); width: 400px; margin-right: 10px;} + .artist { font-size: 26px; margin-bottom: 10px; } + .tour { margin-bottom: 5px; color: rgb(150,150,150); } + .venue { margin-bottom: 5px; color: rgb(150,150,150);} + .date { margin-bottom: 5px; color: rgb(150,150,150);} + .setlists { overflow: auto; max-height: 650px; margin-top: 15px; padding-right: 10px;} + .setlist { margin-top: 10px; margin-bottom: 20px; } + .song { padding: 5px; line-height: 30px; } + .song-number { width: 15px; margin-right: 10px; color: rgb(120,120,120); text-align: right } + .song:hover { background-color: rgb(40,40,40);} + .show-list-search-pane {min-width: 400px; max-width: 500px; overflow: auto;} + .album-image {width: 100px; margin-right: 20px;} + .album-result {padding: 20px;} + div::-webkit-scrollbar { + width: 5px; + } + div::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); + } + div::-webkit-scrollbar-thumb { + background-color: rgba(255,255,255,.5);; + outline: 1px solid slategrey; + border-radius: 25px; + } + .ui-header {padding: 10px; margin-bottom: 10px; font-size: 20px;} + .track-search {padding: 10px; padding-top: 20px; padding-bottom: 20px; background-color: rgb(18,18,18);} + .navigation { background-color: rgb(18,18,18); } + .navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(30,30,30); user-select: none; cursor: default; margin: 0px; border: 0px; width: 100%; min-height: 75px;} + .navigation-button:hover { color: rgb(255,255,255); border: 0px; } + .groove-stream-player-playback-control {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} + .groove-stream-player-playback-control:hover {background-colora: rgb(0,158,224,0); border-color: rgb(0,158,224); color: white;} + .groove-stream-player-playback-control .ion-pause {background-color: rgb(0,158,224,1); border-color: white; color: white;} + .track-name {padding-bottom: 10px;} + .results-list .ui-list {padding-right: 10px;} + .results-list {overflow: auto; max-height: 700px;} + .track-info {width: 150px;} + table { border-collapse: collapse; margin: 20px;} + td { padding: 10px; } + table, th, td { border: 1px solid rgb(80,80,80); } + .scrollable { overflow: auto; height: 800px; } + "] end \ No newline at end of file diff --git a/libraries/groove/groove.css b/libraries/groove/groove.css new file mode 100644 index 0000000..aa19dfb --- /dev/null +++ b/libraries/groove/groove.css @@ -0,0 +1,7 @@ +.groove-stream-player-playback-control { + height: "100px"; + width: "100px"; + border-radius: "50%"; + border: "2px solid #f5f5f5"; + text-align: center; +} \ No newline at end of file diff --git a/libraries/groove/groove.eve b/libraries/groove/groove.eve new file mode 100644 index 0000000..7ed1a24 --- /dev/null +++ b/libraries/groove/groove.eve @@ -0,0 +1,210 @@ +# Groove API + +The groove API is configured with a record tagged `#groove` with the following shape: + +`[#groove client-id client-secret endpoint redirect-uri]` + + +## Logging Into Groove + +Commit a `#groove/login` to initiate the login process. This redirects to a +login portal, which asks the user to authorize access to Eve. Once the user +grants access, the browser is redirect back to the supplied `redirect-uri`. + +search + [#groove/login] + [#groove client-id client-secret redirect-uri] + response-type = "token" + scopes = string/url-encode[text: "MicrosoftMediaServices.GrooveApiAccess offline_access"] + encoded-redirect-uri = string/url-encode[text: redirect-uri] + address = "https://login.live.com/oauth20_authorize.srf/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{encoded-redirect-uri}}&scope={{scopes}}" +commit + [#html/redirect url: address] +end + +A successful login will return to the app an access token + +search + [#html/url query: [#html/url/query key: "access_token" value: access-token]] + groove = [#groove] +commit + groove.access-token := access-token +end + + +## Getting Groove User Data + +With an access token in hand, we can use that to get user-specific information. + +search + [#groove/get-user] + [#groove access-token endpoint] +commit + [#http/request #groove-profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-profile response] +commit + [#json/decode #groove-profile json: response.body] +end + +Create a Groove user from the response + +search + [#groove-profile json-object: profile] +commit + [#groove/user region: profile.Culture subscription: profile.HasSubscription] +end + + +## Search Groove + +search + [#groove/search-track query] + [#groove access-token endpoint] + encoded-query = string/replace[text: query replace: " " with: "+"] + address = "{{endpoint}}/1/content/music/search?q={{encoded-query}}&filters=tracks" +commit + [#http/request #groove-search query address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-search query response: [body]] +commit + [#json/decode #groove-search query json: body] +end + +search + [#groove-search query json-object] + json-object = [Tracks: [Items: [value: [Id Name ReleaseDate Duration ImageUrl Album Artists: [value: [Artist]]]]]] +commit + [#groove/track query name: Name, groove-id: Id, duration: Duration, image: ImageUrl album: Album | artist: Artist] +end + + +## Get a full song strem + +search + [#groove/full-stream track-id] + [#groove access-token endpoint] + address = "{{endpoint}}/1/content/{{track-id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" +commit + [#http/request #groove-get-song track-id address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-get-song track-id response: [body]] +commit + [#json/decode #groove-get-song track-id json: body] +end + +search + [#groove-get-song track-id json-object] + groove-stream = [#groove/full-stream track-id] +commit + groove-stream <- [stream-url: json-object.Url content-type: json-object.ContentType] +end + + +## Streaming Player + +search + stream = [#groove/stream-player track] +bind + stream.controls += [#groove/stream-player/playback-control #ui/button stream] + stream.playback += [#html/stream controls: "true" #groove/stream-player/playback stream style: [display: "none"]] +end + +The icon of a control matches the state of its player + +search + control = [#groove/stream-player/playback-control stream] + playback = [#groove/stream-player/playback stream] + not(playback = [#pending]) + state = if playback = [#playing] then "pause" + else "play" +bind + control.icon += state +end + +The icon of a pending stream is a loading animation + +search + control = [#groove/stream-player/playback-control stream] + playback = [#groove/stream-player/playback #pending stream] +bind + control.icon += "load-a" +end + +Display controls in the DOM + +search + stream = [#groove/stream-player #display controls playback] +bind + stream += #html/div + controls += #display + stream.children += controls + stream.children += playback +end + +Hide controls that aren't tagged `#dispaly` + +search + controls = [#groove/stream-player/playback-control] + not(controls = [#display]) +bind + controls.style.display += "none" +end + +Clicking the play button for the first time gets a link to the stream and +starts playback when the stream is ready. + +search + [#html/event/click element: [#groove/stream-player/playback-control stream]] + pending-playback = [#groove/stream-player/playback stream] + stream.track = [#groove/track groove-id] + not(pending-playback = [#ready]) +commit + pending-playback += #pending + [#groove/full-stream stream track-id: groove-id] +end + +Attach the stream info to the stream playback + +search + [#groove/full-stream stream stream-url content-type] + playback = [#groove/stream-player/playback stream] +commit + playback <- [source: stream-url content-type] +end + +search + playback = [#groove/stream-player/playback #ready #pending stream] +commit + playback.play := "true" + playback -= #pending +end + +Clicking the playback button when the stream is ready will toggle the stream + +search + q = [#html/event/click element: [#groove/stream-player/playback-control stream]] + playback = [#groove/stream-player/playback #ready stream] + state = if playback = [#playing] then "false" + else "true" +commit + playback.play := state +end + +If we ever get into a state where a stream is both `#playing` and `#paused` we +can default to paused. + +search + stream = [#groove/stream-player #playing #paused] +commit + stream -= #playing +end \ No newline at end of file diff --git a/libraries/html/html.eve b/libraries/html/html.eve index 6e16f12..5b07852 100644 --- a/libraries/html/html.eve +++ b/libraries/html/html.eve @@ -16,6 +16,9 @@ commit "audio" "source" "video" + "table" + "tr" + "td" )] end ~~~ diff --git a/libraries/html/stream.eve b/libraries/html/stream.eve index e0913d7..f334e01 100644 --- a/libraries/html/stream.eve +++ b/libraries/html/stream.eve @@ -18,3 +18,25 @@ watch client/websocket ("stream/play", stream, play) end +search + [#html/event/stream-ready stream] + stream = [#html/stream] +commit + stream += #ready +end + +search + [#html/event/stream-play stream] + stream = [#html/stream] +commit + stream += #playing + stream -= #paused +end + +search + [#html/event/stream-pause stream] + stream = [#html/stream] +commit + stream += #paused + stream -= #playing +end \ No newline at end of file diff --git a/libraries/html/stream.ts b/libraries/html/stream.ts index 39e8f2b..b4abaeb 100644 --- a/libraries/html/stream.ts +++ b/libraries/html/stream.ts @@ -1,4 +1,4 @@ -import {Library, RawValue, RawEAV, handleTuples, libraries} from "../../ts"; +import {Library, createId, RawValue, RawEAV, handleTuples, libraries} from "../../ts"; import Hls from "hls.js" const EMPTY:never[] = []; @@ -19,19 +19,34 @@ export class Stream extends Library { if(Hls.isSupported()) { let video: any = document.getElementById(`${streamID}`); var hls = new Hls(); + let program = this.program hls.loadSource(`${source}`); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED,function() { - console.log("Stream Ready"); + }); video.onplay = function () { - console.log("PLAYING"); + let play_id = createId(); + program.inputEAVs([ + [play_id, "tag", "html/event/stream-play"], + [play_id, "stream", streamID], + ]); }; video.onpause = function () { - console.log("Paused"); + let paused_id = createId(); + program.inputEAVs([ + [paused_id, "tag", "html/event/stream-pause"], + [paused_id, "stream", streamID], + ]); }; - this.program.inputEAVs([[streamID, "tag", "ready"]]); - window.addEventListener("pageshow", video.onplay()); + video.onloadeddata = function () { + let ready_id = createId(); + program.inputEAVs([ + [ready_id, "tag", "html/event/stream-ready"], + [ready_id, "stream", streamID], + ]); + } + //window.addEventListener("pageshow", video.onplay()); this.streams[streamID] = video; } } From 3ff52d5ea29e3250097a0ca654332864dc316d14 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 30 Aug 2017 11:00:11 -0700 Subject: [PATCH 65/99] updates to groove and setlist --- examples/setlist-aggregator.eve | 175 +++++++++++++++++++++----------- libraries/groove/groove.eve | 80 ++++++++++----- 2 files changed, 173 insertions(+), 82 deletions(-) diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve index a3e340f..be705ef 100644 --- a/examples/setlist-aggregator.eve +++ b/examples/setlist-aggregator.eve @@ -1,7 +1,7 @@ # Setlist Aggregator -This app takes a set of concerts, and accesses the setlist.fm api to retrieve +This app takes a set of concerts, and accesses the setlist.fm api to retreive the setlist that was played at that concert. This is cross-referenced with the MS Groove API to create playlists for those concerts. @@ -11,15 +11,15 @@ commit playing = [#app/page name: "Now Playing" icon: "ios-musical-notes" sort: 0] showlist = [#app/page name: "Show List" icon: "ios-list-outline" sort: 1] collection = [#app/page name: "Collection" icon: "ios-albums-outline" sort: 2] - map = [#app/page name: "Map" icon: "map" sort: 3] + stats = [#app/page name: "Show Stats" icon: "stats-bars" sort: 3] + map = [#app/page name: "Map" icon: "map" sort: 4] settings = [#app/page name: "Settings" icon: "gear-a"] [#app/configuration groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"] - pages: (showlist, collection, map, playing)] + pages: (showlist, collection, map, playing, stats)] [#app/interface page: showlist] end - ## Layout ### Skeleton Layout @@ -58,7 +58,6 @@ search page = [#app/page name icon sort] settings = [#app/page name: "Settings"] bind - nav <- [style: [width: "200px" height: "100%"]] nav <- [#ui/column | children: [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: [#navigation/button #ui/button icon page text: name sort]] @@ -85,12 +84,32 @@ search playback = [#global-playback] bind - playback += #ui/row + playback <- [#ui/row] +end + +search + playback = [#global-playback] + [#now-playing track] +bind + playback <- [|children: + [#ui/spacer sort: 1] + [#groove/stream-player/playback-control #global-play sort: 2 track] + [#ui/spacer sort: 3]] +end + +Display control placeholders + +search + playback = [#global-playback] + not([#now-playing track]) +bind playback <- [|children: - [] - ] + [#ui/spacer sort: 1] + [#groove/stream-player/playback-control #ui/button #global-play icon: "play"] + [#ui/spacer sort: 3]] end + ### Page Container search @@ -144,22 +163,35 @@ end ### Now Playing search - page = [#app/page name: "Now Playing" stream] + page.name = "Now Playing" [#app/interface page] - //stream.track = [#groove/track image name image duration artist album] - [#html/test ] + [#now-playing track] + track = [#groove/track image name duration artist album] bind - page <- [#html/div text: stream] + page <-[#html/div | children: + [#ui/column track #album-result children: + [#html/div #album-image children: + [#html/img track src: image style: [width: "250px" height: "250px"]]] + [#html/div #track-name sort: 1 track text: name] + [#html/div sort: 2 track text: artist.Name] + [#html/div sort: 3 track text: album.Name] + [#ui/spacer sort: 4] + [#html/div sort: 5 track text: duration]]] end -Clicking on a playback control sets the stream attached to "Now Playing" +Clicking on a playback control sets a new `#now-playing` stream and pauses any +currently playing track. search [#html/event/click element: playback-control] - playback-control = [#groove/stream-player/playback-control stream] - now-playing = [#app/page name: "Now Playing"] + playback-control = [#groove/stream-player/playback-control track] + playback = [#groove/stream-player/playback track] + now-playing-tracks = if playing = [#groove/stream-player/playback #now-playing] then playing + else "None" commit - now-playing.stream := stream + playback += #now-playing + now-playing-tracks -= #now-playing + now-playing-tracks.play := "false" end ### Collection @@ -200,23 +232,19 @@ search show = [#show artist date] not(show = [#matched]) bind - show-list += #ui/column - show-list <- [|children: + show-list <- [#ui/column | children: [#html/div #ui/header text: "Unmatched Shows"] [#show-list/unmatched-shows #ui/list #ui/selectable #ui/single-selectable | item: [#html/div #show-list/unmatched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "] ]] end - search [#ui/list item] bind item += #ui/list/item end - - Clicking on an unmatched show searches setlist.fm search @@ -235,27 +263,25 @@ commit [#setlist-fm/search/setlists artist: show.artist date: show.date] end -Commit the setlist to the show - -search - [#show-list/show-detail show] - [#setlist-fm/search/setlists artist: show.artist date: show.date setlist] -commit - show.setlist += setlist -end - Display search results search show-detail = [#show-list/show-detail show] - setlist = show.setlist + [#setlist-fm/search/setlists artist: show.artist date: show.date setlist] not(show = [#matched]) bind show-detail <- [#ui/column | children: - [#html/div #tour sort: -2 text: setlist.tour] - [#html/div #artist sort: -1 text: setlist.artist.name] - [#html/div #date sort: 0 text: setlist.date] - [#html/div #venue sort: 0 text: setlist.venue.name] + [#ui/row #info-head | children: + [#ui/column #show-info sort: 1 | children: + [#html/div #artist sort: -1 text: setlist.artist.name] + [#html/div #tour sort: -2 text: setlist.tour] + [#html/div #date sort: -3 text: setlist.date] + [#html/div #venue sort: 0 text: setlist.venue.name] + ] + [#ui/spacer sort: 2] + [#ui/button #circle-button #large-button #match-show show setlist sort: 3 icon: "checkmark"] + [#ui/spacer sort: 4] + ] [#html/div #setlists | children: [#ui/list #ui/selectable #ui/single-selectable #setlist set: setlist.sets sort: setlist.sets.number | item: @@ -266,18 +292,46 @@ bind end Display a loading screen while results are being fetched - +TODO This isn't working right search show-detail = [#show-list/show-detail show] find-setlist = [#setlist-fm/search/setlists artist: show.artist date: show.date] not(find-setlist = [#finished]) bind show-detail <- [#html/div | children: - [#html/div text: "Loading..."] - ] + [#html/div text: "Loading..."]] +end + +When the `#match-show` button is click, mark the show as matched and attach a setlist. + + +search + [#html/event/click element: [#match-show show setlist]] +commit + show += #matched + show.setlist := setlist end -#### Get search results for unmatched songs +#### List all matched Shows + +search + show-list = [#show-list/shows] + show = [#show #matched artist date setlist] +bind + show-list += #ui/column + show-list.children += + [#ui/column | children: + [#html/div #ui/header text: "Matched Shows"] + [#show-list/matched-shows #ui/list #ui/selectable #ui/single-selectable | item: + [#html/div #show-list/matched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "]]] +end + + + + + + +#### Get search results for a song Clicking on an unmatched song kicks off a groove search for that track @@ -306,8 +360,10 @@ search song.artist = artist.Name bind search-pane <- [#ui/column | children: - [#html/div #track-search children: - [#ui/input value: query]] + [#ui/row #track-search children: + [#ui/spacer sort: 1] + [#ui/input sort: 2 icon: "play" value: query] + [#ui/spacer sort: 3]] [#html/div #results-list | children: [#ui/list | item: [#ui/row track #album-result children: @@ -359,6 +415,8 @@ end commit [#show artist: "Incubus" date: "16-08-2017"] [#show artist: "Jimmy Eat World" date: "16-08-2017"] + [#show artist: "Third Eye Blind" date: "23-07-2017"] + [#show artist: "Silversun Pickups" date: "23-07-2017"] end @@ -459,11 +517,22 @@ end ## Styles +Style all playback controls as circle buttons + +search + control = [#groove/stream-player/playback-control] +bind + control += #circle-button +end + + commit [#html/style text: " body { background-color: rgb(24,24,24); height: 40px; color: rgb(200,200,200); } div { user-select: none; cursor: default;} - + div::-webkit-scrollbar { width: 5px; } + div::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); } + div::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.5); outline: 1px solid slategrey; border-radius: 25px; } .ui-button { background-color: rgb(40,40,40); color: rgb(200,200,200); padding: 10px; border: 1px solid rgb(60,60,60); margin-bottom: 10px; } .ui-button:hover { background-color: rgb(50,50,50); color: rgb(200,200,200); border: 1px solid rgb(60,60,60);} .header { background-color: rgb(18,18,18); padding: 10px; } @@ -484,25 +553,13 @@ commit .show-list-search-pane {min-width: 400px; max-width: 500px; overflow: auto;} .album-image {width: 100px; margin-right: 20px;} .album-result {padding: 20px;} - div::-webkit-scrollbar { - width: 5px; - } - div::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); - } - div::-webkit-scrollbar-thumb { - background-color: rgba(255,255,255,.5);; - outline: 1px solid slategrey; - border-radius: 25px; - } .ui-header {padding: 10px; margin-bottom: 10px; font-size: 20px;} .track-search {padding: 10px; padding-top: 20px; padding-bottom: 20px; background-color: rgb(18,18,18);} - .navigation { background-color: rgb(18,18,18); } + .navigation { background-color: rgb(18,18,18); width: 150px; height: 100%; } .navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(30,30,30); user-select: none; cursor: default; margin: 0px; border: 0px; width: 100%; min-height: 75px;} .navigation-button:hover { color: rgb(255,255,255); border: 0px; } - .groove-stream-player-playback-control {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} - .groove-stream-player-playback-control:hover {background-colora: rgb(0,158,224,0); border-color: rgb(0,158,224); color: white;} - .groove-stream-player-playback-control .ion-pause {background-color: rgb(0,158,224,1); border-color: white; color: white;} + .circle-button {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} + .circle-button:hover {background-color: rgb(0,158,224,1); border-color: white; color: white;} .track-name {padding-bottom: 10px;} .results-list .ui-list {padding-right: 10px;} .results-list {overflow: auto; max-height: 700px;} @@ -511,5 +568,9 @@ commit td { padding: 10px; } table, th, td { border: 1px solid rgb(80,80,80); } .scrollable { overflow: auto; height: 800px; } + .global-playback {padding-top: 8px;} + .global-play {width: 50px; height: 50px; font-size: 25px; padding-left: 17px;} + .ui-input {padding: 5px 10px 5px 10px; border-radius: 20px; border: 1px solid rgb(80,80,80); outline: none;} + .large-button {width: 50px; height: 50px; font-size: 25px; padding-left: 15px;} "] end \ No newline at end of file diff --git a/libraries/groove/groove.eve b/libraries/groove/groove.eve index 7ed1a24..14fc4b2 100644 --- a/libraries/groove/groove.eve +++ b/libraries/groove/groove.eve @@ -81,30 +81,30 @@ search [#groove-search query json-object] json-object = [Tracks: [Items: [value: [Id Name ReleaseDate Duration ImageUrl Album Artists: [value: [Artist]]]]]] commit - [#groove/track query name: Name, groove-id: Id, duration: Duration, image: ImageUrl album: Album | artist: Artist] + [#groove/track query name: Name, id: Id, duration: Duration, image: ImageUrl album: Album | artist: Artist] end ## Get a full song strem search - [#groove/full-stream track-id] + [#groove/full-stream track] [#groove access-token endpoint] - address = "{{endpoint}}/1/content/{{track-id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" + address = "{{endpoint}}/1/content/{{track.id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" commit - [#http/request #groove-get-song track-id address headers: + [#http/request #groove-get-song track address headers: [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] end search - [#groove-get-song track-id response: [body]] + [#groove-get-song track response: [body]] commit - [#json/decode #groove-get-song track-id json: body] + [#json/decode #groove-get-song track json: body] end search - [#groove-get-song track-id json-object] - groove-stream = [#groove/full-stream track-id] + [#groove-get-song track json-object] + groove-stream = [#groove/full-stream track] commit groove-stream <- [stream-url: json-object.Url content-type: json-object.ContentType] end @@ -112,18 +112,39 @@ end ## Streaming Player +A stream player has a playback control + search stream = [#groove/stream-player track] bind - stream.controls += [#groove/stream-player/playback-control #ui/button stream] - stream.playback += [#html/stream controls: "true" #groove/stream-player/playback stream style: [display: "none"]] + stream.controls += [#groove/stream-player/playback-control track] +end + +Playback controls are also buttons, so they render + +search + playback-control = [#groove/stream-player/playback-control track] +bind + playback-control += #ui/button +end + + +A stream player also has a playback element + +search + groove = [#groove client-id] + player = [#groove/stream-player track] +commit + playback = [#html/stream #groove/stream-player/playback track style: [display: "none"]] + player.playback := playback + groove.streams += playback end The icon of a control matches the state of its player search - control = [#groove/stream-player/playback-control stream] - playback = [#groove/stream-player/playback stream] + control = [#groove/stream-player/playback-control track] + playback = [#groove/stream-player/playback track] not(playback = [#pending]) state = if playback = [#playing] then "pause" else "play" @@ -134,8 +155,8 @@ end The icon of a pending stream is a loading animation search - control = [#groove/stream-player/playback-control stream] - playback = [#groove/stream-player/playback #pending stream] + control = [#groove/stream-player/playback-control track] + playback = [#groove/stream-player/playback #pending track] bind control.icon += "load-a" end @@ -143,12 +164,11 @@ end Display controls in the DOM search - stream = [#groove/stream-player #display controls playback] + stream = [#groove/stream-player #display controls] bind stream += #html/div controls += #display stream.children += controls - stream.children += playback end Hide controls that aren't tagged `#dispaly` @@ -164,26 +184,26 @@ Clicking the play button for the first time gets a link to the stream and starts playback when the stream is ready. search - [#html/event/click element: [#groove/stream-player/playback-control stream]] - pending-playback = [#groove/stream-player/playback stream] - stream.track = [#groove/track groove-id] + [#html/event/click element: [#groove/stream-player/playback-control track]] + pending-playback = [#groove/stream-player/playback track] + track = [#groove/track id] not(pending-playback = [#ready]) commit pending-playback += #pending - [#groove/full-stream stream track-id: groove-id] + [#groove/full-stream track] end Attach the stream info to the stream playback search - [#groove/full-stream stream stream-url content-type] - playback = [#groove/stream-player/playback stream] + [#groove/full-stream track stream-url content-type] + playback = [#groove/stream-player/playback track] commit playback <- [source: stream-url content-type] end search - playback = [#groove/stream-player/playback #ready #pending stream] + playback = [#groove/stream-player/playback #ready #pending track] commit playback.play := "true" playback -= #pending @@ -192,8 +212,8 @@ end Clicking the playback button when the stream is ready will toggle the stream search - q = [#html/event/click element: [#groove/stream-player/playback-control stream]] - playback = [#groove/stream-player/playback #ready stream] + q = [#html/event/click element: [#groove/stream-player/playback-control track]] + playback = [#groove/stream-player/playback #ready track] state = if playback = [#playing] then "false" else "true" commit @@ -207,4 +227,14 @@ search stream = [#groove/stream-player #playing #paused] commit stream -= #playing +end + +A streaming player that has no controls is paused, because you'd have no way to pause it yourself. + +search + [#groove streams] + streams = [#groove/stream-player/playback #playing track] + not([#groove/stream-player/playback-control track]) +commit + streams.play := "false" end \ No newline at end of file From 43f05794a0c7b914eb0baecbd44e343d83ccb44e Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 30 Aug 2017 14:10:22 -0700 Subject: [PATCH 66/99] save state for update --- examples/json-demo.eve | 37 +++++++++++++++++++++++++++++++++++++ libraries/json/json.eve | 21 ++++++++++----------- src/bin/server.rs | 4 +++- src/ops.rs | 4 ++++ src/watchers/json.rs | 19 +++++++++++-------- 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 examples/json-demo.eve diff --git a/examples/json-demo.eve b/examples/json-demo.eve new file mode 100644 index 0000000..d126e94 --- /dev/null +++ b/examples/json-demo.eve @@ -0,0 +1,37 @@ +# JSON Testing + +## Test Encoding + +commit + ellen = [#person name: "Ellen" age: 28.8] + [#person name: "Hugh" age: 30.5 wife: ellen zip: 12345] +end + +search + p = [#person name: "Hugh"] +commit + [#json/encode record: p] +end + +search + [#json/encode json-string] +commit + [#console/log text: "Encoded JSON: {{json-string}}"] +end + +## Test Decode + +Decode the record we just encoded + +search + [#json/encode json-string] +commit + [#json/decode json: json-string] +end + +search + [#json/decode json-object] +commit + [#console/log text: "Decoded: {{json-object}}"] + //[#console/log text: "Decoded a JSON: {{json-object.name}} is {{json-object.age}} years old and lives at {{json-object.zip}}"] +end \ No newline at end of file diff --git a/libraries/json/json.eve b/libraries/json/json.eve index ad513bb..decaa92 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -7,8 +7,7 @@ A library for encoding and decoding Eve records into and from JSON Encoding starts with a `#json/encode` record. search - j = [#json/encode record] - lookup[entity: record attribute value] + [#json/encode record] commit [#json/encode/target record] [#json/encode/flatten record] @@ -17,13 +16,13 @@ end Target records are specified for encoding search - j = [#json/encode/target record] + encode = [#json/encode/target record] watch json - ("encode/target", j, record) + ("encode/target", encode, record) end Flattening is done recursively. We start with the target record and look up all -a/v pairs. Any v that is aslo a record (satisfies `lookup[entity]`) we will also +a/v pairs. Any v that is also a record (satisfies `lookup[entity]`) we will also flatten. search @@ -39,9 +38,9 @@ end Raw EAVs are encoded as JSON search - j = [#json/encode/eav record attribute value] + encode = [#json/encode/eav record attribute value] watch json - ("encode/eav", j, record, attribute, value) + ("encode/eav", encode, record, attribute, value) end We get back a `json-string` as a change. Apply it as an attribute to the @@ -49,15 +48,15 @@ original `json/encode` search [#json/encode/change record json-string] - r = [#json/encode record] + encode = [#json/encode record] commit - r.json-string := json-string + encode.json-string := json-string end ## Decoding search - j = [#json/decode json] + decode = [#json/decode json] watch json - ("decode", j, json) + ("decode", decode, json) end \ No newline at end of file diff --git a/src/bin/server.rs b/src/bin/server.rs index af70ad3..ce64330 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -24,6 +24,7 @@ use eve::watchers::compiler::{CompilerWatcher}; use eve::watchers::compiler2::{RawTextCompilerWatcher}; use eve::watchers::console::{ConsoleWatcher}; use eve::watchers::remote::{Router, RemoteWatcher}; +use eve::watchers::json::JsonWatcher; extern crate iron; extern crate staticfile; @@ -68,11 +69,12 @@ impl ClientHandler { if !clean { runner.program.attach(Box::new(SystemTimerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(CompilerWatcher::new(outgoing.clone()))); - runner.program.attach(Box::new(RawTextCompilerWatcher::new(outgoing))); + runner.program.attach(Box::new(RawTextCompilerWatcher::new(outgoing.clone()))); runner.program.attach(Box::new(WebsocketClientWatcher::new(out.clone()))); runner.program.attach(Box::new(ConsoleWatcher::new())); runner.program.attach(Box::new(PanicWatcher::new())); runner.program.attach(Box::new(RemoteWatcher::new(client_name, &router.lock().unwrap().deref()))); + runner.program.attach(Box::new(JsonWatcher::new(outgoing.clone()))); } for file in files { diff --git a/src/ops.rs b/src/ops.rs index b5f912e..13d2317 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -628,6 +628,10 @@ impl Internable { Internable::Number(value) } + pub fn from_str(s: &str) -> Internable { + Internable::String(s.to_string()) + } + pub fn print(&self) -> String { match self { &Internable::String(ref s) => { diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 0627d74..e637907 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -32,20 +32,20 @@ impl Watcher for JsonWatcher { for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); - let j_arg = Internable::to_string(interner.get_value(add[2])); - match (&kind[..], j_arg) { - ("encode/target", _) => { + match kind.as_ref() { + "encode/target" => { id = Internable::to_string(interner.get_value(add[2])); }, - ("encode/eav", j_arg) => { - let e = j_arg; + "encode/eav" => { + let e = Internable::to_string(interner.get_value(add[2])); let a = Internable::to_string(interner.get_value(add[3])); let v: Value = match interner.get_value(add[4]) { - &Internable::Number(ref n) => Value::Number(Number::from(n.clone())), + internable@&Internable::Number(_) => Value::Number(Number::from_f64(Internable::to_number(internable) as f64).unwrap()), &Internable::String(ref n) => Value::String(String::from(n.clone())), - _ => Value::Null, + _ => Value::Null }; + println!("Encoding: {:?} {:?}",a,v); if record_map.contains_key(&e) { let record = record_map.get_mut(&e).unwrap(); let sub_record = record.as_object_mut().unwrap(); @@ -56,7 +56,9 @@ impl Watcher for JsonWatcher { record_map.insert(e, Value::Object(new_record)); } }, - ("decode", value) => { + "decode" => { + println!("DECODING"); + let value = Internable::to_string(interner.get_value(add[2])); let v: Value = serde_json::from_str(&value).unwrap(); value_to_changes(&record_id.to_string(), "json-object", v, "json/decode", &mut changes); }, @@ -67,6 +69,7 @@ impl Watcher for JsonWatcher { if let Some(target_record) = record_map.get(&id) { let inner_map = target_record.as_object().unwrap(); let dereferenced_target = dereference(inner_map, &record_map); + println!("Dereferenced Target: {:?}", dereferenced_target); let json = serde_json::to_string(&dereferenced_target).unwrap(); let change_id = format!("json/encode/change|{:?}",id); changes.push(new_change(&change_id, "tag", Internable::from_str("json/encode/change"), "json/encode")); From b051e0a9224801a815160f1a49d72bd7ed59a437 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 30 Aug 2017 17:32:21 -0700 Subject: [PATCH 67/99] Fix some json bugs --- examples/json-demo.eve | 10 +++--- examples/test.eve | 78 ++++++++++++++++++++++++++++++++++-------- src/watchers/json.rs | 26 ++++++++++---- 3 files changed, 88 insertions(+), 26 deletions(-) diff --git a/examples/json-demo.eve b/examples/json-demo.eve index d126e94..18dd67f 100644 --- a/examples/json-demo.eve +++ b/examples/json-demo.eve @@ -3,12 +3,12 @@ ## Test Encoding commit - ellen = [#person name: "Ellen" age: 28.8] - [#person name: "Hugh" age: 30.5 wife: ellen zip: 12345] + rachel = [#person name: "Rachel" age: 28] + [#person name: "Corey" age: 30 wife: rachel zip: 95348] end search - p = [#person name: "Hugh"] + p = [#person name: "Corey"] commit [#json/encode record: p] end @@ -16,7 +16,7 @@ end search [#json/encode json-string] commit - [#console/log text: "Encoded JSON: {{json-string}}"] + [#html/div text: "Encoded JSON: {{json-string}}"] end ## Test Decode @@ -32,6 +32,6 @@ end search [#json/decode json-object] commit - [#console/log text: "Decoded: {{json-object}}"] + [#html/div text: "Decoded: {{json-object}}"] //[#console/log text: "Decoded a JSON: {{json-object.name}} is {{json-object.age}} years old and lives at {{json-object.zip}}"] end \ No newline at end of file diff --git a/examples/test.eve b/examples/test.eve index 3f84507..c8f0bcb 100644 --- a/examples/test.eve +++ b/examples/test.eve @@ -1,40 +1,88 @@ -# JSON Testing -## Test Encoding +## Id Example 1 +disabled commit - rachel = [#person name: "Rachel" age: 28.0] - [#person name: "Corey" age: 30.0 wife: rachel] + [#true-condition-one value: 3] + [#true-condition-two value: 4] end search - p = [#person name: "Corey"] + [#true-condition-one value] +bind + [#yep value] +end + +search + [#true-condition-two value] +bind + [#yep value] +end + +search + yeps = [#yep] + number = gather/count[for: yeps] +bind + [#html/div text: "I found {{number}} yeps: {{yeps}}"] +end + +disabled commit - [#json/encode record: p] + [#ui/button #set-value text: "Set value!"] end search - [#json/encode json-string] + [#html/event/click element: [#set-value]] + one = [#true-condition-one] + two = [#true-condition-two] commit - [#console/log text: "Converted some JSON in Eve: {{json-string}}"] + one.value := 5 + two.value := 5 end -## Test Decode + +## Id Example 2 commit - [#file/read #file path: "person.json"] + thing1 = [#thing value: 1] + thing2 = [#thing value: 2] + thing3 = [#thing value: 3] + [#cat minion: thing1] end search - [#file contents] + [#disable] + cats = [#cat minion] + number = gather/count[for: cats] +bind + [#html/div children: + [#html/div text: "I found {{number}} cats:" ] + [#ui/column cats | children: + [#ui/text sort: 1 text: " Cat Id: {{cats}}"] + [#ui/text sort: 2 text: " {{minion}}"] + ] + ] +end + +## Id Example 3 + commit - [#console/log text: contents] - [#json/decode json: contents] + [#system/timer resolution: 1000] + [#timer #up value: 0] + [#timer #down value: 10] end search - [#json/decode json-object: [Value: [#array index value: [#array index: ix value]]]] + t = [#system/timer/change tick] + up = [#timer #up] + down = [#timer #down] commit - [#console/log text: "Got a thing: {{index}} {{value}}"] + up.value := up.value + 1 + down.value := down.value - 1 end +search + [#timer value] +bind + [#html/div text: value] +end \ No newline at end of file diff --git a/src/watchers/json.rs b/src/watchers/json.rs index e637907..6ed69aa 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -45,14 +45,16 @@ impl Watcher for JsonWatcher { &Internable::String(ref n) => Value::String(String::from(n.clone())), _ => Value::Null }; - println!("Encoding: {:?} {:?}",a,v); + println!("Encoding: {:?} {:?} {:?}",e,a,v); if record_map.contains_key(&e) { let record = record_map.get_mut(&e).unwrap(); let sub_record = record.as_object_mut().unwrap(); sub_record.insert(a, v); + println!("Sub Record: {:?}",sub_record); } else { let mut new_record = Map::new(); new_record.insert(a, v); + println!("New Record: {:?}",new_record); record_map.insert(e, Value::Object(new_record)); } }, @@ -65,11 +67,11 @@ impl Watcher for JsonWatcher { _ => {}, } } - + println!("Record Map: {:?}",record_map); if let Some(target_record) = record_map.get(&id) { let inner_map = target_record.as_object().unwrap(); + println!("Inner Map: {:?}", inner_map); let dereferenced_target = dereference(inner_map, &record_map); - println!("Dereferenced Target: {:?}", dereferenced_target); let json = serde_json::to_string(&dereferenced_target).unwrap(); let change_id = format!("json/encode/change|{:?}",id); changes.push(new_change(&change_id, "tag", Internable::from_str("json/encode/change"), "json/encode")); @@ -88,6 +90,15 @@ fn dereference(target: &Map, flatmap: &Map) -> Map println!("{:?}",s), + &Value::Number(ref n) => println!("{:?}",n), + x => println!("{:?}",x), + } + + + match value.as_str() { Some(v) => { if flatmap.contains_key(v) { @@ -125,13 +136,16 @@ pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, cha changes.push(new_change(id,attribute,Internable::String(n.clone()),node)); }, Value::Bool(ref n) => { - let b = format!("{:?}", n); - changes.push(new_change(id,attribute,Internable::String(b),node)); + let b = match n { + &true => "true", + &false => "false", + }; + changes.push(new_change(id,attribute,Internable::from_str(b),node)); }, Value::Array(ref n) => { for (ix, value) in n.iter().enumerate() { let ix = ix + 1; - let array_id = format!("array|{:?}|{:?}",ix,value); + let array_id = format!("array|{:?}|{:?}|{:?}", id, ix, value); let array_id = &array_id[..]; changes.push(new_change(id,attribute,Internable::from_str(array_id),node)); changes.push(new_change(array_id,"tag",Internable::from_str("array"),node)); From c288c275ae653d4d29b24daddd5957631e16ea72 Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 31 Aug 2017 22:28:39 -0700 Subject: [PATCH 68/99] Do encoding in Eve --- examples/json-demo.eve | 60 ++++++--- libraries/html/html.eve | 3 + libraries/json/json.eve | 250 +++++++++++++++++++++++++++++++++--- libraries/system/system.eve | 10 +- src/watchers/json.rs | 179 +++++++++----------------- 5 files changed, 342 insertions(+), 160 deletions(-) diff --git a/examples/json-demo.eve b/examples/json-demo.eve index 18dd67f..be8cfb1 100644 --- a/examples/json-demo.eve +++ b/examples/json-demo.eve @@ -3,35 +3,63 @@ ## Test Encoding commit - rachel = [#person name: "Rachel" age: 28] - [#person name: "Corey" age: 30 wife: rachel zip: 95348] + corey = [#person name: "Corey" | age: 31] + giselle = [#cat name: "Giselle" | age: 7] + twylah = [#cat name: "Twylah" | age: 7] + rachel = [#person name: "Rachel" | age: 28 cats: (giselle, twylah) husband: corey] end search - p = [#person name: "Corey"] -commit + p = [#person name: "Rachel"] +bind [#json/encode record: p] end +commit + [#ui/button #change-record sort: -1 text: "Change"] + [#ui/button #add-record sort: -1 text: "Add"] +end + search - [#json/encode json-string] + [#html/event/click element: [#change-record]] + p = [#person name: "Rachel"] commit - [#html/div text: "Encoded JSON: {{json-string}}"] + p.age := 29 +end + +search + [#html/event/click element: [#add-record]] + p = [#person name: "Rachel"] +commit + p.foo := "Hello" end -## Test Decode -Decode the record we just encoded search [#json/encode json-string] -commit - [#json/decode json: json-string] +bind + [#html/div text: "Encoded JSON: {{json-string}}"] end -search - [#json/decode json-object] -commit - [#html/div text: "Decoded: {{json-object}}"] - //[#console/log text: "Decoded a JSON: {{json-object.name}} is {{json-object.age}} years old and lives at {{json-object.zip}}"] -end \ No newline at end of file +## Test Decode + +Decode the record we just encoded + +//search + //[#json/encode json-string] +//commit + //[#json/decode json: json-string] +//end + +//search + //[#json/decode json-object] +//commit + //[#html/div text: "Decoded JSON: {{json-object.name}} is {{json-object.age}} years old"] +//end + + +//commit + //[#ui/button #add-record text: "Add to record"] + //[#ui/button #remove-record text: "Remove From Record"] +//end \ No newline at end of file diff --git a/libraries/html/html.eve b/libraries/html/html.eve index bf31b06..05c5c45 100644 --- a/libraries/html/html.eve +++ b/libraries/html/html.eve @@ -16,6 +16,9 @@ commit "li" "ul" "ol" + "table" + "tr" + "td" )] end ~~~ diff --git a/libraries/json/json.eve b/libraries/json/json.eve index decaa92..c8b3548 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -4,45 +4,238 @@ A library for encoding and decoding Eve records into and from JSON ## Encoding -Encoding starts with a `#json/encode` record. +Encoding a record is kicked off with `#json/encode`. It creates two records of consequence: + +- `#json/encode/record`: handles encoding records into json. This one is tagged `#json/encode/target-record`, which means it is flagged for output. +- `#json/encode/flatten`: flattens a record into a/v pairs search [#json/encode record] -commit - [#json/encode/target record] +bind + [#json/encode/record #json/encode/target-record record] [#json/encode/flatten record] end -Target records are specified for encoding +`#json/encode/record` are given a starting point for JSON search - encode = [#json/encode/target record] -watch json - ("encode/target", encode, record) + target = [#json/encode/record record] + not(target.json-string) +commit + target.json-string := "{ " end -Flattening is done recursively. We start with the target record and look up all -a/v pairs. Any v that is also a record (satisfies `lookup[entity]`) we will also -flatten. +We flatten records with lookup. search [#json/encode/flatten record] lookup[entity: record attribute value] - sub-record = if lookup[entity: value] then value - else "" -commit - [#json/encode/flatten record: sub-record] - [#json/encode/eav record attribute value] +bind + encode-eav = [#json/encode/eav record attribute value] end -Raw EAVs are encoded as JSON +sub-records are marked `#json/encode/entity` search encode = [#json/encode/eav record attribute value] + lookup[entity: value] +bind + encode += #json/encode/entity +end + +## Encode A/V Pairs + +We can join all non entities in a json encoded string + +search + eav = [#json/encode/eav record attribute value] + not(eav = [#json/encode/entity]) +bind + [#json/encode/entity/av-pair record av: "'{{attribute}}': '{{value}}'"] +end + +search + [#json/encode/entity/av-pair record av] +bind + [#string/join #json/encode/join-avs record with: ", " | strings: av] +end + +search + [#json/encode/join-avs record result] +bind + [#json/encode/complete-av record json-string: result] +end + +## Encode Sub records + +`#json/encode/entity` records can be encoded just like the target record. + +search + [#json/encode/eav record attribute value #json/encode/entity] +bind + [#json/encode/record record: value] + [#json/encode/flatten record: value] +end + +Join eavs into a json object + +search + encode = [#json/encode/eav #json/encode/entity attribute] + finished = [#json/encode/finished] + encode.value = finished.record +bind + [#string/join #json/encode/join-object parent: encode.record attribute record: "{{encode.record}}|{{attribute}}" with: ", " | strings: finished.json-string ] +end + +Put all json object strings into an array form. attaching its attribute + +search + [#json/encode/join-object result parent attribute] +bind + [#json/encode/complete-av record: parent json-string: "'{{attribute}}': [ {{result}} ]"] +end + +## Bring it all together + +Join all encoded avs into a compelte string + +search + complete-av = [#json/encode/complete-av record] + target-record = [#json/encode/record record] +bind + [#string/join #json/encode/join-complete record with: ", " | strings: complete-av.json-string] +end + +Ensconce finished records with curly braces + +search + [#json/encode/join-complete record result] +bind + [#json/encode/finished record json-string: "{ {{result}} }"] +end + +When the full target record is encoded, hang it on the orginal `#json-encode` record + +search + [#json/encode/finished record json-string] + encode = [#json/encode record] + [#json/encode/target-record record] +bind + encode.json-string += json-string +end + + + + + + +## Joining Strings in Eve + +search + join = [#string/join strings with] watch json - ("encode/eav", encode, record, attribute, value) + ("join", join, strings, with) +end + +Put the joined string in the original `#string/join` record, and get rid of the result + +search + join-result = [#string/join/result result] + join = [#string/join strings with] + join-result.record = join +commit + join-result := none + join.result := result +end + +## Debug Display + +search + [#json/encode] + [#json/encode/record record json-string] + output = [#output] +bind + output.children += [#html/div sort: json-string text: "Target: {{json-string}}"] +end + +search + [#json/encode] + [#json/encode/sub-target record json-string] + output = [#output] +bind + output.children += [#html/div text: "Sub: {{json-string}}"] +end + + +search + [#json/encode] + [#completed/target record json-string] + output = [#application] +bind + output.children += [#html/div text: "Completed {{json-string}}"] +end + + + +search + [#json/encode] + encode = [#encode-eavs] + eav = [#json/encode/eav record attribute value] + entity? = if eav = [#json/encode/entity] then "entity" + else "" +bind + encode <- [| children: + [#html/div sort: 0 text: "Encode EAVs"] + [#html/table #eav-table | children: + [#html/tr #eav-row eav children: + [#html/td sort:1 text: record] + [#html/td sort:2 text: attribute] + [#html/td sort:3 text: value] + [#html/td sort:4 text: entity?] + ] + ] + + ] end +search + [#json/encode] + encode = [#flatten] + [#json/encode/flatten record] +bind + encode <- [| children: + [#html/div sort: 0 text: "Flatten"] + [#html/div sort: 1 text: "{{record}}"] + ] +end + + +search + [#json/encode] +commit + [#ui/column #application | children: + [#ui/column #output | children: + [#ui/button #next sort: -1 text: "Next"] + ] + [#ui/row #debug | children: + [#ui/column #encode-eavs] + [#ui/column #flatten] + ] + ] +end + +Flattening is done recursively. We start with the target record and look up all +a/v pairs. Any v that is also a record (satisfies `lookup[entity]`) we will also +flatten. + +Raw EAVs are encoded as JSON + +//search +// encode = [#json/encode/eav record attribute value] +//watch json +// ("encode/eav", encode, record, attribute, value) +//end + We get back a `json-string` as a change. Apply it as an attribute to the original `json/encode` @@ -50,7 +243,8 @@ search [#json/encode/change record json-string] encode = [#json/encode record] commit - encode.json-string := json-string + [#html/div text: "json/encode/change {{json-string}}"] + encode.json-string += json-string end ## Decoding @@ -59,4 +253,24 @@ search decode = [#json/decode json] watch json ("decode", decode, json) +end + +A decoded string comes through on a change + +search + decode-change = [#json/decode/change decode json-object] +commit + decode.json-object := json-object + decode-change := none +end + +commit + [#html/style text: " + td {padding: 10px;} + table {margin: 10px;} + .ui/column {padding: 10px;} + .ui/row {padding: 10px;} + .output {padding: 20px;} + .encode-eavs {min-width: 350px;} + "] end \ No newline at end of file diff --git a/libraries/system/system.eve b/libraries/system/system.eve index 84b211d..dca611f 100644 --- a/libraries/system/system.eve +++ b/libraries/system/system.eve @@ -12,11 +12,11 @@ end search t = [#system/timer/change resolution hour minute second tick] - for = [#system/timer resolution] + //for = [#system/timer resolution] commit - for.minute := minute - for.second := second - for.hour := hour - for.tick := tick + //for.minute := minute + //for.second := second + //for.hour := hour + //for.tick := tick t := none end diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 6ed69aa..75e242a 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -6,15 +6,32 @@ use super::Watcher; extern crate serde_json; extern crate serde; use self::serde_json::{Map, Value, Number}; +use std::collections::HashMap; pub struct JsonWatcher { name: String, outgoing: Sender, + join_strings_map: HashMap, } impl JsonWatcher { pub fn new(outgoing: Sender) -> JsonWatcher { - JsonWatcher { name: "json".to_string(), outgoing } + JsonWatcher { name: "json".to_string(), join_strings_map: HashMap::new(), outgoing } + } +} + +#[derive(Debug, Clone)] +pub struct JoinStrings { + strings: Vec, + with: String +} + +impl JoinStrings { + pub fn new(with: String) -> JoinStrings { + JoinStrings { with, strings: vec![] } + } + pub fn join(&self) -> String { + self.strings.join(self.with.as_ref()) } } @@ -26,57 +43,57 @@ impl Watcher for JsonWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - let mut record_map = Map::new(); + println!("New Diff"); let mut changes: Vec = vec![]; - let mut id = "".to_string(); + for remove in diff.removes { + let kind = Internable::to_string(interner.get_value(remove[0])); + match kind.as_ref() { + "join" => { + let id = Internable::to_string(interner.get_value(remove[1])); + let string = Internable::to_string(interner.get_value(remove[2])); + let with = Internable::to_string(interner.get_value(remove[3])); + let join_strings = self.join_strings_map.get_mut(&id).unwrap(); + let index = join_strings.strings.iter().position(|x| *x == string).unwrap(); + join_strings.strings.remove(index); + println!("Remove: {:?} {:?}", index, join_strings.strings); + }, + _ => {}, + } + } for add in diff.adds { let kind = Internable::to_string(interner.get_value(add[0])); let record_id = Internable::to_string(interner.get_value(add[1])); - match kind.as_ref() { - "encode/target" => { - id = Internable::to_string(interner.get_value(add[2])); - }, - "encode/eav" => { - let e = Internable::to_string(interner.get_value(add[2])); - let a = Internable::to_string(interner.get_value(add[3])); - let v: Value = match interner.get_value(add[4]) { - internable@&Internable::Number(_) => Value::Number(Number::from_f64(Internable::to_number(internable) as f64).unwrap()), - &Internable::String(ref n) => Value::String(String::from(n.clone())), - _ => Value::Null - }; - println!("Encoding: {:?} {:?} {:?}",e,a,v); - if record_map.contains_key(&e) { - let record = record_map.get_mut(&e).unwrap(); - let sub_record = record.as_object_mut().unwrap(); - sub_record.insert(a, v); - println!("Sub Record: {:?}",sub_record); - } else { - let mut new_record = Map::new(); - new_record.insert(a, v); - println!("New Record: {:?}",new_record); - record_map.insert(e, Value::Object(new_record)); - } - }, "decode" => { - println!("DECODING"); let value = Internable::to_string(interner.get_value(add[2])); let v: Value = serde_json::from_str(&value).unwrap(); - value_to_changes(&record_id.to_string(), "json-object", v, "json/decode", &mut changes); + let change_id = format!("json/decode/change|{:?}",record_id); + value_to_changes(change_id.as_ref(), "json-object", v, "json/decode", &mut changes); + changes.push(new_change(&change_id, "tag", Internable::from_str("json/decode/change"), "json/decode")); + changes.push(new_change(&change_id, "decode", Internable::String(record_id), "json/decode")); + }, + "join" => { + let id = Internable::to_string(interner.get_value(add[1])); + let string = Internable::to_string(interner.get_value(add[2])); + let with = Internable::to_string(interner.get_value(add[3])); + if self.join_strings_map.contains_key(&id) { + let join_strings = self.join_strings_map.get_mut(&id).unwrap(); + join_strings.strings.push(string); + } else { + let mut join_strings = JoinStrings::new(with); + join_strings.strings.push(string); + self.join_strings_map.insert(id, join_strings); + } }, _ => {}, } } - println!("Record Map: {:?}",record_map); - if let Some(target_record) = record_map.get(&id) { - let inner_map = target_record.as_object().unwrap(); - println!("Inner Map: {:?}", inner_map); - let dereferenced_target = dereference(inner_map, &record_map); - let json = serde_json::to_string(&dereferenced_target).unwrap(); - let change_id = format!("json/encode/change|{:?}",id); - changes.push(new_change(&change_id, "tag", Internable::from_str("json/encode/change"), "json/encode")); - changes.push(new_change(&change_id, "json-string", Internable::String(json), "json/encode")); - changes.push(new_change(&change_id, "record", Internable::String(id), "json/encode")); + + for (record_id, join_strings) in self.join_strings_map.iter() { + let join_id = format!("string/join|{:?}",record_id); + changes.push(new_change(&join_id, "tag", Internable::from_str("string/join/result"), "string/join")); + changes.push(new_change(&join_id, "result", Internable::String(join_strings.join()), "string/join")); + changes.push(new_change(&join_id, "record", Internable::String(record_id.to_owned()), "string/join")); } match self.outgoing.send(RunLoopMessage::Transaction(changes)) { Err(_) => (), @@ -85,35 +102,6 @@ impl Watcher for JsonWatcher { } } -// Resolves all the object links in the flat map -fn dereference(target: &Map, flatmap: &Map) -> Map { - let mut dereferenced = Map::new(); - for key in target.keys() { - let value = target.get(key).unwrap(); - println!("DeRefVal: {:?}",value.as_str()); - match value { - &Value::String(ref s) => println!("{:?}",s), - &Value::Number(ref n) => println!("{:?}",n), - x => println!("{:?}",x), - } - - - - match value.as_str() { - Some(v) => { - if flatmap.contains_key(v) { - let value = flatmap.get(v).unwrap().as_object().unwrap(); - dereferenced.insert(key.to_string(),Value::Object(dereference(value, flatmap))); - } else { - dereferenced.insert(key.to_string(),value.clone()); - } - }, - None => (), - }; - } - dereferenced -} - pub fn new_change(e: &str, a: &str, v: Internable, n: &str) -> RawChange { RawChange {e: Internable::from_str(e), a: Internable::from_str(a), v: v.clone(), n: Internable::from_str(n), count: 1} } @@ -163,55 +151,4 @@ pub fn value_to_changes(id: &str, attribute: &str, value: Value, node: &str, cha }, _ => {}, } -} - -/* -#[derive(Debug)] -pub enum ChangeVec { - Changes(Vec) -} - -impl ChangeVec { - pub fn new() -> ChangeVec { - ChangeVec::Changes(Vec::new()) - } -} - -impl<'de> Deserialize<'de> for ChangeVec { - - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> - { - struct ChangeVecVisitor { - marker: PhantomData - } - - impl ChangeVecVisitor { - fn new() -> Self { - ChangeVecVisitor { - marker: PhantomData - } - } - } - - impl<'de> Visitor<'de> for ChangeVecVisitor { - type Value = ChangeVec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("expecting a thing") - } - - fn visit_map(self, mut access: M) -> Result - where M: MapAccess<'de> - { - let mut vec = Vec::new(); - while let Some(kv) = try!(access.next_entry()) { - vec.push(kv); - } - Ok(ChangeVec::new()) - } - } - - deserializer.deserialize_any(ChangeVecVisitor::new()) - } -}*/ \ No newline at end of file +} \ No newline at end of file From bdf44a20958002d7f96911c37650a3586bf96e1e Mon Sep 17 00:00:00 2001 From: cmontella Date: Thu, 31 Aug 2017 22:59:30 -0700 Subject: [PATCH 69/99] undo accidental commit --- examples/json-demo.eve | 10 ++++++++++ libraries/system/system.eve | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/json-demo.eve b/examples/json-demo.eve index be8cfb1..c0c099e 100644 --- a/examples/json-demo.eve +++ b/examples/json-demo.eve @@ -9,6 +9,16 @@ commit rachel = [#person name: "Rachel" | age: 28 cats: (giselle, twylah) husband: corey] end +commit + [#system/timer resolution: 1000] +end + +search + [#system/timer] +commit + [#html/div text: "Hello"] +end + search p = [#person name: "Rachel"] bind diff --git a/libraries/system/system.eve b/libraries/system/system.eve index dca611f..84b211d 100644 --- a/libraries/system/system.eve +++ b/libraries/system/system.eve @@ -12,11 +12,11 @@ end search t = [#system/timer/change resolution hour minute second tick] - //for = [#system/timer resolution] + for = [#system/timer resolution] commit - //for.minute := minute - //for.second := second - //for.hour := hour - //for.tick := tick + for.minute := minute + for.second := second + for.hour := hour + for.tick := tick t := none end From d85779a772142416206f0b20eb7fedd7e718d508 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 08:23:41 -0700 Subject: [PATCH 70/99] update markdown in json --- examples/json-demo.eve | 94 +++++++++++++++++++++++++++++-- libraries/json/json.eve | 122 ++-------------------------------------- 2 files changed, 94 insertions(+), 122 deletions(-) diff --git a/examples/json-demo.eve b/examples/json-demo.eve index c0c099e..1b3bb1d 100644 --- a/examples/json-demo.eve +++ b/examples/json-demo.eve @@ -14,9 +14,10 @@ commit end search - [#system/timer] -commit - [#html/div text: "Hello"] + p = [#person name: "Rachel" age] + [#system/timer second] +bind + p.time += second end search @@ -72,4 +73,89 @@ Decode the record we just encoded //commit //[#ui/button #add-record text: "Add to record"] //[#ui/button #remove-record text: "Remove From Record"] -//end \ No newline at end of file +//end + + + +## Debug Display + +search + [#json/encode] + [#json/encode/record record json-string] + output = [#output] +bind + output.children += [#html/div sort: json-string text: "Target: {{json-string}}"] +end + +search + [#json/encode] + [#json/encode/sub-target record json-string] + output = [#output] +bind + output.children += [#html/div text: "Sub: {{json-string}}"] +end + +search + [#json/encode] + [#completed/target record json-string] + output = [#application] +bind + output.children += [#html/div text: "Completed {{json-string}}"] +end + +search + [#json/encode] + encode = [#encode-eavs] + eav = [#json/encode/eav record attribute value] + entity? = if eav = [#json/encode/entity] then "entity" + else "" +bind + encode <- [| children: + [#html/div sort: 0 text: "Encode EAVs"] + [#html/table #eav-table | children: + [#html/tr #eav-row eav children: + [#html/td sort:1 text: record] + [#html/td sort:2 text: attribute] + [#html/td sort:3 text: value] + [#html/td sort:4 text: entity?] + ] + ] + + ] +end + +search + [#json/encode] + encode = [#flatten] + [#json/encode/flatten record] +bind + encode <- [| children: + [#html/div sort: 0 text: "Flatten"] + [#html/div sort: 1 text: "{{record}}"] + ] +end + +search + [#json/encode] +commit + [#ui/column #application | children: + [#ui/column #output | children: + [#ui/button #next sort: -1 text: "Next"] + ] + [#ui/row #debug | children: + [#ui/column #encode-eavs] + [#ui/column #flatten] + ] + ] +end + +commit + [#html/style text: " + td {padding: 10px;} + table {margin: 10px;} + .ui/column {padding: 10px;} + .ui/row {padding: 10px;} + .output {padding: 20px;} + .encode-eavs {min-width: 350px;} + "] +end \ No newline at end of file diff --git a/libraries/json/json.eve b/libraries/json/json.eve index c8b3548..741395f 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -43,7 +43,7 @@ bind encode += #json/encode/entity end -## Encode A/V Pairs +### Encode A/V Pairs We can join all non entities in a json encoded string @@ -66,7 +66,7 @@ bind [#json/encode/complete-av record json-string: result] end -## Encode Sub records +### Encode Sub records `#json/encode/entity` records can be encoded just like the target record. @@ -95,7 +95,7 @@ bind [#json/encode/complete-av record: parent json-string: "'{{attribute}}': [ {{result}} ]"] end -## Bring it all together +### Bring it all together Join all encoded avs into a compelte string @@ -124,12 +124,7 @@ bind encode.json-string += json-string end - - - - - -## Joining Strings in Eve +### Joining Strings in Eve search join = [#string/join strings with] @@ -148,105 +143,6 @@ commit join.result := result end -## Debug Display - -search - [#json/encode] - [#json/encode/record record json-string] - output = [#output] -bind - output.children += [#html/div sort: json-string text: "Target: {{json-string}}"] -end - -search - [#json/encode] - [#json/encode/sub-target record json-string] - output = [#output] -bind - output.children += [#html/div text: "Sub: {{json-string}}"] -end - - -search - [#json/encode] - [#completed/target record json-string] - output = [#application] -bind - output.children += [#html/div text: "Completed {{json-string}}"] -end - - - -search - [#json/encode] - encode = [#encode-eavs] - eav = [#json/encode/eav record attribute value] - entity? = if eav = [#json/encode/entity] then "entity" - else "" -bind - encode <- [| children: - [#html/div sort: 0 text: "Encode EAVs"] - [#html/table #eav-table | children: - [#html/tr #eav-row eav children: - [#html/td sort:1 text: record] - [#html/td sort:2 text: attribute] - [#html/td sort:3 text: value] - [#html/td sort:4 text: entity?] - ] - ] - - ] -end - -search - [#json/encode] - encode = [#flatten] - [#json/encode/flatten record] -bind - encode <- [| children: - [#html/div sort: 0 text: "Flatten"] - [#html/div sort: 1 text: "{{record}}"] - ] -end - - -search - [#json/encode] -commit - [#ui/column #application | children: - [#ui/column #output | children: - [#ui/button #next sort: -1 text: "Next"] - ] - [#ui/row #debug | children: - [#ui/column #encode-eavs] - [#ui/column #flatten] - ] - ] -end - -Flattening is done recursively. We start with the target record and look up all -a/v pairs. Any v that is also a record (satisfies `lookup[entity]`) we will also -flatten. - -Raw EAVs are encoded as JSON - -//search -// encode = [#json/encode/eav record attribute value] -//watch json -// ("encode/eav", encode, record, attribute, value) -//end - -We get back a `json-string` as a change. Apply it as an attribute to the -original `json/encode` - -search - [#json/encode/change record json-string] - encode = [#json/encode record] -commit - [#html/div text: "json/encode/change {{json-string}}"] - encode.json-string += json-string -end - ## Decoding search @@ -264,13 +160,3 @@ commit decode-change := none end -commit - [#html/style text: " - td {padding: 10px;} - table {margin: 10px;} - .ui/column {padding: 10px;} - .ui/row {padding: 10px;} - .output {padding: 20px;} - .encode-eavs {min-width: 350px;} - "] -end \ No newline at end of file From 692a058d4856732d5b00d9867ce1080a7eba19b7 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 08:24:26 -0700 Subject: [PATCH 71/99] remove println --- src/watchers/json.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 75e242a..6907090 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -43,7 +43,6 @@ impl Watcher for JsonWatcher { self.name = name.to_string(); } fn on_diff(&mut self, interner:&mut Interner, diff:WatchDiff) { - println!("New Diff"); let mut changes: Vec = vec![]; for remove in diff.removes { let kind = Internable::to_string(interner.get_value(remove[0])); @@ -55,7 +54,6 @@ impl Watcher for JsonWatcher { let join_strings = self.join_strings_map.get_mut(&id).unwrap(); let index = join_strings.strings.iter().position(|x| *x == string).unwrap(); join_strings.strings.remove(index); - println!("Remove: {:?} {:?}", index, join_strings.strings); }, _ => {}, } From c26bca7806b177942392870f36c3676e1c4acae8 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 08:25:50 -0700 Subject: [PATCH 72/99] fix warnings --- src/watchers/json.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/watchers/json.rs b/src/watchers/json.rs index 6907090..f1bfc1f 100644 --- a/src/watchers/json.rs +++ b/src/watchers/json.rs @@ -5,7 +5,7 @@ use super::Watcher; extern crate serde_json; extern crate serde; -use self::serde_json::{Map, Value, Number}; +use self::serde_json::{Value}; use std::collections::HashMap; pub struct JsonWatcher { @@ -50,7 +50,6 @@ impl Watcher for JsonWatcher { "join" => { let id = Internable::to_string(interner.get_value(remove[1])); let string = Internable::to_string(interner.get_value(remove[2])); - let with = Internable::to_string(interner.get_value(remove[3])); let join_strings = self.join_strings_map.get_mut(&id).unwrap(); let index = join_strings.strings.iter().position(|x| *x == string).unwrap(); join_strings.strings.remove(index); From 529e3ae4407c675d4c0833f419388861c1e8207d Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 10:24:49 -0700 Subject: [PATCH 73/99] remove test file --- examples/test.eve | 88 ----------------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 examples/test.eve diff --git a/examples/test.eve b/examples/test.eve deleted file mode 100644 index c8f0bcb..0000000 --- a/examples/test.eve +++ /dev/null @@ -1,88 +0,0 @@ - -## Id Example 1 - -disabled -commit - [#true-condition-one value: 3] - [#true-condition-two value: 4] -end - -search - [#true-condition-one value] -bind - [#yep value] -end - -search - [#true-condition-two value] -bind - [#yep value] -end - -search - yeps = [#yep] - number = gather/count[for: yeps] -bind - [#html/div text: "I found {{number}} yeps: {{yeps}}"] -end - -disabled -commit - [#ui/button #set-value text: "Set value!"] -end - -search - [#html/event/click element: [#set-value]] - one = [#true-condition-one] - two = [#true-condition-two] -commit - one.value := 5 - two.value := 5 -end - - -## Id Example 2 - -commit - thing1 = [#thing value: 1] - thing2 = [#thing value: 2] - thing3 = [#thing value: 3] - [#cat minion: thing1] -end - -search - [#disable] - cats = [#cat minion] - number = gather/count[for: cats] -bind - [#html/div children: - [#html/div text: "I found {{number}} cats:" ] - [#ui/column cats | children: - [#ui/text sort: 1 text: " Cat Id: {{cats}}"] - [#ui/text sort: 2 text: " {{minion}}"] - ] - ] -end - -## Id Example 3 - -commit - [#system/timer resolution: 1000] - [#timer #up value: 0] - [#timer #down value: 10] -end - -search - t = [#system/timer/change tick] - up = [#timer #up] - down = [#timer #down] -commit - up.value := up.value + 1 - down.value := down.value - 1 -end - -search - [#timer value] -bind - [#html/div text: value] -end \ No newline at end of file From 3b9a66075091c50fb8969365b30b81ae9d6723a7 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 11:02:10 -0700 Subject: [PATCH 74/99] done define json twice --- src/watchers/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 8109f6b..0d0cca0 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -15,6 +15,5 @@ pub mod json; pub mod http; pub mod textcompiler; pub mod editor; -pub mod json; pub mod remote; pub mod websocket; From 2ddb1d9245e12a8d92abb6a9eb1d357a7f89b0ec Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 11:03:07 -0700 Subject: [PATCH 75/99] done use json twice --- src/bin/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index 213c77e..d7d3eed 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -32,7 +32,6 @@ use eve::watchers::http::{HttpWatcher}; use eve::watchers::json::JsonWatcher; use eve::watchers::textcompiler::{RawTextCompilerWatcher}; use eve::watchers::console::{ConsoleWatcher}; -use eve::watchers::json::JsonWatcher; use eve::watchers::file::{FileWatcher}; use eve::watchers::editor::EditorWatcher; use eve::watchers::remote::{Router, RouterMessage, RemoteWatcher}; From 54fe7898500198979799e526fbcb2cbf22cac816 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 15:03:26 -0700 Subject: [PATCH 76/99] fix error in package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5a7041..c508b5c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "postinstall-build": "^5.0.1", "setimmediate": "^1.0.5", "uuid": "^3.1.0", - "vis": "^4.20.1" + "vis": "^4.20.1", "url": "^0.11.0" }, "devDependencies": { From 026e54b85acc763088aaba903d98e07adc4d7963 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 16:10:22 -0700 Subject: [PATCH 77/99] Update http intro --- libraries/http/http.eve | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 8c18375..70eed12 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -4,9 +4,12 @@ ## Send an HTTP Request -Watch for HTTP requests. requests with multiple headers will be processed -several times, but the request is not send until the diff is completely -resolved to avoid duplicate requests. +HTTP requests accept several attributes: + +- address - the destination for the request +- method - the method of the request is one of GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH +- body - the body of the HTTP request +- headers - request headers take the form of `[#http/header key value]` search request = [#http/request address method body headers: [#http/header key value]] From b66c05813876cb93ac9914950b4003ea6911cc5a Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 18:54:02 -0700 Subject: [PATCH 78/99] fix some merge errors, use " instead of ' --- libraries/json/json.eve | 4 ++-- package.json | 2 +- src/bin/server.rs | 1 - src/watchers/mod.rs | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/json/json.eve b/libraries/json/json.eve index 70a5fde..e57ebd3 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -51,7 +51,7 @@ search eav = [#json/encode/eav record attribute value] not(eav = [#json/encode/entity]) bind - [#json/encode/entity/av-pair record av: "'{{attribute}}': '{{value}}'"] + [#json/encode/entity/av-pair record av: "\"{{attribute}}\": \"{{value}}\""] end search @@ -92,7 +92,7 @@ Put all json object strings into an array form. attaching its attribute search [#json/encode/join-object result parent attribute] bind - [#json/encode/complete-av record: parent json-string: "'{{attribute}}': [ {{result}} ]"] + [#json/encode/complete-av record: parent json-string: "\"{{attribute}}\": [ {{result}} ]"] end ### Bring it all together diff --git a/package.json b/package.json index a5a7041..c508b5c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "postinstall-build": "^5.0.1", "setimmediate": "^1.0.5", "uuid": "^3.1.0", - "vis": "^4.20.1" + "vis": "^4.20.1", "url": "^0.11.0" }, "devDependencies": { diff --git a/src/bin/server.rs b/src/bin/server.rs index 213c77e..fb71d78 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -29,7 +29,6 @@ use eve::ops::{ProgramRunner, RunLoop, RunLoopMessage, RawChange, Internable, Pe use eve::watchers::system::{SystemTimerWatcher, PanicWatcher}; use eve::watchers::compiler::{CompilerWatcher}; use eve::watchers::http::{HttpWatcher}; -use eve::watchers::json::JsonWatcher; use eve::watchers::textcompiler::{RawTextCompilerWatcher}; use eve::watchers::console::{ConsoleWatcher}; use eve::watchers::json::JsonWatcher; diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 8109f6b..3c3717d 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -11,7 +11,6 @@ pub mod file; pub mod console; pub mod system; pub mod compiler; -pub mod json; pub mod http; pub mod textcompiler; pub mod editor; From ab69895196d917bc48015245c3f6ec089bafc114 Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 1 Sep 2017 19:00:21 -0700 Subject: [PATCH 79/99] remove test --- examples/test.eve | 77 ----------------------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 examples/test.eve diff --git a/examples/test.eve b/examples/test.eve deleted file mode 100644 index c24cff4..0000000 --- a/examples/test.eve +++ /dev/null @@ -1,77 +0,0 @@ -Get the auth - -disabled -search - [#html/event/click element: [#login]] - client-id = "3354b6a98e3c4fb3b804c5a9e064a3df" - response-type = "code" - redirect-uri = string/url-encode[text: "http://localhost:8081/#spotify-callback"] - address = "https://accounts.spotify.com/authorize/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{redirect-uri}}&scope=user-read-private%20user-read-email&state=34fFs29kd09" -commit - [#html/redirect #req url: address] -end - -Display a button to login - -disabled -search - not([#logged-in]) -bind - [#ui/button #login text: "Login to Spotify"] -end - -When a code is received, the user is logged in - -disabled -search - [#html/event/page-show url: [#html/url query: [key: "code" value index]]] -commit - [#ui/text text: "Logged in"] - [#logged-in] -end - -search - //address = "https://accounts.spotify.com/api/token?grant_type={{grant-type}}&code={{code}}&redirec_uri={{redirect-uri}}" - //encoded-authorization = string/encode[text: "3354b6a98e3c4fb3b804c5a9e064a3df:b431c961f8d1457a91e0b90857bc8fa0"] - //grant-type = [] -commit - //[#http/request #goog address: "http://echo.jsontest.com/key/shenberger/one/manifold" method: "GET"] - //[#http/request address: "https://accounts.spotify.com/authorize/" headers: [#http/header key: "Authorization" value: "Basic {{encoded-authorization}}"]] -end - -search - (address, text) = if [#http/request address response] then (address, response.body) - else if [#http/request address error] then (address, error.error) -commit - [#html/div text: "{{address}} {{text}}"] -end - -disabled -search - [#req response: [#http/response status body]] -commit - [#json/decode #qq json: body] -end - - -disabled -search - [#qq json-object: [one key]] -commit - [#console/log text: "{{one}} {{key}}"] -end - - - -disabled -commit - [#http/server address: "127.0.0.1:8080"] -end - - - -search - encoded-string = string/url-encode[text: "http://localhost:8081/#spotify-callback"] -commit - [#ui/text text: encoded-string] -end \ No newline at end of file From f95d7cfc7d282a081f7dbb40a3a4b1f23b7f8373 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 4 Sep 2017 18:22:54 -0700 Subject: [PATCH 80/99] App skeletons and UI --- .gitignore | 1 + libraries/app/app.eve | 149 ++++++++++++++++ libraries/html/html.eve | 1 + libraries/ui/ui.eve | 381 +++++++++++++++++++++++++--------------- 4 files changed, 389 insertions(+), 143 deletions(-) create mode 100644 libraries/app/app.eve diff --git a/.gitignore b/.gitignore index b84b594..d9c9084 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/ dist/ .vscode/tasks.json examples/test.eve +*.log diff --git a/libraries/app/app.eve b/libraries/app/app.eve new file mode 100644 index 0000000..ba67c75 --- /dev/null +++ b/libraries/app/app.eve @@ -0,0 +1,149 @@ +# App + +This library provides a set of components to assist in quickly prototyping +single and multi page applications. + +## Components + +An app has three components: + +- `#app/settings` - Holds app-wide configuration data +- `#app/page` - A page of your application. Holds the name of the page, store + page data, and hold the UI of a page. +- `#app/ui` - Holds the layout of the application. UI stored on an `#app/page` + is injected into this layout for display. + +## Configuration + +The `#app/settings` record is a place to store app-wide configuration data. You +can attach any records you like here, and they will be available for viewing +and editing in the `#app/page/settings` record, which will be displayed by +default in your app. + +Attributes of `#app/settings`: + +- name - the name of your application. Will be displayed in the header +- settings - forms that allow you to view and edit configuration settings + of your app. + +## Pages + +`#app/page`s are the core of your application. These are the top-level sections +of your application, and will be accessible by a button in the navigation bar. +You can of course have sub pages as well. The navigation bar can be configured +to display these. + +Attributes of an `#app/page`: + +- name - Required: the name of the page +- icon - an optional icon for your page. This will be displayed on the nav bar. +- sort - an optoinal sorting for your page. Determines the order pages are + displayed in the navigation bar. + +## Layout + +An `#app/ui` has four basic components: + +- Header - Located at the top of the application, spanning the entire width +- Navigation - Contains controls that switch between pages in an application. + Can be located on any of the sides of an appliation. +- Content - Contains the pages of an application +- Footer - Located at the bottom of an application, spanning the entire width. + +search + app-ui = [#app/ui] +bind + app-ui.interface += [#app/layout #ui/column | children: + [#app/layout/header sort: 1] + [#app/layout/content sort: 3] + [#app/layout/footer sort: 5]] +end + +### Header + +The `#app/layout/header` spans the entire width of the top of the application. It +automatically displays the name of your application, and contains two +containers, `#app/layout/header/left` and `#app/layout/header/right` +for displaying controls at the top corners of your application. + +search + header = [#app/layout/header] + [#app/settings name] +bind + header <- [#ui/row | children: + [#html/div #app/layout/header/left sort: 1] + [#html/div #app/layout/header/middle sort: 2 text: name] + [#html/div #app/layout/header/right sort: 3]] +end + +### Content and Navigation + +search + content = [#app/layout/content] +bind + content <- [#ui/row | children: + [#app/layout/navigation sort: 1] + [#app/layout/page-container sort: 2]] +end + +#### Navigation + +search + nav = [#app/layout/navigation] + page = [#app/page name icon sort] +bind + nav <- [#ui/column | children: + [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: + [#app/layout/navigation-button #ui/button icon page text: name sort]] + [#ui/spacer #nav-spacer sort: 99]] +end + +Clicking a navigation button switches the page. + +search + [#html/event/click element: [#app/layout/navigation-button page]] + ui = [#app/ui] +commit + ui.page := page +end + +#### Page Container + +search + page-container = [#app/layout/page-container] + [#app/ui page] +bind + page-container <- [#html/div #app/layout/page | children: page] +end + +### Footer + +The footer spans the entire width of the bottom of the application. It +contains three containers`#app/layout/footer/left`, `#app/layout/footer/middle` +and `#app/layout/footer/right`. + +search + header = [#app/layout/footer] +bind + header <- [#ui/row | children: + [#html/div #app/layout/footer/left sort: 1] + [#html/div #app/layout/footer/middle sort: 2 text: "Footer"] + [#html/div #app/layout/footer/right sort: 3]] +end + +## Styles + +commit + [#html/style text: " + .app-layout { background-color: rgb(255,255,255); width: 100vw; height: 100vh; color: rgb(80,80,80); font-family: sans-serif; user-select: none; cursor: default; display: flex; } + .app-layout-header { background-color: rgb(200,200,200); display: flex; justify-content: space-between; } + .app-layout-header-middle { padding: 10px; } + .app-layout-footer { background-color: rgb(200,200,200); display: flex; justify-content: space-between; } + .app-layout-footer-middle { padding: 10px; } + .app-layout-content {flex-grow: 1;} + .app-layout-page-container { padding: 10px; } + .app-layout-navigation { background-color: rgb(130,130,130); } + .app-layout-navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(230,230,230); margin: 0px; border: 0px; border-radius: 0px; width: 100%; min-height: 75px; } + .app-layout-navigation-button.ui-selected { background-color: rgb(255,255,255); color: rgb(0,158,224); } + "] +end \ No newline at end of file diff --git a/libraries/html/html.eve b/libraries/html/html.eve index 033f365..5468826 100644 --- a/libraries/html/html.eve +++ b/libraries/html/html.eve @@ -118,6 +118,7 @@ search type != "checkbox" type != "submit" type != "radio" + type != "range" then input if input = [#html/element tagname: "input"] not(input.type) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 61e4e5b..565068c 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -2,17 +2,17 @@ ## Deprecation Warnings -~~~ eve + search [#html/shortcut-tag shortcut tagname] not([#ui/shortcut-tag tagname]) bind [#ui/deprecated-shortcut-tag shortcut: "ui/{{tagname}}" new-shortcut: shortcut tagname] end -~~~ + Report deprecated shortcuts as warnings. -~~~ eve + search [#ui/deprecated-shortcut-tag shortcut: tag new-shortcut tagname] element = [tag] @@ -20,12 +20,12 @@ bind [#eve/warning #ui/warning #eve/deprecated message: "The shortcut tag '#{{tag}}' for creating basic HTML elements has been deprecated. Instead, use '#{{new-shortcut}}'."] element <- [#html/element tagname] end -~~~ + ## Shortcut Tags -~~~ eve + commit [#ui/shortcut-tag shortcut: "ui/row" tagname: "row"] [#ui/shortcut-tag shortcut: "ui/column" tagname: "column"] @@ -34,51 +34,51 @@ commit [#ui/shortcut-tag shortcut: "ui/button" tagname: "button"] [#ui/shortcut-tag shortcut: "ui/input" tagname: "input"] end -~~~ + Decorate shortcut elements as html. -~~~ eve + search [#ui/shortcut-tag shortcut: tag tagname] element = [tag] bind element <- [#html/element tagname] end -~~~ + ## General Setup Clear ui events. -~~~ eve + search event = [#ui/event] commit event := none end -~~~ + Translate bubble event names to event tags. -~~~ eve + search bubble = [#ui/bubble-event event: name] bind bubble.event-tag += "ui/event/{{name}}" end -~~~ + Bubble ui events. -~~~ eve + search event = [#ui/event tag: event-tag element: from] [#ui/bubble-event event-tag from to] bind event.element += to end -~~~ + Translate state/start/stop event names to event tags. -~~~ eve + search transition = [#ui/state-tag state start-event stop-event] bind @@ -86,46 +86,46 @@ bind transition.start-tag += "ui/event/{{start-event}}" transition.stop-tag += "ui/event/{{stop-event}}" end -~~~ + Apply state when start-event occurs. -~~~ eve + search [#ui/event tag: start-tag element: for] [#ui/state-tag for state-tag start-tag] commit for.tag += state-tag end -~~~ + Remove state when stop-event occurs. -~~~ eve + search [#ui/event tag: stop-tag element: for] [#ui/state-tag for state-tag stop-tag] commit for.tag -= state-tag end -~~~ + ## Buttons Give button elements icons if specified. -~~~ eve + search element = [#ui/button icon] bind element.class += "iconic" element.class += "ion-{{icon}}" end -~~~ + ## Toggle A toggle is a checkbox and a label decorated as a toggle switch. -~~~ eve + search element = [#ui/toggle] bind @@ -133,20 +133,20 @@ bind [#html/element tagname: "label" for: "ui-toggle-{{element}}"] [#html/input #ui/toggle/input type: "checkbox" id: "ui-toggle-{{element}}"]] end -~~~ + Copy checked from input to toggle. -~~~ eve + search element = [#ui/toggle children: [#ui/toggle/input checked]] bind element.checked += checked end -~~~ + Copy initial from toggle to input. -~~~ eve + search element = [#ui/toggle initial children: input] input = [#ui/toggle/input] @@ -154,33 +154,33 @@ search bind input.initial += initial end -~~~ + ## List Decorate list as html. -~~~ eve + search list = [#ui/list] bind list <- [#html/element tagname: "div"] end -~~~ + Drop items into list (will eventually be flywheel'd). -~~~ eve + search list = [#ui/list item] bind list.children += item end -~~~ + ## Selectable The default cursor for a selectable list is the first item. -~~~ eve + search selectable = [#ui/selectable item] not(selectable.cursor) @@ -188,30 +188,30 @@ search commit selectable.cursor := item end -~~~ + If the cursor is no longer an item, clear it. -~~~ eve + search selectable = [#ui/selectable cursor] not(selectable = [#ui/selectable item: cursor]) commit selectable.cursor := none end -~~~ + Selectable items are sorted by autosort if they don't specify a sort. -~~~ eve + search selectable = [#ui/selectable item] sort = if s = item.sort then s else item.eve-auto-index bind item.sort += sort end -~~~ + Build a linked list of the items in the selectable for navigation. -~~~ eve + search selectable = [#ui/selectable item] (next-sort next) = gather/next[for: (item.sort item) per: selectable] @@ -219,71 +219,71 @@ bind item.next-selectable-item += next next.prev-selectable-item += item end -~~~ + Mark the currently selected element. -~~~ eve + search [#ui/selectable selected] bind selected += #ui/selected end -~~~ + Mark the cursor element. -~~~ eve + search [#ui/selectable #ui/active cursor] bind cursor += #ui/current end -~~~ + A focused selectable is active. -~~~ eve + search selectable = [#ui/selectable #html/focused] bind selectable += #ui/active end -~~~ + ### Handlers If a selectable is focused by the client, activate it. -~~~ eve + search selectable = [#ui/selectable] [#html/event/focus element: selectable] bind [#ui/event #ui/event/activate element: selectable] end -~~~ + If a selectable is blurred by the client, deactivate it. -~~~ eve + search selectable = [#ui/selectable] [#html/event/blur element: selectable] bind [#ui/event #ui/event/deactivate element: selectable] end -~~~ + Clicking an item in a selectable selects the item. -~~~ eve + search selectable = [#ui/selectable item] [#html/event/mouse-down element: item] bind [#ui/event #ui/event/select element: selectable item] end -~~~ + Clicking outside an active selectable deactivates it. @FIXME: This just won't work :( It clashes when used as a subcomponent. -~~~ eve + // search // selectable = [#ui/selectable #ui/active] // [#html/event/click] @@ -292,10 +292,10 @@ Clicking outside an active selectable deactivates it. // bind // [#ui/event #ui/event/deactivate element: selectable] // end -~~~ + Escape or tab in a active selectable deactivates it. -~~~ eve + search selectable = [#ui/selectable #ui/active] event = if e = [#html/event/key-down key: "escape"] then e @@ -304,124 +304,124 @@ search bind [#ui/event #ui/event/deactivate element: selectable] end -~~~ + Enter in a active selectable selects its cursor. -~~~ eve + search selectable = [#ui/selectable #ui/active cursor:item] [#html/event/key-down key: "enter"] bind [#ui/event #ui/event/select element: selectable item] end -~~~ + Down in a active selectable advances the cursor. -~~~ eve + search selectable = [#ui/selectable #ui/active cursor] event = [#html/event/key-down key: "down"] commit selectable.cursor := cursor.next-selectable-item end -~~~ + Up in a active selectable retreats the cursor. -~~~ eve + search selectable = [#ui/selectable #ui/active cursor] [#html/event/key-down key: "up"] commit selectable.cursor := cursor.prev-selectable-item end -~~~ + ### Events Describe selectable states. -~~~ eve + search selectable = [#ui/selectable] bind [#ui/state-tag for: selectable state: "active" start-event: "activate" stop-event: "deactivate"] end -~~~ + Selecting an element updates the selected and cursor. -~~~ eve + search event = [#ui/event/select element: selectable item] commit selectable.selected += item selectable.cursor := item end -~~~ + In a single-selectable selectable, selection overwrites the previous selected. -~~~ eve + search event = [#ui/event/select element: selectable item] selectable = [#ui/single-selectable] commit selectable.selected := item end -~~~ + Clearing a selectable clears its selected. -~~~ eve + search event = [#ui/event/clear element: selectable] commit selectable.selected := none end -~~~ + ### Dropdown Decorate dropdown as html. -~~~ eve + search dropdown = [#ui/dropdown] bind dropdown <- [#html/element tagname: "div"] end -~~~ + A dropdown's first child is its input. -~~~ eve + search dropdown = [#ui/dropdown input] bind dropdown.children += input input.sort += 1 end -~~~ + A dropdown creates a selectable list of its items. -~~~ eve + search dropdown = [#ui/dropdown item] bind dropdown.children += [#ui/dropdown/list #ui/list #ui/selectable #ui/single-selectable dropdown container: dropdown sort: 999999 | item] end -~~~ + A dropdown's selected is its list's -~~~ eve + search [#ui/dropdown/list dropdown selected] bind dropdown.selected += selected end -~~~ + ### Handlers Clicking a dropdown opens it. -~~~ eve + search dropdown = [#ui/dropdown] not(dropdown = [#ui/active]) @@ -429,10 +429,10 @@ search bind [#ui/event #ui/event/activate element: dropdown] end -~~~ + Clicking anywhere outside an open dropdown closes it. -~~~ eve + search dropdown = [#ui/dropdown #ui/active] [#html/event/click] @@ -440,13 +440,13 @@ search bind [#ui/event #ui/event/deactivate element: dropdown] end -~~~ + ### Events Describe dropdown event bubbling and states. -~~~ eve + search dropdown = [#ui/dropdown input] list = [#ui/dropdown/list dropdown] @@ -455,83 +455,83 @@ bind [#ui/bubble-event from: list to: dropdown event: ("activate" "deactivate" "clear" "select")] [#ui/state-tag for: dropdown state: "active" start-event: "activate" stop-event: "deactivate"] end -~~~ + Opening a button-based dropdown blurs the button. -~~~ eve + search [#ui/event/activate element: dropdown] dropdown.input.tagname = "button" commit dropdown.input += #html/trigger/blur end -~~~ + ## Completer Decorate completer. -~~~ eve + search completer = [#ui/completer] bind completer <- [#ui/dropdown input: [#ui/input #ui/completer/input completer]] end -~~~ + ### Setup Copy input placeholder. -~~~ eve + search input = [#ui/completer/input completer] bind input.placeholder += completer.placeholder end -~~~ + Copy input initial. -~~~ eve + search input = [#ui/completer/input completer] bind input.initial += completer.initial end -~~~ + A completer's value is its input's. -~~~ eve + search completer = [#ui/completer] value = if [#ui/completer/input completer value: v] then v else "" bind completer.value += value end -~~~ + ### Handlers Focusing the completer opens the dropdown. -~~~ eve + search input = [#ui/completer/input completer] [#html/event/focus element: input] bind [#ui/event #ui/event/activate element: completer] end -~~~ + Blurring the completer closes the dropdown. -~~~ eve + search input = [#ui/completer/input completer] [#html/event/blur element: input] bind [#ui/event #ui/event/deactivate element: completer] end -~~~ + Changing the completer moves the cursor to the top of the list. -~~~ eve + search completer = [#ui/completer #ui/active item] list = [#ui/dropdown/list dropdown: completer] @@ -541,57 +541,57 @@ search commit list.cursor := item end -~~~ + ### Events Closing a completer blurs it. -~~~ eve + search [#ui/event/deactivate element: completer] completer = [#ui/completer input] commit input += #html/trigger/blur end -~~~ + Opening a completer focuses it. -~~~ eve + search [#ui/event/activate element: completer] completer = [#ui/completer input] commit input += #html/trigger/focus end -~~~ + ## Autocomplete Decorate autocomplete -~~~ eve + search completer = [#ui/autocomplete] bind completer <- [#ui/completer] end -~~~ + ### Logic If an autocomplete's value disagrees with its selected, clear the selected. -~~~ eve + search completer = [#ui/autocomplete value: term selected] selected.text != term commit [#ui/event #ui/event/clear element: completer] end -~~~ + Completions that match the current input value are matches, sorted by length. -~~~ eve + search completer = [#ui/autocomplete value: term completion] ix = string/index-of[text: completion.text substring: string/lowercase[text: term]] @@ -600,12 +600,12 @@ bind completer.item += completion completion.sort += "{{sort}}{{completion.text}}" end -~~~ + ### Handlers If the value matches perfectly on blur, select that match. -~~~ eve + search input = [#ui/completer/input completer] completer = [#ui/autocomplete] @@ -616,33 +616,33 @@ search bind [#ui/event #ui/event/select element: completer item: match] end -~~~ + Autocompletes update their values on select. -~~~ eve + search autocomplete = [#ui/autocomplete input] [#ui/event/select item element: autocomplete] commit input.value := item.text end -~~~ + ### Events Clear the specified autocomplete. -~~~ eve + search event = [#ui/event/clear element: autocomplete] input = [#ui/autocomplete/input autocomplete] commit input.value := none end -~~~ + When an autocomplete is opened, store its previous value. -~~~ eve + search event = [#ui/event/activate element: autocomplete] input = [#ui/autocomplete/input autocomplete] @@ -650,74 +650,74 @@ search commit autocomplete.previous := value end -~~~ + When an autocomplete is closed, erase its previous value. -~~~ eve + search event = [#ui/event/deactivate element: autocomplete] input = [#ui/autocomplete/input autocomplete] commit autocomplete.previous := none end -~~~ + When an autocomplete is closed and its value is changed, emit a change event. -~~~ eve + search event = [#ui/event/deactivate element: autocomplete] autocomplete.value != autocomplete.previous commit [#ui/event #ui/event/change element: autocomplete value: autocomplete.value] end -~~~ + When a selection is made that differs from the previous value, emit a change event. -~~~ eve + search event = [#ui/event/select element: autocomplete item] item.text != autocomplete.previous commit [#ui/event #ui/event/change element: autocomplete value: item.text] end -~~~ + ## Token Completer Token completers are completers. -~~~ eve + search completer = [#ui/token-completer] bind completer <- [#ui/completer #html/listener/key | captured-key: ("space" "up" "down")] end -~~~ + Token items are divs. -~~~ eve + search completer = [#ui/token-completer item] bind item += #html/div end -~~~ + ### Logic The current term is the last word of the value. -~~~ eve + search completer = [#ui/token-completer value] (token, 1) = eve-internal/string/split-reverse[text: value by: " "] bind completer.needle += token end -~~~ + Completions that match the current input value are matches, sorted by length. -~~~ eve + search completer = [#ui/token-completer needle: term completion] ix = string/index-of[text: string/lowercase[text: completion.text] substring: string/lowercase[text: term]] @@ -726,32 +726,32 @@ bind completer.item += completion completion.sort += "{{sort}}{{completion.text}}" end -~~~ + Space-separated words are tokens of the completer. -~~~ eve + search completer = [#ui/token-completer value] (token, ix) = string/split[text: value by: " "] bind completer.token += [#ui/token-completer/token token ix] end -~~~ + Track the index of the last token. -~~~ eve + search completer = [#ui/token-completer token: [ix]] gather/top[for: ix per: completer limit: 1] bind completer.last-ix += ix end -~~~ + ### Handlers Token completers append the new completed token in place of the in progress token on select. -~~~ eve + search event = [#ui/event/select element: completer item] input = [#ui/completer/input completer] @@ -762,10 +762,10 @@ search commit input.value := "{{value}}{{item.text}} " end -~~~ + Token completers without an in-progress token just append the new one. -~~~ eve + search event = [#ui/event/select element: completer item] input = [#ui/completer/input completer] @@ -773,10 +773,10 @@ search commit input.value := "{{completer.value}}{{item.text}} " end -~~~ + Space in a active selectable selects its cursor. -~~~ eve + search completer = [#ui/token-completer #ui/active] list = [#ui/dropdown/list dropdown: completer cursor:item] @@ -784,9 +784,104 @@ search bind [#ui/event #ui/event/select element: list item] end -~~~ +## Progress + +search + progress = [#ui/progress min max value width] + val = if value >= max then max else value + //percentage = val / (max - min) + //percentage-display = math/to-fixed[value: percentage * 100 to: 0] + //progress-width = width * val / (max - min) + //display = if progress = [#label] then "inline" + // else "none" +bind + progress <- [#ui/row children: + [#html/div progress sort: 2 text: value] + // [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] + [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"]]] + // [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] +end + +Progress bars at 100% are marked `#ui/progress/complete` + +search + progress = [#ui/progress max value] + max = value +bind + progress += #ui/progress/complete +end + +## Slider + +search + slider = [#ui/slider min max] + initial-value = if slider.initial-value then slider.initial-value else min +bind + slider <- [#ui/input type: "range" initial-value] +end + +If a slider as an `initial-value` but no calculated `value`, set it + +search + slider = [#ui/slider initial-value] + not(slider.value) +commit + slider.value := initial-value +end + +An initial value out of range of the slider is set to its closest extreme and a warning is issued + +search + slider = [#ui/slider min max] + value = if slider.value > max then max + else if slider.value < min then min +commit + slider.value := value + [#console/warn text: "Value of slider ({{slider.initial-value}}) is outside of the bounds of the slider"] +end + +When the user changes the slider value, update its `value` in Eve + +search + [#html/event/change element: slider value] + slider = [#ui/slider] +commit + slider.value := value +end + +## Styles + +commit + [#html/style text: " + .ui-progress {margin: 10px;} + .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} + .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } + .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; transition: width 0.5s; } + input[type=range]{ -webkit-appearance: none; } + + input[type=range]::-webkit-slider-runnable-track { + width: 300px; + height: 7px; + background: #ccc; + border: none; + border-radius: 3px; + } + + input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: rgb(0,158,224); + margin-top: -5px; + } + input[type=range]:focus { outline: none; } + input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } + "] +end Todo: @@ -801,7 +896,7 @@ Todo: - list (navigable) - [ ] container - card -- [ ] progress +- [x] progress - [ ] Tree - [ ] chip - [ ] date picker From a7cb593bcb478bd9888999c1e69b407cc4ceb140 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 4 Sep 2017 18:29:43 -0700 Subject: [PATCH 81/99] fix sliders --- libraries/ui/ui.eve | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 565068c..2e1302c 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -790,18 +790,17 @@ end search progress = [#ui/progress min max value width] - val = if value >= max then max else value - //percentage = val / (max - min) - //percentage-display = math/to-fixed[value: percentage * 100 to: 0] - //progress-width = width * val / (max - min) - //display = if progress = [#label] then "inline" - // else "none" + val = if value > max then max else value + percentage = val / (max - min) + percentage-display = math/to-fixed[value: percentage * 100 to: 0] + progress-width = width * val / (max - min) + display = if progress = [#label] then "inline" + else "none" bind - progress <- [#ui/row children: - [#html/div progress sort: 2 text: value] - // [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] - [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"]]] - // [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] + progress <- [#ui/row | children: + [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] + [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"] children: + [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] end Progress bars at 100% are marked `#ui/progress/complete` @@ -847,8 +846,9 @@ When the user changes the slider value, update its `value` in Eve search [#html/event/change element: slider value] slider = [#ui/slider] + numeric-value = eve/parse-value[value] commit - slider.value := value + slider.value := numeric-value end ## Styles @@ -858,7 +858,7 @@ commit .ui-progress {margin: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } - .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; transition: width 0.5s; } + .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; } input[type=range]{ -webkit-appearance: none; } input[type=range]::-webkit-slider-runnable-track { From c013f404856cebc12acc40724e31d6b10dde3a84 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 10:25:39 -0700 Subject: [PATCH 82/99] resizeable spinner and tabbed box --- libraries/ui/ui.eve | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 2e1302c..cfe81f6 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -851,10 +851,95 @@ commit slider.value := numeric-value end +## Tabbed Box + +search + tab-container = [#ui/tabbed-box tabs] +bind + tab-container <- [#html/div children: + [#ui/tabbed-box/tab-row #ui/row | children: + [#html/div #ui/tabbed-box/tab-display tab-container tab: tabs sort: tabs.sort, text: tabs.title]] + [#ui/tabbed-box/content tabs tab-container]] +end + +Sort tabs by `title` if no sort is specified + +search + tab = [#ui/tabbed-box/tab title] + sort = if tab.sort then tab.sort else tab.title +commit + tab.sort := sort +end + +Inject `#selected` tab contents into the `#ui/tabbed-box` for display + +search + content-container = [#ui/tabbed-box/content tabs: [#ui/tabbed-box/tab #selected title]] +bind + content-container <- [#html/div text: title] +end + +If no tabs are `#selected`, then the first tab is `#selected` + + +## Spinner + +search + spinner = [#ui/spinner size] + radius = "{{size}}px" + diameter = "{{size * 2}}px" + border = "{{size / 4}}px solid rgb(0,158,224)" + +bind + [#html/div text: size] + spinner <- [#html/div #top #rotate | style: [ + height: radius + width: diameter + border-top-left-radius: diameter + border-top-right-radius: diameter + border-left: border + border-right: border + border-top: border + ]] +end + +Give spinners a default size if none is specified + +search + spinner = [#ui/spinner] + not(spinner = [size]) +commit + spinner.size += 10 +end + +Remove the default size if one is set adter the fact + +search + spinner = [#ui/spinner size != 25] +commit + spinner.size -= 25 +end + +Clicking on a tab changes the selected tab + +search + [#html/event/click element: [#ui/tabbed-box/tab-display tab-container tab: clicked-tab]] + [#ui/tabbed-box/tab-display tab: selected-tab] + selected-tab = [#selected] + selected-tab != clicked-tab +commit + selected-tab -= #selected + clicked-tab += #selected +end + ## Styles commit [#html/style text: " + .ui-tabbed-box {border: 1px solid rgb(230,230,230);} + .ui-tabbed-box-tab-display { background-color: rgb(230,230,230); padding: 20px;} + .ui-tabbed-box-tab-display:hover { background-color: rgb(240,240,240); } + .ui-tabbed-box-content {padding: 20px;} .ui-progress {margin: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } @@ -880,6 +965,30 @@ commit } input[type=range]:focus { outline: none; } input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } + +.top { + background-color: rgba(0,0,0,0); +} + +.rotate { + animation: 1s linear infinite rotate; + position: relative; + transform-origin: 50% 100%; +} + +@keyframes rotate { + from { + transform: rotate(0deg) + } + to { + transform: rotate(360deg) + } +} + + + + + "] end From 5e9c99eb5f62e6be0a6ea7901dc3f6d6a6fa5b1f Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 15:02:15 -0700 Subject: [PATCH 83/99] fix tabbed box displaying extra content --- libraries/ui/ui.eve | 101 ++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index cfe81f6..0b4a1a5 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -853,34 +853,57 @@ end ## Tabbed Box +Draw the `#ui/tabbed-box` + search tab-container = [#ui/tabbed-box tabs] bind - tab-container <- [#html/div children: + tab-container <- [#html/div | children: [#ui/tabbed-box/tab-row #ui/row | children: - [#html/div #ui/tabbed-box/tab-display tab-container tab: tabs sort: tabs.sort, text: tabs.title]] - [#ui/tabbed-box/content tabs tab-container]] + [#html/div #ui/tabbed-box/tab-display tab: tabs sort: tabs.title text: tabs.title]] + [#ui/tabbed-box/content-display tabs container: tab-container]] end -Sort tabs by `title` if no sort is specified +Give a default tag search - tab = [#ui/tabbed-box/tab title] - sort = if tab.sort then tab.sort else tab.title + content = [#ui/tabbed-box/content-display tabs] + gather/bottom[for: tabs limit: 1] commit - tab.sort := sort + tabs += #selected end -Inject `#selected` tab contents into the `#ui/tabbed-box` for display +Apply the selected tag to the `tag-display` for special styling search - content-container = [#ui/tabbed-box/content tabs: [#ui/tabbed-box/tab #selected title]] + tab = [#ui/tabbed-box/tab #selected title] + display = [#ui/tabbed-box/tab-display tab] bind - content-container <- [#html/div text: title] + display += #selected end -If no tabs are `#selected`, then the first tab is `#selected` +Inject tab content into the content are of thet `#ui/tabbed-box` +search + content-display = [#ui/tabbed-box/content-display tabs] + tabs = [#ui/tabbed-box/tab #selected] + [#ui/tabbed-box/content tab: tabs content] +bind + content-display <- [#html/div #ui/tabbed-box/has-content] + content-display.children += content +end + +Clicking on a tab changes the selected tab + +search + [#html/event/click element: [#ui/tabbed-box/tab-display tab: clicked-tab]] + [#ui/tabbed-box/tab-display tab: selected-tab] + selected-tab = [#selected] + selected-tab != clicked-tab +commit + selected-tab -= #selected + clicked-tab += #selected +end ## Spinner @@ -891,16 +914,14 @@ search border = "{{size / 4}}px solid rgb(0,158,224)" bind - [#html/div text: size] - spinner <- [#html/div #top #rotate | style: [ + spinner <- [#html/div #ui/spinner/circle #ui/spinner/rotate | style: [ height: radius width: diameter border-top-left-radius: diameter border-top-right-radius: diameter border-left: border border-right: border - border-top: border - ]] + border-top: border]] end Give spinners a default size if none is specified @@ -920,26 +941,15 @@ commit spinner.size -= 25 end -Clicking on a tab changes the selected tab - -search - [#html/event/click element: [#ui/tabbed-box/tab-display tab-container tab: clicked-tab]] - [#ui/tabbed-box/tab-display tab: selected-tab] - selected-tab = [#selected] - selected-tab != clicked-tab -commit - selected-tab -= #selected - clicked-tab += #selected -end - ## Styles commit [#html/style text: " .ui-tabbed-box {border: 1px solid rgb(230,230,230);} .ui-tabbed-box-tab-display { background-color: rgb(230,230,230); padding: 20px;} + .ui-tabbed-box-tab-display.selected { border-bottom: 3px solid rgb(0,158,224);} .ui-tabbed-box-tab-display:hover { background-color: rgb(240,240,240); } - .ui-tabbed-box-content {padding: 20px;} + .ui-tabbed-box-content-display {padding: 20px;} .ui-progress {margin: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } @@ -966,29 +976,17 @@ commit input[type=range]:focus { outline: none; } input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } -.top { - background-color: rgba(0,0,0,0); -} - -.rotate { - animation: 1s linear infinite rotate; - position: relative; - transform-origin: 50% 100%; -} - -@keyframes rotate { - from { - transform: rotate(0deg) - } - to { - transform: rotate(360deg) - } -} - - - - + .ui-spinner-circle { background-color: rgba(0,0,0,0); margin: 10px; } + .ui-spinner-rotate { animation: 1s linear infinite ui-spinner-rotate; position: relative; transform-origin: 50% 100%; } + @keyframes ui-spinner-rotate { + from { + transform: rotate(0deg) + } + to { + transform: rotate(360deg) + } + } "] end @@ -999,13 +997,14 @@ Todo: - [x] Dropdown - [x] *bug* commit removal not working for enter / click (blurs list -> closes dropdown) - [x] *bug* gather/bottom filtering randomly (always includes correct answer + optionally any others in set). -- [ ] Tab Box +- [x] Tab Box - [ ] Rewrite AC on Dropdown - input - list (navigable) - [ ] container - card - [x] progress +- [x] spinner - [ ] Tree - [ ] chip - [ ] date picker From f37c3b35f116957cdc2bd14b7aa4a0f664103e13 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 17:28:07 -0700 Subject: [PATCH 84/99] draw a fancy slider --- libraries/ui/ui.eve | 65 +++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 0b4a1a5..577c194 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -794,13 +794,13 @@ search percentage = val / (max - min) percentage-display = math/to-fixed[value: percentage * 100 to: 0] progress-width = width * val / (max - min) - display = if progress = [#label] then "inline" - else "none" + display = if progress = [#label] then "inline" else "none" + color = if progress.color then progress.color else "rgb(111, 165, 81)" bind progress <- [#ui/row | children: [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"] children: - [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] + [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px" background-color: color]]]] end Progress bars at 100% are marked `#ui/progress/complete` @@ -815,19 +815,21 @@ end ## Slider search - slider = [#ui/slider min max] + slider = [#ui/slider min max width] initial-value = if slider.initial-value then slider.initial-value else min bind - slider <- [#ui/input type: "range" initial-value] + slider <- [#html/div children: [#ui/input min max slider type: "range" initial-value style: [width: "{{width}}px"]]] end If a slider as an `initial-value` but no calculated `value`, set it search slider = [#ui/slider initial-value] + input = [#ui/input slider] not(slider.value) commit slider.value := initial-value + input.value := initial-value end An initial value out of range of the slider is set to its closest extreme and a warning is issued @@ -841,16 +843,40 @@ commit [#console/warn text: "Value of slider ({{slider.initial-value}}) is outside of the bounds of the slider"] end -When the user changes the slider value, update its `value` in Eve +When the user changes the slider value in the UI, update its `value` in Eve search - [#html/event/change element: slider value] - slider = [#ui/slider] + [#html/event/change element: input value] + input = [#ui/input slider] numeric-value = eve/parse-value[value] commit slider.value := numeric-value end +Sliders without a width are given a default + +search + slider = [#ui/slider] + not(slider = [width]) +commit + slider.width := 400 +end + +Draw a progress bar under the slider + +search + slider = [#ui/slider min max value width] +bind + slider.children += [#ui/progress #slider-progress slider min max width sort: 0 | color: "rgb(0,121,177)" value] +end + +search + [#ui/slider value] +bind + [#html/div text: value] +end + + ## Tabbed Box Draw the `#ui/tabbed-box` @@ -950,31 +976,30 @@ commit .ui-tabbed-box-tab-display.selected { border-bottom: 3px solid rgb(0,158,224);} .ui-tabbed-box-tab-display:hover { background-color: rgb(240,240,240); } .ui-tabbed-box-content-display {padding: 20px;} - .ui-progress {margin: 10px;} + .ui-progress {padding: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } - .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; } - input[type=range]{ -webkit-appearance: none; } - + .ui-progress-bar { height: 100%; border-radius: 5px; } + input[type=range]{ -webkit-appearance: none; background-color: rgba(0,0,0,0);} + .ui-slider {margin: 10px; margin-left: 8px; } + .slider-progress {padding: 0px; padding-left: 2px; margin-bottom: -13px; } input[type=range]::-webkit-slider-runnable-track { - width: 300px; - height: 7px; - background: #ccc; + height: 10px; + background: rgba(0,0,0,0); border: none; - border-radius: 3px; + border-radius: 5px; } input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; - border: none; - height: 16px; - width: 16px; + height: 20px; + width: 20px; border-radius: 50%; background: rgb(0,158,224); margin-top: -5px; } input[type=range]:focus { outline: none; } - input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } + input[type=range]:focus::-webkit-slider-runnable-track { background: rgba(0,0,0,0); } .ui-spinner-circle { background-color: rgba(0,0,0,0); margin: 10px; } .ui-spinner-rotate { animation: 1s linear infinite ui-spinner-rotate; position: relative; transform-origin: 50% 100%; } From 605ba9caa0401d6113823b3abe77b2a2b30c8c06 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 17:29:11 -0700 Subject: [PATCH 85/99] delete accidental file --- person.json | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 person.json diff --git a/person.json b/person.json deleted file mode 100644 index e33e13e..0000000 --- a/person.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Solver": "clingo version 4.5.4", - "Input": [ - "puzzle_easy.lp","sudoku.lp" - ], - "Call": [ - { - "Witnesses": [ - { - "Value": [ - "paint(1,1,7)", "paint(1,3,3)", "paint(1,5,8)", "paint(1,8,1)", "paint(2,5,6)", "paint(3,1,6)", "paint(3,3,8)", "paint(3,5,5)", "paint(3,6,7)", "paint(3,7,4)", "paint(3,9,9)", "paint(4,4,8)", "paint(4,5,9)", "paint(4,7,1)", "paint(4,8,2)", "paint(4,9,5)", "paint(5,3,2)", "paint(5,7,6)", "paint(6,1,8)", "paint(6,2,1)", "paint(6,3,6)", "paint(6,5,2)", "paint(6,6,5)", "paint(7,1,3)", "paint(7,3,5)", "paint(7,4,2)", "paint(7,5,4)", "paint(7,7,9)", "paint(7,9,1)", "paint(8,5,7)", "paint(9,2,7)", "paint(9,5,1)", "paint(9,7,5)", "paint(9,9,2)", "paint(1,2,5)", "paint(1,4,9)", "paint(1,6,4)", "paint(1,7,2)", "paint(1,9,6)", "paint(2,1,1)", "paint(2,2,4)", "paint(2,3,9)", "paint(2,4,3)", "paint(2,6,2)", "paint(2,7,8)", "paint(2,8,5)", "paint(2,9,7)", "paint(3,2,2)", "paint(3,4,1)", "paint(3,8,3)", "paint(4,2,3)", "paint(4,1,4)", "paint(4,6,6)", "paint(4,3,7)", "paint(5,4,7)", "paint(5,5,3)", "paint(5,8,4)", "paint(5,6,1)", "paint(6,4,4)", "paint(6,9,3)", "paint(6,7,7)", "paint(7,2,6)", "paint(7,8,7)", "paint(8,1,2)", "paint(5,1,5)", "paint(5,9,8)", "paint(5,2,9)", "paint(6,8,9)", "paint(7,6,8)", "paint(8,3,1)", "paint(8,7,3)", "paint(8,8,6)", "paint(8,9,4)", "paint(8,4,5)", "paint(8,2,8)", "paint(8,6,9)", "paint(9,4,6)", "paint(9,6,3)", "paint(9,3,4)", "paint(9,8,8)", "paint(9,1,9)" - ] - } - ] - } - ], - "Result": "SATISFIABLE", - "Models": { - "Number": 1, - "More": "no" - }, - "Calls": 1, - "Time": { - "Total": 0.084, - "Solve": 0.000, - "Model": 0.000, - "Unsat": 0.000, - "CPU": 0.080 - } -} \ No newline at end of file From 2752fc5a98b0e24891a3073e2ab0b3b560babe00 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 17:53:23 -0700 Subject: [PATCH 86/99] fix includes --- src/bin/server.rs | 1 + src/watchers/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index dee9b6b..f9bdd33 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -29,6 +29,7 @@ use eve::ops::{ProgramRunner, RunLoop, RunLoopMessage, RawChange, Internable, Pe use eve::watchers::system::{SystemTimerWatcher, PanicWatcher}; use eve::watchers::compiler::{CompilerWatcher}; use eve::watchers::http::{HttpWatcher}; +use eve::watchers::json::{JsonWatcher}; use eve::watchers::textcompiler::{RawTextCompilerWatcher}; use eve::watchers::console::{ConsoleWatcher}; use eve::watchers::file::{FileWatcher}; diff --git a/src/watchers/mod.rs b/src/watchers/mod.rs index 04d9e72..2a15189 100644 --- a/src/watchers/mod.rs +++ b/src/watchers/mod.rs @@ -12,7 +12,8 @@ pub mod console; pub mod system; pub mod compiler; pub mod http; +pub mod json; pub mod textcompiler; pub mod editor; pub mod remote; -pub mod websocket; +pub mod websocket; \ No newline at end of file From 04df1c25ae872b674ac0fddccfe5a4149732da1b Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 17:55:50 -0700 Subject: [PATCH 87/99] remove setlist app from this branch --- examples/setlist-aggregator.eve | 576 -------------------------------- libraries/groove/groove.css | 7 - libraries/groove/groove.eve | 240 ------------- 3 files changed, 823 deletions(-) delete mode 100644 examples/setlist-aggregator.eve delete mode 100644 libraries/groove/groove.css delete mode 100644 libraries/groove/groove.eve diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve deleted file mode 100644 index be705ef..0000000 --- a/examples/setlist-aggregator.eve +++ /dev/null @@ -1,576 +0,0 @@ -# Setlist Aggregator - - -This app takes a set of concerts, and accesses the setlist.fm api to retreive -the setlist that was played at that concert. This is cross-referenced with -the MS Groove API to create playlists for those concerts. - -## App Configuration - -commit - playing = [#app/page name: "Now Playing" icon: "ios-musical-notes" sort: 0] - showlist = [#app/page name: "Show List" icon: "ios-list-outline" sort: 1] - collection = [#app/page name: "Collection" icon: "ios-albums-outline" sort: 2] - stats = [#app/page name: "Show Stats" icon: "stats-bars" sort: 3] - map = [#app/page name: "Map" icon: "map" sort: 4] - settings = [#app/page name: "Settings" icon: "gear-a"] - [#app/configuration groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] - setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"] - pages: (showlist, collection, map, playing, stats)] - [#app/interface page: showlist] -end - -## Layout - -### Skeleton Layout - -search - interface = [#app/interface] -commit - interface <- [|children: - [#ui/column style: [width: "1600px" height: "900px"] | children: - [#header] - [#content] - [#global-playback] - ]] -end - -### Header - -search - header = [#header] -bind - header += #ui/row - header <- [|children: - [#html/div #header/top-left sort: 1] - [#ui/spacer sort: 2] - [#html/div sort: 3 text: "Setlist Aggregator"] - [#ui/spacer sort: 4] - [#html/div #header/top-right sort: 5] - ] -end - -### Navigation - -search - nav = [#navigation] - [#app/configuration pages: page] - page = [#app/page name icon sort] - settings = [#app/page name: "Settings"] -bind - nav <- [#ui/column | children: - [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: - [#navigation/button #ui/button icon page text: name sort]] - [#ui/spacer sort: 99] - [#navigation/button #ui/button page: settings icon: settings.icon text: settings.name sort: 100] - ] -end - -### Content - -search - content = [#content] -bind - content <- [#ui/row style: [height: "100%"] |children: - [#navigation sort: 1] - [#page-container sort: 2] - [#ui/spacer sort: 3] - ] -end - -### Footer - -search - playback = [#global-playback] - -bind - playback <- [#ui/row] -end - -search - playback = [#global-playback] - [#now-playing track] -bind - playback <- [|children: - [#ui/spacer sort: 1] - [#groove/stream-player/playback-control #global-play sort: 2 track] - [#ui/spacer sort: 3]] -end - -Display control placeholders - -search - playback = [#global-playback] - not([#now-playing track]) -bind - playback <- [|children: - [#ui/spacer sort: 1] - [#groove/stream-player/playback-control #ui/button #global-play icon: "play"] - [#ui/spacer sort: 3]] -end - - -### Page Container - -search - page-container = [#page-container] - [#app/interface page] -commit - page-container += #html/div - page-container <- [|children: page] -end - -### Navigate Between Pages - -search - [#html/event/click element: [#navigation/button page]] - interface = [#app/interface] -commit - interface.page := page -end - - -## Header - -search - top-right = [#header/top-right] - not([#groove/user]) -bind - top-right <- [|children: - [#ui/button #groove-login text: "Log in to Groove"] - ] -end - -Logging in is kicked off by clicking the login button. - -search - [#html/event/click element: [#groove-login]] -commit - [#groove/login] -end - -When we've gotten an access token, we get get the user profile. - -search - [#groove access-token] -commit - [#groove/get-user] -end - - -## Pages - -### Now Playing - -search - page.name = "Now Playing" - [#app/interface page] - [#now-playing track] - track = [#groove/track image name duration artist album] -bind - page <-[#html/div | children: - [#ui/column track #album-result children: - [#html/div #album-image children: - [#html/img track src: image style: [width: "250px" height: "250px"]]] - [#html/div #track-name sort: 1 track text: name] - [#html/div sort: 2 track text: artist.Name] - [#html/div sort: 3 track text: album.Name] - [#ui/spacer sort: 4] - [#html/div sort: 5 track text: duration]]] -end - -Clicking on a playback control sets a new `#now-playing` stream and pauses any -currently playing track. - -search - [#html/event/click element: playback-control] - playback-control = [#groove/stream-player/playback-control track] - playback = [#groove/stream-player/playback track] - now-playing-tracks = if playing = [#groove/stream-player/playback #now-playing] then playing - else "None" -commit - playback += #now-playing - now-playing-tracks -= #now-playing - now-playing-tracks.play := "false" -end - -### Collection - -search - page.name = "Collection" - [#app/interface page] - track = [#groove/track image name image duration artist album] -bind - page <- [#html/div #scrollable | children: - [#html/table #tracks | children: - [#html/tr track | children: - [#html/td sort: 0 children: - [#html/img src: image style: [width: "25px" height: "25px"]]] - [#html/td sort: 1 text: name] - [#html/td sort: 2 text: artist.Name] - [#html/td sort: 3 text: album.Name] - [#html/td sort: 4 text: duration]]]] -end - -### Show List - -search - page.name = "Show List" - [#app/interface page] -bind - page <- [#ui/row | style: [width: "100%"] children: - [#show-list/shows] - [#show-list/show-detail] - [#show-list/search-pane] - ] -end - -#### List all the unmatched shows - -search - show-list = [#show-list/shows] - show = [#show artist date] - not(show = [#matched]) -bind - show-list <- [#ui/column | children: - [#html/div #ui/header text: "Unmatched Shows"] - [#show-list/unmatched-shows #ui/list #ui/selectable #ui/single-selectable | item: - [#html/div #show-list/unmatched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "] - ]] -end - -search - [#ui/list item] -bind - item += #ui/list/item -end - -Clicking on an unmatched show searches setlist.fm - -search - show-detail = [#show-list/show-detail] - [#html/event/click element: [#show-list/unmatched-show show]] -commit - show-detail.show := show -end - -Get the setlist details from the Setlist.fm API - -search - show-detail = [#show-list/show-detail show] - not(show = [#matched]) -commit - [#setlist-fm/search/setlists artist: show.artist date: show.date] -end - -Display search results - -search - show-detail = [#show-list/show-detail show] - [#setlist-fm/search/setlists artist: show.artist date: show.date setlist] - not(show = [#matched]) -bind - show-detail <- [#ui/column | children: - [#ui/row #info-head | children: - [#ui/column #show-info sort: 1 | children: - [#html/div #artist sort: -1 text: setlist.artist.name] - [#html/div #tour sort: -2 text: setlist.tour] - [#html/div #date sort: -3 text: setlist.date] - [#html/div #venue sort: 0 text: setlist.venue.name] - ] - [#ui/spacer sort: 2] - [#ui/button #circle-button #large-button #match-show show setlist sort: 3 icon: "checkmark"] - [#ui/spacer sort: 4] - ] - [#html/div #setlists | children: - [#ui/list #ui/selectable #ui/single-selectable #setlist - set: setlist.sets sort: setlist.sets.number | item: - [#ui/row #song song: setlist.sets.songs sort: setlist.sets.songs.number | children: - [#ui/text #song-number text: setlist.sets.songs.number] - [#ui/text #song-name text: setlist.sets.songs.name] - [#ui/spacer]]]]] -end - -Display a loading screen while results are being fetched -TODO This isn't working right -search - show-detail = [#show-list/show-detail show] - find-setlist = [#setlist-fm/search/setlists artist: show.artist date: show.date] - not(find-setlist = [#finished]) -bind - show-detail <- [#html/div | children: - [#html/div text: "Loading..."]] -end - -When the `#match-show` button is click, mark the show as matched and attach a setlist. - - -search - [#html/event/click element: [#match-show show setlist]] -commit - show += #matched - show.setlist := setlist -end - -#### List all matched Shows - -search - show-list = [#show-list/shows] - show = [#show #matched artist date setlist] -bind - show-list += #ui/column - show-list.children += - [#ui/column | children: - [#html/div #ui/header text: "Matched Shows"] - [#show-list/matched-shows #ui/list #ui/selectable #ui/single-selectable | item: - [#html/div #show-list/matched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "]]] -end - - - - - - -#### Get search results for a song - -Clicking on an unmatched song kicks off a groove search for that track - -search - [#html/event/click element: [#song song]] - not(song = [#matched]) - search-pane = [#show-list/search-pane] -commit - search-pane.song := song - search-pane.query := "{{song.artist}} {{song.name}}" -end - -Create a request for a query - -search - [#show-list/search-pane query] -commit - [#groove/search-track query] -end - - -search - - search-pane = [#show-list/search-pane song query] - track = [#groove/track query name image duration artist album] - song.artist = artist.Name -bind - search-pane <- [#ui/column | children: - [#ui/row #track-search children: - [#ui/spacer sort: 1] - [#ui/input sort: 2 icon: "play" value: query] - [#ui/spacer sort: 3]] - [#html/div #results-list | children: - [#ui/list | item: - [#ui/row track #album-result children: - [#html/div #album-image children: - [#html/img track src: image style: [width: "100px" height: "100px"]]] - [#ui/column #track-info track | children: - [#html/div #track-name sort: 1 track text: name] - [#html/div sort: 2 track text: artist.Name] - [#html/div sort: 3 track text: album.Name] - [#ui/spacer sort: 4] - [#html/div sort: 5 track text: duration] - ] - [#ui/spacer sort: 97] - [#ui/column track sort: 98 | children: - [#ui/spacer sort: 1] - [#groove/stream-player #display sort: 2 track] - [#ui/spacer sort: 3] - ] - [#ui/spacer sort: 99] - [#ui/button #link-track track icon: "link" sort: 100]]]]] -end - -search - [#html/event/click element: [#link-track track]] - search-pane = [#show-list/search-pane song] -commit - search-pane.query := none - song += #matched - song.track := track -end - -Clicking on a matched song will play it - -search - matched-song = [#song song: [#matched track]] -bind - matched-song.children += [#groove/stream-player #display track] -end - - - - - - - - -## App Data - -commit - [#show artist: "Incubus" date: "16-08-2017"] - [#show artist: "Jimmy Eat World" date: "16-08-2017"] - [#show artist: "Third Eye Blind" date: "23-07-2017"] - [#show artist: "Silversun Pickups" date: "23-07-2017"] -end - - -### Settings - -search - page.name = "Settings" - [#app/interface page] -bind - page <- [#html/div text: "Settings"] -end - - - - - - - -## Get Data From Setlist.fm - -search - find-setlist = [#setlist-fm/search/setlists artist date] - [#setlist-fm api-key endpoint] - encoded-artist = string/url-encode[text: artist] - address = "{{endpoint}}/1.0/search/setlists?artistName={{encoded-artist}}&date={{date}}&p=1" -commit - [#http/request #find-setlist find-setlist address headers: - [#http/header key: "x-api-key" value: api-key] - [#http/header key: "Accept" value: "application/json"]] -end - -search - [#find-setlist find-setlist response: [body]] -commit - [#json/decode #find-setlist find-setlist json: body] -end - -search - result = [#find-setlist find-setlist json-object] - not(result = [#finished]) - json-object = [setlist: [value: [artist id eventDate tour venue sets: [set]]]] - set = [index: set-number value: [song: [index: song-number value: [name: song-name]]]] -commit - result += #finished - find-setlist += #finished - songs = [#setlist-fm/song artist: artist.name number: song-number, name: song-name] - sets = [#setlist-fm/set id number: set-number | songs] - find-setlist.setlist += [#setlist-fm/setlist id artist venue date: eventDate, tour: tour.name | sets] -end - -Clean up records - -search - complete = [#find-setlist #finished json-object] - find-setlist = [#find-setlist] -commit - find-setlist := none - complete := none -end - - -## Misc Diagnostics - -search - [#http/request/error error] -commit - [#html/div text: error] -end - -search - [#disable] - [#http/request/finished request] -commit - [#html/div request text: "***Finished*** {{request}}"] -end - -search - [#disable] - q = [#http/response body] -commit - [#html/div text: "THIS IS THE BODY: {{body}}"] - //[#json/decode json: body] -end - -search - [#disable] - [#json/decode json-object] -commit - [#html/div text: "{{json-object.Tracks.Items.value.Name}} {{json-object.Tracks.Items.value.Album.Name}} - {{json-object.Tracks.Items.value.Id}}"] -end - -search - request = [#http/request] - not(request = [#finished]) -bind - [#html/div text: "Processing request..."] -end - -## Styles - -Style all playback controls as circle buttons - -search - control = [#groove/stream-player/playback-control] -bind - control += #circle-button -end - - -commit - [#html/style text: " - body { background-color: rgb(24,24,24); height: 40px; color: rgb(200,200,200); } - div { user-select: none; cursor: default;} - div::-webkit-scrollbar { width: 5px; } - div::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); } - div::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.5); outline: 1px solid slategrey; border-radius: 25px; } - .ui-button { background-color: rgb(40,40,40); color: rgb(200,200,200); padding: 10px; border: 1px solid rgb(60,60,60); margin-bottom: 10px; } - .ui-button:hover { background-color: rgb(50,50,50); color: rgb(200,200,200); border: 1px solid rgb(60,60,60);} - .header { background-color: rgb(18,18,18); padding: 10px; } - .global-playback { background-color: rgb(40,40,40); height: 100px; color: rgb(200,200,200); } - .ui-list-item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px solid rgb(80,80,80); } - .ui-list-item:hover { background-color: rgb(40,40,40); } - .show-list-shows { width: 250px; } - .show-list-show-detail {padding-left: 20px; padding-right: 20px; background-color: rgb(22,22,22); width: 400px; margin-right: 10px;} - .artist { font-size: 26px; margin-bottom: 10px; } - .tour { margin-bottom: 5px; color: rgb(150,150,150); } - .venue { margin-bottom: 5px; color: rgb(150,150,150);} - .date { margin-bottom: 5px; color: rgb(150,150,150);} - .setlists { overflow: auto; max-height: 650px; margin-top: 15px; padding-right: 10px;} - .setlist { margin-top: 10px; margin-bottom: 20px; } - .song { padding: 5px; line-height: 30px; } - .song-number { width: 15px; margin-right: 10px; color: rgb(120,120,120); text-align: right } - .song:hover { background-color: rgb(40,40,40);} - .show-list-search-pane {min-width: 400px; max-width: 500px; overflow: auto;} - .album-image {width: 100px; margin-right: 20px;} - .album-result {padding: 20px;} - .ui-header {padding: 10px; margin-bottom: 10px; font-size: 20px;} - .track-search {padding: 10px; padding-top: 20px; padding-bottom: 20px; background-color: rgb(18,18,18);} - .navigation { background-color: rgb(18,18,18); width: 150px; height: 100%; } - .navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(30,30,30); user-select: none; cursor: default; margin: 0px; border: 0px; width: 100%; min-height: 75px;} - .navigation-button:hover { color: rgb(255,255,255); border: 0px; } - .circle-button {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} - .circle-button:hover {background-color: rgb(0,158,224,1); border-color: white; color: white;} - .track-name {padding-bottom: 10px;} - .results-list .ui-list {padding-right: 10px;} - .results-list {overflow: auto; max-height: 700px;} - .track-info {width: 150px;} - table { border-collapse: collapse; margin: 20px;} - td { padding: 10px; } - table, th, td { border: 1px solid rgb(80,80,80); } - .scrollable { overflow: auto; height: 800px; } - .global-playback {padding-top: 8px;} - .global-play {width: 50px; height: 50px; font-size: 25px; padding-left: 17px;} - .ui-input {padding: 5px 10px 5px 10px; border-radius: 20px; border: 1px solid rgb(80,80,80); outline: none;} - .large-button {width: 50px; height: 50px; font-size: 25px; padding-left: 15px;} - "] -end \ No newline at end of file diff --git a/libraries/groove/groove.css b/libraries/groove/groove.css deleted file mode 100644 index aa19dfb..0000000 --- a/libraries/groove/groove.css +++ /dev/null @@ -1,7 +0,0 @@ -.groove-stream-player-playback-control { - height: "100px"; - width: "100px"; - border-radius: "50%"; - border: "2px solid #f5f5f5"; - text-align: center; -} \ No newline at end of file diff --git a/libraries/groove/groove.eve b/libraries/groove/groove.eve deleted file mode 100644 index 14fc4b2..0000000 --- a/libraries/groove/groove.eve +++ /dev/null @@ -1,240 +0,0 @@ -# Groove API - -The groove API is configured with a record tagged `#groove` with the following shape: - -`[#groove client-id client-secret endpoint redirect-uri]` - - -## Logging Into Groove - -Commit a `#groove/login` to initiate the login process. This redirects to a -login portal, which asks the user to authorize access to Eve. Once the user -grants access, the browser is redirect back to the supplied `redirect-uri`. - -search - [#groove/login] - [#groove client-id client-secret redirect-uri] - response-type = "token" - scopes = string/url-encode[text: "MicrosoftMediaServices.GrooveApiAccess offline_access"] - encoded-redirect-uri = string/url-encode[text: redirect-uri] - address = "https://login.live.com/oauth20_authorize.srf/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{encoded-redirect-uri}}&scope={{scopes}}" -commit - [#html/redirect url: address] -end - -A successful login will return to the app an access token - -search - [#html/url query: [#html/url/query key: "access_token" value: access-token]] - groove = [#groove] -commit - groove.access-token := access-token -end - - -## Getting Groove User Data - -With an access token in hand, we can use that to get user-specific information. - -search - [#groove/get-user] - [#groove access-token endpoint] -commit - [#http/request #groove-profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] -end - -search - [#groove-profile response] -commit - [#json/decode #groove-profile json: response.body] -end - -Create a Groove user from the response - -search - [#groove-profile json-object: profile] -commit - [#groove/user region: profile.Culture subscription: profile.HasSubscription] -end - - -## Search Groove - -search - [#groove/search-track query] - [#groove access-token endpoint] - encoded-query = string/replace[text: query replace: " " with: "+"] - address = "{{endpoint}}/1/content/music/search?q={{encoded-query}}&filters=tracks" -commit - [#http/request #groove-search query address headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] -end - -search - [#groove-search query response: [body]] -commit - [#json/decode #groove-search query json: body] -end - -search - [#groove-search query json-object] - json-object = [Tracks: [Items: [value: [Id Name ReleaseDate Duration ImageUrl Album Artists: [value: [Artist]]]]]] -commit - [#groove/track query name: Name, id: Id, duration: Duration, image: ImageUrl album: Album | artist: Artist] -end - - -## Get a full song strem - -search - [#groove/full-stream track] - [#groove access-token endpoint] - address = "{{endpoint}}/1/content/{{track.id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" -commit - [#http/request #groove-get-song track address headers: - [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] -end - -search - [#groove-get-song track response: [body]] -commit - [#json/decode #groove-get-song track json: body] -end - -search - [#groove-get-song track json-object] - groove-stream = [#groove/full-stream track] -commit - groove-stream <- [stream-url: json-object.Url content-type: json-object.ContentType] -end - - -## Streaming Player - -A stream player has a playback control - -search - stream = [#groove/stream-player track] -bind - stream.controls += [#groove/stream-player/playback-control track] -end - -Playback controls are also buttons, so they render - -search - playback-control = [#groove/stream-player/playback-control track] -bind - playback-control += #ui/button -end - - -A stream player also has a playback element - -search - groove = [#groove client-id] - player = [#groove/stream-player track] -commit - playback = [#html/stream #groove/stream-player/playback track style: [display: "none"]] - player.playback := playback - groove.streams += playback -end - -The icon of a control matches the state of its player - -search - control = [#groove/stream-player/playback-control track] - playback = [#groove/stream-player/playback track] - not(playback = [#pending]) - state = if playback = [#playing] then "pause" - else "play" -bind - control.icon += state -end - -The icon of a pending stream is a loading animation - -search - control = [#groove/stream-player/playback-control track] - playback = [#groove/stream-player/playback #pending track] -bind - control.icon += "load-a" -end - -Display controls in the DOM - -search - stream = [#groove/stream-player #display controls] -bind - stream += #html/div - controls += #display - stream.children += controls -end - -Hide controls that aren't tagged `#dispaly` - -search - controls = [#groove/stream-player/playback-control] - not(controls = [#display]) -bind - controls.style.display += "none" -end - -Clicking the play button for the first time gets a link to the stream and -starts playback when the stream is ready. - -search - [#html/event/click element: [#groove/stream-player/playback-control track]] - pending-playback = [#groove/stream-player/playback track] - track = [#groove/track id] - not(pending-playback = [#ready]) -commit - pending-playback += #pending - [#groove/full-stream track] -end - -Attach the stream info to the stream playback - -search - [#groove/full-stream track stream-url content-type] - playback = [#groove/stream-player/playback track] -commit - playback <- [source: stream-url content-type] -end - -search - playback = [#groove/stream-player/playback #ready #pending track] -commit - playback.play := "true" - playback -= #pending -end - -Clicking the playback button when the stream is ready will toggle the stream - -search - q = [#html/event/click element: [#groove/stream-player/playback-control track]] - playback = [#groove/stream-player/playback #ready track] - state = if playback = [#playing] then "false" - else "true" -commit - playback.play := state -end - -If we ever get into a state where a stream is both `#playing` and `#paused` we -can default to paused. - -search - stream = [#groove/stream-player #playing #paused] -commit - stream -= #playing -end - -A streaming player that has no controls is paused, because you'd have no way to pause it yourself. - -search - [#groove streams] - streams = [#groove/stream-player/playback #playing track] - not([#groove/stream-player/playback-control track]) -commit - streams.play := "false" -end \ No newline at end of file From de547ae7c45ffb0fa409c47c88be6c761a7561b0 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 18:31:24 -0700 Subject: [PATCH 88/99] remove unnecessary pipes --- examples/json-demo.eve | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/json-demo.eve b/examples/json-demo.eve index 1b3bb1d..c2fce1d 100644 --- a/examples/json-demo.eve +++ b/examples/json-demo.eve @@ -75,8 +75,6 @@ Decode the record we just encoded //[#ui/button #remove-record text: "Remove From Record"] //end - - ## Debug Display search @@ -110,7 +108,7 @@ search entity? = if eav = [#json/encode/entity] then "entity" else "" bind - encode <- [| children: + encode <- [children: [#html/div sort: 0 text: "Encode EAVs"] [#html/table #eav-table | children: [#html/tr #eav-row eav children: @@ -129,7 +127,7 @@ search encode = [#flatten] [#json/encode/flatten record] bind - encode <- [| children: + encode <- [children: [#html/div sort: 0 text: "Flatten"] [#html/div sort: 1 text: "{{record}}"] ] From 608cc7ec4428ddefce8a49ba9aa552bcff3ebbf4 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 18:31:37 -0700 Subject: [PATCH 89/99] don't parse query strings and hash strings automatically --- libraries/html/html.ts | 78 +++--------------------------------------- package.json | 3 +- 2 files changed, 6 insertions(+), 75 deletions(-) diff --git a/libraries/html/html.ts b/libraries/html/html.ts index 1302b1f..a70b07a 100644 --- a/libraries/html/html.ts +++ b/libraries/html/html.ts @@ -120,9 +120,6 @@ export class HTML extends Library { window.addEventListener("keydown", this._keyEventHandler("key-down")); window.addEventListener("keyup", this._keyEventHandler("key-up")); - // Frame events - window.addEventListener("hashchange", this._hashChangeHandler("url-change")); - //window.addEventListener("pageshow", this._pageShowHandler("page-show")); this.getURL(window.location); } @@ -645,89 +642,24 @@ export class HTML extends Library { }; } - _hashChangeHandler(tagname:string) { - console.log("Hash Change"); - return (event: HashChangeEvent) => { - if (event.newURL !== null) { - let eavs:RawEAV[] = []; - let eventId = createId(); - let urlId = createId(); - let {hash, host, hostname, href, path, pathname, port, protocol, query} = url.parse(event.newURL, true); - eavs.push( - [eventId, "tag", "html/event"], - [eventId, "tag", `html/event/${tagname}`], - [eventId, "url", `${urlId}`], - [urlId, "tag", "html/url"], - [urlId, "host", `${host}`], - [urlId, "hash", `${hash}`], - [urlId, "hostname", `${hostname}`], - [urlId, "href", `${href}`], - [urlId, "pathname", `${pathname}`], - [urlId, "port", `${port}`], - [urlId, "protocol", `${protocol}`], - ); - let ix = 1; - for (var key in query) { - let value = query[key]; - let queryId = createId(); - eavs.push( - [urlId, "query", `${queryId}`], - [queryId, "index", `${ix}`], - [queryId, "key", key], - [queryId, "value", value], - ) - ix++; - } - this._sendEvent(eavs); - } - } - } - getURL(location: Location) { - let {hash, host, hostname, href, path, pathname, port, protocol, query} = url.parse(location.href, true); + let {hash, host, hostname, href, pathname, port, protocol, search} = location; let eavs:RawEAV[] = []; let urlId = createId(); eavs.push( [urlId, "tag", "html/url"], [urlId, "host", `${host}`], - [urlId, "hash", `${hash}`], [urlId, "hostname", `${hostname}`], [urlId, "href", `${href}`], [urlId, "pathname", `${pathname}`], [urlId, "port", `${port}`], [urlId, "protocol", `${protocol}`], ); - // Parse Query String - let ix = 1; - for (var key in query) { - let value = query[key]; - let queryId = createId(); - eavs.push( - [urlId, "query", `${queryId}`], - [queryId, "tag", `html/url/query`], - [queryId, "index", `${ix}`], - [queryId, "key", key], - [queryId, "value", value], - ) - ix++; + if(hash !== "") { + eavs.push([urlId, "hash", `${hash.substring(1)}`]); } - // Parse Hash String - if (hash !== undefined && hash !== null) { - let pairs = hash.substring(1).split('&'); - for (var pair of pairs) { - let queryId = createId(); - let parts = pair.split('='); - if (parts.length == 2) { - eavs.push( - [urlId, "query", `${queryId}`], - [queryId, "tag", `html/url/query`], - [queryId, "index", `${ix}`], - [queryId, "key", parts[0]], - [queryId, "value", parts[1]], - ) - } - ix++; - } + if(search !== "") { + eavs.push([urlId, "query", `${search.substring(1)}`]); } this._sendEvent(eavs); } diff --git a/package.json b/package.json index c508b5c..e62d119 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,7 @@ "postinstall-build": "^5.0.1", "setimmediate": "^1.0.5", "uuid": "^3.1.0", - "vis": "^4.20.1", - "url": "^0.11.0" + "vis": "^4.20.1" }, "devDependencies": { "@types/codemirror": "0.0.41", From 22a6ba89e408bbb7fc647030663fc478d4233051 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 18:32:27 -0700 Subject: [PATCH 90/99] cut hls from this PR --- libraries/html/stream.eve | 42 ------------------------ libraries/html/stream.ts | 67 --------------------------------------- package.json | 2 -- 3 files changed, 111 deletions(-) delete mode 100644 libraries/html/stream.eve delete mode 100644 libraries/html/stream.ts diff --git a/libraries/html/stream.eve b/libraries/html/stream.eve deleted file mode 100644 index f334e01..0000000 --- a/libraries/html/stream.eve +++ /dev/null @@ -1,42 +0,0 @@ -# Stream Element - -search - stream = [#html/stream source] -watch client/websocket - ("stream/create", stream, source) -end - -search - stream = [#html/stream] -bind - stream <- [#html/video id: stream] -end - -search - stream = [#html/stream play] -watch client/websocket - ("stream/play", stream, play) -end - -search - [#html/event/stream-ready stream] - stream = [#html/stream] -commit - stream += #ready -end - -search - [#html/event/stream-play stream] - stream = [#html/stream] -commit - stream += #playing - stream -= #paused -end - -search - [#html/event/stream-pause stream] - stream = [#html/stream] -commit - stream += #paused - stream -= #playing -end \ No newline at end of file diff --git a/libraries/html/stream.ts b/libraries/html/stream.ts deleted file mode 100644 index b4abaeb..0000000 --- a/libraries/html/stream.ts +++ /dev/null @@ -1,67 +0,0 @@ -import {Library, createId, RawValue, RawEAV, handleTuples, libraries} from "../../ts"; -import Hls from "hls.js" - -const EMPTY:never[] = []; - -export class Stream extends Library { - static id = "stream"; - streams: any = {}; - - html:libraries.HTML; - - setup() { - this.html = this.program.attach("html") as libraries.HTML; - } - - handlers = { - "create": handleTuples(({adds}) => { - for(let [streamID, source] of adds || EMPTY) { - if(Hls.isSupported()) { - let video: any = document.getElementById(`${streamID}`); - var hls = new Hls(); - let program = this.program - hls.loadSource(`${source}`); - hls.attachMedia(video); - hls.on(Hls.Events.MANIFEST_PARSED,function() { - - }); - video.onplay = function () { - let play_id = createId(); - program.inputEAVs([ - [play_id, "tag", "html/event/stream-play"], - [play_id, "stream", streamID], - ]); - }; - video.onpause = function () { - let paused_id = createId(); - program.inputEAVs([ - [paused_id, "tag", "html/event/stream-pause"], - [paused_id, "stream", streamID], - ]); - }; - video.onloadeddata = function () { - let ready_id = createId(); - program.inputEAVs([ - [ready_id, "tag", "html/event/stream-ready"], - [ready_id, "stream", streamID], - ]); - } - //window.addEventListener("pageshow", video.onplay()); - this.streams[streamID] = video; - } - } - }), - "play": handleTuples(({adds}) => { - for(let [streamID, play] of adds || EMPTY) { - let video = this.streams[streamID]; - if (play === "true") { - video.play(); - } else { - video.pause(); - } - } - }) - } -} - -Library.register(Stream.id, Stream); \ No newline at end of file diff --git a/package.json b/package.json index e62d119..b71b071 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "license": "UNLICENSED", "dependencies": { "@types/vis": "^4.18.4", - "hls.js": "^0.7.11", "codemirror": "^5.28.0", "codemirror-mode-eve": "0.0.1", "md5": "^2.2.1", @@ -30,7 +29,6 @@ "@types/codemirror": "0.0.41", "@types/md5": "^2.1.32", "@types/uuid": "^3.4.0", - "@types/hls.js": "^0.7.5", "autoprefixer": "^7.1.2", "postcss-color-function": "^4.0.0", "postcss-custom-properties": "^6.1.0", From 0e140367108a151784d64b399b6b229b13ac7aa6 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 18:34:38 -0700 Subject: [PATCH 91/99] remove response when we've got a handle on it --- libraries/http/http.eve | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 70eed12..a6a1554 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -22,7 +22,7 @@ Default method search request = [#http/request] not(request.method) -commit +bind request.method := "GET" end @@ -50,6 +50,7 @@ search response = [#http/response request] request = [#http/request] commit + response := none request.response := response end From 78531695c163fff846bc0f7e1d934952af6c994c Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 18:46:23 -0700 Subject: [PATCH 92/99] add a response/change event --- libraries/http/http.eve | 7 ++++--- src/watchers/http.rs | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index a6a1554..39d4ae6 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -23,7 +23,7 @@ search request = [#http/request] not(request.method) bind - request.method := "GET" + request.method += "GET" end Default empty body @@ -47,10 +47,11 @@ end Associate response with its request search - response = [#http/response request] + response-change = [#http/response/change response] + response = [#html/response request] request = [#http/request] commit - response := none + response-change := none request.response := response end diff --git a/src/watchers/http.rs b/src/watchers/http.rs index c209eae..5d66ab3 100644 --- a/src/watchers/http.rs +++ b/src/watchers/http.rs @@ -162,6 +162,9 @@ fn send_http_request(id: &String, request: hyper::Request, outgoing: &Sender = vec![]; let status = res.status().as_u16(); let response_id = format!("http/response|{:?}",id); + let response_change_id = format!("http/response/received|{:?}",id); + response_changes.push(new_change(&response_change_id, "tag", Internable::from_str("http/response/received"), node)); + response_changes.push(new_change(&response_change_id, "response", Internable::String(response_id.clone()), node)); response_changes.push(new_change(&response_id, "tag", Internable::from_str("http/response"), node)); response_changes.push(new_change(&response_id, "status", Internable::String(status.to_string()), node)); response_changes.push(new_change(&response_id, "request", Internable::String(id.clone()), node)); From f7668c25f0e2938035fd43b175c695ea3053ee00 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 19:01:29 -0700 Subject: [PATCH 93/99] fix typo --- libraries/json/json.eve | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/json/json.eve b/libraries/json/json.eve index e57ebd3..bb1ed2c 100644 --- a/libraries/json/json.eve +++ b/libraries/json/json.eve @@ -97,7 +97,7 @@ end ### Bring it all together -Join all encoded avs into a compelte string +Join all encoded avs into a complete string search complete-av = [#json/encode/complete-av record] From fbadded37501a612848b07527b59bc726d96d2eb Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 19:01:34 -0700 Subject: [PATCH 94/99] pass through numbers --- src/ops.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ops.rs b/src/ops.rs index 0e0a726..c89be96 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1612,6 +1612,7 @@ pub fn string_length(params: Vec<&Internable>) -> Option { pub fn string_encode(params: Vec<&Internable>) -> Option { match params.as_slice() { &[&Internable::String(ref text)] => Some(Internable::String(base64::encode(text.as_bytes()))), + &[&Internable::Number(ref number)] => Some(Internable::String(number.to_string())), _ => None } } @@ -1619,6 +1620,7 @@ pub fn string_encode(params: Vec<&Internable>) -> Option { pub fn string_urlencode(params: Vec<&Internable>) -> Option { match params.as_slice() { &[&Internable::String(ref text)] => Some(Internable::String(urlencoding::encode(text))), + &[&Internable::Number(ref number)] => Some(Internable::String(number.to_string())), _ => None } } From b255048b064bdf4b00f4b1068eb90ac40117b1a9 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 19:03:21 -0700 Subject: [PATCH 95/99] try to update this diff --- src/bin/server.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/server.rs b/src/bin/server.rs index f9bdd33..a52b296 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -29,14 +29,13 @@ use eve::ops::{ProgramRunner, RunLoop, RunLoopMessage, RawChange, Internable, Pe use eve::watchers::system::{SystemTimerWatcher, PanicWatcher}; use eve::watchers::compiler::{CompilerWatcher}; use eve::watchers::http::{HttpWatcher}; -use eve::watchers::json::{JsonWatcher}; use eve::watchers::textcompiler::{RawTextCompilerWatcher}; use eve::watchers::console::{ConsoleWatcher}; use eve::watchers::file::{FileWatcher}; use eve::watchers::editor::EditorWatcher; use eve::watchers::remote::{Router, RouterMessage, RemoteWatcher}; use eve::watchers::websocket::WebsocketClientWatcher; - +use eve::watchers::json::{JsonWatcher}; extern crate iron; extern crate staticfile; From 4ea7ab2cc165a02b4046c09c56e6794b7b615be6 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 19:36:40 -0700 Subject: [PATCH 96/99] bring in the setlist aggregator --- examples/setlist-aggregator.eve | 576 ++++++++++++++++++++++++++++++++ libraries/groove/groove.css | 7 + libraries/groove/groove.eve | 240 +++++++++++++ libraries/html/stream.eve | 42 +++ libraries/html/stream.ts | 67 ++++ package.json | 2 + 6 files changed, 934 insertions(+) create mode 100644 examples/setlist-aggregator.eve create mode 100644 libraries/groove/groove.css create mode 100644 libraries/groove/groove.eve create mode 100644 libraries/html/stream.eve create mode 100644 libraries/html/stream.ts diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve new file mode 100644 index 0000000..be705ef --- /dev/null +++ b/examples/setlist-aggregator.eve @@ -0,0 +1,576 @@ +# Setlist Aggregator + + +This app takes a set of concerts, and accesses the setlist.fm api to retreive +the setlist that was played at that concert. This is cross-referenced with +the MS Groove API to create playlists for those concerts. + +## App Configuration + +commit + playing = [#app/page name: "Now Playing" icon: "ios-musical-notes" sort: 0] + showlist = [#app/page name: "Show List" icon: "ios-list-outline" sort: 1] + collection = [#app/page name: "Collection" icon: "ios-albums-outline" sort: 2] + stats = [#app/page name: "Show Stats" icon: "stats-bars" sort: 3] + map = [#app/page name: "Map" icon: "map" sort: 4] + settings = [#app/page name: "Settings" icon: "gear-a"] + [#app/configuration groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] + setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"] + pages: (showlist, collection, map, playing, stats)] + [#app/interface page: showlist] +end + +## Layout + +### Skeleton Layout + +search + interface = [#app/interface] +commit + interface <- [|children: + [#ui/column style: [width: "1600px" height: "900px"] | children: + [#header] + [#content] + [#global-playback] + ]] +end + +### Header + +search + header = [#header] +bind + header += #ui/row + header <- [|children: + [#html/div #header/top-left sort: 1] + [#ui/spacer sort: 2] + [#html/div sort: 3 text: "Setlist Aggregator"] + [#ui/spacer sort: 4] + [#html/div #header/top-right sort: 5] + ] +end + +### Navigation + +search + nav = [#navigation] + [#app/configuration pages: page] + page = [#app/page name icon sort] + settings = [#app/page name: "Settings"] +bind + nav <- [#ui/column | children: + [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: + [#navigation/button #ui/button icon page text: name sort]] + [#ui/spacer sort: 99] + [#navigation/button #ui/button page: settings icon: settings.icon text: settings.name sort: 100] + ] +end + +### Content + +search + content = [#content] +bind + content <- [#ui/row style: [height: "100%"] |children: + [#navigation sort: 1] + [#page-container sort: 2] + [#ui/spacer sort: 3] + ] +end + +### Footer + +search + playback = [#global-playback] + +bind + playback <- [#ui/row] +end + +search + playback = [#global-playback] + [#now-playing track] +bind + playback <- [|children: + [#ui/spacer sort: 1] + [#groove/stream-player/playback-control #global-play sort: 2 track] + [#ui/spacer sort: 3]] +end + +Display control placeholders + +search + playback = [#global-playback] + not([#now-playing track]) +bind + playback <- [|children: + [#ui/spacer sort: 1] + [#groove/stream-player/playback-control #ui/button #global-play icon: "play"] + [#ui/spacer sort: 3]] +end + + +### Page Container + +search + page-container = [#page-container] + [#app/interface page] +commit + page-container += #html/div + page-container <- [|children: page] +end + +### Navigate Between Pages + +search + [#html/event/click element: [#navigation/button page]] + interface = [#app/interface] +commit + interface.page := page +end + + +## Header + +search + top-right = [#header/top-right] + not([#groove/user]) +bind + top-right <- [|children: + [#ui/button #groove-login text: "Log in to Groove"] + ] +end + +Logging in is kicked off by clicking the login button. + +search + [#html/event/click element: [#groove-login]] +commit + [#groove/login] +end + +When we've gotten an access token, we get get the user profile. + +search + [#groove access-token] +commit + [#groove/get-user] +end + + +## Pages + +### Now Playing + +search + page.name = "Now Playing" + [#app/interface page] + [#now-playing track] + track = [#groove/track image name duration artist album] +bind + page <-[#html/div | children: + [#ui/column track #album-result children: + [#html/div #album-image children: + [#html/img track src: image style: [width: "250px" height: "250px"]]] + [#html/div #track-name sort: 1 track text: name] + [#html/div sort: 2 track text: artist.Name] + [#html/div sort: 3 track text: album.Name] + [#ui/spacer sort: 4] + [#html/div sort: 5 track text: duration]]] +end + +Clicking on a playback control sets a new `#now-playing` stream and pauses any +currently playing track. + +search + [#html/event/click element: playback-control] + playback-control = [#groove/stream-player/playback-control track] + playback = [#groove/stream-player/playback track] + now-playing-tracks = if playing = [#groove/stream-player/playback #now-playing] then playing + else "None" +commit + playback += #now-playing + now-playing-tracks -= #now-playing + now-playing-tracks.play := "false" +end + +### Collection + +search + page.name = "Collection" + [#app/interface page] + track = [#groove/track image name image duration artist album] +bind + page <- [#html/div #scrollable | children: + [#html/table #tracks | children: + [#html/tr track | children: + [#html/td sort: 0 children: + [#html/img src: image style: [width: "25px" height: "25px"]]] + [#html/td sort: 1 text: name] + [#html/td sort: 2 text: artist.Name] + [#html/td sort: 3 text: album.Name] + [#html/td sort: 4 text: duration]]]] +end + +### Show List + +search + page.name = "Show List" + [#app/interface page] +bind + page <- [#ui/row | style: [width: "100%"] children: + [#show-list/shows] + [#show-list/show-detail] + [#show-list/search-pane] + ] +end + +#### List all the unmatched shows + +search + show-list = [#show-list/shows] + show = [#show artist date] + not(show = [#matched]) +bind + show-list <- [#ui/column | children: + [#html/div #ui/header text: "Unmatched Shows"] + [#show-list/unmatched-shows #ui/list #ui/selectable #ui/single-selectable | item: + [#html/div #show-list/unmatched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "] + ]] +end + +search + [#ui/list item] +bind + item += #ui/list/item +end + +Clicking on an unmatched show searches setlist.fm + +search + show-detail = [#show-list/show-detail] + [#html/event/click element: [#show-list/unmatched-show show]] +commit + show-detail.show := show +end + +Get the setlist details from the Setlist.fm API + +search + show-detail = [#show-list/show-detail show] + not(show = [#matched]) +commit + [#setlist-fm/search/setlists artist: show.artist date: show.date] +end + +Display search results + +search + show-detail = [#show-list/show-detail show] + [#setlist-fm/search/setlists artist: show.artist date: show.date setlist] + not(show = [#matched]) +bind + show-detail <- [#ui/column | children: + [#ui/row #info-head | children: + [#ui/column #show-info sort: 1 | children: + [#html/div #artist sort: -1 text: setlist.artist.name] + [#html/div #tour sort: -2 text: setlist.tour] + [#html/div #date sort: -3 text: setlist.date] + [#html/div #venue sort: 0 text: setlist.venue.name] + ] + [#ui/spacer sort: 2] + [#ui/button #circle-button #large-button #match-show show setlist sort: 3 icon: "checkmark"] + [#ui/spacer sort: 4] + ] + [#html/div #setlists | children: + [#ui/list #ui/selectable #ui/single-selectable #setlist + set: setlist.sets sort: setlist.sets.number | item: + [#ui/row #song song: setlist.sets.songs sort: setlist.sets.songs.number | children: + [#ui/text #song-number text: setlist.sets.songs.number] + [#ui/text #song-name text: setlist.sets.songs.name] + [#ui/spacer]]]]] +end + +Display a loading screen while results are being fetched +TODO This isn't working right +search + show-detail = [#show-list/show-detail show] + find-setlist = [#setlist-fm/search/setlists artist: show.artist date: show.date] + not(find-setlist = [#finished]) +bind + show-detail <- [#html/div | children: + [#html/div text: "Loading..."]] +end + +When the `#match-show` button is click, mark the show as matched and attach a setlist. + + +search + [#html/event/click element: [#match-show show setlist]] +commit + show += #matched + show.setlist := setlist +end + +#### List all matched Shows + +search + show-list = [#show-list/shows] + show = [#show #matched artist date setlist] +bind + show-list += #ui/column + show-list.children += + [#ui/column | children: + [#html/div #ui/header text: "Matched Shows"] + [#show-list/matched-shows #ui/list #ui/selectable #ui/single-selectable | item: + [#html/div #show-list/matched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "]]] +end + + + + + + +#### Get search results for a song + +Clicking on an unmatched song kicks off a groove search for that track + +search + [#html/event/click element: [#song song]] + not(song = [#matched]) + search-pane = [#show-list/search-pane] +commit + search-pane.song := song + search-pane.query := "{{song.artist}} {{song.name}}" +end + +Create a request for a query + +search + [#show-list/search-pane query] +commit + [#groove/search-track query] +end + + +search + + search-pane = [#show-list/search-pane song query] + track = [#groove/track query name image duration artist album] + song.artist = artist.Name +bind + search-pane <- [#ui/column | children: + [#ui/row #track-search children: + [#ui/spacer sort: 1] + [#ui/input sort: 2 icon: "play" value: query] + [#ui/spacer sort: 3]] + [#html/div #results-list | children: + [#ui/list | item: + [#ui/row track #album-result children: + [#html/div #album-image children: + [#html/img track src: image style: [width: "100px" height: "100px"]]] + [#ui/column #track-info track | children: + [#html/div #track-name sort: 1 track text: name] + [#html/div sort: 2 track text: artist.Name] + [#html/div sort: 3 track text: album.Name] + [#ui/spacer sort: 4] + [#html/div sort: 5 track text: duration] + ] + [#ui/spacer sort: 97] + [#ui/column track sort: 98 | children: + [#ui/spacer sort: 1] + [#groove/stream-player #display sort: 2 track] + [#ui/spacer sort: 3] + ] + [#ui/spacer sort: 99] + [#ui/button #link-track track icon: "link" sort: 100]]]]] +end + +search + [#html/event/click element: [#link-track track]] + search-pane = [#show-list/search-pane song] +commit + search-pane.query := none + song += #matched + song.track := track +end + +Clicking on a matched song will play it + +search + matched-song = [#song song: [#matched track]] +bind + matched-song.children += [#groove/stream-player #display track] +end + + + + + + + + +## App Data + +commit + [#show artist: "Incubus" date: "16-08-2017"] + [#show artist: "Jimmy Eat World" date: "16-08-2017"] + [#show artist: "Third Eye Blind" date: "23-07-2017"] + [#show artist: "Silversun Pickups" date: "23-07-2017"] +end + + +### Settings + +search + page.name = "Settings" + [#app/interface page] +bind + page <- [#html/div text: "Settings"] +end + + + + + + + +## Get Data From Setlist.fm + +search + find-setlist = [#setlist-fm/search/setlists artist date] + [#setlist-fm api-key endpoint] + encoded-artist = string/url-encode[text: artist] + address = "{{endpoint}}/1.0/search/setlists?artistName={{encoded-artist}}&date={{date}}&p=1" +commit + [#http/request #find-setlist find-setlist address headers: + [#http/header key: "x-api-key" value: api-key] + [#http/header key: "Accept" value: "application/json"]] +end + +search + [#find-setlist find-setlist response: [body]] +commit + [#json/decode #find-setlist find-setlist json: body] +end + +search + result = [#find-setlist find-setlist json-object] + not(result = [#finished]) + json-object = [setlist: [value: [artist id eventDate tour venue sets: [set]]]] + set = [index: set-number value: [song: [index: song-number value: [name: song-name]]]] +commit + result += #finished + find-setlist += #finished + songs = [#setlist-fm/song artist: artist.name number: song-number, name: song-name] + sets = [#setlist-fm/set id number: set-number | songs] + find-setlist.setlist += [#setlist-fm/setlist id artist venue date: eventDate, tour: tour.name | sets] +end + +Clean up records + +search + complete = [#find-setlist #finished json-object] + find-setlist = [#find-setlist] +commit + find-setlist := none + complete := none +end + + +## Misc Diagnostics + +search + [#http/request/error error] +commit + [#html/div text: error] +end + +search + [#disable] + [#http/request/finished request] +commit + [#html/div request text: "***Finished*** {{request}}"] +end + +search + [#disable] + q = [#http/response body] +commit + [#html/div text: "THIS IS THE BODY: {{body}}"] + //[#json/decode json: body] +end + +search + [#disable] + [#json/decode json-object] +commit + [#html/div text: "{{json-object.Tracks.Items.value.Name}} {{json-object.Tracks.Items.value.Album.Name}} - {{json-object.Tracks.Items.value.Id}}"] +end + +search + request = [#http/request] + not(request = [#finished]) +bind + [#html/div text: "Processing request..."] +end + +## Styles + +Style all playback controls as circle buttons + +search + control = [#groove/stream-player/playback-control] +bind + control += #circle-button +end + + +commit + [#html/style text: " + body { background-color: rgb(24,24,24); height: 40px; color: rgb(200,200,200); } + div { user-select: none; cursor: default;} + div::-webkit-scrollbar { width: 5px; } + div::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); } + div::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.5); outline: 1px solid slategrey; border-radius: 25px; } + .ui-button { background-color: rgb(40,40,40); color: rgb(200,200,200); padding: 10px; border: 1px solid rgb(60,60,60); margin-bottom: 10px; } + .ui-button:hover { background-color: rgb(50,50,50); color: rgb(200,200,200); border: 1px solid rgb(60,60,60);} + .header { background-color: rgb(18,18,18); padding: 10px; } + .global-playback { background-color: rgb(40,40,40); height: 100px; color: rgb(200,200,200); } + .ui-list-item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px solid rgb(80,80,80); } + .ui-list-item:hover { background-color: rgb(40,40,40); } + .show-list-shows { width: 250px; } + .show-list-show-detail {padding-left: 20px; padding-right: 20px; background-color: rgb(22,22,22); width: 400px; margin-right: 10px;} + .artist { font-size: 26px; margin-bottom: 10px; } + .tour { margin-bottom: 5px; color: rgb(150,150,150); } + .venue { margin-bottom: 5px; color: rgb(150,150,150);} + .date { margin-bottom: 5px; color: rgb(150,150,150);} + .setlists { overflow: auto; max-height: 650px; margin-top: 15px; padding-right: 10px;} + .setlist { margin-top: 10px; margin-bottom: 20px; } + .song { padding: 5px; line-height: 30px; } + .song-number { width: 15px; margin-right: 10px; color: rgb(120,120,120); text-align: right } + .song:hover { background-color: rgb(40,40,40);} + .show-list-search-pane {min-width: 400px; max-width: 500px; overflow: auto;} + .album-image {width: 100px; margin-right: 20px;} + .album-result {padding: 20px;} + .ui-header {padding: 10px; margin-bottom: 10px; font-size: 20px;} + .track-search {padding: 10px; padding-top: 20px; padding-bottom: 20px; background-color: rgb(18,18,18);} + .navigation { background-color: rgb(18,18,18); width: 150px; height: 100%; } + .navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(30,30,30); user-select: none; cursor: default; margin: 0px; border: 0px; width: 100%; min-height: 75px;} + .navigation-button:hover { color: rgb(255,255,255); border: 0px; } + .circle-button {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} + .circle-button:hover {background-color: rgb(0,158,224,1); border-color: white; color: white;} + .track-name {padding-bottom: 10px;} + .results-list .ui-list {padding-right: 10px;} + .results-list {overflow: auto; max-height: 700px;} + .track-info {width: 150px;} + table { border-collapse: collapse; margin: 20px;} + td { padding: 10px; } + table, th, td { border: 1px solid rgb(80,80,80); } + .scrollable { overflow: auto; height: 800px; } + .global-playback {padding-top: 8px;} + .global-play {width: 50px; height: 50px; font-size: 25px; padding-left: 17px;} + .ui-input {padding: 5px 10px 5px 10px; border-radius: 20px; border: 1px solid rgb(80,80,80); outline: none;} + .large-button {width: 50px; height: 50px; font-size: 25px; padding-left: 15px;} + "] +end \ No newline at end of file diff --git a/libraries/groove/groove.css b/libraries/groove/groove.css new file mode 100644 index 0000000..aa19dfb --- /dev/null +++ b/libraries/groove/groove.css @@ -0,0 +1,7 @@ +.groove-stream-player-playback-control { + height: "100px"; + width: "100px"; + border-radius: "50%"; + border: "2px solid #f5f5f5"; + text-align: center; +} \ No newline at end of file diff --git a/libraries/groove/groove.eve b/libraries/groove/groove.eve new file mode 100644 index 0000000..14fc4b2 --- /dev/null +++ b/libraries/groove/groove.eve @@ -0,0 +1,240 @@ +# Groove API + +The groove API is configured with a record tagged `#groove` with the following shape: + +`[#groove client-id client-secret endpoint redirect-uri]` + + +## Logging Into Groove + +Commit a `#groove/login` to initiate the login process. This redirects to a +login portal, which asks the user to authorize access to Eve. Once the user +grants access, the browser is redirect back to the supplied `redirect-uri`. + +search + [#groove/login] + [#groove client-id client-secret redirect-uri] + response-type = "token" + scopes = string/url-encode[text: "MicrosoftMediaServices.GrooveApiAccess offline_access"] + encoded-redirect-uri = string/url-encode[text: redirect-uri] + address = "https://login.live.com/oauth20_authorize.srf/?client_id={{client-id}}&response_type={{response-type}}&redirect_uri={{encoded-redirect-uri}}&scope={{scopes}}" +commit + [#html/redirect url: address] +end + +A successful login will return to the app an access token + +search + [#html/url query: [#html/url/query key: "access_token" value: access-token]] + groove = [#groove] +commit + groove.access-token := access-token +end + + +## Getting Groove User Data + +With an access token in hand, we can use that to get user-specific information. + +search + [#groove/get-user] + [#groove access-token endpoint] +commit + [#http/request #groove-profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-profile response] +commit + [#json/decode #groove-profile json: response.body] +end + +Create a Groove user from the response + +search + [#groove-profile json-object: profile] +commit + [#groove/user region: profile.Culture subscription: profile.HasSubscription] +end + + +## Search Groove + +search + [#groove/search-track query] + [#groove access-token endpoint] + encoded-query = string/replace[text: query replace: " " with: "+"] + address = "{{endpoint}}/1/content/music/search?q={{encoded-query}}&filters=tracks" +commit + [#http/request #groove-search query address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-search query response: [body]] +commit + [#json/decode #groove-search query json: body] +end + +search + [#groove-search query json-object] + json-object = [Tracks: [Items: [value: [Id Name ReleaseDate Duration ImageUrl Album Artists: [value: [Artist]]]]]] +commit + [#groove/track query name: Name, id: Id, duration: Duration, image: ImageUrl album: Album | artist: Artist] +end + + +## Get a full song strem + +search + [#groove/full-stream track] + [#groove access-token endpoint] + address = "{{endpoint}}/1/content/{{track.id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" +commit + [#http/request #groove-get-song track address headers: + [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] +end + +search + [#groove-get-song track response: [body]] +commit + [#json/decode #groove-get-song track json: body] +end + +search + [#groove-get-song track json-object] + groove-stream = [#groove/full-stream track] +commit + groove-stream <- [stream-url: json-object.Url content-type: json-object.ContentType] +end + + +## Streaming Player + +A stream player has a playback control + +search + stream = [#groove/stream-player track] +bind + stream.controls += [#groove/stream-player/playback-control track] +end + +Playback controls are also buttons, so they render + +search + playback-control = [#groove/stream-player/playback-control track] +bind + playback-control += #ui/button +end + + +A stream player also has a playback element + +search + groove = [#groove client-id] + player = [#groove/stream-player track] +commit + playback = [#html/stream #groove/stream-player/playback track style: [display: "none"]] + player.playback := playback + groove.streams += playback +end + +The icon of a control matches the state of its player + +search + control = [#groove/stream-player/playback-control track] + playback = [#groove/stream-player/playback track] + not(playback = [#pending]) + state = if playback = [#playing] then "pause" + else "play" +bind + control.icon += state +end + +The icon of a pending stream is a loading animation + +search + control = [#groove/stream-player/playback-control track] + playback = [#groove/stream-player/playback #pending track] +bind + control.icon += "load-a" +end + +Display controls in the DOM + +search + stream = [#groove/stream-player #display controls] +bind + stream += #html/div + controls += #display + stream.children += controls +end + +Hide controls that aren't tagged `#dispaly` + +search + controls = [#groove/stream-player/playback-control] + not(controls = [#display]) +bind + controls.style.display += "none" +end + +Clicking the play button for the first time gets a link to the stream and +starts playback when the stream is ready. + +search + [#html/event/click element: [#groove/stream-player/playback-control track]] + pending-playback = [#groove/stream-player/playback track] + track = [#groove/track id] + not(pending-playback = [#ready]) +commit + pending-playback += #pending + [#groove/full-stream track] +end + +Attach the stream info to the stream playback + +search + [#groove/full-stream track stream-url content-type] + playback = [#groove/stream-player/playback track] +commit + playback <- [source: stream-url content-type] +end + +search + playback = [#groove/stream-player/playback #ready #pending track] +commit + playback.play := "true" + playback -= #pending +end + +Clicking the playback button when the stream is ready will toggle the stream + +search + q = [#html/event/click element: [#groove/stream-player/playback-control track]] + playback = [#groove/stream-player/playback #ready track] + state = if playback = [#playing] then "false" + else "true" +commit + playback.play := state +end + +If we ever get into a state where a stream is both `#playing` and `#paused` we +can default to paused. + +search + stream = [#groove/stream-player #playing #paused] +commit + stream -= #playing +end + +A streaming player that has no controls is paused, because you'd have no way to pause it yourself. + +search + [#groove streams] + streams = [#groove/stream-player/playback #playing track] + not([#groove/stream-player/playback-control track]) +commit + streams.play := "false" +end \ No newline at end of file diff --git a/libraries/html/stream.eve b/libraries/html/stream.eve new file mode 100644 index 0000000..f334e01 --- /dev/null +++ b/libraries/html/stream.eve @@ -0,0 +1,42 @@ +# Stream Element + +search + stream = [#html/stream source] +watch client/websocket + ("stream/create", stream, source) +end + +search + stream = [#html/stream] +bind + stream <- [#html/video id: stream] +end + +search + stream = [#html/stream play] +watch client/websocket + ("stream/play", stream, play) +end + +search + [#html/event/stream-ready stream] + stream = [#html/stream] +commit + stream += #ready +end + +search + [#html/event/stream-play stream] + stream = [#html/stream] +commit + stream += #playing + stream -= #paused +end + +search + [#html/event/stream-pause stream] + stream = [#html/stream] +commit + stream += #paused + stream -= #playing +end \ No newline at end of file diff --git a/libraries/html/stream.ts b/libraries/html/stream.ts new file mode 100644 index 0000000..b4abaeb --- /dev/null +++ b/libraries/html/stream.ts @@ -0,0 +1,67 @@ +import {Library, createId, RawValue, RawEAV, handleTuples, libraries} from "../../ts"; +import Hls from "hls.js" + +const EMPTY:never[] = []; + +export class Stream extends Library { + static id = "stream"; + streams: any = {}; + + html:libraries.HTML; + + setup() { + this.html = this.program.attach("html") as libraries.HTML; + } + + handlers = { + "create": handleTuples(({adds}) => { + for(let [streamID, source] of adds || EMPTY) { + if(Hls.isSupported()) { + let video: any = document.getElementById(`${streamID}`); + var hls = new Hls(); + let program = this.program + hls.loadSource(`${source}`); + hls.attachMedia(video); + hls.on(Hls.Events.MANIFEST_PARSED,function() { + + }); + video.onplay = function () { + let play_id = createId(); + program.inputEAVs([ + [play_id, "tag", "html/event/stream-play"], + [play_id, "stream", streamID], + ]); + }; + video.onpause = function () { + let paused_id = createId(); + program.inputEAVs([ + [paused_id, "tag", "html/event/stream-pause"], + [paused_id, "stream", streamID], + ]); + }; + video.onloadeddata = function () { + let ready_id = createId(); + program.inputEAVs([ + [ready_id, "tag", "html/event/stream-ready"], + [ready_id, "stream", streamID], + ]); + } + //window.addEventListener("pageshow", video.onplay()); + this.streams[streamID] = video; + } + } + }), + "play": handleTuples(({adds}) => { + for(let [streamID, play] of adds || EMPTY) { + let video = this.streams[streamID]; + if (play === "true") { + video.play(); + } else { + video.pause(); + } + } + }) + } +} + +Library.register(Stream.id, Stream); \ No newline at end of file diff --git a/package.json b/package.json index b71b071..e62d119 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "license": "UNLICENSED", "dependencies": { "@types/vis": "^4.18.4", + "hls.js": "^0.7.11", "codemirror": "^5.28.0", "codemirror-mode-eve": "0.0.1", "md5": "^2.2.1", @@ -29,6 +30,7 @@ "@types/codemirror": "0.0.41", "@types/md5": "^2.1.32", "@types/uuid": "^3.4.0", + "@types/hls.js": "^0.7.5", "autoprefixer": "^7.1.2", "postcss-color-function": "^4.0.0", "postcss-custom-properties": "^6.1.0", From fc38edff6535988ed5fc538a39186114fda743a1 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 19:45:56 -0700 Subject: [PATCH 97/99] fix namespacing --- libraries/groove/groove.eve | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/groove/groove.eve b/libraries/groove/groove.eve index 14fc4b2..8ebec04 100644 --- a/libraries/groove/groove.eve +++ b/libraries/groove/groove.eve @@ -40,20 +40,20 @@ search [#groove/get-user] [#groove access-token endpoint] commit - [#http/request #groove-profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: + [#http/request #groove/profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] end search - [#groove-profile response] + [#groove/profile response] commit - [#json/decode #groove-profile json: response.body] + [#json/decode #groove/profile json: response.body] end Create a Groove user from the response search - [#groove-profile json-object: profile] + [#groove/profile json-object: profile] commit [#groove/user region: profile.Culture subscription: profile.HasSubscription] end @@ -67,18 +67,18 @@ search encoded-query = string/replace[text: query replace: " " with: "+"] address = "{{endpoint}}/1/content/music/search?q={{encoded-query}}&filters=tracks" commit - [#http/request #groove-search query address headers: + [#http/request #groove/search query address headers: [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] end search - [#groove-search query response: [body]] + [#groove/search query response: [body]] commit - [#json/decode #groove-search query json: body] + [#json/decode #groove/search query json: body] end search - [#groove-search query json-object] + [#groove/search query json-object] json-object = [Tracks: [Items: [value: [Id Name ReleaseDate Duration ImageUrl Album Artists: [value: [Artist]]]]]] commit [#groove/track query name: Name, id: Id, duration: Duration, image: ImageUrl album: Album | artist: Artist] @@ -92,18 +92,18 @@ search [#groove access-token endpoint] address = "{{endpoint}}/1/content/{{track.id}}/stream?clientInstanceId=2E19AC92-8600-11E7-8200-4CC9641576C9" commit - [#http/request #groove-get-song track address headers: + [#http/request #groove/get-song track address headers: [#http/header key: "Authorization" value: "Bearer {{access-token}}"]] end search - [#groove-get-song track response: [body]] + [#groove/get-song track response: [body]] commit - [#json/decode #groove-get-song track json: body] + [#json/decode #groove/get-song track json: body] end search - [#groove-get-song track json-object] + [#groove/get-song track json-object] groove-stream = [#groove/full-stream track] commit groove-stream <- [stream-url: json-object.Url content-type: json-object.ContentType] From e35fa071c216720b96f8f43cd105e7644b9fa1a4 Mon Sep 17 00:00:00 2001 From: cmontella Date: Wed, 6 Sep 2017 15:29:27 -0700 Subject: [PATCH 98/99] parse query strings automatically --- libraries/http/http.eve | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/libraries/http/http.eve b/libraries/http/http.eve index 39d4ae6..f2572f8 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -1,7 +1,5 @@ # HTTP - - ## Send an HTTP Request HTTP requests accept several attributes: @@ -99,10 +97,40 @@ commit chunk := none end -## Process HTTP Requests +## Receive HTTP Requests search server = [#http/server address] watch http ("server", server, address) -end \ No newline at end of file +end + + +## Parse Query Strings + +search + url = [#html/url query] +bind + [#html/url/parse-query url query] +end + +search + parse = [#html/url/parse-query query] + (pair, i) = string/split[text: query by: "&"] + (token, index) = string/split[text: pair by: "="] +bind + [#html/url/query-kvs parse pair token index] +end + +search + [#html/url/query-kvs parse pair, token: key, index: 1] + [#html/url/query-kvs parse pair, token: value, index: 2] +bind + parse.result += [#html/url/query key value] +end + +search + [#html/url/parse-query url result] +bind + url.parsed-query += result +end From ea7d5fb35ee6aee8cd01f74fb65124c850d4e96d Mon Sep 17 00:00:00 2001 From: cmontella Date: Fri, 15 Sep 2017 17:58:48 -0700 Subject: [PATCH 99/99] merge --- examples/bouncing-balls.eve | 10 +- examples/card-validation.eve | 168 +++++++++++++++++ examples/gui.eve | 34 ++++ examples/setlist-aggregator.eve | 314 ++++++++------------------------ libraries/app/app.eve | 33 +++- libraries/groove/groove.eve | 19 +- libraries/groove/setlist-fm.eve | 55 ++++++ libraries/html/stream.eve | 14 ++ libraries/html/stream.ts | 35 +++- libraries/http/http.eve | 50 ++++- libraries/ui/ui.eve | 4 + 11 files changed, 465 insertions(+), 271 deletions(-) create mode 100644 examples/card-validation.eve create mode 100644 examples/gui.eve create mode 100644 libraries/groove/setlist-fm.eve diff --git a/examples/bouncing-balls.eve b/examples/bouncing-balls.eve index d3f56a7..ed2c497 100644 --- a/examples/bouncing-balls.eve +++ b/examples/bouncing-balls.eve @@ -8,7 +8,7 @@ commit end search - order = math/range[from:1 to:200] + order = math/range[from:1 to:1] rand = random/number[seed: order] rand2 = random/number[seed: order * order] x = rand * 500 @@ -20,17 +20,17 @@ commit end search - boid = [#boid x > 490] + boid = [#boid x > 500] boid.vx > 0 commit boid.vx := boid.vx * -0.9 end search - boid = [#boid y > 490] + boid = [#boid y > 500] boid.vy > 0 commit - boid.vy := boid.vy * -0.9 + boid.vy := boid.vy * -0.7 end search @@ -53,7 +53,7 @@ search commit boid.x := x + vx boid.y := y + vy - boid.vy := vy + 0.07 + boid.vy := vy + 0.16 end search diff --git a/examples/card-validation.eve b/examples/card-validation.eve new file mode 100644 index 0000000..84e2b26 --- /dev/null +++ b/examples/card-validation.eve @@ -0,0 +1,168 @@ +# Credit Card Validation + +commit + [#credit-card number: "438857601841070"] +end + +## Rules for Valid cards + +- Be between 13 and 16 digits + +- Must start with: +- 4 for Visa cards +- 5 for Master cards +- 37 for American Express cards +- 6 for Discover cards + +- Each number goes through a transformation, then is checked whether or not the + result is divisible by 10. If divisible, then the number is valid. If not, + then it is not. +- The transformation can be described below: + 1. Double every second digit from right to left. If doubling of a digit results + in a two-digit number, add up the two digits to get a single-digit number. + 2. Now add all single-digit numbers from Step 1. + 3. Add all digits in the odd places from right to left in the card number + 4. Sum the results from Step 2 and Step 3. + 5. The result is the number transformed + +## Identify Bank + +search + cc = [#credit-card number] + first-digits = string/substring[text: number from: 1 to: 3] +bind + cc.first-digits += first-digits +end + +search + cc = [#credit-card first-digits] + bank = if "4" = string/substring[text: first-digits from: 1 to: 2] then "Visa" + if "5" = string/substring[text: first-digits from: 1 to: 2] then "Mastercard" + if "37" = first-digits then "American Express" + if "6" = string/substring[text: first-digits from: 1 to: 2] then "Discover" +bind + cc.bank += bank +end + +## Transform the cc number + +Index every digit in each number + +search + cc = [#credit-card number] + (token,index) = string/split[text: number by: ""] + index - 1 > 0 + index - 1 < 17 + numerical-digit = eve/parse-value[value: token] +bind + [#digits cc index: index - 1 digit: numerical-digit] +end + +search + digits = [#digits cc index digit] + number-length = string/length[text: cc.number] + reverse-index = -1 * (index - number-length) + 1 +bind + + digits.reverse-index += reverse-index +end + +### Step 1. + +Double every second digit from the right. We'll tag these digits as `#even-index` + +search + digits = [#digits cc reverse-index digit] + 0 = math/mod[value: reverse-index, by: 2] +bind + digits += #even-index +end + +If a doubled digit is >= 10, sum the digits of that doubled digit. Othewise +just double the digit + +search + digi = [#digits #even-index] + doubled = if digi.digit < 5 then digi.digit * 2 + else if digi.digit = 5 then 1 + else if digi.digit = 6 then 3 + else if digi.digit = 7 then 5 + else if digi.digit = 8 then 7 + else if digi.digit = 9 then 9 +bind + digi.doubled += doubled +end + +### Step 2 + +Sum all of the doubled digits in Step 1 together + +search + digi = [#digits #even-index cc reverse-index doubled] + doubled-sum = gather/sum[value: doubled for: (doubled,reverse-index) per: cc] +bind + cc.step-two-sum += doubled-sum +end + +### Step 3 + +Sum the odd digits starting from the right + +search + digits = [#digits cc reverse-index] + 1 = math/mod[value: reverse-index, by: 2] +bind + digits += #odd-index +end + +search + digits = [#digits #odd-index cc reverse-index] + step-three-sum = gather/sum[value: digits.digit for: reverse-index per: cc] +bind + cc.step-three-sum += step-three-sum +end + +### Step 4 + +Sum the results from step 2 and 3 + +search + cc = [#credit-card step-two-sum step-three-sum] + transformed-number = step-two-sum + step-three-sum +bind + cc.transformed-number += transformed-number +end + +## Test Validity + +A number is valid if it is between 13 and 16 digits and the transformation +is divisible by 10 + +search + cc = [#credit-card number transformed-number] + number-length = string/length[text: number] + number-length >= 13 + number-length <= 16 + 0 = math/mod[value: transformed-number by: 10] +bind + cc += #valid +end + + +## Some Output for Testing + +search + digi = [#digits cc index reverse-index digit] + doubled = if digi.doubled then digi.doubled + else "" + valid = if cc = [#valid] then "valid" + else "invalid" +bind + [#html/div #container cc | children: + [#html/div sort: -1 text: cc.number] + [#html/div sort: -2 text: cc.bank] + [#html/div sort: -2 text: valid]] +end + + + diff --git a/examples/gui.eve b/examples/gui.eve new file mode 100644 index 0000000..cea69f8 --- /dev/null +++ b/examples/gui.eve @@ -0,0 +1,34 @@ +# Gui Editor + +This is a drag and drop editor for interfaces in Eve + +commit + [#app/settings name: "GUI Builder"] + showlist = [#app/page #default name: "Editor" icon: "ios-list-outline" sort: 1] + [#app/ui] +end + + +search + page.name = "Editor" + [#app/ui page] +bind + page <- [#html/div #html/listener/context-menu #main-area | children: + [#html/div text: "Hello world"]] +end + + +search + [#html/event/mouse-up button: "right" target: container page-x page-y] + //canvas = [#main-area] +commit + //container.children += [#container page-x page-y] + container.children += [#container text: "{{page-x}}{{page-y}}" x: page-x y: page-y] +end + + +search + container = [#container x y] +bind + container <- [#html/div #html/listener/context-menu style: [min-width: "100px" min-height: "100px" padding: "10px" border: "1px solid black"]] +end \ No newline at end of file diff --git a/examples/setlist-aggregator.eve b/examples/setlist-aggregator.eve index be705ef..2679c53 100644 --- a/examples/setlist-aggregator.eve +++ b/examples/setlist-aggregator.eve @@ -1,4 +1,4 @@ -# Setlist Aggregator + # Setlist Aggregator This app takes a set of concerts, and accesses the setlist.fm api to retreive @@ -8,92 +8,36 @@ the MS Groove API to create playlists for those concerts. ## App Configuration commit + [#app/settings name: "Setlist Aggregator" + groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" redirect-uri: "http://localhost:8081"] + setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275"]] + [#app/ui] playing = [#app/page name: "Now Playing" icon: "ios-musical-notes" sort: 0] - showlist = [#app/page name: "Show List" icon: "ios-list-outline" sort: 1] + showlist = [#app/page #default name: "Show List" icon: "ios-list-outline" sort: 1] collection = [#app/page name: "Collection" icon: "ios-albums-outline" sort: 2] stats = [#app/page name: "Show Stats" icon: "stats-bars" sort: 3] map = [#app/page name: "Map" icon: "map" sort: 4] settings = [#app/page name: "Settings" icon: "gear-a"] - [#app/configuration groove: [#groove client-id: "1efe0909-4134-4740-96e6-dfb02ac095ba" client-secret: "UskJMTfCRNffOLbuBnGSBb2" endpoint: "https://music.xboxlive.com" redirect-uri: "http://localhost:8081"] - setlist-fm: [#setlist-fm api-key: "f6c6164c-e52f-4aa5-bd22-1c76b208d275" endpoint: "https://api.setlist.fm/rest"] - pages: (showlist, collection, map, playing, stats)] - [#app/interface page: showlist] -end - -## Layout - -### Skeleton Layout - -search - interface = [#app/interface] -commit - interface <- [|children: - [#ui/column style: [width: "1600px" height: "900px"] | children: - [#header] - [#content] - [#global-playback] - ]] -end - -### Header - -search - header = [#header] -bind - header += #ui/row - header <- [|children: - [#html/div #header/top-left sort: 1] - [#ui/spacer sort: 2] - [#html/div sort: 3 text: "Setlist Aggregator"] - [#ui/spacer sort: 4] - [#html/div #header/top-right sort: 5] - ] -end - -### Navigation - -search - nav = [#navigation] - [#app/configuration pages: page] - page = [#app/page name icon sort] - settings = [#app/page name: "Settings"] -bind - nav <- [#ui/column | children: - [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: - [#navigation/button #ui/button icon page text: name sort]] - [#ui/spacer sort: 99] - [#navigation/button #ui/button page: settings icon: settings.icon text: settings.name sort: 100] - ] -end - -### Content - -search - content = [#content] -bind - content <- [#ui/row style: [height: "100%"] |children: - [#navigation sort: 1] - [#page-container sort: 2] - [#ui/spacer sort: 3] - ] end ### Footer search - playback = [#global-playback] - + middle = [#app/layout/footer/middle] bind - playback <- [#ui/row] + middle += #global-playback end search playback = [#global-playback] [#now-playing track] + [#html/stream track current-time duration] bind - playback <- [|children: + playback <- [children: [#ui/spacer sort: 1] - [#groove/stream-player/playback-control #global-play sort: 2 track] + [#ui/column #now-playing-controls sort: 2 | children: + [#groove/stream-player/playback-control #global-play sort: 1 track] + [#ui/progress #song-progress min: 0 max: duration value: current-time width: 400]] [#ui/spacer sort: 3]] end @@ -103,42 +47,19 @@ search playback = [#global-playback] not([#now-playing track]) bind - playback <- [|children: + playback <- [children: [#ui/spacer sort: 1] [#groove/stream-player/playback-control #ui/button #global-play icon: "play"] [#ui/spacer sort: 3]] end - -### Page Container - -search - page-container = [#page-container] - [#app/interface page] -commit - page-container += #html/div - page-container <- [|children: page] -end - -### Navigate Between Pages - -search - [#html/event/click element: [#navigation/button page]] - interface = [#app/interface] -commit - interface.page := page -end - - ## Header search - top-right = [#header/top-right] + top-right = [#html/div #app/layout/header/right] not([#groove/user]) bind - top-right <- [|children: - [#ui/button #groove-login text: "Log in to Groove"] - ] + top-right.children += [#ui/button #groove-login text: "Log in to Groove"] end Logging in is kicked off by clicking the login button. @@ -164,7 +85,7 @@ end search page.name = "Now Playing" - [#app/interface page] + [#app/ui page] [#now-playing track] track = [#groove/track image name duration artist album] bind @@ -198,7 +119,7 @@ end search page.name = "Collection" - [#app/interface page] + [#app/ui page] track = [#groove/track image name image duration artist album] bind page <- [#html/div #scrollable | children: @@ -216,12 +137,12 @@ end search page.name = "Show List" - [#app/interface page] + [#app/ui page] bind page <- [#ui/row | style: [width: "100%"] children: - [#show-list/shows] - [#show-list/show-detail] - [#show-list/search-pane] + [#app/page/pane #show-list/shows width: 1] + [#app/page/pane #show-list/show-detail width: 1] + [#app/page/pane #show-list/search-pane width: 2] ] end @@ -232,7 +153,7 @@ search show = [#show artist date] not(show = [#matched]) bind - show-list <- [#ui/column | children: + show-list <- [children: [#html/div #ui/header text: "Unmatched Shows"] [#show-list/unmatched-shows #ui/list #ui/selectable #ui/single-selectable | item: [#html/div #show-list/unmatched-show sort: "{{date}}{{artist}}" show text: "{{date}} - {{artist}} "] @@ -270,7 +191,7 @@ search [#setlist-fm/search/setlists artist: show.artist date: show.date setlist] not(show = [#matched]) bind - show-detail <- [#ui/column | children: + show-detail <- [children: [#ui/row #info-head | children: [#ui/column #show-info sort: 1 | children: [#html/div #artist sort: -1 text: setlist.artist.name] @@ -359,7 +280,7 @@ search track = [#groove/track query name image duration artist album] song.artist = artist.Name bind - search-pane <- [#ui/column | children: + search-pane <- [children: [#ui/row #track-search children: [#ui/spacer sort: 1] [#ui/input sort: 2 icon: "play" value: query] @@ -403,13 +324,6 @@ bind matched-song.children += [#groove/stream-player #display track] end - - - - - - - ## App Data commit @@ -431,90 +345,6 @@ end - - - - -## Get Data From Setlist.fm - -search - find-setlist = [#setlist-fm/search/setlists artist date] - [#setlist-fm api-key endpoint] - encoded-artist = string/url-encode[text: artist] - address = "{{endpoint}}/1.0/search/setlists?artistName={{encoded-artist}}&date={{date}}&p=1" -commit - [#http/request #find-setlist find-setlist address headers: - [#http/header key: "x-api-key" value: api-key] - [#http/header key: "Accept" value: "application/json"]] -end - -search - [#find-setlist find-setlist response: [body]] -commit - [#json/decode #find-setlist find-setlist json: body] -end - -search - result = [#find-setlist find-setlist json-object] - not(result = [#finished]) - json-object = [setlist: [value: [artist id eventDate tour venue sets: [set]]]] - set = [index: set-number value: [song: [index: song-number value: [name: song-name]]]] -commit - result += #finished - find-setlist += #finished - songs = [#setlist-fm/song artist: artist.name number: song-number, name: song-name] - sets = [#setlist-fm/set id number: set-number | songs] - find-setlist.setlist += [#setlist-fm/setlist id artist venue date: eventDate, tour: tour.name | sets] -end - -Clean up records - -search - complete = [#find-setlist #finished json-object] - find-setlist = [#find-setlist] -commit - find-setlist := none - complete := none -end - - -## Misc Diagnostics - -search - [#http/request/error error] -commit - [#html/div text: error] -end - -search - [#disable] - [#http/request/finished request] -commit - [#html/div request text: "***Finished*** {{request}}"] -end - -search - [#disable] - q = [#http/response body] -commit - [#html/div text: "THIS IS THE BODY: {{body}}"] - //[#json/decode json: body] -end - -search - [#disable] - [#json/decode json-object] -commit - [#html/div text: "{{json-object.Tracks.Items.value.Name}} {{json-object.Tracks.Items.value.Album.Name}} - {{json-object.Tracks.Items.value.Id}}"] -end - -search - request = [#http/request] - not(request = [#finished]) -bind - [#html/div text: "Processing request..."] -end - ## Styles Style all playback controls as circle buttons @@ -526,51 +356,49 @@ bind end -commit - [#html/style text: " - body { background-color: rgb(24,24,24); height: 40px; color: rgb(200,200,200); } - div { user-select: none; cursor: default;} - div::-webkit-scrollbar { width: 5px; } - div::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); } - div::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.5); outline: 1px solid slategrey; border-radius: 25px; } - .ui-button { background-color: rgb(40,40,40); color: rgb(200,200,200); padding: 10px; border: 1px solid rgb(60,60,60); margin-bottom: 10px; } - .ui-button:hover { background-color: rgb(50,50,50); color: rgb(200,200,200); border: 1px solid rgb(60,60,60);} - .header { background-color: rgb(18,18,18); padding: 10px; } - .global-playback { background-color: rgb(40,40,40); height: 100px; color: rgb(200,200,200); } - .ui-list-item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px solid rgb(80,80,80); } - .ui-list-item:hover { background-color: rgb(40,40,40); } - .show-list-shows { width: 250px; } - .show-list-show-detail {padding-left: 20px; padding-right: 20px; background-color: rgb(22,22,22); width: 400px; margin-right: 10px;} - .artist { font-size: 26px; margin-bottom: 10px; } - .tour { margin-bottom: 5px; color: rgb(150,150,150); } - .venue { margin-bottom: 5px; color: rgb(150,150,150);} - .date { margin-bottom: 5px; color: rgb(150,150,150);} - .setlists { overflow: auto; max-height: 650px; margin-top: 15px; padding-right: 10px;} - .setlist { margin-top: 10px; margin-bottom: 20px; } - .song { padding: 5px; line-height: 30px; } - .song-number { width: 15px; margin-right: 10px; color: rgb(120,120,120); text-align: right } - .song:hover { background-color: rgb(40,40,40);} - .show-list-search-pane {min-width: 400px; max-width: 500px; overflow: auto;} - .album-image {width: 100px; margin-right: 20px;} - .album-result {padding: 20px;} - .ui-header {padding: 10px; margin-bottom: 10px; font-size: 20px;} - .track-search {padding: 10px; padding-top: 20px; padding-bottom: 20px; background-color: rgb(18,18,18);} - .navigation { background-color: rgb(18,18,18); width: 150px; height: 100%; } - .navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(30,30,30); user-select: none; cursor: default; margin: 0px; border: 0px; width: 100%; min-height: 75px;} - .navigation-button:hover { color: rgb(255,255,255); border: 0px; } - .circle-button {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} - .circle-button:hover {background-color: rgb(0,158,224,1); border-color: white; color: white;} - .track-name {padding-bottom: 10px;} - .results-list .ui-list {padding-right: 10px;} - .results-list {overflow: auto; max-height: 700px;} - .track-info {width: 150px;} - table { border-collapse: collapse; margin: 20px;} - td { padding: 10px; } - table, th, td { border: 1px solid rgb(80,80,80); } - .scrollable { overflow: auto; height: 800px; } - .global-playback {padding-top: 8px;} - .global-play {width: 50px; height: 50px; font-size: 25px; padding-left: 17px;} - .ui-input {padding: 5px 10px 5px 10px; border-radius: 20px; border: 1px solid rgb(80,80,80); outline: none;} - .large-button {width: 50px; height: 50px; font-size: 25px; padding-left: 15px;} - "] -end \ No newline at end of file + +[#html/style text: " + body { background-color: rgb(24,24,24); height: 40px; color: rgb(200,200,200); } + div { user-select: none; cursor: default;} + div::-webkit-scrollbar { width: 5px; } + div::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0); } + div::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.5); outline: 1px solid slategrey; border-radius: 25px; } + .ui-button { background-color: rgb(40,40,40); color: rgb(200,200,200); padding: 10px; border: 1px solid rgb(60,60,60); margin-bottom: 10px; } + .ui-button:hover { background-color: rgb(50,50,50); color: rgb(200,200,200); border: 1px solid rgb(60,60,60);} + .header { background-color: rgb(18,18,18); padding: 10px; } + .global-playback { background-color: rgb(40,40,40); height: 100px; color: rgb(200,200,200); } + + .show-list-shows { width: 250px; } + .show-list-show-detail {padding-left: 20px; padding-right: 20px; background-color: rgb(22,22,22); width: 400px; margin-right: 10px;} + .artist { font-size: 26px; margin-bottom: 10px; } + .tour { margin-bottom: 5px; color: rgb(150,150,150); } + .venue { margin-bottom: 5px; color: rgb(150,150,150);} + .date { margin-bottom: 5px; color: rgb(150,150,150);} + .setlists { overflow: auto; max-height: 650px; margin-top: 15px; padding-right: 10px;} + .setlist { margin-top: 10px; margin-bottom: 20px; } + .song { padding: 5px; line-height: 30px; } + .song-number { width: 15px; margin-right: 10px; color: rgb(120,120,120); text-align: right } + .song:hover { background-color: rgb(40,40,40);} + .show-list-search-pane {min-width: 400px; max-width: 500px; overflow: auto;} + .album-image {width: 100px; margin-right: 20px;} + .album-result {padding: 20px;} + + .track-search {padding: 10px; padding-top: 20px; padding-bottom: 20px; background-color: rgb(18,18,18);} + .navigation { background-color: rgb(18,18,18); width: 150px; height: 100%; } + .navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(30,30,30); user-select: none; cursor: default; margin: 0px; border: 0px; width: 100%; min-height: 75px;} + .navigation-button:hover { color: rgb(255,255,255); border: 0px; } + .circle-button {padding: 0px; padding-left: 10px; border-radius: 40px; width: 30px; height: 30px; background-color: rgba(0,0,0,0); border: 1px solid white; margin: 10px;} + .circle-button:hover {background-color: rgb(0,158,224,1); border-color: white; color: white;} + .track-name {padding-bottom: 10px;} + .results-list .ui-list {padding-right: 10px;} + .results-list {overflow: auto; max-height: 700px;} + .track-info {width: 150px;} + table { border-collapse: collapse; margin: 20px;} + td { padding: 10px; } + table, th, td { border: 1px solid rgb(80,80,80); } + .scrollable { overflow: auto; height: 800px; } + .global-playback {padding-top: 8px;} + .global-play {width: 50px; height: 50px; font-size: 25px; padding-left: 17px;} + + .large-button {width: 50px; height: 50px; font-size: 25px; padding-left: 15px;} + "] diff --git a/libraries/app/app.eve b/libraries/app/app.eve index ba67c75..761b10f 100644 --- a/libraries/app/app.eve +++ b/libraries/app/app.eve @@ -107,13 +107,36 @@ commit ui.page := page end +If no tab is selected, display the default page + +search + default-page = [#app/page #default] + default-page-button = [#app/layout/navigation-button page: default-page] + ui = [#app/ui] + nav = [#navigation/pages] + not([#app/layout/navigation-button #ui/selected]) +commit + nav.selected := default-page-button + ui.page := default-page +end + #### Page Container search page-container = [#app/layout/page-container] [#app/ui page] bind - page-container <- [#html/div #app/layout/page | children: page] + page-container <- [#html/div #app/layout/page children: page] +end + +#### Panes + +search + pane = [#app/page/pane] + width = if pane.width then pane.width else 1 +bind + pane += #ui/column + pane <- [style: [flex-grow: "{{width}}"]] end ### Footer @@ -127,7 +150,7 @@ search bind header <- [#ui/row | children: [#html/div #app/layout/footer/left sort: 1] - [#html/div #app/layout/footer/middle sort: 2 text: "Footer"] + [#html/div #app/layout/footer/middle sort: 2] [#html/div #app/layout/footer/right sort: 3]] end @@ -140,8 +163,10 @@ commit .app-layout-header-middle { padding: 10px; } .app-layout-footer { background-color: rgb(200,200,200); display: flex; justify-content: space-between; } .app-layout-footer-middle { padding: 10px; } - .app-layout-content {flex-grow: 1;} - .app-layout-page-container { padding: 10px; } + .app-layout-content {flex-grow: 1; display: flex;} + .app-page { display: flex; height: 100%; } + .app-page-pane {padding: 10px; overflow: auto;} + .app-layout-page-container { padding: 10px; flex-grow: 1; } .app-layout-navigation { background-color: rgb(130,130,130); } .app-layout-navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(230,230,230); margin: 0px; border: 0px; border-radius: 0px; width: 100%; min-height: 75px; } .app-layout-navigation-button.ui-selected { background-color: rgb(255,255,255); color: rgb(0,158,224); } diff --git a/libraries/groove/groove.eve b/libraries/groove/groove.eve index 8ebec04..8f56c81 100644 --- a/libraries/groove/groove.eve +++ b/libraries/groove/groove.eve @@ -2,8 +2,16 @@ The groove API is configured with a record tagged `#groove` with the following shape: -`[#groove client-id client-secret endpoint redirect-uri]` +`[#groove client-id client-secret redirect-uri]` +## Specify Groove endpoint + +search + groove = [#groove] + e-point = if groove.endpoint then groove.endpoint else "https://music.xboxlive.com" +bind + groove.endpoint += e-point +end ## Logging Into Groove @@ -22,10 +30,16 @@ commit [#html/redirect url: address] end +search + [#html/url hash] +bind + [#html/url/parse-query query: hash] +end + A successful login will return to the app an access token search - [#html/url query: [#html/url/query key: "access_token" value: access-token]] + [#html/url/query key: "access_token" value: access-token] groove = [#groove] commit groove.access-token := access-token @@ -37,7 +51,6 @@ end With an access token in hand, we can use that to get user-specific information. search - [#groove/get-user] [#groove access-token endpoint] commit [#http/request #groove/profile address: "{{endpoint}}/1/user/music/profile" method: "GET" headers: diff --git a/libraries/groove/setlist-fm.eve b/libraries/groove/setlist-fm.eve new file mode 100644 index 0000000..c4071ff --- /dev/null +++ b/libraries/groove/setlist-fm.eve @@ -0,0 +1,55 @@ +# Setlist.fm + + +## Specify endpoint + + +search + setlist = [#setlist-fm] + e-point = if setlist.endpoint then setlist.endpoint else "https://api.setlist.fm/rest" +bind + setlist.endpoint += e-point +end + +## Search Setlists + +search + find-setlist = [#setlist-fm/search/setlists artist date] + [#setlist-fm api-key endpoint] + encoded-artist = string/url-encode[text: artist] + address = "{{endpoint}}/1.0/search/setlists?artistName={{encoded-artist}}&date={{date}}&p=1" +commit + [#http/request #setlist-fm/find-setlist find-setlist address headers: + [#http/header key: "x-api-key" value: api-key] + [#http/header key: "Accept" value: "application/json"]] +end + +search + [#setlist-fm/find-setlist find-setlist response: [body]] +commit + [#json/decode #setlist-fm/find-setlist find-setlist json: body] +end + +search + result = [#setlist-fm/find-setlist find-setlist json-object] + not(result = [#finished]) + json-object = [setlist: [value: [artist id eventDate tour venue sets: [set]]]] + set = [index: set-number value: [song: [index: song-number value: [name: song-name]]]] +commit + result += #finished + find-setlist += #finished + songs = [#setlist-fm/song artist: artist.name number: song-number, name: song-name] + sets = [#setlist-fm/set id number: set-number | songs] + find-setlist.setlist += [#setlist-fm/setlist id artist venue date: eventDate, tour: tour.name | sets] +end + +Clean up records + +search + complete = [#setlist-fm/find-setlist #finished json-object] + find-setlist = [#setlist-fm/find-setlist] +commit + find-setlist := none + complete := none +end + diff --git a/libraries/html/stream.eve b/libraries/html/stream.eve index f334e01..f282a08 100644 --- a/libraries/html/stream.eve +++ b/libraries/html/stream.eve @@ -39,4 +39,18 @@ search commit stream += #paused stream -= #playing +end + +search + time-change = [#html/event/time-change stream time] +commit + time-change := none + stream.current-time := time +end + +search + duration-change = [#html/event/duration-change stream duration] +commit + duration-change := none + stream.duration := duration end \ No newline at end of file diff --git a/libraries/html/stream.ts b/libraries/html/stream.ts index b4abaeb..e6fd60b 100644 --- a/libraries/html/stream.ts +++ b/libraries/html/stream.ts @@ -26,27 +26,42 @@ export class Stream extends Library { }); video.onplay = function () { - let play_id = createId(); + let id = createId(); program.inputEAVs([ - [play_id, "tag", "html/event/stream-play"], - [play_id, "stream", streamID], + [id, "tag", "html/event/stream-play"], + [id, "stream", streamID], ]); }; video.onpause = function () { - let paused_id = createId(); + let id = createId(); program.inputEAVs([ - [paused_id, "tag", "html/event/stream-pause"], - [paused_id, "stream", streamID], + [id, "tag", "html/event/stream-pause"], + [id, "stream", streamID], ]); }; video.onloadeddata = function () { - let ready_id = createId(); + let id = createId(); program.inputEAVs([ - [ready_id, "tag", "html/event/stream-ready"], - [ready_id, "stream", streamID], + [id, "tag", "html/event/stream-ready"], + [id, "stream", streamID], + ]); + } + video.ontimeupdate = function () { + let id = createId(); + program.inputEAVs([ + [id, "tag", "html/event/time-change"], + [id, "stream", streamID], + [id, "time", video.currentTime] + ]); + } + video.ondurationchange = function() { + let id = createId(); + program.inputEAVs([ + [id, "tag", "html/event/duration-change"], + [id, "stream", streamID], + [id, "duration", video.duration] ]); } - //window.addEventListener("pageshow", video.onplay()); this.streams[streamID] = video; } } diff --git a/libraries/http/http.eve b/libraries/http/http.eve index f2572f8..0de3ee1 100644 --- a/libraries/http/http.eve +++ b/libraries/http/http.eve @@ -19,9 +19,9 @@ Default method search request = [#http/request] - not(request.method) + method = if m = request.method then m else "GET" bind - request.method += "GET" + request.method += method end Default empty body @@ -45,11 +45,11 @@ end Associate response with its request search - response-change = [#http/response/change response] - response = [#html/response request] + response-received = [#http/response/received response] + response = [#http/response] request = [#http/request] commit - response-change := none + response-received := none request.response := response end @@ -116,7 +116,7 @@ end search parse = [#html/url/parse-query query] - (pair, i) = string/split[text: query by: "&"] + pair = if (qq, i) = string/split[text: query by: "&"] then qq else query (token, index) = string/split[text: pair by: "="] bind [#html/url/query-kvs parse pair token index] @@ -134,3 +134,41 @@ search bind url.parsed-query += result end + + +## Diagnostics + +search + [#http/request/error error] +commit + [#html/div text: error] +end + +search + [#disable] + [#http/request/finished request] +commit + [#html/div request text: "***Finished*** {{request}}"] +end + +search + [#disable] + q = [#http/response body] +commit + [#html/div text: "THIS IS THE BODY: {{body}}"] + //[#json/decode json: body] +end + +search + [#disable] + [#json/decode json-object] +commit + [#html/div text: "{{json-object.Tracks.Items.value.Name}} {{json-object.Tracks.Items.value.Album.Name}} - {{json-object.Tracks.Items.value.Id}}"] +end + +search + request = [#http/request] + not(request = [#finished]) +bind + [#html/div text: "Processing request..."] +end \ No newline at end of file diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 577c194..8c76194 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -1012,6 +1012,10 @@ commit transform: rotate(360deg) } } + .ui-header {padding: 10px; margin-bottom: 10px; font-size: 20px;} + .ui-list-item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px solid rgb(200,200,200); } + .ui-list-item:hover { background-color: rgb(250,250,250); } + .ui-input {padding: 5px 10px 5px 10px; border-radius: 20px; border: 1px solid rgb(200,200,200); outline: none;} "] end