FIX: color picker and border issues
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
47acd0d600
commit
d08fe32f97
@@ -1219,6 +1219,42 @@ impl UserModel {
|
|||||||
let new_style = self.model.get_style_for_cell(sheet, source_row, column)?;
|
let new_style = self.model.get_style_for_cell(sheet, source_row, column)?;
|
||||||
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
||||||
|
|
||||||
|
if column == column1 && column > 1 {
|
||||||
|
// Fix the right border of the cell to the left
|
||||||
|
let old_left_style = self.model.get_style_for_cell(sheet, row, column - 1)?;
|
||||||
|
if old_left_style.border.right != new_style.border.left {
|
||||||
|
let mut new_left_style = old_left_style.clone();
|
||||||
|
new_left_style.border.right = new_style.border.left.clone();
|
||||||
|
self.model
|
||||||
|
.set_cell_style(sheet, row, column - 1, &new_left_style)?;
|
||||||
|
// Add the diff
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column: column - 1,
|
||||||
|
old_value: Box::new(old_left_style),
|
||||||
|
new_value: Box::new(new_left_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if column == column1 + width1 - 1 && column < LAST_COLUMN {
|
||||||
|
// Fix the left border of the cell to the right
|
||||||
|
let old_right_style = self.model.get_style_for_cell(sheet, row, column + 1)?;
|
||||||
|
if old_right_style.border.left != new_style.border.right {
|
||||||
|
let mut new_right_style = old_right_style.clone();
|
||||||
|
new_right_style.border.left = new_style.border.right.clone();
|
||||||
|
self.model
|
||||||
|
.set_cell_style(sheet, row, column + 1, &new_right_style)?;
|
||||||
|
// Add the diff
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column: column + 1,
|
||||||
|
old_value: Box::new(old_right_style),
|
||||||
|
new_value: Box::new(new_right_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the diffs
|
// Add the diffs
|
||||||
diff_list.push(Diff::SetCellStyle {
|
diff_list.push(Diff::SetCellStyle {
|
||||||
sheet,
|
sheet,
|
||||||
@@ -1248,27 +1284,27 @@ impl UserModel {
|
|||||||
pub fn auto_fill_columns(&mut self, source_area: &Area, to_column: i32) -> Result<(), String> {
|
pub fn auto_fill_columns(&mut self, source_area: &Area, to_column: i32) -> Result<(), String> {
|
||||||
let mut diff_list = Vec::new();
|
let mut diff_list = Vec::new();
|
||||||
let sheet = source_area.sheet;
|
let sheet = source_area.sheet;
|
||||||
let row1 = source_area.row;
|
let first_row = source_area.row;
|
||||||
let column1 = source_area.column;
|
let first_column = source_area.column;
|
||||||
let width1 = source_area.width;
|
let last_column = first_column + source_area.width - 1;
|
||||||
let height1 = source_area.height;
|
let last_row = first_row + source_area.height - 1;
|
||||||
|
|
||||||
// Check first all parameters are valid
|
// Check first all parameters are valid
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
if self.model.workbook.worksheet(sheet).is_err() {
|
||||||
return Err(format!("Invalid worksheet index: '{sheet}'"));
|
return Err(format!("Invalid worksheet index: '{sheet}'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_valid_column_number(column1) {
|
if !is_valid_column_number(first_column) {
|
||||||
return Err(format!("Invalid column: '{column1}'"));
|
return Err(format!("Invalid column: '{first_column}'"));
|
||||||
}
|
}
|
||||||
if !is_valid_row(row1) {
|
if !is_valid_row(first_row) {
|
||||||
return Err(format!("Invalid row: '{row1}'"));
|
return Err(format!("Invalid row: '{first_row}'"));
|
||||||
}
|
}
|
||||||
if !is_valid_column_number(column1 + width1 - 1) {
|
if !is_valid_column_number(last_column) {
|
||||||
return Err(format!("Invalid column: '{}'", column1 + width1 - 1));
|
return Err(format!("Invalid column: '{}'", last_column));
|
||||||
}
|
}
|
||||||
if !is_valid_row(row1 + height1 - 1) {
|
if !is_valid_row(last_row) {
|
||||||
return Err(format!("Invalid row: '{}'", row1 + height1 - 1));
|
return Err(format!("Invalid row: '{}'", last_row));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_valid_row(to_column) {
|
if !is_valid_row(to_column) {
|
||||||
@@ -1281,21 +1317,21 @@ impl UserModel {
|
|||||||
// this is the range of columns we are going to fill
|
// this is the range of columns we are going to fill
|
||||||
let column_range: Vec<i32>;
|
let column_range: Vec<i32>;
|
||||||
|
|
||||||
if to_column >= column1 + width1 {
|
if to_column > last_column {
|
||||||
// we go right, we start from `1 + width` to `to_column`,
|
// we go right, we start from `1 + width` to `to_column`,
|
||||||
anchor_column = column1;
|
anchor_column = first_column;
|
||||||
sign = 1;
|
sign = 1;
|
||||||
column_range = (column1 + width1..to_column + 1).collect();
|
column_range = (last_column + 1..to_column + 1).collect();
|
||||||
} else if to_column < column1 {
|
} else if to_column < first_column {
|
||||||
// we go left, starting from `column1 - `` all the way to `to_column`
|
// we go left, starting from `column1 - `` all the way to `to_column`
|
||||||
anchor_column = column1 + width1 - 1;
|
anchor_column = last_column;
|
||||||
sign = -1;
|
sign = -1;
|
||||||
column_range = (to_column..column1).rev().collect();
|
column_range = (to_column..first_column).rev().collect();
|
||||||
} else {
|
} else {
|
||||||
return Err("Invalid parameters for autofill".to_string());
|
return Err("Invalid parameters for autofill".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
for row in row1..row1 + height1 {
|
for row in first_row..=last_row {
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
for column_ref in &column_range {
|
for column_ref in &column_range {
|
||||||
let column = *column_ref;
|
let column = *column_ref;
|
||||||
@@ -1316,8 +1352,46 @@ impl UserModel {
|
|||||||
self.model
|
self.model
|
||||||
.set_user_input(sheet, row, column, target_value.to_string())?;
|
.set_user_input(sheet, row, column, target_value.to_string())?;
|
||||||
|
|
||||||
// Compute the new style and set it
|
|
||||||
let new_style = self.model.get_style_for_cell(sheet, row, source_column)?;
|
let new_style = self.model.get_style_for_cell(sheet, row, source_column)?;
|
||||||
|
|
||||||
|
if row == first_row && row > 1 {
|
||||||
|
// Fix the lower border of the upper cell
|
||||||
|
let old_upper_style = self.model.get_style_for_cell(sheet, row - 1, column)?;
|
||||||
|
if old_upper_style.border.bottom != new_style.border.top {
|
||||||
|
let mut new_upper_style = old_upper_style.clone();
|
||||||
|
new_upper_style.border.bottom = new_style.border.top.clone();
|
||||||
|
self.model
|
||||||
|
.set_cell_style(sheet, row - 1, column, &new_upper_style)?;
|
||||||
|
// Add the diffs
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row: row - 1,
|
||||||
|
column,
|
||||||
|
old_value: Box::new(old_upper_style),
|
||||||
|
new_value: Box::new(new_upper_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if row == last_row && row < LAST_ROW {
|
||||||
|
// Fix the upper border of the lower cell
|
||||||
|
let old_lower_style = self.model.get_style_for_cell(sheet, row + 1, column)?;
|
||||||
|
if old_lower_style.border.top != new_style.border.bottom {
|
||||||
|
let mut new_lower_style = old_lower_style.clone();
|
||||||
|
new_lower_style.border.top = new_style.border.bottom.clone();
|
||||||
|
self.model
|
||||||
|
.set_cell_style(sheet, row + 1, column, &new_lower_style)?;
|
||||||
|
// Add the diffs
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row: row + 1,
|
||||||
|
column,
|
||||||
|
old_value: Box::new(old_lower_style),
|
||||||
|
new_value: Box::new(new_lower_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the new style and set it
|
||||||
|
|
||||||
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
||||||
|
|
||||||
// Add the diffs
|
// Add the diffs
|
||||||
@@ -1336,7 +1410,7 @@ impl UserModel {
|
|||||||
old_value: Box::new(old_value),
|
old_value: Box::new(old_value),
|
||||||
});
|
});
|
||||||
|
|
||||||
index = (index + sign) % width1;
|
index = (index + sign) % source_area.width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.push_diff_list(diff_list);
|
self.push_diff_list(diff_list);
|
||||||
@@ -1478,6 +1552,76 @@ impl UserModel {
|
|||||||
.model
|
.model
|
||||||
.get_style_for_cell(sheet, target_row, target_column)?;
|
.get_style_for_cell(sheet, target_row, target_column)?;
|
||||||
|
|
||||||
|
let new_style = value.style.clone();
|
||||||
|
|
||||||
|
// Fix borders
|
||||||
|
if target_row == source_first_row && target_row > 1 {
|
||||||
|
// Bottom border of the top cell
|
||||||
|
let old_top_style =
|
||||||
|
self.model
|
||||||
|
.get_style_for_cell(sheet, target_row - 1, target_column)?;
|
||||||
|
if new_style.border.top != old_top_style.border.bottom {
|
||||||
|
let mut new_top_style = old_top_style.clone();
|
||||||
|
new_top_style.border.bottom = new_style.border.top.clone();
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row: target_row - 1,
|
||||||
|
column: target_column,
|
||||||
|
old_value: Box::new(old_top_style),
|
||||||
|
new_value: Box::new(new_top_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if target_row == source_last_row && target_row < LAST_ROW {
|
||||||
|
// Top border of the lower cell
|
||||||
|
let old_bottom_style =
|
||||||
|
self.model
|
||||||
|
.get_style_for_cell(sheet, target_row + 1, target_column)?;
|
||||||
|
if new_style.border.bottom != old_bottom_style.border.top {
|
||||||
|
let mut new_top_style = old_bottom_style.clone();
|
||||||
|
new_top_style.border.top = new_style.border.bottom.clone();
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row: target_row + 1,
|
||||||
|
column: target_column,
|
||||||
|
old_value: Box::new(old_bottom_style),
|
||||||
|
new_value: Box::new(new_top_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target_column == source_first_column && target_column > 1 {
|
||||||
|
// Right border of the cell to the left
|
||||||
|
let old_left_style =
|
||||||
|
self.model
|
||||||
|
.get_style_for_cell(sheet, target_row, target_column - 1)?;
|
||||||
|
if new_style.border.left != old_left_style.border.bottom {
|
||||||
|
let mut new_left_style = old_left_style.clone();
|
||||||
|
new_left_style.border.right = new_style.border.left.clone();
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row: target_row,
|
||||||
|
column: target_column - 1,
|
||||||
|
old_value: Box::new(old_left_style),
|
||||||
|
new_value: Box::new(new_left_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if target_column == source_last_column && target_column < LAST_COLUMN {
|
||||||
|
// Left border of the cell to the right
|
||||||
|
let old_right_style =
|
||||||
|
self.model
|
||||||
|
.get_style_for_cell(sheet, target_row, target_column + 1)?;
|
||||||
|
if new_style.border.right != old_right_style.border.left {
|
||||||
|
let mut new_right_style = old_right_style.clone();
|
||||||
|
new_right_style.border.left = new_style.border.right.clone();
|
||||||
|
diff_list.push(Diff::SetCellStyle {
|
||||||
|
sheet,
|
||||||
|
row: target_row,
|
||||||
|
column: target_column + 1,
|
||||||
|
old_value: Box::new(old_right_style),
|
||||||
|
new_value: Box::new(new_right_style),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.model
|
self.model
|
||||||
.set_user_input(sheet, target_row, target_column, new_value.clone())?;
|
.set_user_input(sheet, target_row, target_column, new_value.clone())?;
|
||||||
self.model
|
self.model
|
||||||
@@ -1509,6 +1653,7 @@ impl UserModel {
|
|||||||
.worksheet(sheet)?
|
.worksheet(sheet)?
|
||||||
.cell(row, column)
|
.cell(row, column)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
// TODO: also clear the styles and adjacent borders
|
||||||
diff_list.push(Diff::CellClearContents {
|
diff_list.push(Diff::CellClearContents {
|
||||||
sheet,
|
sheet,
|
||||||
row,
|
row,
|
||||||
|
|||||||
@@ -143,10 +143,12 @@ export enum BorderStyle {
|
|||||||
Thin = "thin",
|
Thin = "thin",
|
||||||
Medium = "medium",
|
Medium = "medium",
|
||||||
Thick = "thick",
|
Thick = "thick",
|
||||||
Dashed = "dashed",
|
|
||||||
Dotted = "dotted",
|
|
||||||
Double = "double",
|
Double = "double",
|
||||||
None = "none",
|
Dotted = "dotted",
|
||||||
|
SlantDashDot = "slantdashdot",
|
||||||
|
MediumDashed = "mediumdashed",
|
||||||
|
MediumDashDotDot = "mediumdashdotdot",
|
||||||
|
MediumDashDot = "mediumdashdot",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BorderItem {
|
interface BorderItem {
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ const BorderPicker = (properties: BorderPickerProps) => {
|
|||||||
const [colorPickerOpen, setColorPickerOpen] = useState(false);
|
const [colorPickerOpen, setColorPickerOpen] = useState(false);
|
||||||
const [stylePickerOpen, setStylePickerOpen] = useState(false);
|
const [stylePickerOpen, setStylePickerOpen] = useState(false);
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
// FIXME
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: We don't want updating the function every time the properties.onChange
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!borderSelected) {
|
if (!borderSelected) {
|
||||||
return;
|
return;
|
||||||
@@ -57,21 +58,29 @@ const BorderPicker = (properties: BorderPickerProps) => {
|
|||||||
|
|
||||||
const onClose = properties.onClose;
|
const onClose = properties.onClose;
|
||||||
|
|
||||||
|
// The reason is that the border picker doesn't start with the properties of the selected area
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: We reset the styles, every time we open (or close) the widget
|
||||||
|
useEffect(() => {
|
||||||
|
setBorderSelected(null);
|
||||||
|
setBorderColor("#000000");
|
||||||
|
setBorderStyle(BorderStyle.Thin);
|
||||||
|
}, [properties.open]);
|
||||||
|
|
||||||
const borderColorButton = useRef(null);
|
const borderColorButton = useRef(null);
|
||||||
const borderStyleButton = useRef(null);
|
const borderStyleButton = useRef(null);
|
||||||
return (
|
return (
|
||||||
<>
|
<StyledPopover
|
||||||
<StyledPopover
|
open={properties.open}
|
||||||
open={properties.open}
|
onClose={onClose}
|
||||||
onClose={onClose}
|
anchorEl={properties.anchorEl.current}
|
||||||
anchorEl={properties.anchorEl.current}
|
anchorOrigin={
|
||||||
anchorOrigin={
|
properties.anchorOrigin || { vertical: "bottom", horizontal: "left" }
|
||||||
properties.anchorOrigin || { vertical: "bottom", horizontal: "left" }
|
}
|
||||||
}
|
transformOrigin={
|
||||||
transformOrigin={
|
properties.transformOrigin || { vertical: "top", horizontal: "left" }
|
||||||
properties.transformOrigin || { vertical: "top", horizontal: "left" }
|
}
|
||||||
}
|
>
|
||||||
>
|
<div>
|
||||||
<BorderPickerDialog>
|
<BorderPickerDialog>
|
||||||
<Borders>
|
<Borders>
|
||||||
<Line>
|
<Line>
|
||||||
@@ -283,16 +292,6 @@ const BorderPicker = (properties: BorderPickerProps) => {
|
|||||||
transformOrigin={{ vertical: 38, horizontal: -6 }}
|
transformOrigin={{ vertical: 38, horizontal: -6 }}
|
||||||
>
|
>
|
||||||
<BorderStyleDialog>
|
<BorderStyleDialog>
|
||||||
<LineWrapper
|
|
||||||
onClick={() => {
|
|
||||||
setBorderStyle(BorderStyle.Dashed);
|
|
||||||
setStylePickerOpen(false);
|
|
||||||
}}
|
|
||||||
$checked={borderStyle === BorderStyle.None}
|
|
||||||
>
|
|
||||||
<BorderDescription>None</BorderDescription>
|
|
||||||
<NoneLine />
|
|
||||||
</LineWrapper>
|
|
||||||
<LineWrapper
|
<LineWrapper
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBorderStyle(BorderStyle.Thin);
|
setBorderStyle(BorderStyle.Thin);
|
||||||
@@ -323,40 +322,10 @@ const BorderPicker = (properties: BorderPickerProps) => {
|
|||||||
<BorderDescription>Thick</BorderDescription>
|
<BorderDescription>Thick</BorderDescription>
|
||||||
<ThickLine />
|
<ThickLine />
|
||||||
</LineWrapper>
|
</LineWrapper>
|
||||||
<LineWrapper
|
|
||||||
onClick={() => {
|
|
||||||
setBorderStyle(BorderStyle.Dotted);
|
|
||||||
setStylePickerOpen(false);
|
|
||||||
}}
|
|
||||||
$checked={borderStyle === BorderStyle.Dotted}
|
|
||||||
>
|
|
||||||
<BorderDescription>Dotted</BorderDescription>
|
|
||||||
<DottedLine />
|
|
||||||
</LineWrapper>
|
|
||||||
<LineWrapper
|
|
||||||
onClick={() => {
|
|
||||||
setBorderStyle(BorderStyle.Dashed);
|
|
||||||
setStylePickerOpen(false);
|
|
||||||
}}
|
|
||||||
$checked={borderStyle === BorderStyle.Dashed}
|
|
||||||
>
|
|
||||||
<BorderDescription>Dashed</BorderDescription>
|
|
||||||
<DashedLine />
|
|
||||||
</LineWrapper>
|
|
||||||
<LineWrapper
|
|
||||||
onClick={() => {
|
|
||||||
setBorderStyle(BorderStyle.Dashed);
|
|
||||||
setStylePickerOpen(false);
|
|
||||||
}}
|
|
||||||
$checked={borderStyle === BorderStyle.Double}
|
|
||||||
>
|
|
||||||
<BorderDescription>Double</BorderDescription>
|
|
||||||
<DoubleLine />
|
|
||||||
</LineWrapper>
|
|
||||||
</BorderStyleDialog>
|
</BorderStyleDialog>
|
||||||
</StyledPopover>
|
</StyledPopover>
|
||||||
</StyledPopover>
|
</div>
|
||||||
</>
|
</StyledPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -380,10 +349,6 @@ const LineWrapper = styled("div")<LineWrapperProperties>`
|
|||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const NoneLine = styled("div")`
|
|
||||||
width: 68px;
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
`;
|
|
||||||
const SolidLine = styled("div")`
|
const SolidLine = styled("div")`
|
||||||
width: 68px;
|
width: 68px;
|
||||||
border-top: 1px solid #333333;
|
border-top: 1px solid #333333;
|
||||||
@@ -396,18 +361,6 @@ const ThickLine = styled("div")`
|
|||||||
width: 68px;
|
width: 68px;
|
||||||
border-top: 3px solid #333333;
|
border-top: 3px solid #333333;
|
||||||
`;
|
`;
|
||||||
const DashedLine = styled("div")`
|
|
||||||
width: 68px;
|
|
||||||
border-top: 1px dashed #333333;
|
|
||||||
`;
|
|
||||||
const DottedLine = styled("div")`
|
|
||||||
width: 68px;
|
|
||||||
border-top: 1px dotted #333333;
|
|
||||||
`;
|
|
||||||
const DoubleLine = styled("div")`
|
|
||||||
width: 68px;
|
|
||||||
border-top: 3px double #333333;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Divider = styled("div")`
|
const Divider = styled("div")`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -103,25 +103,33 @@ const ColorPicker = (properties: ColorPickerProps) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ColorList>
|
</ColorList>
|
||||||
<HorizontalDivider />
|
|
||||||
<RecentLabel>{"Recent"}</RecentLabel>
|
{recentColors.current.length > 0 ? (
|
||||||
<ColorList>
|
<>
|
||||||
{recentColors.current.map((recentColor) => (
|
<HorizontalDivider />
|
||||||
<Button
|
<RecentLabel>{"Recent"}</RecentLabel>
|
||||||
key={recentColor}
|
<ColorList>
|
||||||
$color={recentColor}
|
{recentColors.current.map((recentColor) => (
|
||||||
onClick={(): void => {
|
<Button
|
||||||
closePicker(recentColor);
|
key={recentColor}
|
||||||
}}
|
$color={recentColor}
|
||||||
/>
|
onClick={(): void => {
|
||||||
))}
|
closePicker(recentColor);
|
||||||
</ColorList>
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ColorList>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
</ColorPickerDialog>
|
</ColorPickerDialog>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RecentLabel = styled.div`
|
const RecentLabel = styled.div`
|
||||||
|
font-family: "Inter";
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: ${theme.palette.text.secondary};
|
color: ${theme.palette.text.secondary};
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user