UNPKG

@odoo/o-spreadsheet

Version:
1,363 lines (1,311 loc) 281 kB
<!-- This file is generated by o-spreadsheet build tools. Do not edit it. @see https://github.com/odoo/o-spreadsheet @version 19.0.11 @date 2025-11-24T07:47:37.683Z @hash f5bdbcc --> <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"> <div class="o-spreadsheet-topbar d-flex flex-column user-select-none" t-on-click="props.onClick"> <div t-if="!env.isSmall" 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 text-nowrap 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 o-topbar-responsive" t-att-class="{'o-topbar-responsive': !env.model.getters.isReadonly()}" t-ref="toolBarContainer"> <div class="o-topbar-toolbar d-flex" t-att-class="{'flex-shrink-0': env.model.getters.isReadonly()}"> <!-- 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 ms-4 flex-grow-1" t-ref="toolBar"> <div class="d-flex tool-container" t-foreach="toolsCategories" t-as="category" t-key="category" t-att-id="category"> <t t-foreach="toolbarMenuRegistry.getEntries(category)" t-as="toolbarAction" t-key="toolbarAction_index"> <t t-component="toolbarAction.component" t-props="toolbarAction.props"/> </t> <div t-if="showDivider(category_index)" class="o-topbar-divider"/> </div> <div t-ref="moreToolsContainer" class="d-flex align-items-center flex-grow-1 me-auto more-tools-container"> <span class="o-toolbar-button o-menu-item-button o-hoverable-button me-2 px-1 py-2 more-tools" t-ref="moreToolsButton" t-on-click.stop="toggleMoreTools"> <t t-call="o-spreadsheet-Icon.SHORT_THIN_DRAG_HANDLE"/> </span> </div> </div> </div> <TopBarComposer t-if="!env.isSmall"/> </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 rounded-0 alert alert-info ps-0 py-0 my-0"> <span class="d-flex align-items-center"> This tool analyzes formulas for patterns and highlights inconsistencies. Irregularities may indicate potential errors in formula structures, references or arguments. </span> <div class="ps-3 btn btn-link flex-shrink-0" t-on-click="() => this.fingerprints.disable()"> Turn off </div> </div> </div> </div> <MenuPopover t-if="state.menuState.isOpen" anchorRect="state.menuState.anchorRect" menuItems="state.menuState.menuItems" onClose="() => this.closeMenus()" onMenuClicked="() => this.props.onClick()" popoverPositioning="'bottom-left'" /> <Popover t-if="state.toolsPopoverState.isOpen" t-props="toolsPopoverProps"> <div class="d-flex px-2 py-1 flex-wrap align-items-center" style="background-color:white;"> <t t-foreach="state.invisibleToolsCategories" t-as="category" t-key="category" t-att-id="category"> <t t-foreach="toolbarMenuRegistry.getEntries(category)" t-as="toolbarAction" t-key="toolbarAction_index"> <t t-component="toolbarAction.component" t-props="toolbarAction.props"/> </t> <div t-if="category_index &lt; state.invisibleToolsCategories.length-1" class="o-topbar-divider" /> </t> </div> </Popover> </t> <div t-name="o-spreadsheet-NumberFormatsTool" t-ref="buttonRef" t-on-click.stop=""> <ActionButton action="formatNumberMenuItemSpec" hasTriangleDownIcon="true" onClick.bind="toggleMenu" class="props.class" /> <MenuPopover t-if="isActive" anchorRect="state.anchorRect" menuItems="state.menuItems" onClose="() => {}" popoverPositioning="'bottom-left'" /> </div> <t t-name="o-spreadsheet-TopBarFontSizeEditor"> <FontSizeEditor currentFontSize="currentFontSize" onFontSizeChanged.bind="this.setFontSize" class="props.class" onToggle.bind="this.onToggle" onFocusInput.bind="this.onFocusInput" /> </t> <div t-name="o-spreadsheet-DropdownAction" class="o-dropdown" t-ref="actionRef" t-on-click.stop=""> <ActionButton action="props.parentAction" hasTriangleDownIcon="true" onClick.bind="toggleDropdown" class="props.class" /> <Popover t-if="isActive" t-props="popoverProps"> <div class="o-dropdown-content p-1" t-if="isActive" t-on-click.stop=""> <div class="o-dropdown-line"> <t t-foreach="props.childActions" t-as="action" t-key="action_index"> <ActionButton action="action" class="props.childClass"/> </t> </div> </div> </Popover> </div> <t t-name="o-spreadsheet-ColorEditor"> <div class="d-flex align-items-center"> <ColorPickerWidget currentColor="currentColor" toggleColorPicker.bind="onClick" showColorPicker="isMenuOpen" onColorPicked="(color) => this.setColor(color)" title="props.title" icon="props.icon" class="props.class" /> </div> </t> <div t-name="o-spreadsheet-TextInput" class="w-100"> <input t-ref="input" class="os-input w-100" type="text" t-att-class="inputClass" 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> <MenuPopover t-if="menu.isOpen" menuItems="menu.menuItems" anchorRect="menu.anchorRect" 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" t-att-class="props.class"> <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-att-class="{'o-spreadsheet-mobile': env.isSmall}" t-ref="spreadsheet" t-att-style="getStyle()"> <t t-if="env.isDashboard()"> <SpreadsheetDashboard getGridSize.bind="getGridSize"/> <FullScreenFigure/> </t> <t t-else=""> <div class="o-spreadsheet-topbar-wrapper o-two-columns"> <TopBar onClick="() => this.focusGrid()" dropdownMaxHeight="gridHeight"/> </div> <div class="o-grid-container" t-att-class="{'o-two-columns': !sidePanel.isMainPanelOpen}" 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" getGridSize.bind="getGridSize"/> </div> </div> <SidePanels/> <div class="o-spreadsheet-bottombar-wrapper o-two-columns overflow-hidden"> <SmallBottomBar t-if="env.isSmall" onClick="() => this.focusGrid()"/> <BottomBar t-else="" onClick="() => this.focusGrid()"/> </div> </t> </div> </t> <t t-name="o-spreadsheet-SmallBottomBar"> <div class="o-spreadsheet-small-bottom-bar o-two-columns d-flex flex-column overflow-hidden"> <t t-if="menuState.isOpen"> <RibbonMenu onClose="() => this.menuState.isOpen=false"/> </t> <t t-else=""> <div class="o-small-composer px-2 py-2 position-relative"> <div class="w-100" t-ref="bottombarComposer"> <Composer t-props="composerProps"/> <span t-if="showFxIcon" class="position-absolute top-50 translate-middle-y ps-2 pe-none"> <t t-call="o-spreadsheet-Icon.FX_SVG"/> </span> </div> <span class="align-items-center d-flex justify-content-center o-selection-button" title="confirm edition" t-if="this.focus !== 'inactive'" t-on-click="() => this.composerStore.stopEdition()"> <span class="d-flex"> <t t-call="o-spreadsheet-Icon.CHECK"/> </span> </span> </div> <div class="d-flex flex-row mb-1" t-if="this.focus !== 'inactive'"> <div t-foreach="symbols" t-as="symbol" t-key="symbol_index" class="o-spreadsheet-editor-symbol w-100 d-flex justify-content-center align-items-center mx-1" t-esc="symbol" tabindex="-1" t-att-title="symbol" t-on-click="() => this.insertSymbol(symbol)" composerFocusableElement="true" /> </div> <div class="d-flex flex-fill align-items-center bottom-bar-menu"> <Ripple> <div class="py-1 px-1 mx-2 ribbon-toggler" t-on-click="toggleRibbon"> <i class="o-icon fa fa-cog"/> </div> </Ripple> <BottomBar onClick="props.onClick"/> </div> </t> </div> </t> <div t-name="o-spreadsheet-RibbonMenu"> <div class="o-ribbon-menu d-flex flex-column" t-ref="menu"> <div class="o-ribbon-title d-flex py-2 fw-bold"> <div class="o-previous-button px-3 py-1 mx-2 rounded" t-on-click="onClickBack" t-att-title="backTitle"> <i class="fa fa-angle-left"/> </div> <span class="d-flex align-items-center" t-esc="state.title"/> </div> <div class="o-ribbon-menu-wrapper overflow-auto" t-ref="container" t-on-scroll="updateShadows"> <Menu t-props="menuProps"/> </div> </div> </div> <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-SidePanels" t-if="sidePanelStore.isMainPanelOpen"> <div class="o-sidePanels d-flex overflow-hidden"> <t t-foreach="panelList" t-as="panel" t-key="panel.key"> <div t-att-style="panel.style"> <SidePanel t-key="panel.key" t-props="panel.props"/> </div> </t> </div> </t> <t t-name="o-spreadsheet-SidePanel"> <t t-if="props.isCollapsed" t-call="o-spreadsheet-SidePanelCollapsed"/> <t t-else="" t-call="o-spreadsheet-SidePanelExtended"/> </t> <t t-name="o-spreadsheet-SidePanelExtended"> <div class="o-sidePanel h-100"> <div class="o-sidePanelHeader d-flex align-items-center justify-content-between"> <div t-if="props.onToggleCollapsePanel" class="o-collapse-panel o-sidePanelAction rounded" t-on-click="props.onToggleCollapsePanel"> <i class="fa fa-angle-double-right"/> </div> <div class="o-sidePanelTitle o-fw-bold ms-2" t-esc="getTitle()"/> <div t-if="props.onTogglePinPanel" class="o-pin-panel o-sidePanelAction ms-auto rounded" t-att-class="{'active': props.isPinned}" t-on-click="props.onTogglePinPanel" t-att-title="pinInfoMessage"> <i class="fa fa-thumb-tack"/> </div> <div class="o-sidePanelClose o-sidePanelAction rounded" t-on-click="props.onCloseSidePanel"> ✕ </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="props.onStartHandleDrag" t-on-dblclick="props.onResetPanelSize"> <t t-call="o-spreadsheet-Icon.THIN_DRAG_HANDLE"/> </div> </div> <div class="o-sidePanelBody"> <t t-component="props.panelContent.Body" t-props="props.panelProps" onCloseSidePanel="props.onCloseSidePanel" /> </div> <div class="o-sidePanelFooter" t-if="props.panelContent?.Footer"> <t t-component="props.panelContent.Footer" t-props="props.panelProps"/> </div> </div> </div> </t> <t t-name="o-spreadsheet-SidePanelCollapsed"> <div class="o-sidePanel collapsed w-100 h-100" t-on-click="props.onToggleCollapsePanel"> <div class="d-flex flex-column align-items-center"> <div t-if="props.onToggleCollapsePanel" class="o-collapse-panel o-sidePanelAction rounded mb-1"> <i class="fa fa-angle-double-left"/> </div> <div t-if="props.onTogglePinPanel" class="o-pin-panel o-sidePanelAction rounded mb-1" t-att-class="{'active': props.isPinned}" t-on-click.stop="props.onTogglePinPanel" t-att-title="pinInfoMessage"> <i class="fa fa-thumb-tack"/> </div> <div class="o-sidePanelClose o-sidePanelAction rounded mb-1" t-on-click.stop="props.onCloseSidePanel"> ✕ </div> <div class="o-sidePanelTitle o-fw-bold" t-esc="getTitle()"/> </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">Those settings affect 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> <MenuPopover t-if="state.isMenuOpen" menuItems="props.menuItems" anchorRect="menuAnchorRect" onClose.bind="onMenuClosed" menuId="menuId" popoverPositioning="'bottom-left'" /> </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"> <div class="h-100 position-relative overflow-x-hidden overflow-y-auto" t-ref="pivotSidePanel"> <div t-att="isReadonly ? ['inert', 1] : []" t-att-class="{ 'pe-none opacity-50': isReadonly }"> <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> </div> <PivotDeferUpdate t-if="!isReadonly" 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_index"> <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"> <t t-set-slot="upper-right-icons"> <t t-set="errorMessage" t-value="getHugeDimensionErrorMessage(col)"/> <i t-if="errorMessage" class="text-warning fa fa-exclamation-triangle" t-att-title="errorMessage" /> </t> <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"/> <PivotCustomGroupsCollapsible t-if="col.isCustomField" pivotId="props.pivotId" customField="getCustomField(col)" onCustomFieldUpdated="props.onDimensionsUpdated" /> </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_index"> <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"> <t t-set-slot="upper-right-icons"> <t t-set="errorMessage" t-value="getHugeDimensionErrorMessage(row)"/> <i t-if="errorMessage" class="text-warning fa fa-exclamation-triangle" t-att-title="errorMessage" /> </t> <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"/> <PivotCustomGroupsCollapsible t-if="row.isCustomField" pivotId="props.pivotId" customField="getCustomField(row)" onCustomFieldUpdated="props.onDimensionsUpdated" /> </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" invalid="isCalculatedMeasureInvalid" /> </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]" /> <option t-if="measure.computedBy" t-att-value="''" t-att-selected="'' === measure.aggregator"> Compute from totals </option> </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="granularityProps" t-value="props.dimension.granularity || 'month'"/> <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 === granularityProps" t-att-value="granularity" t-esc="periods[granularity]" t-att-selected="granularity === granularityProps or (granularity === 'month' and !granularityProps)" /> </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-PivotCustomGroupsCollapsible"> <SidePanelCollapsible class="'o-pivot-custom-groups'" isInitiallyCollapsed="true" title.translate="Groups"> <t t-set-slot="content"> <div class="ps-4"> <div t-foreach="groups" t-as="group" t-key="group_index" class="o-pivot-custom-group pb-1"> <div class="d-flex align-items-center justify-content-between small" t-on-pointerdown.stop=""> <TextInput value="group.name" onChange="(newName) => this.onRenameGroup(group_index, newName)" /> <i class="o-button-icon ps-3 fa fa-trash" t-on-click="() => this.onDeleteGroup(group_index)" /> </div> </div> <div t-if="!hasOthersGroup" class="o-button-link o-add-others-group small pb-1" t-on-click="() => this.addOthersGroup()"> <span>+ &quot;Others&quot; group</span> </div> </div> </t> </SidePanelCollapsible> </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"/> /