tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
working alpha version up online!
Orual
2 months ago
0f1c03d1
c353b2e0
+305
-130
10 changed files
expand all
collapse all
unified
split
Cargo.lock
crates
weaver-app
Cargo.toml
assets
styling
main.css
navbar.css
record-view.css
src
components
login.rs
main.rs
views
home.rs
mod.rs
record.rs
+1
Cargo.lock
···
8692
8692
"sqlite-wasm-rs",
8693
8693
"time",
8694
8694
"tokio",
8695
8695
+
"tracing",
8695
8696
"wasm-bindgen",
8696
8697
"wasm-bindgen-futures",
8697
8698
"weaver-api",
+1
crates/weaver-app/Cargo.toml
···
47
47
dioxus-logger = "0.7.1"
48
48
serde_html_form = "0.2.8"
49
49
webbrowser = "1.0.6"
50
50
+
tracing.workspace = true
50
51
51
52
52
53
+33
crates/weaver-app/assets/styling/main.css
···
7
7
#header {
8
8
max-width: 1200px;
9
9
}
10
10
+
11
11
+
.record-view-container {
12
12
+
max-width: 1200px;
13
13
+
margin: 2rem auto;
14
14
+
padding: 0 1rem;
15
15
+
}
16
16
+
17
17
+
.uri-input-section {
18
18
+
margin-bottom: 2.5rem;
19
19
+
}
20
20
+
21
21
+
.uri-input {
22
22
+
font-family: var(--font-mono);
23
23
+
font-size: 0.9rem;
24
24
+
width: 100%;
25
25
+
max-width: 100%;
26
26
+
box-sizing: border-box;
27
27
+
padding: 0.5rem 0.75rem;
28
28
+
background: var(--color-surface, rgba(0, 0, 0, 0.2));
29
29
+
border: 1px solid var(--color-border);
30
30
+
color: var(--color-text);
31
31
+
outline: none;
32
32
+
transition: border-color 0.2s;
33
33
+
}
34
34
+
35
35
+
.uri-input:focus {
36
36
+
border-color: var(--color-primary);
37
37
+
}
38
38
+
39
39
+
.uri-input::placeholder {
40
40
+
color: var(--color-subtle);
41
41
+
opacity: 0.5;
42
42
+
}
+2
-2
crates/weaver-app/assets/styling/navbar.css
···
2
2
display: flex;
3
3
flex-direction: row;
4
4
justify-content: space-between;
5
5
-
padding-left: 4rem;
5
5
+
padding-left: 1rem;
6
6
padding-top: 1rem;
7
7
-
padding-right: 4rem;
7
7
+
padding-right: 1rem;
8
8
}
9
9
10
10
.breadcrumbs {
+64
crates/weaver-app/assets/styling/record-view.css
···
21
21
letter-spacing: 0.15em;
22
22
}
23
23
24
24
+
.uri-input-section {
25
25
+
margin-bottom: 1rem;
26
26
+
}
27
27
+
28
28
+
.uri-input {
29
29
+
font-family: var(--font-mono);
30
30
+
font-size: 0.9rem;
31
31
+
width: 100%;
32
32
+
max-width: 100%;
33
33
+
box-sizing: border-box;
34
34
+
padding: 0.5rem 0.75rem;
35
35
+
background: var(--color-surface, rgba(0, 0, 0, 0.2));
36
36
+
border: 1px solid var(--color-border);
37
37
+
color: var(--color-text);
38
38
+
outline: none;
39
39
+
transition: border-color 0.2s;
40
40
+
}
41
41
+
42
42
+
.uri-input:focus {
43
43
+
border-color: var(--color-primary);
44
44
+
}
45
45
+
46
46
+
.uri-input::placeholder {
47
47
+
color: var(--color-subtle);
48
48
+
opacity: 0.5;
49
49
+
}
50
50
+
24
51
.record-metadata {
25
52
display: flex-wrap;
26
53
flex-direction: column;
···
807
834
font-style: italic;
808
835
padding-top: 0.25rem;
809
836
}
837
837
+
838
838
+
/* Dialog Actions for Delete Confirmation */
839
839
+
.dialog-actions {
840
840
+
display: flex;
841
841
+
flex-direction: row;
842
842
+
justify-content: flex-end;
843
843
+
gap: 0;
844
844
+
margin-top: 8px;
845
845
+
}
846
846
+
847
847
+
.dialog-actions button {
848
848
+
font-family: var(--font-mono);
849
849
+
font-size: 0.75rem;
850
850
+
text-transform: uppercase;
851
851
+
letter-spacing: 0.05em;
852
852
+
padding: 0.5rem 1rem;
853
853
+
background: transparent;
854
854
+
border: none;
855
855
+
border-bottom: 2px solid transparent;
856
856
+
color: var(--color-subtle);
857
857
+
cursor: pointer;
858
858
+
transition: all 0.2s;
859
859
+
}
860
860
+
861
861
+
.dialog-actions button:hover {
862
862
+
color: var(--color-primary);
863
863
+
border-bottom-color: var(--color-primary);
864
864
+
}
865
865
+
866
866
+
.dialog-actions button:first-child {
867
867
+
color: var(--color-error, #ff6b6b);
868
868
+
}
869
869
+
870
870
+
.dialog-actions button:first-child:hover {
871
871
+
color: var(--color-error, #ff5252);
872
872
+
border-bottom-color: var(--color-error, #ff6b6b);
873
873
+
}
+9
-5
crates/weaver-app/src/components/login.rs
···
44
44
let handle = handle.clone();
45
45
let fetcher = fetcher.clone();
46
46
spawn(async move {
47
47
-
if let Err(e) = start_oauth_flow(handle, fetcher).await {
48
48
-
error!("Authentication failed: {}", e);
49
49
-
error.set(Some(format!("Authentication failed: {}", e)));
50
50
-
is_loading.set(false);
47
47
+
match start_oauth_flow(handle, fetcher).await {
48
48
+
Ok(_) => {
49
49
+
open.set(false);
50
50
+
}
51
51
+
Err(e) => {
52
52
+
error!("Authentication failed: {}", e);
53
53
+
error.set(Some(format!("Authentication failed: {}", e)));
54
54
+
is_loading.set(false);
55
55
+
}
51
56
}
52
52
-
open.set(false);
53
57
});
54
58
});
55
59
};
+13
-4
crates/weaver-app/src/main.rs
···
9
9
#[cfg(all(feature = "fullstack-server", feature = "server"))]
10
10
use dioxus::fullstack::response::Extension;
11
11
use dioxus_logger::tracing::Level;
12
12
-
use jacquard::oauth::{client::OAuthClient, session::ClientData};
12
12
+
use jacquard::{
13
13
+
oauth::{client::OAuthClient, session::ClientData},
14
14
+
types::aturi::AtUri,
15
15
+
};
13
16
#[allow(unused)]
14
17
use jacquard::{
15
18
smol_str::SmolStr,
···
19
22
use std::sync::Arc;
20
23
use std::sync::{LazyLock, Mutex};
21
24
#[allow(unused)]
22
22
-
use views::{Callback, Home, Navbar, Notebook, NotebookIndex, NotebookPage, RecordView};
25
25
+
use views::{
26
26
+
Callback, Home, Navbar, Notebook, NotebookIndex, NotebookPage, RecordIndex, RecordView,
27
27
+
};
23
28
24
29
use crate::{
25
30
auth::{AuthState, AuthStore},
···
56
61
#[route("/")]
57
62
Home {},
58
63
#[layout(ErrorLayout)]
59
59
-
#[route("/record#:uri")]
60
60
-
RecordView { uri: SmolStr },
64
64
+
#[nest("/record")]
65
65
+
#[layout(RecordIndex)]
66
66
+
#[route("/:..uri")]
67
67
+
RecordView { uri: Vec<String> },
68
68
+
#[end_layout]
69
69
+
#[end_nest]
61
70
#[route("/callback?:state&:iss&:code")]
62
71
Callback { state: SmolStr, iss: SmolStr, code: SmolStr },
63
72
#[nest("/:ident")]
+54
-21
crates/weaver-app/src/views/home.rs
···
1
1
-
use crate::{components::identity::NotebookCard, fetch};
1
1
+
use crate::{Route, components::identity::NotebookCard, fetch};
2
2
use dioxus::prelude::*;
3
3
+
use jacquard::{IntoStatic, smol_str::ToSmolStr, types::aturi::AtUri};
3
4
4
5
const NOTEBOOK_CARD_CSS: Asset = asset!("/assets/styling/notebook-card.css");
5
6
···
13
14
let fetcher = fetcher.clone();
14
15
async move { fetcher.fetch_notebooks_from_ufos().await }
15
16
});
17
17
+
let navigator = use_navigator();
18
18
+
let mut uri_input = use_signal(|| String::new());
19
19
+
20
20
+
let handle_uri_submit = move || {
21
21
+
let input_uri = uri_input.read().clone();
22
22
+
if !input_uri.is_empty() {
23
23
+
if let Ok(parsed) = AtUri::new(&input_uri) {
24
24
+
navigator.push(Route::RecordView {
25
25
+
uri: vec![parsed.to_string()],
26
26
+
});
27
27
+
}
28
28
+
}
29
29
+
};
16
30
17
31
rsx! {
18
32
document::Link { rel: "stylesheet", href: NOTEBOOK_CARD_CSS }
19
19
-
20
20
-
div { class: "notebooks-list",
21
21
-
match notebooks() {
22
22
-
Some(Ok(notebook_list)) => rsx! {
23
23
-
for notebook in notebook_list.iter() {
24
24
-
{
25
25
-
let view = ¬ebook.0;
26
26
-
let entries = ¬ebook.1;
27
27
-
rsx! {
28
28
-
div {
29
29
-
key: "{view.cid}",
30
30
-
NotebookCard {
31
31
-
notebook: view.clone(),
32
32
-
entry_refs: entries.clone()
33
33
+
div {
34
34
+
class: "record-view-container",
35
35
+
div { class: "record-header",
36
36
+
div { class: "uri-input-section",
37
37
+
input {
38
38
+
r#type: "text",
39
39
+
class: "uri-input",
40
40
+
placeholder: "at://did:plc:.../collection/rkey",
41
41
+
value: "{uri_input}",
42
42
+
oninput: move |evt| uri_input.set(evt.value()),
43
43
+
onkeydown: move |evt| {
44
44
+
if evt.key() == Key::Enter {
45
45
+
handle_uri_submit();
46
46
+
}
47
47
+
},
48
48
+
}
49
49
+
}
50
50
+
}
51
51
+
div { class: "notebooks-list",
52
52
+
match notebooks() {
53
53
+
Some(Ok(notebook_list)) => rsx! {
54
54
+
for notebook in notebook_list.iter() {
55
55
+
{
56
56
+
let view = ¬ebook.0;
57
57
+
let entries = ¬ebook.1;
58
58
+
rsx! {
59
59
+
div {
60
60
+
key: "{view.cid}",
61
61
+
NotebookCard {
62
62
+
notebook: view.clone(),
63
63
+
entry_refs: entries.clone()
64
64
+
}
33
65
}
34
66
}
35
67
}
36
68
}
69
69
+
},
70
70
+
Some(Err(_)) => rsx! {
71
71
+
div { "Error loading notebooks" }
72
72
+
},
73
73
+
None => rsx! {
74
74
+
div { "Loading notebooks..." }
37
75
}
38
38
-
},
39
39
-
Some(Err(_)) => rsx! {
40
40
-
div { "Error loading notebooks" }
41
41
-
},
42
42
-
None => rsx! {
43
43
-
div { "Loading notebooks..." }
44
76
}
45
77
}
46
78
}
79
79
+
47
80
}
48
81
}
+1
-1
crates/weaver-app/src/views/mod.rs
···
21
21
pub use notebook::{Notebook, NotebookIndex};
22
22
23
23
mod record;
24
24
-
pub use record::RecordView;
24
24
+
pub use record::{RecordIndex, RecordView};
25
25
26
26
mod callback;
27
27
pub use callback::Callback;
+127
-97
crates/weaver-app/src/views/record.rs
···
2
2
use crate::auth::AuthState;
3
3
use crate::components::dialog::{DialogContent, DialogDescription, DialogRoot, DialogTitle};
4
4
use crate::fetch::CachedFetcher;
5
5
-
use dioxus::prelude::*;
5
5
+
use dioxus::{CapturedError, prelude::*};
6
6
use humansize::format_size;
7
7
use jacquard::api::com_atproto::repo::get_record::GetRecordOutput;
8
8
use jacquard::client::AgentError;
···
12
12
client::AgentSessionExt,
13
13
common::{Data, IntoStatic},
14
14
identity::lexicon_resolver::LexiconSchemaResolver,
15
15
-
smol_str::SmolStr,
16
15
types::{aturi::AtUri, cid::Cid, ident::AtIdentifier, string::Nsid},
17
16
};
18
17
use mime_sniffer::MimeTypeSniffer;
···
28
27
}
29
28
30
29
#[component]
31
31
-
pub fn RecordView(uri: ReadSignal<SmolStr>) -> Element {
32
32
-
let fetcher = use_context::<CachedFetcher>();
33
33
-
let at_uri = AtUri::new_owned(uri());
34
34
-
if let Err(err) = &at_uri {
35
35
-
let error = format!("{:?}", err);
36
36
-
return rsx! {
37
37
-
div {
30
30
+
pub fn RecordIndex() -> Element {
31
31
+
let navigator = use_navigator();
32
32
+
let mut uri_input = use_signal(|| String::new());
33
33
+
34
34
+
let handle_uri_submit = move || {
35
35
+
let input_uri = uri_input.read().clone();
36
36
+
if !input_uri.is_empty() {
37
37
+
if let Ok(parsed) = AtUri::new(&input_uri) {
38
38
+
let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, parsed);
39
39
+
navigator.push(link);
40
40
+
}
41
41
+
}
42
42
+
};
43
43
+
rsx! {
44
44
+
document::Stylesheet { href: asset!("/assets/styling/record-view.css") }
45
45
+
div {
46
46
+
class: "record-view-container",
47
47
+
div { class: "record-header",
38
48
h1 { "Record View" }
39
39
-
p { "URI: {uri}" }
40
40
-
p { "Error: {error}" }
49
49
+
div { class: "uri-input-section",
50
50
+
input {
51
51
+
r#type: "text",
52
52
+
class: "uri-input",
53
53
+
placeholder: "at://did:plc:.../collection/rkey",
54
54
+
value: "{uri_input}",
55
55
+
oninput: move |evt| uri_input.set(evt.value()),
56
56
+
onkeydown: move |evt| {
57
57
+
if evt.key() == Key::Enter {
58
58
+
handle_uri_submit();
59
59
+
}
60
60
+
},
61
61
+
}
62
62
+
}
41
63
}
42
42
-
};
64
64
+
65
65
+
Outlet::<Route> {}
66
66
+
}
67
67
+
}
68
68
+
}
69
69
+
70
70
+
#[component]
71
71
+
pub fn RecordView(uri: ReadSignal<Vec<String>>) -> Element {
72
72
+
let fetcher = use_context::<CachedFetcher>();
73
73
+
info!("Uri:{:?}", uri().join("/"));
74
74
+
let at_uri = AtUri::new_owned(&*uri.read().join("/"));
75
75
+
if at_uri.is_err() {
76
76
+
return rsx! {};
43
77
}
44
44
-
let uri = use_signal(|| at_uri.unwrap());
78
78
+
let uri = use_signal(move || AtUri::new_owned(&*uri.read().join("/")).unwrap());
45
79
let mut view_mode = use_signal(|| ViewMode::Pretty);
46
80
let mut edit_mode = use_signal(|| false);
47
47
-
let navigator = use_navigator();
48
81
49
82
let client = fetcher.get_client();
50
83
let record_resource = use_resource(move || {
51
84
let client = client.clone();
52
52
-
async move { client.fetch_record_slingshot(&uri()).await }
85
85
+
async move { client.fetch_record_slingshot(&*uri.read()).await }
53
86
});
54
87
55
88
// Check ownership for edit access
···
61
94
}
62
95
63
96
// authority() returns &AtIdentifier which can be Did or Handle
64
64
-
match uri().authority() {
97
97
+
match &*uri.read().authority() {
65
98
AtIdentifier::Did(record_did) => auth.did.as_ref() == Some(record_did),
66
99
AtIdentifier::Handle(_) => {
67
100
// Can't easily check ownership for handles without async resolution
···
69
102
}
70
103
}
71
104
});
72
72
-
if let Some(Ok(record)) = &*record_resource.read_unchecked() {
73
73
-
let mut record_value = use_signal(|| record.value.clone().into_static());
74
74
-
let json =
75
75
-
use_memo(move || serde_json::to_string_pretty(&record_value()).unwrap_or_default());
105
105
+
if let Some(Ok(record)) = &*record_resource.read() {
106
106
+
let record_value = record.value.clone().into_static();
107
107
+
let record = record.clone();
76
108
77
109
rsx! {
78
78
-
RecordViewLayout {
79
79
-
uri: uri().clone(),
80
80
-
cid: record.cid.clone(),
81
81
-
if edit_mode() {
82
82
-
EditableRecordContent {
83
83
-
record_value: record_value,
84
84
-
uri: uri,
85
85
-
view_mode: view_mode,
86
86
-
edit_mode: edit_mode,
87
87
-
record_resource: record_resource,
88
88
-
}
89
89
-
} else {
90
90
-
div {
91
91
-
class: "tab-bar",
92
92
-
button {
93
93
-
class: if view_mode() == ViewMode::Pretty { "tab-button active" } else { "tab-button" },
94
94
-
onclick: move |_| view_mode.set(ViewMode::Pretty),
95
95
-
"View"
110
110
+
Fragment { key: "{uri()}",
111
111
+
RecordViewLayout {
112
112
+
uri: uri().clone(),
113
113
+
cid: record.cid.clone(),
114
114
+
if edit_mode() {
115
115
+
116
116
+
EditableRecordContent {
117
117
+
record_value: record_value,
118
118
+
uri: uri,
119
119
+
view_mode: view_mode,
120
120
+
edit_mode: edit_mode,
121
121
+
record_resource: record_resource,
96
122
}
97
97
-
button {
98
98
-
class: if view_mode() == ViewMode::Json { "tab-button active" } else { "tab-button" },
99
99
-
onclick: move |_| view_mode.set(ViewMode::Json),
100
100
-
"JSON"
101
101
-
}
102
102
-
if is_owner() {
123
123
+
} else {
124
124
+
div {
125
125
+
class: "tab-bar",
126
126
+
button {
127
127
+
class: if view_mode() == ViewMode::Pretty { "tab-button active" } else { "tab-button" },
128
128
+
onclick: move |_| view_mode.set(ViewMode::Pretty),
129
129
+
"View"
130
130
+
}
103
131
button {
104
104
-
class: "tab-button edit-button",
105
105
-
onclick: move |_| edit_mode.set(true),
106
106
-
"Edit"
132
132
+
class: if view_mode() == ViewMode::Json { "tab-button active" } else { "tab-button" },
133
133
+
onclick: move |_| view_mode.set(ViewMode::Json),
134
134
+
"JSON"
135
135
+
}
136
136
+
if is_owner() {
137
137
+
button {
138
138
+
class: "tab-button edit-button",
139
139
+
onclick: move |_| edit_mode.set(true),
140
140
+
"Edit"
141
141
+
}
107
142
}
108
143
}
109
109
-
}
110
110
-
div {
111
111
-
class: "tab-content",
112
112
-
match view_mode() {
113
113
-
ViewMode::Pretty => rsx! {
114
114
-
PrettyRecordView { record: record_value(), uri: uri().clone() }
115
115
-
},
116
116
-
ViewMode::Json => rsx! {
117
117
-
CodeView {
118
118
-
code: use_signal(|| json()),
119
119
-
lang: Some("json".to_string()),
120
120
-
}
121
121
-
},
144
144
+
div {
145
145
+
class: "tab-content",
146
146
+
match view_mode() {
147
147
+
ViewMode::Pretty => rsx! {
148
148
+
PrettyRecordView { record: record_value, uri: uri().clone() }
149
149
+
},
150
150
+
ViewMode::Json => {
151
151
+
let json = use_memo(use_reactive!(|record| serde_json::to_string_pretty(
152
152
+
&record.value
153
153
+
)
154
154
+
.unwrap_or_default()));
155
155
+
rsx! {
156
156
+
CodeView {
157
157
+
code: json,
158
158
+
lang: Some("json".to_string()),
159
159
+
}
160
160
+
}
161
161
+
},
162
162
+
}
122
163
}
123
164
}
124
165
}
125
166
}
126
167
}
127
168
} else {
128
128
-
rsx! {
129
129
-
div {
130
130
-
class: "record-view-container",
131
131
-
h1 { "Record" }
132
132
-
p { "URI: {uri}" }
133
133
-
p { "Loading..." }
134
134
-
}
135
135
-
}
169
169
+
rsx! {}
136
170
}
137
171
}
138
172
···
390
424
#[component]
391
425
fn HighlightedUri(uri: AtUri<'static>) -> Element {
392
426
let s = uri.as_str();
393
393
-
let link = format!("/record#{}", s);
427
427
+
let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, s);
394
428
395
429
if let Some(rest) = s.strip_prefix("at://") {
396
430
let parts: Vec<&str> = rest.splitn(3, '/').collect();
397
431
return rsx! {
398
432
a {
399
399
-
href: "{link}",
433
433
+
href: link,
400
434
class: "uri-link",
401
435
span { class: "string-at-uri",
402
436
span { class: "aturi-scheme", "at://" }
···
419
453
};
420
454
}
421
455
422
422
-
rsx! { span { class: "string-at-uri", "{s}" } }
456
456
+
rsx! { a { class: "string-at-uri", href: s } }
423
457
}
424
458
425
459
#[component]
···
2070
2104
#[component]
2071
2105
fn RecordViewLayout(uri: AtUri<'static>, cid: Option<Cid<'static>>, children: Element) -> Element {
2072
2106
rsx! {
2073
2073
-
document::Stylesheet { href: asset!("/assets/styling/record-view.css") }
2074
2107
div {
2075
2075
-
class: "record-view-container",
2076
2076
-
div {
2077
2077
-
class: "record-header",
2078
2078
-
h1 { "Record" }
2079
2079
-
div {
2080
2080
-
class: "record-metadata",
2081
2081
-
div { class: "metadata-row",
2082
2082
-
span { class: "metadata-label", "URI" }
2083
2083
-
span { class: "metadata-value",
2084
2084
-
HighlightedUri { uri: uri.clone() }
2085
2085
-
}
2086
2086
-
}
2087
2087
-
if let Some(cid) = cid {
2088
2088
-
div { class: "metadata-row",
2089
2089
-
span { class: "metadata-label", "CID" }
2090
2090
-
code { class: "metadata-value", "{cid}" }
2091
2091
-
}
2092
2092
-
}
2108
2108
+
class: "record-metadata",
2109
2109
+
div { class: "metadata-row",
2110
2110
+
span { class: "metadata-label", "URI" }
2111
2111
+
span { class: "metadata-value",
2112
2112
+
HighlightedUri { uri: uri.clone() }
2093
2113
}
2094
2114
}
2095
2095
-
{children}
2115
2115
+
if let Some(cid) = cid {
2116
2116
+
div { class: "metadata-row",
2117
2117
+
span { class: "metadata-label", "CID" }
2118
2118
+
code { class: "metadata-value", "{cid}" }
2119
2119
+
}
2120
2120
+
}
2096
2121
}
2122
2122
+
2123
2123
+
{children}
2124
2124
+
2097
2125
}
2098
2126
}
2099
2127
2100
2128
/// Render some text as markdown.
2101
2129
#[component]
2102
2130
fn EditableRecordContent(
2103
2103
-
record_value: Signal<Data<'static>>,
2131
2131
+
record_value: Data<'static>,
2104
2132
uri: ReadSignal<AtUri<'static>>,
2105
2133
view_mode: Signal<ViewMode>,
2106
2134
edit_mode: Signal<bool>,
2107
2135
record_resource: Resource<Result<GetRecordOutput<'static>, AgentError>>,
2108
2136
) -> Element {
2109
2109
-
let mut edit_data = use_signal(|| record_value());
2137
2137
+
let mut edit_data = use_signal(use_reactive!(|record_value| record_value.clone()));
2110
2138
let nsid = use_memo(move || edit_data().type_discriminator().map(|s| s.to_string()));
2111
2139
let navigator = use_navigator();
2112
2140
let fetcher = use_context::<CachedFetcher>();
···
2150
2178
Ok(output) => {
2151
2179
if output.status() == StatusCode::OK {
2152
2180
dioxus_logger::tracing::info!("Record updated successfully");
2153
2153
-
record_value.set(data);
2181
2181
+
edit_data.set(data.clone());
2154
2182
edit_mode.set(false);
2155
2183
} else {
2156
2184
dioxus_logger::tracing::error!("Unexpected status code: {:?}", output.status());
···
2184
2212
Ok(response) => {
2185
2213
if let Ok(output) = response.into_output() {
2186
2214
dioxus_logger::tracing::info!("Record created: {}", output.uri);
2187
2187
-
nav.push(Route::RecordView { uri: output.uri.to_smolstr() });
2215
2215
+
let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, output.uri);
2216
2216
+
nav.push(link);
2188
2217
}
2189
2218
}
2190
2219
Err(e) => {
···
2233
2262
}
2234
2263
2235
2264
dioxus_logger::tracing::info!("Record replaced: {}", create_output.uri);
2236
2236
-
nav.push(Route::RecordView { uri: create_output.uri.to_smolstr() });
2265
2265
+
let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, create_output.uri);
2266
2266
+
nav.push(link);
2237
2267
}
2238
2268
}
2239
2269
Err(e) => {
···
2275
2305
});
2276
2306
},
2277
2307
on_cancel: move |_| {
2278
2278
-
edit_data.set(record_value());
2308
2308
+
edit_data.set(record_value.clone());
2279
2309
edit_mode.set(false);
2280
2310
},
2281
2311
}