Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/actions/menu_items_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,32 @@ export const HIDE_ROWS_NAME = (env: SpreadsheetChildEnv) => {
}
};

export const RESIZE_COLUMNS_NAME = (env: SpreadsheetChildEnv) => {
const cols = [...env.model.getters.getActiveCols()].sort((a, b) => a - b);
const first = cols[0];
const last = cols[cols.length - 1];
if (cols.length === 1) {
return _t("Resize column %s", numberToLetters(first));
} else if (last - first + 1 === cols.length) {
return _t("Resize columns %s - %s", numberToLetters(first), numberToLetters(last));
} else {
return _t("Resize columns");
}
};

export const RESIZE_ROWS_NAME = (env: SpreadsheetChildEnv) => {
const rows = [...env.model.getters.getActiveRows()].sort((a, b) => a - b);
const first = rows[0];
const last = rows[rows.length - 1];
if (rows.length === 1) {
return _t("Resize row %s", first + 1);
} else if (last - first + 1 === rows.length) {
return _t("Resize rows %s - %s", first + 1, last + 1);
} else {
return _t("Resize rows");
}
};

//------------------------------------------------------------------------------
// Charts
//------------------------------------------------------------------------------
Expand Down
16 changes: 16 additions & 0 deletions src/actions/view_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ export const unhideAllRows: ActionSpec = {
icon: "o-spreadsheet-Icon.UNHIDE_ROW",
};

export const resizeCols: ActionSpec = {
name: ACTIONS.RESIZE_COLUMNS_NAME,
execute: (env) => env.openSidePanel("HeaderResizePanel", { dimension: "COL" }),
isVisible: (env) => env.model.getters.getActiveCols().size > 0,
isEnabled: (env) => !env.model.getters.isCurrentSheetLocked(),
icon: "o-spreadsheet-Icon.RESIZE_HORIZONTAL",
};

export const resizeRows: ActionSpec = {
name: ACTIONS.RESIZE_ROWS_NAME,
execute: (env) => env.openSidePanel("HeaderResizePanel", { dimension: "ROW" }),
isVisible: (env) => env.model.getters.getActiveRows().size > 0,
isEnabled: (env) => !env.model.getters.isCurrentSheetLocked(),
icon: "o-spreadsheet-Icon.RESIZE_VERTICAL",
};

