mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 13:23:22 +00:00
Scrubby labels: the Control bar's Stroke: and Opacity: links scrub their fields (#4088)
The Control bar's Stroke: and Opacity: and the Properties panel's Stroke are links to their panels, so a drag on them did nothing while every other field label scrubbed its field. A drag on such a link now scrubs the field after it (same steps and modifiers, one undo step, Escape cancels); a click still opens the panel, and the link's text is not selected by the drag.
This commit is contained in:
@@ -338,7 +338,8 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
ui.add_space(4.0);
|
||||
ui.separator();
|
||||
}
|
||||
if ui.link(egui::RichText::new(tl!("Opacity:")).size(12.0).color(t.text).underline()).clicked() {
|
||||
// A click opens the Transparency panel; a drag scrubs the field (#4088).
|
||||
if crate::scrub::link_label(ui, egui::RichText::new(tl!("Opacity:")).size(12.0).color(t.text).underline()).clicked() {
|
||||
app.ui.open_panel = Some("transparency".into());
|
||||
}
|
||||
if let Some(o) = widgets::plain_field(ui, "cb-opacity", opacity as f64 * 100.0, "%", 0, 56.0)
|
||||
|
||||
@@ -137,10 +137,12 @@ pub(crate) fn weight_field(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, weig
|
||||
}
|
||||
|
||||
/// The underlined Stroke link (`label`: the Control bar's "Stroke:", the Properties panel's
|
||||
/// "Stroke"): a click toggles the Stroke panel in a popover under it.
|
||||
/// "Stroke"): a click toggles the Stroke panel in a popover under it, and a drag scrubs the weight
|
||||
/// field after it ([`crate::scrub`]).
|
||||
pub(crate) fn link(app: &mut VectorcraftApp, ui: &mut Ui, label: &str) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let resp = ui.link(egui::RichText::new(label).size(12.0).color(t.text).underline()).on_hover_text(tl!("Stroke options"));
|
||||
// A drag on it scrubs the weight field after it (#4088).
|
||||
let resp = crate::scrub::link_label(ui, egui::RichText::new(label).size(12.0).color(t.text).underline()).on_hover_text(tl!("Stroke options"));
|
||||
widgets::popover(&resp, resp.clicked(), |ui| {
|
||||
ui.set_width(260.0);
|
||||
show(app, ui);
|
||||
|
||||
+40
-12
@@ -1,8 +1,10 @@
|
||||
//! Scrubbing numeric fields (#400, beyond Illustrator; Photoshop's scrubby labels): a horizontal
|
||||
//! drag on a numeric field's label (`W:`, `Opacity:`, the Character panel's size icon) steps the
|
||||
//! field's value by one unit every [`PX_PER_STEP`] points, ten with Shift and a tenth with
|
||||
//! Ctrl/Cmd, as the arrow keys step a focused field; a click on the label focuses the field. The
|
||||
//! text box itself never scrubs, and a field without a label doesn't.
|
||||
//! Ctrl/Cmd, as the arrow keys step a focused field; a click on the label focuses the field. A
|
||||
//! label that is a link (the Control bar's `Stroke:` and `Opacity:`, which open their panels;
|
||||
//! #4088) scrubs its field the same way, and a click on it still follows the link. The text box
|
||||
//! itself never scrubs, and a field without a label doesn't.
|
||||
//!
|
||||
//! The panels apply each value the drag passes as they apply a typed one, so the art follows the
|
||||
//! drag live; the app holds an undo group open meanwhile ([`begin_frame`], [`end_frame`]), so the
|
||||
@@ -113,24 +115,44 @@ pub(crate) fn end_frame(app: &mut VectorcraftApp, ctx: &Context) {
|
||||
struct Label {
|
||||
rect: Rect,
|
||||
pass: u64,
|
||||
/// The label is a link, this widget: a click follows it instead of focusing the field.
|
||||
link: Option<Id>,
|
||||
}
|
||||
|
||||
/// Note the label (text or icon) drawn at `rect`: a numeric field drawn right after it on the
|
||||
/// same line takes it as its label, which scrubs the field.
|
||||
pub(crate) fn note_label(ui: &Ui, rect: Rect) {
|
||||
note(ui, rect, None);
|
||||
}
|
||||
|
||||
/// A link that labels the numeric field drawn after it (it opens a panel when clicked): a drag on
|
||||
/// it scrubs the field, and a click stays the link's. Its text can't be selected, which a drag
|
||||
/// across it would otherwise do.
|
||||
pub(crate) fn link_label(ui: &mut Ui, text: egui::RichText) -> Response {
|
||||
let link = ui
|
||||
.scope(|ui| {
|
||||
ui.style_mut().interaction.selectable_labels = false;
|
||||
ui.link(text)
|
||||
})
|
||||
.inner;
|
||||
note(ui, link.rect, Some(link.id));
|
||||
link
|
||||
}
|
||||
|
||||
fn note(ui: &Ui, rect: Rect, link: Option<Id>) {
|
||||
let pass = ui.ctx().cumulative_pass_nr();
|
||||
ui.data_mut(|d| d.insert_temp(label_id(), Label { rect, pass }));
|
||||
ui.data_mut(|d| d.insert_temp(label_id(), Label { rect, pass, link }));
|
||||
}
|
||||
|
||||
/// The label noted just before the field at `field`, on its line.
|
||||
fn take_label(ui: &Ui, field: Rect) -> Option<Rect> {
|
||||
fn take_label(ui: &Ui, field: Rect) -> Option<Label> {
|
||||
let l = ui.data_mut(|d| {
|
||||
let l = d.get_temp::<Label>(label_id());
|
||||
d.remove::<Label>(label_id());
|
||||
l
|
||||
})?;
|
||||
let gap = field.left() - l.rect.right();
|
||||
(l.pass == ui.ctx().cumulative_pass_nr() && (-1.0..=LABEL_GAP).contains(&gap) && field.y_range().contains(l.rect.center().y)).then_some(l.rect)
|
||||
(l.pass == ui.ctx().cumulative_pass_nr() && (-1.0..=LABEL_GAP).contains(&gap) && field.y_range().contains(l.rect.center().y)).then_some(l)
|
||||
}
|
||||
|
||||
/// Scrub numeric field `field`, its box at `rect`, showing `text`: `value` in its units, rounded
|
||||
@@ -144,14 +166,20 @@ pub(crate) fn field(ui: &Ui, field: Id, rect: Rect, text: &str, value: Option<f6
|
||||
if !enabled(ui.ctx()) || !ui.is_enabled() {
|
||||
return None;
|
||||
}
|
||||
// On top of the label, which may sense clicks itself (selectable text).
|
||||
let resp = ui.interact(label, field.with("scrub-label"), Sense::click_and_drag());
|
||||
drag(ui, &resp, field, text, value, decimals).map(|v| v.max(*bounds.start()).min(*bounds.end()))
|
||||
// On top of the label, which may sense clicks itself (selectable text). A link senses drags
|
||||
// too and keeps its clicks (and its pointing hand while hovered): a widget laid over it would
|
||||
// take them.
|
||||
let resp = match label.link {
|
||||
Some(link) => ui.interact(label.rect, link, Sense::drag()),
|
||||
None => ui.interact(label.rect, field.with("scrub-label"), Sense::click_and_drag()),
|
||||
};
|
||||
drag(ui, &resp, field, text, value, decimals, label.link.is_some()).map(|v| v.max(*bounds.start()).min(*bounds.end()))
|
||||
}
|
||||
|
||||
/// What a drag on `resp`, the label of field `field`, does this frame.
|
||||
fn drag(ui: &Ui, resp: &Response, field: Id, text: &str, value: f64, decimals: i32) -> Option<f64> {
|
||||
if resp.hovered() || resp.dragged() {
|
||||
/// What a drag on `resp`, the label of field `field`, does this frame (`link`: the label is a link,
|
||||
/// which a click follows, so a press held without stepping doesn't focus the field either).
|
||||
fn drag(ui: &Ui, resp: &Response, field: Id, text: &str, value: f64, decimals: i32, link: bool) -> Option<f64> {
|
||||
if (resp.hovered() && !link) || resp.dragged() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal);
|
||||
}
|
||||
if resp.clicked() {
|
||||
@@ -174,7 +202,7 @@ fn drag(ui: &Ui, resp: &Response, field: Id, text: &str, value: f64, decimals: i
|
||||
s.moved.then_some(s.start)
|
||||
} else if resp.drag_stopped() {
|
||||
// Held without stepping: a slow click.
|
||||
if !s.moved {
|
||||
if !s.moved && !link {
|
||||
crate::widgets::focus_field(ui, field, text);
|
||||
}
|
||||
None
|
||||
|
||||
@@ -365,3 +365,64 @@ fn a_scrub_closes_its_group_in_its_own_document_after_a_switch() {
|
||||
assert_eq!(second.history.undo.len(), undo + 1, "one undo step for the drag");
|
||||
assert!(app.session.documents()[0].undo_group.is_some(), "the active document's group stays open");
|
||||
}
|
||||
|
||||
/// Press at `at`, move by `dx` in four frames and release, in the app.
|
||||
fn drag_at(app: &mut VectorcraftApp, ctx: &egui::Context, at: Pos2, dx: f32) {
|
||||
let button = |pos, pressed| Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE };
|
||||
app_frame(app, ctx, vec![Event::PointerMoved(at)], "");
|
||||
app_frame(app, ctx, vec![button(at, true)], "");
|
||||
for i in 1..=4 {
|
||||
app_frame(app, ctx, vec![Event::PointerMoved(at + vec2(dx * i as f32 / 4.0, 0.0))], "");
|
||||
}
|
||||
app_frame(app, ctx, vec![button(at + vec2(dx, 0.0), false)], "");
|
||||
app_frame(app, ctx, vec![], "");
|
||||
}
|
||||
|
||||
/// #4088: the Control bar's "Stroke:" and "Opacity:" are links to their panels, and they scrub the
|
||||
/// field after them as the other labels do: a drag steps the weight or the opacity in one undo
|
||||
/// step, while a click still follows the link and leaves the value and the keyboard focus alone.
|
||||
#[test]
|
||||
fn the_control_bars_stroke_and_opacity_links_scrub_their_fields() {
|
||||
let (mut app, ctx) = app_with_rect();
|
||||
app.ui.control_bar = true;
|
||||
let opacity = |app: &VectorcraftApp| crate::panels::current_transparency(app).unwrap().0;
|
||||
let weight = |app: &VectorcraftApp| crate::panels::current_stroke(app).unwrap().width;
|
||||
let find = |app: &mut VectorcraftApp, text: &str| {
|
||||
let mut at = None;
|
||||
for _ in 0..4 {
|
||||
at = app_frame(app, &ctx, vec![], text);
|
||||
}
|
||||
at.unwrap_or_else(|| panic!("{text} is shown")).center()
|
||||
};
|
||||
let (w0, undo) = (weight(&app), undo_len(&app));
|
||||
|
||||
// A click on a link opens its panel: no value changes and no field takes the keyboard.
|
||||
let (o, w) = (opacity(&app), weight(&app));
|
||||
let at = find(&mut app, "Opacity:");
|
||||
let button = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE };
|
||||
app_frame(&mut app, &ctx, vec![Event::PointerMoved(at), button(true)], "");
|
||||
app_frame(&mut app, &ctx, vec![button(false)], "");
|
||||
assert_eq!(app.ui.open_panel.as_deref(), Some("transparency"), "the link opens the Transparency panel");
|
||||
assert_eq!((opacity(&app), weight(&app)), (o, w));
|
||||
assert!(ctx.memory(|m| m.focused()).is_none(), "no field took the focus");
|
||||
|
||||
// Hovering the link keeps its pointing hand.
|
||||
let at = find(&mut app, "Opacity:");
|
||||
let screen_rect = Some(Rect::from_min_size(Pos2::ZERO, vec2(1600.0, 900.0)));
|
||||
let mut out = ctx.run_ui(egui::RawInput { events: vec![Event::PointerMoved(at)], screen_rect, ..Default::default() }, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
assert_ne!(out.platform_output.cursor_icon, CursorIcon::ResizeHorizontal, "the link keeps its cursor while hovered");
|
||||
|
||||
drag_at(&mut app, &ctx, at, -40.0);
|
||||
// 40 points left: 40 / PX_PER_STEP steps of 1% down from 100%.
|
||||
assert!((opacity(&app) as f64 - (1.0 - 40.0 / scrub::PX_PER_STEP as f64 / 100.0)).abs() < 1e-4, "{}", opacity(&app));
|
||||
assert_eq!(undo_len(&app), undo + 1, "one undo step");
|
||||
|
||||
let at = find(&mut app, "Stroke:");
|
||||
drag_at(&mut app, &ctx, at, 40.0);
|
||||
assert!((weight(&app) - (w0 + 40.0 / scrub::PX_PER_STEP as f64)).abs() < 1e-6, "{}", weight(&app));
|
||||
assert_eq!(undo_len(&app), undo + 2, "one undo step");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -717,7 +717,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- A plain step of a number field (Up/Down or a wheel notch) goes to the next whole unit in its direction and a Ctrl/Cmd step goes to the next tenth, so 5.1 mm steps up to 6 mm and down to 5 mm (#2881). A Shift step adds or takes ten and keeps the fraction.
|
||||
- In the number, text and color fields of the panels and the Control bar, Shift+Enter (Shift+Return) applies the value and keeps the field active with its value selected; a text box of several lines takes it as a line break. In a dialog, Shift+Enter applies the value and confirms the dialog, as Enter does.
|
||||
- The Pattern Options panel's Name field and the name fields that a double-click opens in the Layers, Artboards and Asset Export panels take Shift+Enter (Shift+Return) the same way: it applies the name and keeps the field active with the name selected.
|
||||
- Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels. Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off.
|
||||
- Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels, and so do the links that label a field (the Control bar's Stroke: and Opacity:, the Properties panel's Stroke), which a click still follows to their panel (#4088). Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off.
|
||||
- In the Transform panel, the Properties panel's Transform section and the Control bar, Alt+Enter (Option+Return) applies a value to a copy of the selection and selects the copy, in one undo step. With anchor points selected with Direct Selection it applies the value to them, as Enter does. Ctrl/Cmd+Enter in W or H applies the value and scales the selection proportionally with the link off. With the Artboard tool, it scales the active artboard proportionally. A percentage typed alone in W or H (`50%`) is that share of the size the field shows, and the link and Ctrl/Cmd+Enter keep the proportions as with a length (#2401).
|
||||
- The Control bar's underlined Transform link opens the Transform panel in a popover under it (reference point, X/Y/W/H, Rotate and Shear with their icons and angle presets, flips, shape properties, Scale Corners and Scale Strokes & Effects), as its Stroke link does the Stroke panel; X/Y/W/H also show inline while the bar has room. The reference point locator is nine squares, the outer eight joined around the box.
|
||||
- In isolation mode the Control bar has an Exit Isolation Mode button (`object.exitIsolation`), except while a pattern is edited. Outside isolation mode, with one group, compound path, path, image, mesh or placed file selected, it has an Isolate Selected Object button (`object.isolate`), which also isolates a path inside a group that Direct Selection selected.
|
||||
@@ -1692,6 +1692,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-06 | minor | Scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields; a click still opens the panel (#4088); evidence updated; scores unchanged. |
|
||||
| 2026-11-06 | minor | Control bar: Paragraph: link and Align left/center/right while type is selected, trimmed from the bar's right end after X/Y/W/H and the Align buttons (#5372); evidence added; scores unchanged. |
|
||||
| 2026-11-04 | minor | Contour add-on (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `addon.contour.set` is the command behind it. It is a built-in add-on: Object › Plug-ins › Contour, and its own heading in All Tools, with no default shortcut or toolbar slot. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. |
|
||||
| 2026-11-04 | minor | Contour tool (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `object.contour` is the command behind it. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. |
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -38,6 +38,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-06 | minor | Scrubby labels: the Control bar's Stroke: and Opacity: and the Properties panel's Stroke, links to their panels, scrub the field after them as other labels do; a click still opens the panel (#4088); scores and estimates unchanged |
|
||||
| 2026-11-05 | minor | Direct Selection: Shift-click picks several points of a blend's spine and dragging one moves them all, with the key objects on them, in one undo step (#5285); scores and estimates unchanged |
|
||||
| 2026-11-05 | minor | Direct Selection: Cmd/Ctrl held lends the Selection tool, whose bounding box shows while the key is down over objects selected whole; its handles scale and rotate, and a Cmd drag moves the whole object with its direct-selected anchors kept (#3901); scores and estimates unchanged |
|
||||
| 2026-11-05 | minor | Join tool: scrubbing over the ends of two crossing open paths that run past the crossing trims those ends and joins the paths there with a corner, instead of drawing a segment between the ends (#5844); scores and estimates unchanged |
|
||||
@@ -252,7 +253,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Units: pt, pc, in, mm, cm, px, ft-in, m, yd, ft; General/Stroke/Type units | done | units preference reverting to points on SVG output reported (#864) |
|
||||
| Units › East Asian Type (Q and Ha) | done | #394: `unitsAsianType` offers every unit and Q and Ha, a quarter of a millimetre each ([Scripting Guide's Measurement Units](https://ai-scripting.docsforadobe.dev/scripting/measurementUnits/): 0.709 pt per Q; [InDesign's unit notation](https://helpx.adobe.com/in/indesign/desktop/layout-and-grid-tools/rulers-and-measure-tools/change-ruler-measurement-units.html): `q`, `h`); only it lists Q and Ha. The public pages do not say which fields use it, so no field uses it yet and that waits for a source. Tests `q_and_ha_are_a_quarter_millimetre`, `east_asian_type_unit_*` |
|
||||
| Field precision: 3 decimals for steps; values kept as f64 | done | |
|
||||
| Scrubby labels (beyond Illustrator) | done | #400 |
|
||||
| Scrubby labels (beyond Illustrator) | done | #400; the labels that are links (the Control bar's Stroke: and Opacity:, the Properties panel's Stroke) scrub too, and a click on them still opens their panel (#4088) |
|
||||
| 3D Lighting Color picker, preview, reset dot and Reset all | initial | One to ten opaque RGB lights for Revolve, Extrude & Bevel, Inflate and Rotate; select, add or delete a light and edit its settings or gizmo; Standard, Diffuse, Top Left and Right select original rigs, with Custom for manual changes; Cancel restores all lights |
|
||||
| Dialogs open with the first field focused and selected; Enter confirms | done | on every open, not only the first (#1367); a first field that starts greyed out takes the focus once enabled (#1395) |
|
||||
| Value fields: selected text readable in every brightness; a focused field follows its value when a slider or preview changes it | partial | #1490 (part of #1465: the wheel over a hovered, unfocused field still scrolls the panel), #1457 |
|
||||
|
||||
Reference in New Issue
Block a user