tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
[weaver-app] add InlineThemeEditor component
Orual
4 weeks ago
7e01a8b3
88bb15d1
+311
3 changed files
expand all
collapse all
unified
split
crates
weaver-app
assets
styling
inline-theme-editor.css
src
components
inline_theme_editor.rs
mod.rs
+89
crates/weaver-app/assets/styling/inline-theme-editor.css
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
/* Inline theme editor for notebook settings */
2
+
3
+
.inline-theme-editor {
4
+
display: flex;
5
+
flex-direction: column;
6
+
gap: 1rem;
7
+
padding: 1rem;
8
+
background: var(--color-surface);
9
+
border: 1px solid var(--color-border);
10
+
}
11
+
12
+
.inline-theme-editor-heading {
13
+
margin: 0;
14
+
font-size: 1rem;
15
+
font-weight: 600;
16
+
color: var(--color-text);
17
+
}
18
+
19
+
.inline-theme-editor-presets {
20
+
display: flex;
21
+
flex-direction: column;
22
+
gap: 0.25rem;
23
+
}
24
+
25
+
.inline-theme-editor-presets label {
26
+
font-size: 0.875rem;
27
+
color: var(--color-muted);
28
+
}
29
+
30
+
.inline-theme-editor-presets select {
31
+
padding: 0.5rem;
32
+
border: 1px solid var(--color-border);
33
+
background: var(--color-base);
34
+
color: var(--color-text);
35
+
font-family: var(--font-ui);
36
+
font-size: 0.875rem;
37
+
}
38
+
39
+
.inline-theme-editor-presets select:focus {
40
+
outline: none;
41
+
border-color: var(--color-primary);
42
+
}
43
+
44
+
.inline-theme-editor-colours {
45
+
display: grid;
46
+
grid-template-columns: repeat(2, 1fr);
47
+
gap: 0.75rem;
48
+
}
49
+
50
+
.inline-theme-editor-code-theme,
51
+
.inline-theme-editor-mode {
52
+
display: flex;
53
+
flex-direction: column;
54
+
gap: 0.25rem;
55
+
}
56
+
57
+
.inline-theme-editor-code-theme label,
58
+
.inline-theme-editor-mode label {
59
+
font-size: 0.875rem;
60
+
color: var(--color-muted);
61
+
}
62
+
63
+
.inline-theme-editor-code-theme select {
64
+
padding: 0.5rem;
65
+
border: 1px solid var(--color-border);
66
+
background: var(--color-base);
67
+
color: var(--color-text);
68
+
font-family: var(--font-ui);
69
+
font-size: 0.875rem;
70
+
}
71
+
72
+
.inline-theme-editor-code-theme select:focus {
73
+
outline: none;
74
+
border-color: var(--color-primary);
75
+
}
76
+
77
+
.inline-theme-editor-mode {
78
+
gap: 0.5rem;
79
+
}
80
+
81
+
.inline-theme-editor-full-link {
82
+
font-size: 0.875rem;
83
+
color: var(--color-link);
84
+
text-decoration: none;
85
+
}
86
+
87
+
.inline-theme-editor-full-link:hover {
88
+
text-decoration: underline;
89
+
}
+219
crates/weaver-app/src/components/inline_theme_editor.rs
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
//! Inline theme editor for notebook settings.
2
+
//!
3
+
//! Provides core 4 colour pickers (background, text, primary, link) plus code theme selector.
4
+
5
+
use dioxus::prelude::*;
6
+
use weaver_renderer::themes::{BUILTIN_CODE_THEMES, BUILTIN_COLOUR_SCHEMES};
7
+
8
+
use crate::components::toggle_group::{ToggleGroup, ToggleItem};
9
+
use crate::components::HexColourInput;
10
+
11
+
/// Strip leading # or 0x from hex colour strings.
12
+
fn strip_hex_prefix(s: &str) -> String {
13
+
s.trim_start_matches('#')
14
+
.trim_start_matches("0x")
15
+
.trim_start_matches("0X")
16
+
.to_uppercase()
17
+
}
18
+
19
+
/// Convert mode string to toggle index.
20
+
fn mode_to_index(mode: &str) -> std::collections::HashSet<usize> {
21
+
let idx = match mode {
22
+
"light" => 1,
23
+
"dark" => 2,
24
+
_ => 0, // "auto" or default
25
+
};
26
+
std::collections::HashSet::from([idx])
27
+
}
28
+
29
+
/// Convert toggle index to mode string.
30
+
fn index_to_mode(idx: usize) -> &'static str {
31
+
match idx {
32
+
1 => "light",
33
+
2 => "dark",
34
+
_ => "auto",
35
+
}
36
+
}
37
+
38
+
const INLINE_THEME_EDITOR_CSS: Asset = asset!("/assets/styling/inline-theme-editor.css");
39
+
40
+
/// Theme values being edited.
41
+
#[derive(Debug, Clone, PartialEq, Default)]
42
+
pub struct InlineThemeValues {
43
+
pub background: String,
44
+
pub text: String,
45
+
pub primary: String,
46
+
pub link: String,
47
+
pub code_theme: String,
48
+
pub default_mode: String, // "light", "dark", or "auto"
49
+
}
50
+
51
+
/// Props for InlineThemeEditor.
52
+
#[derive(Props, Clone, PartialEq)]
53
+
pub struct InlineThemeEditorProps {
54
+
/// Current theme values.
55
+
pub values: InlineThemeValues,
56
+
/// Callback when any value changes.
57
+
pub onchange: EventHandler<InlineThemeValues>,
58
+
}
59
+
60
+
/// Inline theme editor with core colour pickers and code theme selector.
61
+
#[component]
62
+
pub fn InlineThemeEditor(props: InlineThemeEditorProps) -> Element {
63
+
let values = props.values.clone();
64
+
65
+
// Filter code themes by current mode for the selector.
66
+
let mode = &values.default_mode;
67
+
let relevant_code_themes: Vec<_> = BUILTIN_CODE_THEMES
68
+
.iter()
69
+
.filter(|t| mode == "auto" || t.variant == mode)
70
+
.collect();
71
+
72
+
rsx! {
73
+
document::Stylesheet { href: INLINE_THEME_EDITOR_CSS }
74
+
75
+
div { class: "inline-theme-editor",
76
+
h4 { class: "inline-theme-editor-heading", "Theme" }
77
+
78
+
// Preset selector.
79
+
div { class: "inline-theme-editor-presets",
80
+
label { "Start from preset:" }
81
+
select {
82
+
onchange: {
83
+
let onchange = props.onchange.clone();
84
+
let code_theme = props.values.code_theme.clone();
85
+
move |e: Event<FormData>| {
86
+
let preset_id = e.value();
87
+
if let Some(scheme) = BUILTIN_COLOUR_SCHEMES.iter().find(|s| s.id == preset_id) {
88
+
onchange.call(InlineThemeValues {
89
+
background: strip_hex_prefix(&scheme.colours.base),
90
+
text: strip_hex_prefix(&scheme.colours.text),
91
+
primary: strip_hex_prefix(&scheme.colours.primary),
92
+
link: strip_hex_prefix(&scheme.colours.link),
93
+
code_theme: code_theme.clone(),
94
+
default_mode: scheme.variant.to_string(),
95
+
});
96
+
}
97
+
}
98
+
},
99
+
option { value: "", "Custom" }
100
+
for scheme in BUILTIN_COLOUR_SCHEMES.iter() {
101
+
option {
102
+
value: "{scheme.id}",
103
+
"{scheme.name}"
104
+
}
105
+
}
106
+
}
107
+
}
108
+
109
+
// Colour pickers.
110
+
div { class: "inline-theme-editor-colours",
111
+
HexColourInput {
112
+
label: Some("Background".to_string()),
113
+
value: values.background.clone(),
114
+
onchange: {
115
+
let values = props.values.clone();
116
+
let onchange = props.onchange.clone();
117
+
move |val| {
118
+
let mut v = values.clone();
119
+
v.background = val;
120
+
onchange.call(v);
121
+
}
122
+
},
123
+
}
124
+
HexColourInput {
125
+
label: Some("Text".to_string()),
126
+
value: values.text.clone(),
127
+
onchange: {
128
+
let values = props.values.clone();
129
+
let onchange = props.onchange.clone();
130
+
move |val| {
131
+
let mut v = values.clone();
132
+
v.text = val;
133
+
onchange.call(v);
134
+
}
135
+
},
136
+
}
137
+
HexColourInput {
138
+
label: Some("Primary".to_string()),
139
+
value: values.primary.clone(),
140
+
onchange: {
141
+
let values = props.values.clone();
142
+
let onchange = props.onchange.clone();
143
+
move |val| {
144
+
let mut v = values.clone();
145
+
v.primary = val;
146
+
onchange.call(v);
147
+
}
148
+
},
149
+
}
150
+
HexColourInput {
151
+
label: Some("Link".to_string()),
152
+
value: values.link.clone(),
153
+
onchange: {
154
+
let values = props.values.clone();
155
+
let onchange = props.onchange.clone();
156
+
move |val| {
157
+
let mut v = values.clone();
158
+
v.link = val;
159
+
onchange.call(v);
160
+
}
161
+
},
162
+
}
163
+
}
164
+
165
+
// Code theme selector.
166
+
div { class: "inline-theme-editor-code-theme",
167
+
label { "Code theme:" }
168
+
select {
169
+
value: "{values.code_theme}",
170
+
onchange: {
171
+
let values = props.values.clone();
172
+
let onchange = props.onchange.clone();
173
+
move |e: Event<FormData>| {
174
+
let mut v = values.clone();
175
+
v.code_theme = e.value();
176
+
onchange.call(v);
177
+
}
178
+
},
179
+
for theme in relevant_code_themes {
180
+
option {
181
+
value: "{theme.id}",
182
+
"{theme.name}"
183
+
}
184
+
}
185
+
}
186
+
}
187
+
188
+
// Default mode toggle group.
189
+
// Indexes: 0 = auto, 1 = light, 2 = dark
190
+
div { class: "inline-theme-editor-mode",
191
+
label { "Default mode:" }
192
+
ToggleGroup {
193
+
horizontal: true,
194
+
default_pressed: mode_to_index(&values.default_mode),
195
+
on_pressed_change: {
196
+
let values = props.values.clone();
197
+
let onchange = props.onchange.clone();
198
+
move |pressed: std::collections::HashSet<usize>| {
199
+
if let Some(&idx) = pressed.iter().next() {
200
+
let mut v = values.clone();
201
+
v.default_mode = index_to_mode(idx).to_string();
202
+
onchange.call(v);
203
+
}
204
+
}
205
+
},
206
+
ToggleItem { index: 0usize, "Auto" }
207
+
ToggleItem { index: 1usize, "Light" }
208
+
ToggleItem { index: 2usize, "Dark" }
209
+
}
210
+
}
211
+
212
+
// Link to full editor.
213
+
// TODO: Enable once theme page exists
214
+
// a { href: "/{ident}/themes", class: "inline-theme-editor-full-link",
215
+
// "Edit full theme ->"
216
+
// }
217
+
}
218
+
}
219
+
}
+3
crates/weaver-app/src/components/mod.rs
···
362
363
mod hex_colour_input;
364
pub use hex_colour_input::HexColourInput;
0
0
0
···
362
363
mod hex_colour_input;
364
pub use hex_colour_input::HexColourInput;
365
+
366
+
mod inline_theme_editor;
367
+
pub use inline_theme_editor::{InlineThemeEditor, InlineThemeValues};