* Fix #2104: fmt incorrectly using 1-indexing for columns * Clippy...
This commit is contained in:
1 parent
2dc6cecf2e
commit
d8942a255b
17 files changed
+756
-40
No files matched your search
Generated
+2
-12
@@ -1982,8 +1982,8 @@ name = "dioxus-autofmt"
|
|||||||
version = "0.5.0-alpha.2"
|
version = "0.5.0-alpha.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-rsx",
|
"dioxus-rsx",
|
||||||
"prettier-please",
|
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2045,7 +2045,7 @@ dependencies = [
|
|||||||
"mlua",
|
"mlua",
|
||||||
"notify",
|
"notify",
|
||||||
"open",
|
"open",
|
||||||
"prettier-please",
|
"prettyplease",
|
||||||
"rayon",
|
"rayon",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rsx-rosetta",
|
"rsx-rosetta",
|
||||||
@@ -6670,16 +6670,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prettier-please"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|||||||
+2
-8
@@ -89,16 +89,10 @@ rustc-hash = "1.1.0"
|
|||||||
wasm-bindgen = "0.2.92"
|
wasm-bindgen = "0.2.92"
|
||||||
html_parser = "0.7.0"
|
html_parser = "0.7.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
prettyplease = { package = "prettier-please", version = "0.2", features = [
|
prettyplease = { version = "0.2.16", features = ["verbatim"] }
|
||||||
"verbatim",
|
manganis-cli-support = { version = "0.2.1", features = ["html"] }
|
||||||
] }
|
|
||||||
manganis-cli-support = { version = "0.2.1", features = [
|
|
||||||
"html",
|
|
||||||
] }
|
|
||||||
manganis = { version = "0.2.1" }
|
manganis = { version = "0.2.1" }
|
||||||
|
|
||||||
interprocess = { version = "1.2.1" }
|
interprocess = { version = "1.2.1" }
|
||||||
# interprocess = { git = "https://github.com/kotauskas/interprocess" }
|
|
||||||
|
|
||||||
lru = "0.12.2"
|
lru = "0.12.2"
|
||||||
async-trait = "0.1.77"
|
async-trait = "0.1.77"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{ifmt_to_string, writer::Location, Writer};
|
use crate::{ifmt_to_string, prettier_please::unparse_expr, writer::Location, Writer};
|
||||||
use dioxus_rsx::*;
|
use dioxus_rsx::*;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use std::fmt::{Result, Write};
|
use std::fmt::{Result, Write};
|
||||||
@@ -164,7 +164,7 @@ impl Writer<'_> {
|
|||||||
let name = &field.name;
|
let name = &field.name;
|
||||||
match &field.content {
|
match &field.content {
|
||||||
ContentField::ManExpr(exp) => {
|
ContentField::ManExpr(exp) => {
|
||||||
let out = prettyplease::unparse_expr(exp);
|
let out = unparse_expr(exp);
|
||||||
let mut lines = out.split('\n').peekable();
|
let mut lines = out.split('\n').peekable();
|
||||||
let first = lines.next().unwrap();
|
let first = lines.next().unwrap();
|
||||||
write!(self.out, "{name}: {first}")?;
|
write!(self.out, "{name}: {first}")?;
|
||||||
@@ -186,7 +186,7 @@ impl Writer<'_> {
|
|||||||
write!(self.out, "{}", e.to_token_stream())?;
|
write!(self.out, "{}", e.to_token_stream())?;
|
||||||
}
|
}
|
||||||
ContentField::OnHandlerRaw(exp) => {
|
ContentField::OnHandlerRaw(exp) => {
|
||||||
let out = prettyplease::unparse_expr(exp);
|
let out = unparse_expr(exp);
|
||||||
let mut lines = out.split('\n').peekable();
|
let mut lines = out.split('\n').peekable();
|
||||||
let first = lines.next().unwrap();
|
let first = lines.next().unwrap();
|
||||||
write!(self.out, "{name}: {first}")?;
|
write!(self.out, "{name}: {first}")?;
|
||||||
@@ -228,7 +228,7 @@ impl Writer<'_> {
|
|||||||
ContentField::Formatted(s) => ifmt_to_string(s).len() ,
|
ContentField::Formatted(s) => ifmt_to_string(s).len() ,
|
||||||
ContentField::Shorthand(e) => e.to_token_stream().to_string().len(),
|
ContentField::Shorthand(e) => e.to_token_stream().to_string().len(),
|
||||||
ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
|
ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
|
||||||
let formatted = prettyplease::unparse_expr(exp);
|
let formatted = unparse_expr(exp);
|
||||||
let len = if formatted.contains('\n') {
|
let len = if formatted.contains('\n') {
|
||||||
10000
|
10000
|
||||||
} else {
|
} else {
|
||||||
@@ -242,7 +242,7 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
match manual_props {
|
match manual_props {
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
let content = prettyplease::unparse_expr(p);
|
let content = unparse_expr(p);
|
||||||
if content.len() + attr_len > 80 {
|
if content.len() + attr_len > 80 {
|
||||||
return 100000;
|
return 100000;
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ impl Writer<'_> {
|
|||||||
We want to normalize the expr to the appropriate indent level.
|
We want to normalize the expr to the appropriate indent level.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let formatted = prettyplease::unparse_expr(exp);
|
let formatted = unparse_expr(exp);
|
||||||
|
|
||||||
let mut lines = formatted.lines();
|
let mut lines = formatted.lines();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{ifmt_to_string, Writer};
|
use crate::{ifmt_to_string, prettier_please::unparse_expr, Writer};
|
||||||
use dioxus_rsx::*;
|
use dioxus_rsx::*;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
@@ -112,7 +112,7 @@ impl Writer<'_> {
|
|||||||
ShortOptimization::Oneliner => {
|
ShortOptimization::Oneliner => {
|
||||||
write!(self.out, " ")?;
|
write!(self.out, " ")?;
|
||||||
|
|
||||||
self.write_attributes(attributes, key, true)?;
|
self.write_attributes(brace, attributes, key, true)?;
|
||||||
|
|
||||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||||
write!(self.out, ", ")?;
|
write!(self.out, ", ")?;
|
||||||
@@ -132,7 +132,7 @@ impl Writer<'_> {
|
|||||||
if !attributes.is_empty() || key.is_some() {
|
if !attributes.is_empty() || key.is_some() {
|
||||||
write!(self.out, " ")?;
|
write!(self.out, " ")?;
|
||||||
}
|
}
|
||||||
self.write_attributes(attributes, key, true)?;
|
self.write_attributes(brace, attributes, key, true)?;
|
||||||
|
|
||||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||||
write!(self.out, ",")?;
|
write!(self.out, ",")?;
|
||||||
@@ -145,7 +145,7 @@ impl Writer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShortOptimization::NoOpt => {
|
ShortOptimization::NoOpt => {
|
||||||
self.write_attributes(attributes, key, false)?;
|
self.write_attributes(brace, attributes, key, false)?;
|
||||||
|
|
||||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||||
write!(self.out, ",")?;
|
write!(self.out, ",")?;
|
||||||
@@ -166,6 +166,7 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
fn write_attributes(
|
fn write_attributes(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
brace: &Brace,
|
||||||
attributes: &[AttributeType],
|
attributes: &[AttributeType],
|
||||||
key: &Option<IfmtInput>,
|
key: &Option<IfmtInput>,
|
||||||
sameline: bool,
|
sameline: bool,
|
||||||
@@ -187,9 +188,11 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
while let Some(attr) = attr_iter.next() {
|
while let Some(attr) = attr_iter.next() {
|
||||||
self.out.indent_level += 1;
|
self.out.indent_level += 1;
|
||||||
|
|
||||||
if !sameline {
|
if !sameline {
|
||||||
self.write_comments(attr.start())?;
|
self.write_attr_comments(brace, attr.start())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.out.indent_level -= 1;
|
self.out.indent_level -= 1;
|
||||||
|
|
||||||
if !sameline {
|
if !sameline {
|
||||||
@@ -229,7 +232,7 @@ impl Writer<'_> {
|
|||||||
write!(
|
write!(
|
||||||
self.out,
|
self.out,
|
||||||
"if {condition} {{ ",
|
"if {condition} {{ ",
|
||||||
condition = prettyplease::unparse_expr(condition),
|
condition = unparse_expr(condition),
|
||||||
)?;
|
)?;
|
||||||
self.write_attribute_value(value)?;
|
self.write_attribute_value(value)?;
|
||||||
write!(self.out, " }}")?;
|
write!(self.out, " }}")?;
|
||||||
@@ -241,7 +244,7 @@ impl Writer<'_> {
|
|||||||
write!(self.out, "{value}",)?;
|
write!(self.out, "{value}",)?;
|
||||||
}
|
}
|
||||||
ElementAttrValue::AttrExpr(value) => {
|
ElementAttrValue::AttrExpr(value) => {
|
||||||
let out = prettyplease::unparse_expr(value);
|
let out = unparse_expr(value);
|
||||||
let mut lines = out.split('\n').peekable();
|
let mut lines = out.split('\n').peekable();
|
||||||
let first = lines.next().unwrap();
|
let first = lines.next().unwrap();
|
||||||
|
|
||||||
@@ -308,7 +311,7 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
|
fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
|
||||||
write!(self.out, "..")?;
|
write!(self.out, "..")?;
|
||||||
write!(self.out, "{}", prettyplease::unparse_expr(attr))?;
|
write!(self.out, "{}", unparse_expr(attr))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,13 @@ impl Writer<'_> {
|
|||||||
// If the expr is multiline, we want to collect all of its lines together and write them out properly
|
// If the expr is multiline, we want to collect all of its lines together and write them out properly
|
||||||
// This involves unshifting the first line if it's aligned
|
// This involves unshifting the first line if it's aligned
|
||||||
let first_line = &self.src[start.line - 1];
|
let first_line = &self.src[start.line - 1];
|
||||||
write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?;
|
write!(self.out, "{}", &first_line[start.column..].trim_start())?;
|
||||||
|
|
||||||
let prev_block_indent_level = self.out.indent.count_indents(first_line);
|
let prev_block_indent_level = self.out.indent.count_indents(first_line);
|
||||||
|
|
||||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||||
writeln!(self.out)?;
|
writeln!(self.out)?;
|
||||||
|
|
||||||
// check if this is the last line
|
// check if this is the last line
|
||||||
let line = {
|
let line = {
|
||||||
if id == (end.line - start.line) - 1 {
|
if id == (end.line - start.line) - 1 {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ mod component;
|
|||||||
mod element;
|
mod element;
|
||||||
mod expr;
|
mod expr;
|
||||||
mod indent;
|
mod indent;
|
||||||
|
mod prettier_please;
|
||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
pub use indent::{IndentOptions, IndentType};
|
pub use indent::{IndentOptions, IndentType};
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use prettyplease::unparse;
|
||||||
|
use syn::{Expr, File, Item};
|
||||||
|
|
||||||
|
/// Unparse an expression back into a string
|
||||||
|
///
|
||||||
|
/// This creates a new temporary file, parses the expression into it, and then formats the file.
|
||||||
|
/// This is a bit of a hack, but dtonlay doesn't want to support this very simple usecase, forcing us to clone the expr
|
||||||
|
pub fn unparse_expr(expr: &Expr) -> String {
|
||||||
|
let file = wrapped(expr);
|
||||||
|
let wrapped = unparse(&file);
|
||||||
|
unwrapped(wrapped)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split off the fn main and then cut the tabs off the front
|
||||||
|
fn unwrapped(raw: String) -> String {
|
||||||
|
raw.strip_prefix("fn main() {\n")
|
||||||
|
.unwrap()
|
||||||
|
.strip_suffix("}\n")
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.strip_prefix(" ").unwrap()) // todo: set this to tab level
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrapped(expr: &Expr) -> File {
|
||||||
|
File {
|
||||||
|
shebang: None,
|
||||||
|
attrs: vec![],
|
||||||
|
items: vec![
|
||||||
|
//
|
||||||
|
Item::Verbatim(quote::quote! {
|
||||||
|
fn main() {
|
||||||
|
#expr
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unparses_raw() {
|
||||||
|
let expr = syn::parse_str("1 + 1").unwrap();
|
||||||
|
let unparsed = unparse(&wrapped(&expr));
|
||||||
|
assert_eq!(unparsed, "fn main() {\n 1 + 1\n}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unparses_completely() {
|
||||||
|
let expr = syn::parse_str("1 + 1").unwrap();
|
||||||
|
let unparsed = unparse_expr(&expr);
|
||||||
|
assert_eq!(unparsed, "1 + 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn weird_ifcase() {
|
||||||
|
let contents = r##"
|
||||||
|
fn main() {
|
||||||
|
move |_| timer.with_mut(|t| if t.started_at.is_none() { Some(Instant::now()) } else { None })
|
||||||
|
}
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let expr: File = syn::parse_file(contents).unwrap();
|
||||||
|
let out = unparse(&expr);
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::prettier_please::unparse_expr;
|
||||||
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain};
|
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain};
|
||||||
use proc_macro2::{LineColumn, Span};
|
use proc_macro2::{LineColumn, Span};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
@@ -5,7 +6,7 @@ use std::{
|
|||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
fmt::{Result, Write},
|
fmt::{Result, Write},
|
||||||
};
|
};
|
||||||
use syn::{spanned::Spanned, Expr};
|
use syn::{spanned::Spanned, token::Brace, Expr};
|
||||||
|
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
use crate::ifmt_to_string;
|
use crate::ifmt_to_string;
|
||||||
@@ -61,8 +62,26 @@ impl<'a> Writer<'a> {
|
|||||||
Some(self.out.buf)
|
Some(self.out.buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result {
|
||||||
|
// There's a chance this line actually shares the same line as the previous
|
||||||
|
// Only write comments if the comments actually belong to this line
|
||||||
|
//
|
||||||
|
// to do this, we check if the attr span starts on the same line as the brace
|
||||||
|
// if it doesn't, we write the comments
|
||||||
|
let brace_line = brace.span.span().start().line;
|
||||||
|
let attr_line = attr_span.start().line;
|
||||||
|
|
||||||
|
if brace_line != attr_line {
|
||||||
|
self.write_comments(attr_span)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||||
// collect all comments upwards
|
// collect all comments upwards
|
||||||
|
// make sure we don't collect the comments of the node that we're currently under.
|
||||||
|
|
||||||
let start = child.start();
|
let start = child.start();
|
||||||
let line_start = start.line - 1;
|
let line_start = start.line - 1;
|
||||||
|
|
||||||
@@ -149,7 +168,7 @@ impl<'a> Writer<'a> {
|
|||||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||||
self.cached_formats.entry(location)
|
self.cached_formats.entry(location)
|
||||||
{
|
{
|
||||||
let formatted = prettyplease::unparse_expr(tokens);
|
let formatted = unparse_expr(tokens);
|
||||||
let len = if formatted.contains('\n') {
|
let len = if formatted.contains('\n') {
|
||||||
10000
|
10000
|
||||||
} else {
|
} else {
|
||||||
@@ -207,7 +226,7 @@ impl<'a> Writer<'a> {
|
|||||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||||
self.cached_formats
|
self.cached_formats
|
||||||
.entry(Location::new(expr.span().start()))
|
.entry(Location::new(expr.span().start()))
|
||||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
.or_insert_with(|| unparse_expr(expr))
|
||||||
.as_str()
|
.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +235,7 @@ impl<'a> Writer<'a> {
|
|||||||
self.out,
|
self.out,
|
||||||
"for {} in {} {{",
|
"for {} in {} {{",
|
||||||
forloop.pat.clone().into_token_stream(),
|
forloop.pat.clone().into_token_stream(),
|
||||||
prettyplease::unparse_expr(&forloop.expr)
|
unparse_expr(&forloop.expr)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if forloop.body.is_empty() {
|
if forloop.body.is_empty() {
|
||||||
@@ -249,7 +268,7 @@ impl<'a> Writer<'a> {
|
|||||||
self.out,
|
self.out,
|
||||||
"{} {} {{",
|
"{} {} {{",
|
||||||
if_token.to_token_stream(),
|
if_token.to_token_stream(),
|
||||||
prettyplease::unparse_expr(cond)
|
unparse_expr(cond)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.write_body_indented(then_branch)?;
|
self.write_body_indented(then_branch)?;
|
||||||
|
|||||||
@@ -45,4 +45,5 @@ twoway![
|
|||||||
tiny,
|
tiny,
|
||||||
tinynoopt,
|
tinynoopt,
|
||||||
trailing_expr,
|
trailing_expr,
|
||||||
|
many_exprs,
|
||||||
];
|
];
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#![allow(dead_code, unused)]
|
||||||
|
use dioxus::desktop::use_window;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::{
|
||||||
|
process::exit,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
LaunchBuilder::desktop().launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowPreferences {
|
||||||
|
always_on_top: bool,
|
||||||
|
with_decorations: bool,
|
||||||
|
exiting: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowPreferences {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
with_decorations: true,
|
||||||
|
always_on_top: false,
|
||||||
|
exiting: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowPreferences {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Timer {
|
||||||
|
hours: u8,
|
||||||
|
minutes: u8,
|
||||||
|
seconds: u8,
|
||||||
|
started_at: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration(&self) -> Duration {
|
||||||
|
Duration::from_secs(
|
||||||
|
(self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPD_FREQ: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
fn exit_button(
|
||||||
|
delay: Duration,
|
||||||
|
label: fn(Signal<Option<Instant>>, Duration) -> Option<VNode>,
|
||||||
|
) -> Element {
|
||||||
|
let mut trigger: Signal<Option<Instant>> = use_signal(|| None);
|
||||||
|
use_future(move || async move {
|
||||||
|
loop {
|
||||||
|
sleep(UPD_FREQ).await;
|
||||||
|
if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let stuff: Option<VNode> = rsx! {
|
||||||
|
button {
|
||||||
|
onmouseup: move |_| {
|
||||||
|
trigger.set(None);
|
||||||
|
},
|
||||||
|
onmousedown: move |_| {
|
||||||
|
trigger.set(Some(Instant::now()));
|
||||||
|
},
|
||||||
|
width: 100,
|
||||||
|
{label(trigger, delay)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
let mut timer = use_signal(Timer::new);
|
||||||
|
let mut window_preferences = use_signal(WindowPreferences::new);
|
||||||
|
|
||||||
|
use_future(move || async move {
|
||||||
|
loop {
|
||||||
|
sleep(UPD_FREQ).await;
|
||||||
|
timer.with_mut(|t| {
|
||||||
|
if let Some(started_at) = t.started_at {
|
||||||
|
if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO {
|
||||||
|
t.started_at = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
{
|
||||||
|
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
||||||
|
format!("{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 99,
|
||||||
|
value: format!("{:02}", timer.read().hours),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
value: format!("{:02}", timer.read().minutes),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().minutes = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
value: format!("{:02}", timer.read().seconds),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().seconds = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
id: "start_stop",
|
||||||
|
onclick: move |_| {
|
||||||
|
timer
|
||||||
|
.with_mut(|t| {
|
||||||
|
t
|
||||||
|
.started_at = if t.started_at.is_none() {
|
||||||
|
Some(Instant::now())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) }
|
||||||
|
}
|
||||||
|
div { id: "app",
|
||||||
|
button {
|
||||||
|
onclick: move |_| {
|
||||||
|
let decorations = window_preferences.read().with_decorations;
|
||||||
|
use_window().set_decorations(!decorations);
|
||||||
|
window_preferences.write().with_decorations = !decorations;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: move |_| {
|
||||||
|
window_preferences
|
||||||
|
.with_mut(|wp| {
|
||||||
|
use_window().set_always_on_top(!wp.always_on_top);
|
||||||
|
wp.always_on_top = !wp.always_on_top;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
width: 100,
|
||||||
|
{
|
||||||
|
format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
exit_button(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
|trigger, delay| rsx! {
|
||||||
|
{format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,3 +26,6 @@ twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4, false)
|
|||||||
|
|
||||||
twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));
|
twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4, false)));
|
twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4, false)));
|
||||||
|
twoway!("multiexpr-many" => multiexpr_many (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
|
twoway!("simple-combo-expr" => simple_combo_expr (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
|
twoway!("oneline-expand" => online_expand (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#![allow(dead_code, unused)]
|
||||||
|
use dioxus::desktop::use_window;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::{
|
||||||
|
process::exit,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
LaunchBuilder::desktop().launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowPreferences {
|
||||||
|
always_on_top: bool,
|
||||||
|
with_decorations: bool,
|
||||||
|
exiting: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowPreferences {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
with_decorations: true,
|
||||||
|
always_on_top: false,
|
||||||
|
exiting: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowPreferences {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Timer {
|
||||||
|
hours: u8,
|
||||||
|
minutes: u8,
|
||||||
|
seconds: u8,
|
||||||
|
started_at: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration(&self) -> Duration {
|
||||||
|
Duration::from_secs(
|
||||||
|
(self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPD_FREQ: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
fn exit_button(
|
||||||
|
delay: Duration,
|
||||||
|
label: fn(Signal<Option<Instant>>, Duration) -> Option<VNode>,
|
||||||
|
) -> Element {
|
||||||
|
let mut trigger: Signal<Option<Instant>> = use_signal(|| None);
|
||||||
|
use_future(move || async move {
|
||||||
|
loop {
|
||||||
|
sleep(UPD_FREQ).await;
|
||||||
|
if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let stuff: Option<VNode> = rsx! {
|
||||||
|
button {
|
||||||
|
onmouseup: move |_| {
|
||||||
|
trigger.set(None);
|
||||||
|
},
|
||||||
|
onmousedown: move |_| {
|
||||||
|
trigger.set(Some(Instant::now()));
|
||||||
|
},
|
||||||
|
width: 100,
|
||||||
|
{label(trigger, delay)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
let mut timer = use_signal(Timer::new);
|
||||||
|
let mut window_preferences = use_signal(WindowPreferences::new);
|
||||||
|
|
||||||
|
use_future(move || async move {
|
||||||
|
loop {
|
||||||
|
sleep(UPD_FREQ).await;
|
||||||
|
timer.with_mut(|t| {
|
||||||
|
if let Some(started_at) = t.started_at {
|
||||||
|
if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO {
|
||||||
|
t.started_at = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
{
|
||||||
|
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
||||||
|
format!("{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 99,
|
||||||
|
value: format!("{:02}", timer.read().hours),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
value: format!("{:02}", timer.read().minutes),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().minutes = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
value: format!("{:02}", timer.read().seconds),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().seconds = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
id: "start_stop",
|
||||||
|
onclick: move |_| {
|
||||||
|
timer
|
||||||
|
.with_mut(|t| {
|
||||||
|
t
|
||||||
|
.started_at = if t.started_at.is_none() {
|
||||||
|
Some(Instant::now())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) }
|
||||||
|
}
|
||||||
|
div { id: "app",
|
||||||
|
button {
|
||||||
|
onclick: move |_| {
|
||||||
|
let decorations = window_preferences.read().with_decorations;
|
||||||
|
use_window().set_decorations(!decorations);
|
||||||
|
window_preferences.write().with_decorations = !decorations;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: move |_| {
|
||||||
|
window_preferences
|
||||||
|
.with_mut(|wp| {
|
||||||
|
use_window().set_always_on_top(!wp.always_on_top);
|
||||||
|
wp.always_on_top = !wp.always_on_top;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
width: 100,
|
||||||
|
{
|
||||||
|
format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
exit_button(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
|trigger, delay| rsx! {
|
||||||
|
{format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
#![allow(dead_code, unused)]
|
||||||
|
use dioxus::desktop::use_window;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::{
|
||||||
|
process::exit,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
LaunchBuilder::desktop().launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowPreferences {
|
||||||
|
always_on_top: bool,
|
||||||
|
with_decorations: bool,
|
||||||
|
exiting: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowPreferences {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
with_decorations: true,
|
||||||
|
always_on_top: false,
|
||||||
|
exiting: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowPreferences {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Timer {
|
||||||
|
hours: u8,
|
||||||
|
minutes: u8,
|
||||||
|
seconds: u8,
|
||||||
|
started_at: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration(&self) -> Duration {
|
||||||
|
Duration::from_secs(
|
||||||
|
(self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPD_FREQ: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
fn exit_button(
|
||||||
|
delay: Duration,
|
||||||
|
label: fn(Signal<Option<Instant>>, Duration) -> Option<VNode>,
|
||||||
|
) -> Element {
|
||||||
|
let mut trigger: Signal<Option<Instant>> = use_signal(|| None);
|
||||||
|
use_future(move || async move {
|
||||||
|
loop {
|
||||||
|
sleep(UPD_FREQ).await;
|
||||||
|
if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let stuff: Option<VNode> = rsx! {
|
||||||
|
button {
|
||||||
|
onmouseup: move |_| {
|
||||||
|
trigger.set(None);
|
||||||
|
},
|
||||||
|
onmousedown: move |_| {
|
||||||
|
trigger.set(Some(Instant::now()));
|
||||||
|
},
|
||||||
|
width: 100,
|
||||||
|
{label(trigger, delay)},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
let mut timer = use_signal(Timer::new);
|
||||||
|
let mut window_preferences = use_signal(WindowPreferences::new);
|
||||||
|
|
||||||
|
use_future(move || async move {
|
||||||
|
loop {
|
||||||
|
sleep(UPD_FREQ).await;
|
||||||
|
timer.with_mut(|t| {
|
||||||
|
if let Some(started_at) = t.started_at {
|
||||||
|
if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO {
|
||||||
|
t.started_at = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {{
|
||||||
|
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
||||||
|
format!("{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10)
|
||||||
|
}}
|
||||||
|
div {
|
||||||
|
input { r#type: "number", min: 0, max: 99, value: format!("{:02}",timer.read().hours), oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input { r#type: "number", min: 0, max: 59, value: format!("{:02}",timer.read().minutes), oninput: move |e| {
|
||||||
|
timer.write().minutes = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input { r#type: "number", min: 0, max: 59, value: format!("{:02}",timer.read().seconds), oninput: move |e| {
|
||||||
|
timer.write().seconds = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
id: "start_stop",
|
||||||
|
onclick: move |_| timer.with_mut(|t| t.started_at = if t.started_at.is_none() { Some(Instant::now()) } else { None } ),
|
||||||
|
{ timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) },
|
||||||
|
}
|
||||||
|
div { id: "app",
|
||||||
|
button { onclick: move |_| {
|
||||||
|
let decorations = window_preferences.read().with_decorations;
|
||||||
|
use_window().set_decorations(!decorations);
|
||||||
|
window_preferences.write().with_decorations = !decorations;
|
||||||
|
}, {
|
||||||
|
format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: move |_| {
|
||||||
|
window_preferences.with_mut(|wp| {
|
||||||
|
use_window().set_always_on_top(!wp.always_on_top);
|
||||||
|
wp.always_on_top = !wp.always_on_top;
|
||||||
|
})},
|
||||||
|
width: 100,
|
||||||
|
{
|
||||||
|
format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
exit_button(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
|trigger, delay| rsx! {
|
||||||
|
{format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
fn main() {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
id: "start_stop",
|
||||||
|
onclick: move |_| {
|
||||||
|
timer
|
||||||
|
.with_mut(|t| {
|
||||||
|
t
|
||||||
|
.started_at = if t.started_at.is_none() {
|
||||||
|
Some(Instant::now())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fn main() {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
id: "start_stop",
|
||||||
|
onclick: move |_| timer.with_mut(|t| t.started_at = if t.started_at.is_none() { Some(Instant::now()) } else { None } )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
fn main() {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
{
|
||||||
|
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
||||||
|
format!("{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 99,
|
||||||
|
value: format!("{:02}", timer.read().hours),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// some comment
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
min: 0,
|
||||||
|
max: 99,
|
||||||
|
value: format!("{:02}", timer.read().hours),
|
||||||
|
oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
fn main() {
|
||||||
|
rsx! {
|
||||||
|
div {{
|
||||||
|
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
||||||
|
format!("{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10)
|
||||||
|
}}
|
||||||
|
div {
|
||||||
|
input { r#type: "number", min: 0, max: 99, value: format!("{:02}",timer.read().hours), oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// some comment
|
||||||
|
input { r#type: "number", min: 0, max: 99, value: format!("{:02}",timer.read().hours), oninput: move |e| {
|
||||||
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user