UNPKG

@odoo/o-spreadsheet

Version:
1,317 lines (1,263 loc) 240 kB
<!-- This file is generated by o-spreadsheet build tools. Do not edit it. @see https://github.com/odoo/o-spreadsheet @version 18.2.6 @date 2025-04-04T08:42:06.055Z @hash faa00e2 --> <odoo> <t t-name="o-spreadsheet-ValidationMessages"> <t t-foreach="alertBoxes" t-as="box" t-key="'box' + box_index"> <div t-att-class="divClasses" class="d-flex flex-column p-3 m-1 o-validation"> <div class="d-flex align-items-center"> <t t-if="props.msgType === 'info'" t-call="o-spreadsheet-Icon.CIRCLE_INFO"/> <t t-else="" t-call="o-spreadsheet-Icon.TRIANGLE_EXCLAMATION"/> <div class="d-flex flex-column overflow-hidden"> <span t-foreach="box" t-as="msg" t-key="msg_index" class="ps-2" t-att-class="{'text-truncate': props.singleBox }" t-esc="msg" /> </div> </div> </div> </t> </t> <t t-name="o-spreadsheet-TopBar"> <t t-set="text_color">Text Color</t> <t t-set="fill_color">Fill Color</t> <div class="o-spreadsheet-topbar o-two-columns d-flex flex-column user-select-none" t-on-click="props.onClick"> <div class="o-topbar-top d-flex justify-content-between"> <!-- Menus --> <div class="o-topbar-topleft d-flex"> <t t-foreach="menus" t-as="menu" t-key="menu_index"> <div t-if="menu.children.length !== 0" class="o-topbar-menu o-hoverable-button rounded" t-att-class="{'active': state.menuState.parentMenu and state.menuState.parentMenu.id === menu.id}" t-on-click="(ev) => this.toggleContextMenu(menu, ev)" t-on-mouseover="(ev) => this.onMenuMouseOver(menu, ev)" t-att-data-id="menu.id"> <t t-esc="getMenuName(menu)"/> </div> </t> </div> <div class="o-topbar-topright d-flex justify-content-end align-items-center"> <div t-foreach="topbarComponents" t-as="comp" t-key="comp.id" class="px-1"> <t t-component="comp.component"/> </div> </div> </div> <!-- Toolbar and Cell Content --> <div class="d-flex"> <div class="o-topbar-toolbar d-flex flex-shrink-0"> <!-- Toolbar --> <div t-if="env.model.getters.isReadonly()" class="o-readonly-toolbar d-flex align-items-center text-muted"> <span> <i class="fa fa-eye"/> Readonly Access </span> </div> <div t-else="" class="o-toolbar-tools d-flex flex-shrink-0 ms-4"> <ActionButton action="EDIT.undo" class="'o-hoverable-button'"/> <ActionButton action="EDIT.redo" class="'o-hoverable-button'"/> <PaintFormatButton class="'o-hoverable-button'"/> <ActionButton action="FORMAT.clearFormat" class="'o-hoverable-button'"/> <div class="o-divider"/> <ActionButton action="FORMAT.formatPercent" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.decraseDecimalPlaces" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.incraseDecimalPlaces" class="'o-hoverable-button'"/> <ActionButton action="formatNumberMenuItemSpec" onClick="(ev) => this.toggleToolbarContextMenu(formatNumberMenuItemSpec, ev)" hasTriangleDownIcon="true" class="'o-hoverable-button'" /> <div class="o-divider"/> <FontSizeEditor currentFontSize="currentFontSize" onFontSizeChanged.bind="this.setFontSize" class="'o-hoverable-button'" onToggle.bind="this.onClick" /> <div class="o-divider"/> <ActionButton action="FORMAT.formatBold" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatItalic" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatStrikethrough" class="'o-hoverable-button'"/> <ColorPickerWidget currentColor="state.textColor" toggleColorPicker="(ev) => this.toggleDropdownTool('textColorTool', ev)" showColorPicker="state.activeTool === 'textColorTool'" onColorPicked="(color) => this.setColor('textColor', color)" title="text_color" icon="'o-spreadsheet-Icon.TEXT_COLOR'" dropdownMaxHeight="this.props.dropdownMaxHeight" class="'o-hoverable-button o-menu-item-button'" /> <div class="o-divider"/> <ColorPickerWidget currentColor="state.fillColor" toggleColorPicker="(ev) => this.toggleDropdownTool('fillColorTool', ev)" showColorPicker="state.activeTool === 'fillColorTool'" onColorPicked="(color) => this.setColor('fillColor', color)" title="fill_color" icon="'o-spreadsheet-Icon.FILL_COLOR'" dropdownMaxHeight="this.props.dropdownMaxHeight" class="'o-hoverable-button o-menu-item-button'" /> <BorderEditorWidget class="'o-hoverable-button o-menu-item-button'" toggleBorderEditor="(ev) => this.toggleDropdownTool('borderTool', ev)" showBorderEditor="state.activeTool === 'borderTool'" dropdownMaxHeight="this.props.dropdownMaxHeight" /> <ActionButton action="EDIT.mergeCells" class="'o-hoverable-button'"/> <div class="o-divider"/> <div class="o-dropdown"> <ActionButton action="FORMAT.formatAlignmentHorizontal" hasTriangleDownIcon="true" t-on-click="(ev) => this.toggleDropdownTool('horizontalAlignTool', ev)" class="'o-hoverable-button'" /> <div class="o-dropdown-content" t-if="state.activeTool === 'horizontalAlignTool'" t-att-style="dropdownStyle" t-on-click.stop=""> <div class="o-dropdown-line"> <ActionButton action="FORMAT.formatAlignmentLeft" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatAlignmentCenter" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatAlignmentRight" class="'o-hoverable-button'"/> </div> </div> </div> <div class="o-dropdown"> <ActionButton action="FORMAT.formatAlignmentVertical" hasTriangleDownIcon="true" t-on-click="(ev) => this.toggleDropdownTool('verticalAlignTool', ev)" class="'o-hoverable-button'" /> <div class="o-dropdown-content" t-att-style="dropdownStyle" t-if="state.activeTool === 'verticalAlignTool'" t-on-click.stop=""> <div class="o-dropdown-line"> <ActionButton action="FORMAT.formatAlignmentTop" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatAlignmentMiddle" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatAlignmentBottom" class="'o-hoverable-button'"/> </div> </div> </div> <div class="o-dropdown"> <ActionButton action="FORMAT.formatWrapping" hasTriangleDownIcon="true" t-on-click="(ev) => this.toggleDropdownTool('textWrappingTool', ev)" class="'o-hoverable-button'" /> <div class="o-dropdown-content" t-att-style="dropdownStyle" t-if="state.activeTool === 'textWrappingTool'" t-on-click.stop=""> <div class="o-dropdown-line"> <ActionButton action="FORMAT.formatWrappingOverflow" class="'o-hoverable-button'" /> <ActionButton action="FORMAT.formatWrappingWrap" class="'o-hoverable-button'"/> <ActionButton action="FORMAT.formatWrappingClip" class="'o-hoverable-button'"/> </div> </div> </div> <div class="o-divider"/> <TableDropdownButton/> <ActionButton action="DATA.createRemoveFilterTool" class="'o-hoverable-button'"/> </div> </div> <TopBarComposer/> </div> <div t-if="this.fingerprints.isEnabled" class="irregularity-map d-flex align-items-center justify-content-between"> <div t-on-click="() => this.fingerprints.disable()" role="button" title="This tool analyzes spreadsheet formulas for patterns and highlights inconsistencies. Irregularities may indicate potential errors in formula structures, references, or arguments. (Click to turn off)" class="h-100 d-flex align-items-center text-info px-3"> <t t-call="o-spreadsheet-Icon.IRREGULARITY_MAP"/> Irregularity map </div> <div class="ps-3 h-100 flex-fill d-flex justify-content-between align-items-center rounded-0 alert alert-info ps-0 py-0 my-0"> This tool analyzes formulas for patterns and highlights inconsistencies. Irregularities may indicate potential errors in formula structures, references or arguments. <div class="btn btn-link align-self-end" t-on-click="() => this.fingerprints.disable()"> Turn off </div> </div> </div> </div> <Menu t-if="state.menuState.isOpen" position="state.menuState.position" menuItems="state.menuState.menuItems" onClose="() => this.closeMenus()" onMenuClicked="() => this.props.onClick()" /> </t> <div t-name="o-spreadsheet-TextInput" class="w-100"> <input t-ref="input" class="os-input w-100" type="text" t-att-class="props.class" t-att-id="props.id" t-att-placeholder="props.placeholder" t-att-value="props.value" t-on-change="save" t-on-blur="save" t-on-pointerdown="onMouseDown" t-on-pointerup="onMouseUp" t-on-keydown="onKeyDown" /> </div> <t t-name="o-spreadsheet-TableStylesPopover"> <Popover t-if="props.popoverProps" t-props="props.popoverProps"> <div class="o-table-style-popover d-flex flex-column py-3" t-ref="tableStyleList" t-on-contextmenu.prevent=""> <div class="d-flex o-notebook ps-4 mb-3"> <div t-foreach="Object.keys(categories)" t-as="category" t-key="category" class="o-notebook-tab d-flex align-items-center" t-att-class="{ 'selected': state.selectedCategory === category }" t-on-click="() => state.selectedCategory = category" t-att-data-id="category" t-esc="categories[category_value]" /> </div> <div class="d-flex flex-wrap px-4"> <t t-foreach="displayedStyles" t-as="styleId" t-key="styleId"> <TableStylePreview class="'o-table-style-popover-preview'" styleId="styleId" selected="styleId === props.selectedStyleId" tableConfig="props.tableConfig" tableStyle="env.model.getters.getTableStyle(styleId)" onClick="() => this.props.onStylePicked(styleId)" /> </t> <div t-if="state.selectedCategory === 'custom'" class="o-new-table-style o-table-style-list-item o-table-style-popover-preview d-flex justify-content-center align-items-center" t-on-click="newTableStyle"> + </div> </div> </div> </Popover> </t> <t t-name="o-spreadsheet-TableStylePreview"> <div class="o-table-style-list-item position-relative" t-att-class="{ 'selected': props.selected }" t-att-data-id="props.styleId" t-att-title="styleName" t-on-click="props.onClick" t-on-contextmenu.prevent="(ev) => this.onContextMenu(ev)"> <div t-att-class="props.class"> <canvas t-ref="canvas" class="w-100 h-100"/> </div> <div class="o-table-style-edit-button position-absolute d-none" t-if="isStyleEditable" t-on-click="this.editTableStyle" title="Edit custom table style"> <t t-call="o-spreadsheet-Icon.EDIT"/> </div> </div> <Menu t-if="menu.isOpen" menuItems="menu.menuItems" position="menu.position" onClose.bind="this.closeMenu" /> </t> <t t-name="o-spreadsheet-TableStylePicker"> <div class="o-table-style-picker d-flex flew-row justify-content-between ps-1"> <div class="d-flex flex-row overflow-hidden ps-2"> <t t-foreach="getDisplayedTableStyles()" t-as="styleId" t-key="styleId"> <TableStylePreview class="'o-table-style-picker-preview'" selected="styleId === props.table.config.styleId" tableConfig="props.table.config" tableStyle="env.model.getters.getTableStyle(styleId)" styleId="styleId" onClick="() => this.onStylePicked(styleId)" /> </t> </div> <div class="o-table-style-picker-arrow d-flex align-items-center px-1" t-on-click.stop="onArrowButtonClick"> <t t-call="o-spreadsheet-Icon.CARET_DOWN"/> </div> </div> <TableStylesPopover tableConfig="props.table.config" selectedStyleId="props.table.config.styleId" onStylePicked.bind="onStylePicked" popoverProps="state.popoverProps" closePopover.bind="closePopover" /> </t> <t t-name="o-spreadsheet-TableResizer"> <div class="o-table-resizer position-absolute" t-att-style="containerStyle" t-on-pointerdown="onMouseDown" /> </t> <t t-name="o-spreadsheet-TableDropdownButton"> <div class="o-table-widget d-flex align-item-center"> <ActionButton action="action" hasTriangleDownIcon="true" t-on-click="onClick" class="'o-hoverable-button'" /> </div> <TableStylesPopover tableConfig="tableConfig" onStylePicked.bind="onStylePicked" popoverProps="state.popoverProps" closePopover.bind="closePopover" /> </t> <t t-name="o-spreadsheet-Spreadsheet"> <div class="o-spreadsheet h-100 w-100" t-ref="spreadsheet" t-att-style="getStyle()"> <t t-if="env.isDashboard()"> <SpreadsheetDashboard/> </t> <t t-else=""> <TopBar onClick="() => this.focusGrid()" dropdownMaxHeight="gridHeight"/> <div class="o-grid-container" t-att-class="{'o-two-columns': !sidePanel.isOpen}" t-att-style="gridContainerStyle" t-on-click="this.focusGrid"> <div class="o-top-left"/> <div class="o-column-groups"> <HeaderGroupContainer layers="colLayers" dimension="'COL'"/> </div> <div class="o-row-groups"> <HeaderGroupContainer layers="rowLayers" dimension="'ROW'"/> </div> <div class="o-group-grid overflow-hidden"> <Grid exposeFocus="(focus) => this._focusGrid = focus"/> </div> </div> <SidePanel/> <BottomBar onClick="() => this.focusGrid()"/> </t> </div> </t> <t t-name="o-spreadsheet-TableStyleEditorPanel"> <div class="o-table-style-editor-panel"> <Section title.translate="Style name"> <input type="text" class="o-input" t-model="state.styleName"/> </Section> <Section class="'pt-1'" title.translate="Style color"> <RoundColorPicker currentColor="state.primaryColor" onColorPicked.bind="onColorPicked" disableNoColor="true" /> </Section> <Section class="'pt-1'" title.translate="Style template"> <div class="d-flex flex-wrap"> <t t-foreach="tableTemplates" t-as="templateName" t-key="templateName"> <TableStylePreview class="'o-table-style-edit-template-preview'" selected="templateName === state.selectedTemplateName" tableConfig="previewTableConfig" tableStyle="computeTableStyle(templateName)" onClick="() => this.onTemplatePicked(templateName)" /> </t> </div> </Section> <Section> <div class="o-sidePanelButtons"> <button t-if="props.styleId" t-on-click="onDelete" class="o-delete o-button-danger o-button"> Delete </button> <button t-on-click="onCancel" class="o-cancel o-button">Cancel</button> <button t-on-click="onConfirm" class="o-confirm o-button primary">Confirm</button> </div> </Section> </div> </t> <t t-name="o-spreadsheet-TablePanel"> <div class="o-table-panel"> <Section title.translate="Style options"> <div class="d-flex flex-row"> <div class="w-50"> <div class="d-flex align-items-center"> <Checkbox label="getCheckboxLabel('headerRow')" name="'headerRow'" value="tableConfig.numberOfHeaders > 0" onChange.bind="this.updateHasHeaders" /> <input t-if="tableConfig.numberOfHeaders > 0" t-att-value="tableConfig.numberOfHeaders" type="number" class="o-table-n-of-headers ms-2 o-input" t-on-change="onChangeNumberOfHeaders" /> </div> <Checkbox label="getCheckboxLabel('totalRow')" name="'totalRow'" value="tableConfig.totalRow" onChange="(val) => this.updateTableConfig('totalRow', val)" /> <Checkbox label="getCheckboxLabel('bandedRows')" name="'bandedRows'" value="tableConfig.bandedRows" onChange="(val) => this.updateTableConfig('bandedRows', val)" /> <Checkbox label="getCheckboxLabel('hasFilters')" name="'hasFilters'" value="tableConfig.hasFilters" title="hasFilterCheckboxTooltip" disabled="!this.canHaveFilters" onChange.bind="this.updateHasFilters" /> </div> <div> <Checkbox label="getCheckboxLabel('firstColumn')" name="'firstColumn'" value="tableConfig.firstColumn" onChange="(val) => this.updateTableConfig('firstColumn', val)" /> <Checkbox label="getCheckboxLabel('lastColumn')" name="'lastColumn'" value="tableConfig.lastColumn" onChange="(val) => this.updateTableConfig('lastColumn', val)" /> <Checkbox label="getCheckboxLabel('bandedColumns')" name="'bandedColumns'" value="tableConfig.bandedColumns" onChange="(val) => this.updateTableConfig('bandedColumns', val)" /> </div> </div> </Section> <Section> <TableStylePicker table="props.table"/> </Section> <Section title.translate="Data range"> <SelectionInput t-key="props.table.type" ranges="[this.state.tableXc]" hasSingleRange="true" isInvalid="this.state.tableZoneErrors.length !== 0" onSelectionChanged="(ranges) => this.onRangeChanged(ranges)" onSelectionConfirmed.bind="this.onRangeConfirmed" /> </Section> <Section class="'pt-0'"> <Checkbox label="getCheckboxLabel('automaticAutofill')" name="'automaticAutofill'" value="tableConfig.automaticAutofill" onChange="(val) => this.updateTableConfig('automaticAutofill', val)" className="'mb-1'" /> <div class="d-flex flex-row align-items-center"> <Checkbox label="getCheckboxLabel('isDynamic')" name="'isDynamic'" value="props.table.type === 'dynamic'" onChange.bind="this.updateTableIsDynamic" disabled="!this.canBeDynamic" /> <div class="o-info-icon d-flex flex-row align-items-center text-muted ms-1" t-att-title="dynamicTableTooltip"> <t t-call="o-spreadsheet-Icon.CIRCLE_INFO"/> </div> </div> </Section> <Section> <div class="o-sidePanelButtons"> <button t-on-click="deleteTable" class="o-table-delete o-button o-button-danger"> Delete table </button> </div> </Section> <Section t-if="errorMessages.length"> <ValidationMessages messages="errorMessages" msgType="'error'"/> </Section> </div> </t> <t t-name="o-spreadsheet-SplitIntoColumnsPanel"> <div class="o-split-to-cols-panel"> <Section title.translate="Separator"> <select class="o-input mb-3" t-on-change="(ev) => this.onSeparatorChange(ev.target.value)"> <option t-foreach="separators" t-as="separator" t-key="separator.value" t-att-value="separator.value" t-esc="separator.name" t-att-selected="state.separatorValue === separator.value" /> </select> <input class="o-input mb-3" type="text" t-if="state.separatorValue === 'custom'" t-att-value="state.customSeparator" t-on-input="updateCustomSeparator" placeholder="Add any characters or symbol" /> <t t-set="addColumnsLabel">Add new columns to avoid overwriting cells</t> <Checkbox value="state.addNewColumns" label="addColumnsLabel" onChange.bind="updateAddNewColumnsCheckbox" /> </Section> <Section> <div class="o-sidePanelButtons"> <button class="o-button primary" t-att-class="{'o-disabled': isConfirmDisabled}" t-on-click="confirm"> Confirm </button> </div> </Section> <Section t-if="errorMessages.length || warningMessages.length" class="'pb-0 pt-2'"> <ValidationMessages messages="errorMessages" msgType="'error'"/> <ValidationMessages messages="warningMessages" msgType="'warning'"/> </Section> </div> </t> <t t-name="o-spreadsheet-SidePanel"> <div class="o-sidePanel" t-if="sidePanelStore.isOpen"> <div class="o-sidePanelHeader"> <div class="o-sidePanelTitle o-fw-bold" t-esc="getTitle()"/> <div class="o-sidePanelClose" t-on-click="close">✕</div> </div> <div class="o-sidePanelBody-container d-flex flex-grow-1 "> <div class="o-sidePanel-handle-container"> <div class="o-sidePanel-handle" t-on-pointerdown="startHandleDrag" t-on-dblclick="sidePanelStore.resetPanelSize"> <t t-call="o-spreadsheet-Icon.THIN_DRAG_HANDLE"/> </div> </div> <div class="o-sidePanelBody"> <t t-component="panel.Body" t-props="sidePanelStore.panelProps" onCloseSidePanel.bind="close" t-key="'Body_' + sidePanelStore.componentTag + sidePanelStore.panelKey" /> </div> <div class="o-sidePanelFooter" t-if="panel?.Footer"> <t t-component="panel.Footer" t-props="sidePanelStore.panelProps" t-key="'Footer_' + sidePanelStore.componentTag" /> </div> </div> </div> </t> <t t-name="o-spreadsheet-SettingsPanel"> <div class="o-settings-panel"> <Section title.translate="Locale"> <select class="o-input" t-on-change="(ev) => this.onLocaleChange(ev.target.value)"> <option t-foreach="supportedLocales" t-as="locale" t-key="locale.code" t-att-value="locale.code" t-esc="locale.name" t-att-selected="currentLocale.code === locale.code" /> </select> <div class="o-locale-preview mt-4 p-3 rounded"> <div> <span class="o-fw-bold me-1">Number:</span> <span t-esc="numberFormatPreview"/> </div> <div> <span class="o-fw-bold me-1">Date:</span> <span t-esc="dateFormatPreview"/> </div> <div> <span class="o-fw-bold me-1">Date time:</span> <span t-esc="dateTimeFormatPreview"/> </div> <div> <span class="o-fw-bold me-1">First day of week:</span> <span t-esc="firstDayOfWeek"/> </div> </div> </Section> <Section class="'pt-0'"> <t t-set="message">This setting affects all users.</t> <ValidationMessages messages="[message]" msgType="'info'"/> </Section> </div> </t> <t t-name="o-spreadsheet-SelectMenu"> <select t-att-class="props.class" t-ref="select" t-on-pointerdown.stop.prevent="" t-on-click="onClick"> <option selected="true" t-esc="props.selectedValue"/> </select> <Menu t-if="state.isMenuOpen" menuItems="props.menuItems" position="menuPosition" onClose.bind="onMenuClosed" menuId="menuId" /> </t> <t t-name="o-spreadsheet-RemoveDuplicatesPanel"> <div class="o-remove-duplicates"> <Section> <ValidationMessages messages="[selectionStatisticalInformation]" msgType="'info'"/> </Section> <Section class="'pt-0'"> <t t-set="dataHasHeaderLabel">Data has header row</t> <Checkbox name="'dataHasHeader'" value="state.hasHeader" label="dataHasHeaderLabel" onChange.bind="toggleHasHeader" /> </Section> <Section class="'pt-0'" title.translate="Columns to analyze"> <div class="o-checkbox-selection overflow-auto"> <t t-set="selectAllLabel">Select all</t> <Checkbox value="isEveryColumnSelected" label="selectAllLabel" onChange.bind="toggleAllColumns" /> <t t-foreach="Object.keys(state.columns)" t-as="colIndex" t-key="colIndex"> <Checkbox value="state.columns[colIndex]" label="getColLabel(colIndex)" onChange="() => this.toggleColumn(colIndex)" /> </t> </div> </Section> <Section> <div class="o-sidePanelButtons"> <button class="o-button primary" t-att-class="{'o-disabled': !canConfirm}" t-on-click="onRemoveDuplicates"> Remove duplicates </button> </div> </Section> <Section t-if="errorMessages.length"> <ValidationMessages messages="errorMessages" msgType="'error'"/> </Section> </div> </t> <t t-name="o-spreadsheet-PivotTitleSection"> <Section> <t t-set-slot="title"> <div class="d-flex flex-row justify-content-between align-items-center"> Name <CogWheelMenu items="cogWheelMenuItems"/> </div> </t> <TextInput class="'os-pivot-title'" value="name" onChange.bind="onNameChanged"/> </Section> </t> <t t-name="o-spreadsheet-PivotSidePanel"> <t t-component="sidePanelEditor" t-props="props"/> </t> <t t-name="o-spreadsheet-PivotSpreadsheetSidePanel"> <t t-set="isReadonly" t-value="env.model.getters.isReadonly()"/> <div class="d-flex flex-column h-100 justify-content-between overflow-hidden" t-att="isReadonly ? ['inert', 1] : []" t-att-class="{ 'pe-none': isReadonly, 'opacity-50': isReadonly }"> <div class="h-100 position-relative overflow-x-hidden overflow-y-auto" t-ref="pivotSidePanel"> <PivotTitleSection pivotId="props.pivotId" flipAxis.bind="flipAxis"/> <Section title.translate="Range"> <SelectionInput ranges="ranges" required="true" isInvalid="shouldDisplayInvalidRangeError" hasSingleRange="true" onSelectionChanged="(ranges) => this.onSelectionChanged(ranges)" onSelectionConfirmed="() => this.onSelectionConfirmed()" /> <span class="text-danger sp_range_error_message" t-if="shouldDisplayInvalidRangeError" t-esc="pivot.invalidRangeMessage" /> </Section> <PivotLayoutConfigurator t-if="!pivot.isInvalidRange" unusedGroupableFields="store.unusedGroupableFields" measureFields="store.measureFields" unusedGranularities="store.unusedGranularities" dateGranularities="store.dateGranularities" datetimeGranularities="store.datetimeGranularities" definition="definition" onDimensionsUpdated.bind="onDimensionsUpdated" getScrollableContainerEl.bind="getScrollableContainerEl" pivotId="props.pivotId" /> </div> <PivotDeferUpdate deferUpdate="store.updatesAreDeferred" toggleDeferUpdate="(value) => store.deferUpdates(value)" isDirty="store.isDirty" discard="store.discardPendingUpdate" apply="store.applyUpdate" /> </div> </t> <t t-name="o-spreadsheet-PivotMeasureDisplayPanel"> <Section title.translate="Show measure as:"> <select class="o-pivot-measure-display-type o-input" t-on-change="(ev) => this.store.updateMeasureDisplayType(ev.target.value)"> <t t-foreach="measureDisplayTypeLabels" t-as="measureType" t-key="measureType"> <option t-att-value="measureType" t-att-selected="measureType === store.measureDisplay.type" t-esc="measureType_value" /> </t> </select> <div class="o-pivot-measure-display-description mt-3 ps-3" t-esc="measureDisplayDescription[store.measureDisplay.type]" /> </Section> <Section t-if="store.doesDisplayNeedsField" title.translate="Base field:"> <div class="o-pivot-measure-display-field w-100 py-1 px-3"> <t t-if="store.fields.length"> <RadioSelection choices="fieldChoices" selectedValue="store.measureDisplay.fieldNameWithGranularity" name="'baseField'" onChange.bind="(val) => store.updateMeasureDisplayField(val)" direction="'vertical'" /> </t> <t t-else=""> <div class="text-muted text-center my-3">No active dimension in the pivot</div> </t> </div> </Section> <t t-set="values" t-value="store.values"/> <Section t-if="store.doesDisplayNeedsValue and values.length" title.translate="Base item:"> <div class="o-pivot-measure-display-value w-100 py-1 px-3"> <RadioSelection choices="values" selectedValue="store.measureDisplay.value" name="'baseValue'" onChange.bind="(val) => store.updateMeasureDisplayValue(val)" direction="'vertical'" /> </div> </Section> <Section> <div class="o-sidePanelButtons"> <button t-on-click="onCancel" class="o-pivot-measure-cancel o-button">Cancel</button> <button t-on-click="onSave" class="o-pivot-measure-save o-button primary">Save</button> </div> </Section> </t> <t t-name="o-spreadsheet-PivotLayoutConfigurator"> <div class="pivot-dimensions o-section" t-ref="pivot-dimensions"> <div class="o-fw-bold py-1 d-flex flex-row justify-content-between align-items-center o-section-title"> Columns <AddDimensionButton onFieldPicked.bind="addColumnDimension" fields="props.unusedGroupableFields" /> </div> <t t-foreach="props.definition.columns" t-as="col" t-key="col.nameWithGranularity"> <div t-on-pointerdown="(ev) => this.startDragAndDrop(col, ev)" t-att-style="dragAndDrop.itemsStyle[col.nameWithGranularity]" class="pt-1"> <PivotDimension dimension="col" onRemoved.bind="removeDimension"> <PivotDimensionGranularity t-if="isDateOrDatetimeField(col)" dimension="col" onUpdated.bind="this.updateGranularity" availableGranularities="props.unusedGranularities[col.fieldName]" allGranularities="getGranularitiesFor(col)" /> <PivotDimensionOrder dimension="col" onUpdated.bind="this.updateOrder"/> </PivotDimension> </div> </t> <div class="o-fw-bold pt-4 pb-1 d-flex flex-row justify-content-between align-items-center o-section-title" t-att-style="dragAndDrop.itemsStyle['__rows_title__']"> Rows <AddDimensionButton onFieldPicked.bind="addRowDimension" fields="props.unusedGroupableFields" /> </div> <t t-foreach="props.definition.rows" t-as="row" t-key="row.nameWithGranularity"> <div t-on-pointerdown="(ev) => this.startDragAndDrop(row, ev)" t-att-style="dragAndDrop.itemsStyle[row.nameWithGranularity]" class="pt-1"> <PivotDimension dimension="row" onRemoved.bind="removeDimension"> <PivotDimensionGranularity t-if="isDateOrDatetimeField(row)" dimension="row" onUpdated.bind="this.updateGranularity" availableGranularities="props.unusedGranularities[row.fieldName]" allGranularities="getGranularitiesFor(row)" /> <PivotDimensionOrder dimension="row" onUpdated.bind="this.updateOrder"/> </PivotDimension> </div> </t> <div class="o-fw-bold pt-4 pb-1 d-flex flex-row justify-content-between align-items-center o-section-title o-pivot-measure"> Measures <AddDimensionButton onFieldPicked.bind="addMeasureDimension" fields="props.measureFields"> <div t-on-click="addCalculatedMeasure" class="p-2 bg-white border-top d-flex align-items-center sticky-bottom add-calculated-measure"> <i class="pe-1"> <t t-call="o-spreadsheet-Icon.FORMULA"/> </i> Add calculated measure </div> </AddDimensionButton> </div> <t t-foreach="props.definition.measures" t-as="measure" t-key="measure.id"> <div t-on-pointerdown="(ev) => this.startDragAndDropMeasures(measure, ev)" t-att-style="dragAndDrop.itemsStyle[measure.id]" t-att-class="measure.isHidden ? 'opacity-50' : ''" class="pt-1 pivot-measure"> <PivotMeasureEditor pivotId="props.pivotId" definition="props.definition" measure="measure" aggregators="AGGREGATORS" onRemoved="() => this.removeMeasureDimension(measure)" onMeasureUpdated="(newMeasure) => this.updateMeasure(measure, newMeasure)" generateMeasureId.bind="getMeasureId" /> </div> </t> </div> <PivotSortSection definition="props.definition" pivotId="props.pivotId"/> </t> <t t-name="o-spreadsheet-PivotSortSection"> <Section t-if="hasValidSort" class="'o-pivot-sort'"> <t t-set-slot="title">Sorting</t> <div t-esc="sortDescription" class="pb-2"/> <div class="d-flex flex-column gap-2"> <t t-foreach="sortValuesAndFields" t-as="valueAndField" t-key="valueAndField_index"> <div class="o-sort-card d-flex gap-1 px-2"> <t t-if="valueAndField.field"> <span class="fw-bolder" t-esc="valueAndField.field"/> = </t> <span class="fw-bolder o-sort-value" t-esc="valueAndField.value"/> </div> </t> </div> </Section> </t> <t t-name="o-spreadsheet-PivotMeasureEditor"> <t t-set="measure" t-value="props.measure"/> <PivotDimension dimension="measure" onRemoved="props.onRemoved" onNameUpdated.bind="updateName"> <t t-set-slot="upper-right-icons"> <t t-if="measure.isHidden" t-set="hideTitle">Show</t> <t t-else="" t-set="hideTitle">Hide</t> <i t-att-class="measure.isHidden ? 'fa fa-eye-slash': 'fa fa-eye'" t-att-title="hideTitle" class="o-button-icon pe-1 ps-2" t-on-click="toggleMeasureVisibility" /> <i class="o-button-icon pe-1 ps-2 fa fa-cog" title="Show values as" t-on-click="openShowValuesAs" /> </t> <div t-if="measure.computedBy" class="d-flex flex-row small"> <div class="d-flex flex-column py-2 px-2 w-100" t-on-pointerdown.stop=""> <StandaloneComposer onConfirm.bind="updateMeasureFormula" composerContent="measure.computedBy.formula" defaultRangeSheetId="measure.computedBy.sheetId" contextualAutocomplete="getMeasureAutocomplete()" getContextualColoredSymbolToken.bind="getColoredSymbolToken" /> </div> </div> <div class="d-flex flex-row"> <div class="d-flex py-1 px-2 w-100 small"> <div class="pivot-dim-operator-label">Aggregated by</div> <select class="o-input flex-grow-1" t-on-change="(ev) => this.updateAggregator(ev.target.value)"> <option t-foreach="Object.keys(props.aggregators[measure.type])" t-as="agg" t-key="agg" t-att-value="agg" t-att-selected="agg === measure.aggregator" t-esc="props.aggregators[measure.type][agg]" /> </select> </div> </div> </PivotDimension> </t> <t t-name="o-spreadsheet-PivotDimensionOrder"> <div class="d-flex"> <div class="d-flex py-1 px-2 w-100 small"> <div class="pivot-dim-operator-label">Order by</div> <select class="o-input flex-grow-1" t-on-change="(ev) => props.onUpdated(props.dimension, ev.target.value)"> <option value="asc" t-att-selected="props.dimension.order === 'asc'">Ascending</option> <option value="desc" t-att-selected="props.dimension.order === 'desc'">Descending</option> <option t-if="props.dimension.type !== 'date'" value="" t-att-selected="props.dimension.order === undefined"> Unsorted </option> </select> </div> </div> </t> <t t-name="o-spreadsheet-PivotDimensionGranularity"> <div class="d-flex flex-row"> <div class="d-flex flex-row py-1 px-2 w-100 small"> <t t-set="granularity" t-value="props.dimension.granularity"/> <div class="pivot-dim-operator-label">Granularity</div> <select class="o-input flex-grow-1" t-on-change="(ev) => props.onUpdated(props.dimension, ev.target.value)"> <option t-foreach="props.allGranularities" t-as="granularity" t-key="granularity" t-if="props.availableGranularities.has(granularity) || granularity === props.dimension.granularity" t-att-value="granularity" t-esc="periods[granularity]" t-att-selected="granularity === props.dimension.granularity or (granularity === 'month' and !props.dimension.granularity)" /> </select> </div> </div> </t> <t t-name="o-spreadsheet-PivotDimension"> <div class="py-1 px-2 d-flex flex-column shadow-sm pivot-dimension" t-att-class="{'pivot-dimension-invalid': !props.dimension.isValid}"> <div class="d-flex flex-row justify-content-between align-items-center"> <div class="d-flex align-items-center overflow-hidden text-nowrap"> <span class="text-danger me-1" t-if="!props.dimension.isValid"> <t t-call="o-spreadsheet-Icon.TRIANGLE_EXCLAMATION"/> </span> <TextInput t-if="props.onNameUpdated" value="props.dimension.displayName" onChange.bind="updateName" class="'o-fw-bold'" /> <span t-else="1" class="o-fw-bold" t-esc="props.dimension.displayName"/> </div> <div class="d-flex flex-rows" t-on-pointerdown.stop=""> <t t-slot="upper-right-icons"/> <i class="o-button-icon fa fa-trash pe-1 ps-2" t-if="props.onRemoved" t-on-click="() => props.onRemoved(props.dimension)" /> </div> </div> <t t-slot="default"/> </div> </t> <t t-name="o-spreadsheet-AddDimensionButton"> <button class="add-dimension o-button" t-on-click="togglePopover" t-ref="button">Add</button> <Popover t-if="popover.isOpen" t-props="popoverProps"> <div class="p-2 bg-white border-bottom d-flex sticky-top align-items-baseline pivot-dimension-search"> <i class="pe-1 pivot-dimension-search-field-icon text-muted"> <t t-call="o-spreadsheet-Icon.SEARCH"/> </i> <input t-on-input="(ev) => this.updateSearch(ev.target.value)" t-on-keydown="onKeyDown" class="border-0 w-100 pivot-dimension-search-field" t-ref="autofocus" /> </div> <TextValueProvider proposals="autoComplete.provider.proposals" selectedIndex="autoComplete.selectedIndex" onValueSelected="autoComplete.provider.selectProposal" onValueHovered="() => {}" /> <t t-slot="default" t-on-click="togglePopover"/> </Popover> </t> <t t-name="o-spreadsheet-PivotDeferUpdate"> <Section class="'align-items-center border-top d-flex flex-row justify-content-between py-1 pivot-defer-update'"> <Checkbox label="deferUpdatesLabel" title="deferUpdatesTooltip" value="props.deferUpdate" onChange="(value) => props.toggleDeferUpdate(value)" /> <div t-if="props.isDirty" class="d-flex align-items-center"> <i class="o-button-icon pe-0 fa fa-undo" title="Discard all changes" t-on-click="() => props.discard()" /> <span class="o-button-link sp_apply_update small ps-2" title="Apply all changes" t-on-click="() => props.apply()"> Update </span> </div> </Section> </t> <t t-name="o-spreadsheet-MoreFormatsPanel"> <div class="o-more-formats-panel"> <div t-foreach="dateFormatsActions" t-as="action" t-key="action.name(env)" t-att-data-name="action.name(env)" t-on-click="() => action.execute(env)" class="w-100 d-flex align-items-center border-bottom format-preview"> <span class="ms-3 check-icon"> <t t-if="action.isActive(env)" t-call="o-spreadsheet-Icon.CHECK"/> </span> <span t-out="action.description(env)"/> </div> </div> </t> <t t-name="o-spreadsheet-FindAndReplacePanel"> <div class="o-find-and-replace"> <Section title.translate="Search"> <div class="o-input-search-container"> <input type="text" t-ref="searchInput" class="o-input o-input-with-count o-search" t-on-input="onSearchInput" t-on-focus="onFocusSearch" t-on-keydown="onKeydownSearch" placeholder="e.g. 'search me'" /> <div class="o-input-count" t-if="hasSearchResult"> <t t-esc="store.selectedMatchIndex+1"/> / <t t-esc="store.searchMatches.length"/> </div> <div t-elif="!this.pendingSearch and store.toSearch !== ''" class="o-input-count"> 0 / 0 </div> <div class="d-flex flex-row o-result-buttons align-items-center" t-if="hasSearchResult"> <button t-on-click="() => store.selectPreviousMatch()" class="o-button ms-2 d-flex justify-content-center align-items-center"> <t t-call="o-spreadsheet-Icon.ARROW_UP"/> </button> <button t-on-click="() => store.selectNextMatch()" class="o-button ms-1 d-flex justify-content-center align-items-center"> <t t-call="o-spreadsheet-Icon.ARROW_DOWN"/> </button> </div> </div> <select class="o-input o-type-range-selector mt-3 mb-3" t-on-change="changeSearchScope" t-att-value="searchOptions.searchScope"> <option value="allSheets">All sheets</option> <option value="activeSheet">Current sheet</option> <option value="specificRange">Specific range</option> </select> <div t-if="searchOptions.searchScope === 'specificRange'"> <SelectionInput ranges="[this.state.dataRange]" onSelectionChanged="(ranges) => this.onSearchRangeChanged(ranges)" onSelectionConfirmed.bind="updateDataRange" hasSingleRange="true" required="true" /> </div> <div> <t t-set="matchCaseLabel">Match case</t> <Checkbox value="searchOptions.matchCase" label="matchCaseLabel" onChange.bind="searchMatchCase" className="'mb-1'" /> <t t-set="exactMatchLabel">Match entire cell content</t> <Checkbox value="searchOptions.exactMatch" label="exactMatchLabel" onChange.bind="searchExactMatch" className="'mb-1'" /> <t t-set="searchFormulasLabel">Search in formulas</t> <Checkbox value="searchOptions.searchFormulas" label="searchFormulasLabel" onChange.bind="searchFormulas" /> </div> <div class="o-matches-count mt-4" t-if="searchInfo.length"> <ValidationMessages msgType="'info'" messages="searchInfo" singleBox="true"/> </div> </Section> <Section class="'pt-0'" t-if="!env.model.getters.isReadonly()" title.translate="Replace"> <div class="o-input-search-container"> <input type="text" class="o-input o-input-without-count o-replace" t-on-keydown="onKeydownReplace" t-model="store.toReplace" placeholder="e.g. 'replace me'" /> </div> </Section> <Section> <div class="o-sidePanelButtons" t-if="!env.model.getters.isReadonly()"> <button t-att-disabled="store.selectedMatchIndex === null" t-on-click="() => store.replace()" class="o-button o-replace"> Replace </button> <button t-att-disabled="store.selectedMatchIndex === null" t-on-click="() => store.replaceAll()" class="o-button o-replace-all"> Replace all </button> </div> </Section> </div> </t> <t t-name="o-spreadsheet-DataValidationPanel"> <div class="o-data-validation"> <t t-if="state.mode === 'list'"> <div class="o-dv-preview-list"> <t t-foreach="validationRules" t-as="rule" t-key="rule.id"> <DataValidationPreview rule="localizeDVRule(rule)" onClick="() => this.onPreviewClick(rule.id)" /> </t> </div> <div class="o-dv-add o-button-link p-4 float-end" t-on-click="addDataValidationRule"> + Add another rule </div> </t> <t t-else=""> <DataValidationEditor rule="localizeDVRule(state.activeRule)" onExit.bind="onExitEditMode"/> </t> </div> </t> <t t-name="o-spreadsheet-DataValidationPreview"> <div class="o-dv-preview p-3" t-on-click="props.onClick" t-ref="dvPreview"> <div class="d-flex justify-content-between"> <div class="o-dv-container d-flex flex-column"> <div class="o-dv-preview-description o-fw-bold text-truncate" t-esc="descriptionString"/> <div class="o-dv-preview-ranges text-truncate" t-esc="rangesString"/> </div> <div class="o-dv-preview-delete d-flex align-items-center o-button-icon px-3" t-on-click.stop="deleteDataValidation"> <t t-call="o-spreadsheet-Icon.TRASH_FILLED"/> </div> </div> </div> </t> <t t-name="o-spreadsheet-DataValidationEditor"> <div class="o-dv-form w-100 h-100"> <Section class="'o-dv-range'" title.translate="Apply to range"> <SelectionInput ranges="state.rule.ranges" onSelectionChanged="(ranges) => this.onRangesChanged(ranges)" required="true" /> </Section> <Section class="'pt-0'"> <div class="o-subsection o-dv-settings"> <div class="o-section-title">Criteria</div> <SelectMenu class="'o-dv-type o-input mb-2'" menuItems="dvCriterionMenuItems" selectedValue="selectedCriterionName" /> <t t-if="criterionComponent"