export const unFreezePane: ActionSpec = {
name: _t("Unfreeze"),
isVisible: (env) => {
Expand Down
10 changes: 10 additions & 0 deletions src/components/icons/icons.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,16 @@
/>
</svg>
</t>
<t t-name="o-spreadsheet-Icon.RESIZE_HORIZONTAL">
<div class="o-icon">
<i class="fa fa-arrows-h"/>
</div>
</t>
<t t-name="o-spreadsheet-Icon.RESIZE_VERTICAL">
<div class="o-icon">
<i class="fa fa-arrows-v"/>
</div>
</t>
<t t-name="o-spreadsheet-Icon.INSERT_CELL">
<svg class="o-icon" viewBox="0 0 18 18">
<path
Expand Down
1 change: 1 addition & 0 deletions src/components/number_input/number_input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
t-att-placeholder="this.props.placeholder"
t-on-change="this.save"
t-on-blur="this.save"
t-on-focus="this.onFocus"
t-on-pointerdown="this.onMouseDown"
t-on-pointerup="this.onMouseUp"
t-on-keydown="this.onKeyDown"
Expand Down
123 changes: 123 additions & 0 deletions src/components/side_panel/header_resize_panel/header_resize_panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { props, proxy } from "@odoo/owl";
import {
DEFAULT_CELL_HEIGHT,
DEFAULT_CELL_WIDTH,
MIN_COL_WIDTH,
MIN_ROW_HEIGHT,
} from "../../../constants";
import { Component } from "../../../owl3_compatibility_layer";
import { _t } from "../../../translation";
import { DispatchResult } from "../../../types/commands";
import { SpreadsheetChildEnv } from "../../../types/spreadsheet_env";
import { NumberInput } from "../../number_input/number_input";
import { types } from "../../props_validation";
import { ValidationMessages } from "../../validation_messages/validation_messages";
import { Section } from "../components/section/section";

type ResizeMode = "exactSize" | "fitToData";

interface State {
mode: ResizeMode;
inputValue: string;
}

export class HeaderResizePanel extends Component<SpreadsheetChildEnv> {
static template = "o-spreadsheet-HeaderResizePanel";
static components = { NumberInput, Section, ValidationMessages };

protected props = props({
sheetId: types.UID(),
dimension: types.Dimension(),
elements: types.array(types.HeaderIndex()),
onCloseSidePanel: types.function([]),
});

state = proxy<State>({
mode: "exactSize",
inputValue: String(this.currentSize),
});

get minSize(): number {
return this.props.dimension === "COL" ? MIN_COL_WIDTH : MIN_ROW_HEIGHT;
}

get maxSize(): number {
return 2000;
}

get sizeInputLabel(): string {
return this.props.dimension === "COL"
? _t("Enter new column width in pixels. (Default: %s)", DEFAULT_CELL_WIDTH)
: _t("Enter new row height in pixels. (Default: %s)", DEFAULT_CELL_HEIGHT);
}

get errorMessages(): string[] {
if (this.state.mode === "fitToData") {
return [];
}
const size = this.state.inputValue.trim();
if (!size) {
return [_t("Enter a size in pixels.")];
}
if (!/^\d+$/.test(size)) {
return [_t("Size must be an integer.")];
}
const sizeNumber = Number(size);
if (sizeNumber < this.minSize || sizeNumber > this.maxSize) {
return [
_t(
"Size must be between %s and %s pixels.",
this.minSize.toString(),
this.maxSize.toString()
),
];
}
return [];
}

onModeChanged(mode: ResizeMode) {
this.state.mode = mode;
}

onSizeChanged(inputValue: string) {
this.state.inputValue = inputValue;
this.state.mode = "exactSize";
}

applyResize() {
if (this.errorMessages.length > 0) {
return;
}

let result: DispatchResult;
if (this.state.mode === "exactSize") {
const size = Number(this.state.inputValue.trim());
result = this.env.model.dispatch("RESIZE_COLUMNS_ROWS", {
sheetId: this.props.sheetId,
dimension: this.props.dimension,
elements: this.props.elements,
size,
});
} else if (this.props.dimension === "COL") {
result = this.env.model.dispatch("AUTORESIZE_COLUMNS", {
sheetId: this.props.sheetId,
cols: this.props.elements,
});
} else {
result = this.env.model.dispatch("AUTORESIZE_ROWS", {
sheetId: this.props.sheetId,
rows: this.props.elements,
});
}
if (result.isSuccessful) {
this.props.onCloseSidePanel();
}
}

private get currentSize(): number {
const element = this.props.elements[0];
return this.props.dimension === "COL"
? this.env.model.getters.getColSize(this.props.sheetId, element)
: this.env.model.getters.getRowSize(this.props.sheetId, element);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<templates>
<t t-name="o-spreadsheet-HeaderResizePanel">
<div class="o-header-resize-panel">
<Section title.translate="Size">
<label class="o-radio d-flex align-items-center mb-3">
<input
class="border rounded-circle me-2"
type="radio"
name="headerResizeMode"
value="exactSize"
t-att-checked="this.state.mode === 'exactSize'"
t-on-change="() => this.onModeChanged('exactSize')"
/>
<span t-out="this.sizeInputLabel"/>
</label>
<NumberInput
value="this.state.inputValue"
min="this.minSize"
max="this.maxSize"
class="'w-50 mb-3'"
autofocus="true"
selectContentOnFocus="true"
onFocused="() => this.onModeChanged('exactSize')"
onChange.bind="this.onSizeChanged"
/>
<label class="o-radio d-flex align-items-center">
<input
class="border rounded-circle me-2"
type="radio"
name="headerResizeMode"
value="fitToData"
t-att-checked="this.state.mode === 'fitToData'"
t-on-change="() => this.onModeChanged('fitToData')"
/>
<span>Fit to data</span>
</label>
</Section>
<Section t-if="this.errorMessages.length" class="'pt-0'">
<ValidationMessages messages="this.errorMessages" msgType="'error'"/>
</Section>
<Section>
<div class="o-sidePanelButtons">
<button
class="o-button primary"
t-att-class="{'o-disabled': this.errorMessages.length > 0}"
t-on-click="this.applyResize">
Apply
</button>
</div>
</Section>
</div>
</t>
</templates>
5 changes: 4 additions & 1 deletion src/registries/menus/col_menu_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,14 @@ colMenuRegistry
.add("hide_columns", {
...ACTION_VIEW.hideCols,
sequence: 105,
separator: true,
})
.add("unhide_columns", {
...ACTION_VIEW.unhideCols,
sequence: 106,
})
.add("resize_columns", {
...ACTION_VIEW.resizeCols,
sequence: 107,
separator: true,
})
.add("conditional_formatting", {
Expand Down
5 changes: 4 additions & 1 deletion src/registries/menus/row_menu_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ rowMenuRegistry
.add("hide_rows", {
...ACTION_VIEW.hideRows,
sequence: 85,
separator: true,
})
.add("unhide_rows", {
...ACTION_VIEW.unhideRows,
sequence: 86,
})
.add("resize_rows", {
...ACTION_VIEW.resizeRows,
sequence: 87,
separator: true,
})
.add("conditional_formatting", {
Expand Down
24 changes: 23 additions & 1 deletion src/registries/side_panel_registry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RESIZE_COLUMNS_NAME, RESIZE_ROWS_NAME } from "../actions/menu_items_actions";
import { CarouselPanel } from "../components/side_panel/carousel_panel/carousel_panel";
import { ChartPanel } from "../components/side_panel/chart/main_chart_panel/main_chart_panel";
import { ColumnStatsPanel } from "../components/side_panel/column_stats/column_stats_panel";
Expand All @@ -6,6 +7,7 @@ import { ConditionalFormatPreviewList } from "../components/side_panel/condition
import { DataValidationPanel } from "../components/side_panel/data_validation/data_validation_panel";
import { DataValidationEditor } from "../components/side_panel/data_validation/dv_editor/dv_editor";
import { FindAndReplacePanel } from "../components/side_panel/find_and_replace/find_and_replace";
import { HeaderResizePanel } from "../components/side_panel/header_resize_panel/header_resize_panel";
import { MoreFormatsPanel } from "../components/side_panel/more_formats/more_formats";
import { NamedRangesPanel } from "../components/side_panel/named_ranges_panel/named_ranges_panel";
import { PerfProfilePanel } from "../components/side_panel/perf_profile/perf_profile_panel";
Expand All @@ -21,7 +23,7 @@ import { getTableTopLeft } from "../helpers/table_helpers";
import { _t } from "../translation";
import { ConditionalFormat } from "../types/conditional_formatting";
import { Getters } from "../types/getters";
import { UID } from "../types/misc";
import { Dimension, UID } from "../types/misc";
import { PropsOf } from "../types/props_of";
import { SpreadsheetChildEnv } from "../types/spreadsheet_env";
import { Registry } from "./registry";
Expand Down Expand Up @@ -126,6 +128,26 @@ sidePanelRegistry.add("ColumnStats", {
Body: ColumnStatsPanel,
});

sidePanelRegistry.add("HeaderResizePanel", {
title: (env: SpreadsheetChildEnv, props: PropsOf<HeaderResizePanel>) =>
props.dimension === "COL" ? RESIZE_COLUMNS_NAME(env) : RESIZE_ROWS_NAME(env),
Body: HeaderResizePanel,
computeState: (getters: Getters, props: { dimension: Dimension }) => {
const sheetId = getters.getActiveSheetId();
const elements =
props.dimension === "COL"
? [...getters.getActiveCols()].sort((a, b) => a - b)
: [...getters.getActiveRows()].sort((a, b) => a - b);
if (!elements.length) {
return { isOpen: false };
}
return {
isOpen: true,
props: { ...props, sheetId, elements },
};
},
});

sidePanelRegistry.add("TableSidePanel", {
title: _t("Edit table"),
Body: TablePanel,
Expand Down
Loading