mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Formula UI redesign
Summary: Redesigning column type section to make it more user-friendly. Introducing column behavior concept. Column can be either: - Empty Formula Column: initial state (user can convert to Formula/Data Column) - Data Column: non formula column with or without trigger (with option to add trigger, or convert to formula) - Formula Column: pure formula column, with an option to convert to data column with a trigger. Test Plan: Existing tests. Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D3092
This commit is contained in:
parent
877542225d
commit
e8e614c584
@ -625,6 +625,30 @@ export class GristDoc extends DisposableWithEvents {
|
||||
);
|
||||
}
|
||||
|
||||
// Convert column to pure formula column.
|
||||
public async convertToFormula(colRefs: number, formula: string): Promise<void> {
|
||||
return this.docModel.columns.sendTableAction(
|
||||
['UpdateRecord', colRefs, {
|
||||
isFormula: true,
|
||||
formula,
|
||||
recalcWhen: RecalcWhen.DEFAULT,
|
||||
recalcDeps: null,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
// Convert column to data column with a trigger formula
|
||||
public async convertToTrigger(colRefs: number, formula: string): Promise<void> {
|
||||
return this.docModel.columns.sendTableAction(
|
||||
['UpdateRecord', colRefs, {
|
||||
isFormula: false,
|
||||
formula,
|
||||
recalcWhen: RecalcWhen.DEFAULT,
|
||||
recalcDeps: null,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
public getCsvLink() {
|
||||
const filters = this.viewModel.activeSection.peek().filteredFields.get().map(field=> ({
|
||||
colRef : field.colRef.peek(),
|
||||
|
@ -1,17 +1,20 @@
|
||||
import type {GristDoc} from 'app/client/components/GristDoc';
|
||||
import type {ColumnRec} from 'app/client/models/entities/ColumnRec';
|
||||
import type {CursorPos} from "app/client/components/Cursor";
|
||||
import {CursorPos} from 'app/client/components/Cursor';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {ColumnRec} from 'app/client/models/entities/ColumnRec';
|
||||
import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight';
|
||||
import {cssEmptySeparator, cssLabel, cssRow} from 'app/client/ui/RightPanel';
|
||||
import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas';
|
||||
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
|
||||
import {colors, testId, vars} from 'app/client/ui2018/cssVars';
|
||||
import {textButton} from 'app/client/ui2018/buttons';
|
||||
import {colors, testId} from 'app/client/ui2018/cssVars';
|
||||
import {textInput} from 'app/client/ui2018/editableLabel';
|
||||
import {cssIconButton, icon} from 'app/client/ui2018/icons';
|
||||
import {menu, menuItem} from 'app/client/ui2018/menus';
|
||||
import {selectMenu, selectOption, selectTitle} from 'app/client/ui2018/menus';
|
||||
import {sanitizeIdent} from 'app/common/gutil';
|
||||
import {Computed, dom, fromKo, MultiHolder, Observable, styled, subscribe} from 'grainjs';
|
||||
import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder, Observable,
|
||||
styled, subscribe} from 'grainjs';
|
||||
import * as ko from 'knockout';
|
||||
import debounce = require('lodash/debounce');
|
||||
import {IconName} from 'app/client/ui2018/IconList';
|
||||
|
||||
export function buildNameConfig(owner: MultiHolder, origColumn: ColumnRec, cursor: ko.Computed<CursorPos>) {
|
||||
const untieColId = origColumn.untieColIdFromLabel;
|
||||
@ -63,86 +66,237 @@ export function buildNameConfig(owner: MultiHolder, origColumn: ColumnRec, curso
|
||||
];
|
||||
}
|
||||
|
||||
type BuildEditor = (cellElem: Element) => void;
|
||||
type BuildEditor = (
|
||||
cellElem: Element,
|
||||
editValue?: string,
|
||||
onSave?: (formula: string) => Promise<void>,
|
||||
onCancel?: () => void) => void;
|
||||
|
||||
type BEHAVIOR = "empty"|"formula"|"data";
|
||||
|
||||
export function buildFormulaConfig(
|
||||
owner: MultiHolder, origColumn: ColumnRec, gristDoc: GristDoc, buildEditor: BuildEditor
|
||||
) {
|
||||
const clearColumn = () => gristDoc.clearColumns([origColumn.id.peek()]);
|
||||
const convertIsFormula =
|
||||
(opts: {toFormula: boolean, noRecalc?: boolean}) => gristDoc.convertIsFormula([origColumn.id.peek()], opts);
|
||||
const errorMessage = createFormulaErrorObs(owner, gristDoc, origColumn);
|
||||
|
||||
return dom.maybe(use => {
|
||||
if (!use(origColumn.id)) { return null; } // Invalid column, show nothing.
|
||||
if (use(origColumn.isEmpty)) { return "empty"; }
|
||||
return use(origColumn.isFormula) ? "formula" : "data";
|
||||
},
|
||||
(type: "empty"|"formula"|"data") => {
|
||||
function buildHeader(label: string, menuFunc: () => Element[]) {
|
||||
return cssRow(
|
||||
cssInlineLabel(label,
|
||||
testId('field-is-formula-label'),
|
||||
),
|
||||
cssDropdownLabel('Actions', icon('Dropdown'), menu(menuFunc),
|
||||
cssDropdownLabel.cls('-disabled', origColumn.disableModify),
|
||||
testId('field-actions-menu'),
|
||||
)
|
||||
// Intermediate state - user wants to specify formula, but haven't done yet
|
||||
const maybeFormula = Observable.create(owner, false);
|
||||
|
||||
// Intermediate state - user wants to specify formula, but haven't done yet
|
||||
const maybeTrigger = Observable.create(owner, false);
|
||||
|
||||
// Column behaviour. There are 3 types of behaviors:
|
||||
// - empty: isFormula and formula == ''
|
||||
// - formula: isFormula and formula != ''
|
||||
// - data: not isFormula nd formula == ''
|
||||
const behavior = Computed.create<BEHAVIOR|null>(owner, (use) => {
|
||||
// When no id column is invalid, show nothing.
|
||||
if (!use(origColumn.id)) { return null; }
|
||||
// Column is a formula column, when it is a formula column with valid formula or will be a formula.
|
||||
if (use(origColumn.isRealFormula) || use(maybeFormula)) { return "formula"; }
|
||||
// If column is not empty, or empty but wants to be a trigger
|
||||
if (use(maybeTrigger) || !use(origColumn.isEmpty)) { return "data"; }
|
||||
return "empty";
|
||||
});
|
||||
|
||||
// Reference to current editor, we will open it when user wants to specify a formula or trigger.
|
||||
// And close it dispose it when user opens up behavior menu.
|
||||
let formulaField: HTMLElement|null = null;
|
||||
|
||||
// Helper function to clear temporary state (will be called when column changes or formula editor closes)
|
||||
const clearState = () => bundleChanges(() => {
|
||||
maybeFormula.set(false);
|
||||
maybeTrigger.set(false);
|
||||
formulaField = null;
|
||||
});
|
||||
|
||||
// Clear state when column has changed
|
||||
owner.autoDispose(origColumn.id.subscribe(clearState));
|
||||
|
||||
// Menu helper that will show normal menu with some default options
|
||||
const menu = (label: DomContents, options: DomElementArg[]) =>
|
||||
cssRow(
|
||||
selectMenu(
|
||||
label,
|
||||
() => options,
|
||||
testId("field-behaviour"),
|
||||
// HACK: Menu helper will add tabindex to this element, which will make
|
||||
// this element focusable and will steal focus from clipboard. This in turn,
|
||||
// will not dispose the formula editor when menu is clicked.
|
||||
(el) => el.removeAttribute("tabindex"),
|
||||
dom.cls("disabled", origColumn.disableModify)),
|
||||
);
|
||||
|
||||
// Behaviour label
|
||||
const behaviorName = Computed.create(owner, behavior, (use, type) => {
|
||||
if (type === 'formula') { return "Formula Column"; }
|
||||
if (type === 'data') { return "Data Column"; }
|
||||
return "Empty Column";
|
||||
});
|
||||
const behaviorIcon = Computed.create<IconName>(owner, (use) => {
|
||||
return use(behaviorName) === "Data Column" ? "Database" : "Script";
|
||||
});
|
||||
const behaviourLabel = () => selectTitle(behaviorName, behaviorIcon);
|
||||
|
||||
// Actions on select menu:
|
||||
|
||||
// Converts data column to formula column.
|
||||
const convertDataColumnToFormulaOption = () => selectOption(
|
||||
() => (maybeFormula.set(true), formulaField?.focus()),
|
||||
'Clear and make into formula', 'Script');
|
||||
|
||||
// Converts to empty column and opens up the editor. (label is the same, but this is used when we have no formula)
|
||||
const convertTriggerToFormulaOption = () => selectOption(
|
||||
() => gristDoc.convertIsFormula([origColumn.id.peek()], {toFormula: true, noRecalc: true}),
|
||||
'Clear and make into formula', 'Script');
|
||||
|
||||
// Convert column to data.
|
||||
// This method is also available through a text button.
|
||||
const convertToData = () => gristDoc.convertIsFormula([origColumn.id.peek()], {toFormula: false, noRecalc: true});
|
||||
const convertToDataOption = () => selectOption(
|
||||
convertToData,
|
||||
'Convert column to data', 'Database');
|
||||
|
||||
// Clears the column
|
||||
const clearAndResetOption = () => selectOption(
|
||||
() => gristDoc.clearColumns([origColumn.id.peek()]),
|
||||
'Clear and reset', 'CrossSmall');
|
||||
|
||||
// Actions on text buttons:
|
||||
|
||||
// Tries to convert data column to a trigger column.
|
||||
const convertDataColumnToTriggerColumn = () => {
|
||||
maybeTrigger.set(true);
|
||||
// Open the formula editor.
|
||||
formulaField?.focus();
|
||||
};
|
||||
|
||||
// Converts formula column to trigger formula column.
|
||||
const convertFormulaToTrigger = () =>
|
||||
gristDoc.convertIsFormula([origColumn.id.peek()], {toFormula: false, noRecalc: false});
|
||||
|
||||
const setFormula = () => (maybeFormula.set(true), formulaField?.focus());
|
||||
const setTrigger = () => (maybeTrigger.set(true), formulaField?.focus());
|
||||
|
||||
// Actions on save formula
|
||||
|
||||
// Converts column to formula column or updates formula on a formula column.
|
||||
const onSaveConvertToFormula = async (formula: string) => {
|
||||
const notBlank = Boolean(formula);
|
||||
const trueFormula = !maybeFormula.get();
|
||||
if (notBlank || trueFormula) { await gristDoc.convertToFormula(origColumn.id.peek(), formula); }
|
||||
clearState();
|
||||
};
|
||||
|
||||
// Updates formula or convert column to trigger formula column if necessary.
|
||||
const onSaveConvertToTrigger = async (formula: string) => {
|
||||
if (formula && maybeTrigger.get()) {
|
||||
// Convert column to trigger
|
||||
await gristDoc.convertToTrigger(origColumn.id.peek(), formula);
|
||||
} else if (origColumn.hasTriggerFormula.peek()) {
|
||||
// This is true trigger formula, just update the formula (or make it blank)
|
||||
await origColumn.formula.setAndSave(formula);
|
||||
}
|
||||
function buildFormulaRow(placeholder = 'Enter formula') {
|
||||
return [
|
||||
cssRow(dom.create(buildFormula, origColumn, buildEditor, placeholder)),
|
||||
clearState();
|
||||
};
|
||||
|
||||
const errorMessage = createFormulaErrorObs(owner, gristDoc, origColumn);
|
||||
// Helper that will create different flavors for formula builder.
|
||||
const formulaBuilder = (onSave: (formula: string) => Promise<void>) => [
|
||||
cssRow(formulaField = buildFormula(
|
||||
origColumn,
|
||||
buildEditor,
|
||||
"Enter formula",
|
||||
onSave,
|
||||
clearState)),
|
||||
dom.maybe(errorMessage, errMsg => cssRow(cssError(errMsg), testId('field-error-count'))),
|
||||
];
|
||||
}
|
||||
if (type === "empty") {
|
||||
return [
|
||||
buildHeader('EMPTY COLUMN', () => [
|
||||
menuItem(clearColumn, 'Clear column', dom.cls('disabled', true)),
|
||||
menuItem(() => convertIsFormula({toFormula: false}), 'Make into data column'),
|
||||
|
||||
return dom.maybe(behavior, (type: BEHAVIOR) => [
|
||||
cssLabel('COLUMN BEHAVIOR'),
|
||||
...(type === "empty" ? [
|
||||
menu(behaviourLabel(), [
|
||||
convertToDataOption()
|
||||
]),
|
||||
buildFormulaRow(),
|
||||
];
|
||||
} else if (type === "formula") {
|
||||
return [
|
||||
buildHeader('FORMULA COLUMN', () => [
|
||||
menuItem(clearColumn, 'Clear column'),
|
||||
menuItem(() => convertIsFormula({toFormula: false, noRecalc: true}), 'Convert to data column'),
|
||||
cssEmptySeparator(),
|
||||
cssRow(textButton(
|
||||
"Set formula",
|
||||
dom.on("click", setFormula),
|
||||
dom.prop("disabled", origColumn.disableModify),
|
||||
testId("field-set-formula")
|
||||
)),
|
||||
cssRow(textButton(
|
||||
"Set trigger formula",
|
||||
dom.on("click", setTrigger),
|
||||
dom.prop("disabled", origColumn.disableModify),
|
||||
testId("field-set-trigger")
|
||||
)),
|
||||
cssRow(textButton(
|
||||
"Make into data column",
|
||||
dom.on("click", convertToData),
|
||||
dom.prop("disabled", origColumn.disableModify),
|
||||
testId("field-set-data")
|
||||
))
|
||||
] : type === "formula" ? [
|
||||
menu(behaviourLabel(), [
|
||||
convertToDataOption(),
|
||||
clearAndResetOption(),
|
||||
]),
|
||||
buildFormulaRow(),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
buildHeader('DATA COLUMN', () => {
|
||||
return origColumn.formula.peek() ? [
|
||||
// If there is a formula available, offer a separate option to convert to formula
|
||||
// without clearing it.
|
||||
menuItem(clearColumn, 'Clear column'),
|
||||
menuItem(() => convertIsFormula({toFormula: true}), 'Convert to formula column'),
|
||||
] : [
|
||||
menuItem(clearColumn, 'Clear and make into formula'),
|
||||
];
|
||||
}),
|
||||
buildFormulaRow('Optional formula'),
|
||||
dom.domComputed(use => Boolean(use(origColumn.formula)), (haveFormula) => haveFormula ?
|
||||
dom.create(buildFormulaTriggers, origColumn) :
|
||||
cssHintRow('For default values, automatic updates, and data-cleaning.')
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
);
|
||||
formulaBuilder(onSaveConvertToFormula),
|
||||
cssEmptySeparator(),
|
||||
cssRow(textButton(
|
||||
"Convert to trigger formula",
|
||||
dom.on("click", convertFormulaToTrigger),
|
||||
dom.hide(maybeFormula),
|
||||
dom.prop("disabled", origColumn.disableModify),
|
||||
testId("field-set-trigger")
|
||||
))
|
||||
] : /* type == 'data' */ [
|
||||
menu(behaviourLabel(),
|
||||
[
|
||||
dom.domComputed(origColumn.hasTriggerFormula, (hasTrigger) => hasTrigger ?
|
||||
// If we have trigger, we will convert it directly to a formula column
|
||||
convertTriggerToFormulaOption() :
|
||||
// else we will convert to empty column and open up the editor
|
||||
convertDataColumnToFormulaOption()
|
||||
),
|
||||
clearAndResetOption(),
|
||||
]
|
||||
),
|
||||
// If data column is or wants to be a trigger formula:
|
||||
dom.maybe((use) => use(maybeTrigger) || use(origColumn.hasTriggerFormula), () => [
|
||||
cssLabel('TRIGGER FORMULA'),
|
||||
formulaBuilder(onSaveConvertToTrigger),
|
||||
dom.create(buildFormulaTriggers, origColumn, maybeTrigger)
|
||||
]),
|
||||
// Else offer a way to convert to trigger formula.
|
||||
dom.maybe((use) => !(use(maybeTrigger) || use(origColumn.hasTriggerFormula)), () => [
|
||||
cssEmptySeparator(),
|
||||
cssRow(textButton(
|
||||
"Set trigger formula",
|
||||
dom.on("click", convertDataColumnToTriggerColumn),
|
||||
dom.prop("disabled", origColumn.disableModify),
|
||||
testId("field-set-trigger")
|
||||
))
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
function buildFormula(owner: MultiHolder, column: ColumnRec, buildEditor: BuildEditor, placeholder: string) {
|
||||
function buildFormula(
|
||||
column: ColumnRec,
|
||||
buildEditor: BuildEditor,
|
||||
placeholder: string,
|
||||
onSave?: (formula: string) => Promise<void>,
|
||||
onCancel?: () => void) {
|
||||
return cssFieldFormula(column.formula, {placeholder, maxLines: 2},
|
||||
dom.cls('formula_field_sidepane'),
|
||||
cssFieldFormula.cls('-disabled', column.disableModify),
|
||||
cssFieldFormula.cls('-disabled-icon', use => !use(column.formula)),
|
||||
dom.cls('disabled'),
|
||||
{tabIndex: '-1'},
|
||||
dom.on('focus', (ev, elem) => buildEditor(elem)),
|
||||
// Focus event use used by a user to edit an existing formula.
|
||||
// It can also be triggered manually to open up the editor.
|
||||
dom.on('focus', (_, elem) => buildEditor(elem, undefined, onSave, onCancel)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -223,36 +377,6 @@ const cssToggleButton = styled(cssIconButton, `
|
||||
}
|
||||
`);
|
||||
|
||||
const cssInlineLabel = styled(cssLabel, `
|
||||
padding: 4px 8px;
|
||||
margin: 4px 0 -4px -8px;
|
||||
`);
|
||||
|
||||
const cssDropdownLabel = styled(cssInlineLabel, `
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: ${vars.controlBorderRadius};
|
||||
cursor: pointer;
|
||||
|
||||
color: ${colors.lightGreen};
|
||||
--icon-color: ${colors.lightGreen};
|
||||
|
||||
&:hover, &:focus, &.weasel-popup-open {
|
||||
background-color: ${colors.mediumGrey};
|
||||
}
|
||||
&-disabled {
|
||||
color: ${colors.slate};
|
||||
--icon-color: ${colors.slate};
|
||||
pointer-events: none;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssHintRow = styled('div', `
|
||||
margin: -4px 16px 8px 16px;
|
||||
color: ${colors.slate};
|
||||
`);
|
||||
|
||||
const cssColLabelBlock = styled('div', `
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -230,11 +230,19 @@ export class RightPanel extends Disposable {
|
||||
}
|
||||
|
||||
// Helper to activate the side-pane formula editor over the given HTML element.
|
||||
private _activateFormulaEditor(refElem: Element) {
|
||||
private _activateFormulaEditor(
|
||||
// Element to attach to.
|
||||
refElem: Element,
|
||||
// Simulate user typing on the cell - open editor with an initial value.
|
||||
editValue?: string,
|
||||
// Custom save handler.
|
||||
onSave?: (formula: string) => Promise<void>,
|
||||
// Custom cancel handler.
|
||||
onCancel?: () => void,) {
|
||||
const vsi = this._gristDoc.viewModel.activeSection().viewInstance();
|
||||
if (!vsi) { return; }
|
||||
const editRowModel = vsi.moveEditRowToCursor();
|
||||
vsi.activeFieldBuilder.peek().openSideFormulaEditor(editRowModel, refElem);
|
||||
return vsi.activeFieldBuilder.peek().openSideFormulaEditor(editRowModel, refElem, editValue, onSave, onCancel);
|
||||
}
|
||||
|
||||
private _buildPageWidgetContent(_owner: MultiHolder) {
|
||||
@ -657,6 +665,10 @@ export const cssSeparator = styled('div', `
|
||||
margin-top: 16px;
|
||||
`);
|
||||
|
||||
export const cssEmptySeparator = styled('div', `
|
||||
margin-top: 16px;
|
||||
`);
|
||||
|
||||
const cssConfigContainer = styled('div', `
|
||||
overflow: auto;
|
||||
--color-list-item: none;
|
||||
|
@ -20,7 +20,7 @@ import isEqual = require('lodash/isEqual');
|
||||
/**
|
||||
* Build UI to select triggers for formulas in data columns (such for default values).
|
||||
*/
|
||||
export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec) {
|
||||
export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec, disable: Observable<boolean>|null = null) {
|
||||
// Set up observables to translate between the UI representation of triggers, and what we
|
||||
// actually store.
|
||||
// - We store the pair (recalcWhen, recalcDeps). When recalcWhen is DEFAULT, recalcDeps lists
|
||||
@ -79,7 +79,7 @@ export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec) {
|
||||
labeledSquareCheckbox(
|
||||
applyToNew,
|
||||
'Apply to new records',
|
||||
dom.boolAttr('disabled', applyOnChanges),
|
||||
dom.boolAttr('disabled', (use) => (disable && use(disable)) || use(applyOnChanges)),
|
||||
testId('field-formula-apply-to-new'),
|
||||
),
|
||||
),
|
||||
@ -90,6 +90,7 @@ export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec) {
|
||||
'Apply on changes to:' :
|
||||
'Apply on record changes'
|
||||
),
|
||||
dom.boolAttr('disabled', (use) => disable ? use(disable) : false),
|
||||
testId('field-formula-apply-on-changes'),
|
||||
),
|
||||
),
|
||||
|
@ -38,6 +38,7 @@ export type IconName = "ChartArea" |
|
||||
"Copy" |
|
||||
"CrossBig" |
|
||||
"CrossSmall" |
|
||||
"Database" |
|
||||
"Dots" |
|
||||
"Download" |
|
||||
"DragDrop" |
|
||||
@ -79,6 +80,7 @@ export type IconName = "ChartArea" |
|
||||
"Repl" |
|
||||
"ResizePanel" |
|
||||
"RightAlign" |
|
||||
"Script" |
|
||||
"Search" |
|
||||
"Settings" |
|
||||
"Share" |
|
||||
@ -133,6 +135,7 @@ export const IconList: IconName[] = ["ChartArea",
|
||||
"Copy",
|
||||
"CrossBig",
|
||||
"CrossSmall",
|
||||
"Database",
|
||||
"Dots",
|
||||
"Download",
|
||||
"DragDrop",
|
||||
@ -174,6 +177,7 @@ export const IconList: IconName[] = ["ChartArea",
|
||||
"Repl",
|
||||
"ResizePanel",
|
||||
"RightAlign",
|
||||
"Script",
|
||||
"Search",
|
||||
"Settings",
|
||||
"Share",
|
||||
|
@ -100,6 +100,16 @@ export const bigBasicButtonLink = tbind(button, null, {link: true, large: true})
|
||||
export const primaryButtonLink = tbind(button, null, {link: true, primary: true});
|
||||
export const bigPrimaryButtonLink = tbind(button, null, {link: true, large: true, primary: true});
|
||||
|
||||
// Button that looks like a link (have no background and no border).
|
||||
export const textButton = styled(cssButton, `
|
||||
border: none;
|
||||
padding: 0px;
|
||||
background-color: inherit !important;
|
||||
&:disabled {
|
||||
color: ${colors.inactiveCursor};
|
||||
}
|
||||
`);
|
||||
|
||||
const cssButtonLink = styled('a', `
|
||||
display: inline-block;
|
||||
&, &:hover, &:focus {
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Command } from 'app/client/components/commands';
|
||||
import { NeedUpgradeError, reportError } from 'app/client/models/errors';
|
||||
import { cssCheckboxSquare, cssLabel, cssLabelText } from 'app/client/ui2018/checkbox';
|
||||
import { colors, testId, vars } from 'app/client/ui2018/cssVars';
|
||||
import {cssSelectBtn} from 'app/client/ui2018/select';
|
||||
import { IconName } from 'app/client/ui2018/IconList';
|
||||
import { icon } from 'app/client/ui2018/icons';
|
||||
import { cssSelectBtn } from 'app/client/ui2018/select';
|
||||
import { commonUrls } from 'app/common/gristUrls';
|
||||
import {Computed, dom, DomElementArg, DomElementMethod, MaybeObsArray, MutableObsArray, Observable,
|
||||
styled} from 'grainjs';
|
||||
import { BindableValue, Computed, dom, DomElementArg, DomElementMethod, IDomArgs,
|
||||
MaybeObsArray, MutableObsArray, Observable, styled } from 'grainjs';
|
||||
import * as weasel from 'popweasel';
|
||||
import {cssCheckboxSquare, cssLabel, cssLabelText} from 'app/client/ui2018/checkbox';
|
||||
|
||||
export interface IOptionFull<T> {
|
||||
value: T;
|
||||
@ -304,6 +304,71 @@ export function autocomplete(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates simple (not reactive) static menu that looks like a select-box.
|
||||
* Primary usage is for menus, where you want to control how the options and a default
|
||||
* label will look. Label is not updated or changed when one of the option is clicked, for those
|
||||
* use cases use a select component.
|
||||
* Icons are optional, can use custom elements instead of labels and options.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* selectMenu(selectTitle("Title", "Script"), () => [
|
||||
* selectOption(() => ..., "Option1", "Database"),
|
||||
* selectOption(() => ..., "Option2", "Script"),
|
||||
* ]);
|
||||
*
|
||||
* // Control disabled state (if the menu will be opened or not)
|
||||
*
|
||||
* const disabled = observable(false);
|
||||
* selectMenu(selectTitle("Title", "Script"), () => [
|
||||
* selectOption(() => ..., "Option1", "Database"),
|
||||
* selectOption(() => ..., "Option2", "Script"),
|
||||
* ], disabled);
|
||||
*
|
||||
*/
|
||||
export function selectMenu(
|
||||
label: DomElementArg,
|
||||
items: () => DomElementArg[],
|
||||
...args: IDomArgs<HTMLDivElement>
|
||||
) {
|
||||
return cssSelectBtn(
|
||||
label,
|
||||
icon('Dropdown'),
|
||||
menu(
|
||||
items,
|
||||
{
|
||||
...weasel.defaultMenuOptions,
|
||||
menuCssClass: cssSelectMenuElem.className + ' grist-floating-menu',
|
||||
stretchToSelector : `.${cssSelectBtn.className}`,
|
||||
trigger : [(triggerElem, ctl) => {
|
||||
const isDisabled = () => triggerElem.classList.contains('disabled');
|
||||
dom.onElem(triggerElem, 'click', () => isDisabled() || ctl.toggle());
|
||||
dom.onKeyElem(triggerElem as HTMLElement, 'keydown', {
|
||||
ArrowDown: () => isDisabled() || ctl.open(),
|
||||
ArrowUp: () => isDisabled() || ctl.open()
|
||||
});
|
||||
}]
|
||||
},
|
||||
),
|
||||
...args,
|
||||
);
|
||||
}
|
||||
|
||||
export function selectTitle(label: BindableValue<string>, iconName?: BindableValue<IconName>) {
|
||||
return cssOptionRow(
|
||||
iconName ? dom.domComputed(iconName, (name) => cssOptionRowIcon(name)) : null,
|
||||
dom.text(label)
|
||||
);
|
||||
}
|
||||
|
||||
export function selectOption(
|
||||
action: (item: HTMLElement) => void,
|
||||
label: BindableValue<string>,
|
||||
iconName?: BindableValue<IconName>) {
|
||||
return menuItem(action, selectTitle(label, iconName));
|
||||
}
|
||||
|
||||
export const menuSubHeader = styled('div', `
|
||||
font-size: ${vars.xsmallFontSize};
|
||||
text-transform: uppercase;
|
||||
@ -404,13 +469,13 @@ const cssOptionIcon = styled(icon, `
|
||||
margin: -3px 8px 0 2px;
|
||||
`);
|
||||
|
||||
const cssOptionRow = styled('span', `
|
||||
export const cssOptionRow = styled('span', `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
`);
|
||||
|
||||
const cssOptionRowIcon = styled(cssOptionIcon, `
|
||||
export const cssOptionRowIcon = styled(cssOptionIcon, `
|
||||
margin: 0 8px 0 0;
|
||||
flex: none;
|
||||
|
||||
|
@ -517,13 +517,22 @@ export class FieldBuilder extends Disposable {
|
||||
/**
|
||||
* Open the formula editor in the side pane. It will be positioned over refElem.
|
||||
*/
|
||||
public openSideFormulaEditor(editRow: DataRowModel, refElem: Element) {
|
||||
public openSideFormulaEditor(
|
||||
editRow: DataRowModel,
|
||||
refElem: Element,
|
||||
editValue?: string,
|
||||
onSave?: (formula: string) => Promise<void>,
|
||||
onCancel?: () => void) {
|
||||
const editorHolder = openSideFormulaEditor({
|
||||
gristDoc: this.gristDoc,
|
||||
field: this.field,
|
||||
editRow,
|
||||
refElem,
|
||||
editValue,
|
||||
onSave,
|
||||
onCancel
|
||||
});
|
||||
// Add editor to document holder - this will prevent multiple formula editor instances.
|
||||
this.gristDoc.fieldEditorHolder.autoDispose(editorHolder);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {asyncOnce} from "app/common/AsyncCreate";
|
||||
import {CellValue} from "app/common/DocActions";
|
||||
import {isRaisedException} from 'app/common/gristTypes';
|
||||
import * as gutil from 'app/common/gutil';
|
||||
import {Disposable, Emitter, Holder, IDisposable, MultiHolder, Observable} from 'grainjs';
|
||||
import {Disposable, Emitter, Holder, MultiHolder, Observable} from 'grainjs';
|
||||
import isEqual = require('lodash/isEqual');
|
||||
import { CellPosition } from "app/client/components/CellPosition";
|
||||
|
||||
@ -380,7 +380,10 @@ export function openSideFormulaEditor(options: {
|
||||
field: ViewFieldRec,
|
||||
editRow: DataRowModel, // Needed to get exception value, if any.
|
||||
refElem: Element, // Element in the side pane over which to position the editor.
|
||||
}): IDisposable {
|
||||
editValue?: string,
|
||||
onSave?: (formula: string) => Promise<void>,
|
||||
onCancel?: () => void,
|
||||
}): Disposable {
|
||||
const {gristDoc, field, editRow, refElem} = options;
|
||||
const holder = MultiHolder.create(null);
|
||||
const column = field.column();
|
||||
@ -388,17 +391,19 @@ export function openSideFormulaEditor(options: {
|
||||
// AsyncOnce ensures it's called once even if triggered multiple times.
|
||||
const saveEdit = asyncOnce(async () => {
|
||||
const formula = editor.getCellValue();
|
||||
if (formula !== column.formula.peek()) {
|
||||
if (options.onSave) {
|
||||
await options.onSave(formula as string);
|
||||
} else if (formula !== column.formula.peek()) {
|
||||
await column.updateColValues({formula});
|
||||
}
|
||||
holder.dispose(); // Deactivate the editor.
|
||||
holder.dispose();
|
||||
});
|
||||
|
||||
// These are the commands for while the editor is active.
|
||||
const editCommands = {
|
||||
fieldEditSave: () => { saveEdit().catch(reportError); },
|
||||
fieldEditSaveHere: () => { saveEdit().catch(reportError); },
|
||||
fieldEditCancel: () => { holder.dispose(); },
|
||||
fieldEditCancel: () => { holder.dispose(); options.onCancel?.(); },
|
||||
};
|
||||
|
||||
// Replace the item in the Holder with a new one, disposing the previous one.
|
||||
@ -407,7 +412,7 @@ export function openSideFormulaEditor(options: {
|
||||
field,
|
||||
cellValue: column.formula(),
|
||||
formulaError: getFormulaError(gristDoc, editRow, column),
|
||||
editValue: undefined,
|
||||
editValue: options.editValue,
|
||||
cursorPos: Number.POSITIVE_INFINITY, // Position of the caret within the editor.
|
||||
commands: editCommands,
|
||||
cssClass: 'formula_editor_sidepane',
|
||||
|
@ -39,6 +39,7 @@
|
||||
--icon-Copy: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTExLDMgTDExLDE1IEwyLDE1IEwyLDMgTDExLDMgWiBNMTAsMy45OTkgTDMsMy45OTkgTDMsMTMuOTk5IEwxMCwxMy45OTkgTDEwLDMuOTk5IFogTTE0LDAgTDE0LDEyIEwxMS41LDEyIEwxMS41LDExIEwxMywxMSBMMTMsMSBMNiwxIEw2LDIuNSBMNSwyLjUgTDUsMCBMMTQsMCBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=');
|
||||
--icon-CrossBig: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEzLjQxNDIxMzYsMTIgTDE5LjcwNzEwNjgsMTguMjkyODkzMiBDMjAuMDk3NjMxMSwxOC42ODM0MTc1IDIwLjA5NzYzMTEsMTkuMzE2NTgyNSAxOS43MDcxMDY4LDE5LjcwNzEwNjggQzE5LjMxNjU4MjUsMjAuMDk3NjMxMSAxOC42ODM0MTc1LDIwLjA5NzYzMTEgMTguMjkyODkzMiwxOS43MDcxMDY4IEwxMiwxMy40MTQyMTM2IEw1LjcwNzEwNjc4LDE5LjcwNzEwNjggQzUuMzE2NTgyNDksMjAuMDk3NjMxMSA0LjY4MzQxNzUxLDIwLjA5NzYzMTEgNC4yOTI4OTMyMiwxOS43MDcxMDY4IEMzLjkwMjM2ODkzLDE5LjMxNjU4MjUgMy45MDIzNjg5MywxOC42ODM0MTc1IDQuMjkyODkzMjIsMTguMjkyODkzMiBMMTAuNTg1Nzg2NCwxMiBMNC4yOTI4OTMyMiw1LjcwNzEwNjc4IEMzLjkwMjM2ODkzLDUuMzE2NTgyNDkgMy45MDIzNjg5Myw0LjY4MzQxNzUxIDQuMjkyODkzMjIsNC4yOTI4OTMyMiBDNC42ODM0MTc1MSwzLjkwMjM2ODkzIDUuMzE2NTgyNDksMy45MDIzNjg5MyA1LjcwNzEwNjc4LDQuMjkyODkzMjIgTDEyLDEwLjU4NTc4NjQgTDE4LjI5Mjg5MzIsNC4yOTI4OTMyMiBDMTguNjgzNDE3NSwzLjkwMjM2ODkzIDE5LjMxNjU4MjUsMy45MDIzNjg5MyAxOS43MDcxMDY4LDQuMjkyODkzMjIgQzIwLjA5NzYzMTEsNC42ODM0MTc1MSAyMC4wOTc2MzExLDUuMzE2NTgyNDkgMTkuNzA3MTA2OCw1LjcwNzEwNjc4IEwxMy40MTQyMTM2LDEyIFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==');
|
||||
--icon-CrossSmall: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTguNSw3LjUgTDEyLjUsNy41IEMxMi43NzYxNDI0LDcuNSAxMyw3LjcyMzg1NzYzIDEzLDggQzEzLDguMjc2MTQyMzcgMTIuNzc2MTQyNCw4LjUgMTIuNSw4LjUgTDguNSw4LjUgTDguNSwxMi41IEM4LjUsMTIuNzc2MTQyNCA4LjI3NjE0MjM3LDEzIDgsMTMgQzcuNzIzODU3NjMsMTMgNy41LDEyLjc3NjE0MjQgNy41LDEyLjUgTDcuNSw4LjUgTDMuNSw4LjUgQzMuMjIzODU3NjMsOC41IDMsOC4yNzYxNDIzNyAzLDggQzMsNy43MjM4NTc2MyAzLjIyMzg1NzYzLDcuNSAzLjUsNy41IEw3LjUsNy41IEw3LjUsMy41IEM3LjUsMy4yMjM4NTc2MyA3LjcyMzg1NzYzLDMgOCwzIEM4LjI3NjE0MjM3LDMgOC41LDMuMjIzODU3NjMgOC41LDMuNSBMOC41LDcuNSBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0icm90YXRlKC00NSA4IDgpIi8+PC9zdmc+');
|
||||
--icon-Database: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRhdGFiYXNlIj48ZWxsaXBzZSBjeD0iMTIiIGN5PSI1IiByeD0iOSIgcnk9IjMiIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTMgNXYxNGMwIDEuNjYgNCAzIDkgM3M5LTEuMzQgOS0zVjUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PC9zdmc+');
|
||||
--icon-Dots: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTgsOS41IEM3LjE3MTU3Mjg4LDkuNSA2LjUsOC44Mjg0MjcxMiA2LjUsOCBDNi41LDcuMTcxNTcyODggNy4xNzE1NzI4OCw2LjUgOCw2LjUgQzguODI4NDI3MTIsNi41IDkuNSw3LjE3MTU3Mjg4IDkuNSw4IEM5LjUsOC44Mjg0MjcxMiA4LjgyODQyNzEyLDkuNSA4LDkuNSBaIE0xMi41LDkuNSBDMTEuNjcxNTcyOSw5LjUgMTEsOC44Mjg0MjcxMiAxMSw4IEMxMSw3LjE3MTU3Mjg4IDExLjY3MTU3MjksNi41IDEyLjUsNi41IEMxMy4zMjg0MjcxLDYuNSAxNCw3LjE3MTU3Mjg4IDE0LDggQzE0LDguODI4NDI3MTIgMTMuMzI4NDI3MSw5LjUgMTIuNSw5LjUgWiBNMy41LDkuNSBDMi42NzE1NzI4OCw5LjUgMiw4LjgyODQyNzEyIDIsOCBDMiw3LjE3MTU3Mjg4IDIuNjcxNTcyODgsNi41IDMuNSw2LjUgQzQuMzI4NDI3MTIsNi41IDUsNy4xNzE1NzI4OCA1LDggQzUsOC44Mjg0MjcxMiA0LjMyODQyNzEyLDkuNSAzLjUsOS41IFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==');
|
||||
--icon-Download: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEyLDEzLjI5Mjg5MzIgTDE0LjE0NjQ0NjYsMTEuMTQ2NDQ2NiBDMTQuMzQxNzA4OCwxMC45NTExODQ1IDE0LjY1ODI5MTIsMTAuOTUxMTg0NSAxNC44NTM1NTM0LDExLjE0NjQ0NjYgQzE1LjA0ODgxNTUsMTEuMzQxNzA4OCAxNS4wNDg4MTU1LDExLjY1ODI5MTIgMTQuODUzNTUzNCwxMS44NTM1NTM0IEwxMS44NTM1NTM0LDE0Ljg1MzU1MzQgQzExLjY1ODI5MTIsMTUuMDQ4ODE1NSAxMS4zNDE3MDg4LDE1LjA0ODgxNTUgMTEuMTQ2NDQ2NiwxNC44NTM1NTM0IEw4LjE0NjQ0NjYxLDExLjg1MzU1MzQgQzcuOTUxMTg0NDYsMTEuNjU4MjkxMiA3Ljk1MTE4NDQ2LDExLjM0MTcwODggOC4xNDY0NDY2MSwxMS4xNDY0NDY2IEM4LjM0MTcwODc2LDEwLjk1MTE4NDUgOC42NTgyOTEyNCwxMC45NTExODQ1IDguODUzNTUzMzksMTEuMTQ2NDQ2NiBMMTEsMTMuMjkyODkzMiBMMTEsNy41IEMxMSw3LjIyMzg1NzYzIDExLjIyMzg1NzYsNyAxMS41LDcgQzExLjc3NjE0MjQsNyAxMiw3LjIyMzg1NzYzIDEyLDcuNSBMMTIsMTMuMjkyODkzMiBaIE0xLjA4NTM1Mjg1LDExIEMxLjI5MTI3MTA2LDExLjU4MjU5NjIgMS44NDY4OTA1OSwxMiAyLjUsMTIgTDYuNSwxMiBDNi43NzYxNDIzNywxMiA3LDEyLjIyMzg1NzYgNywxMi41IEM3LDEyLjc3NjE0MjQgNi43NzYxNDIzNywxMyA2LjUsMTMgTDIuNSwxMyBDMS4xMTkyODgxMywxMyAxLjM4Nzc3ODc4ZS0xNiwxMS44ODA3MTE5IDAsMTAuNSBDMCwxMC4yMjM4NTc2IDAuMjIzODU3NjI1LDEwIDAuNSwxMCBMNi41LDEwIEM2Ljc3NjE0MjM3LDEwIDcsMTAuMjIzODU3NiA3LDEwLjUgQzcsMTAuNzc2MTQyNCA2Ljc3NjE0MjM3LDExIDYuNSwxMSBMMS4wODUzNTI4NSwxMSBaIE0yLDguNSBDMiw4Ljc3NjE0MjM3IDEuNzc2MTQyMzcsOSAxLjUsOSBDMS4yMjM4NTc2Myw5IDEsOC43NzYxNDIzNyAxLDguNSBMMSwyLjUgQzEsMS42NzE1NzI4OCAxLjY3MTU3Mjg4LDEgMi41LDEgTDEzLjUsMSBDMTQuMzI4NDI3MSwxIDE1LDEuNjcxNTcyODggMTUsMi41IEwxNSw4LjUgQzE1LDguNzc2MTQyMzcgMTQuNzc2MTQyNCw5IDE0LjUsOSBDMTQuMjIzODU3Niw5IDE0LDguNzc2MTQyMzcgMTQsOC41IEwxNCwyLjUgQzE0LDIuMjIzODU3NjMgMTMuNzc2MTQyNCwyIDEzLjUsMiBMMi41LDIgQzIuMjIzODU3NjMsMiAyLDIuMjIzODU3NjMgMiwyLjUgTDIsOC41IFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==');
|
||||
--icon-DragDrop: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYuNSwzIEM2Ljc3NjE0MjM3LDMgNywzLjIyMzg1NzYzIDcsMy41IEw3LDEyLjUgQzcsMTIuNzc2MTQyNCA2Ljc3NjE0MjM3LDEzIDYuNSwxMyBDNi4yMjM4NTc2MywxMyA2LDEyLjc3NjE0MjQgNiwxMi41IEw2LDMuNSBDNiwzLjIyMzg1NzYzIDYuMjIzODU3NjMsMyA2LjUsMyBaIE05LjUsMyBDOS43NzYxNDIzNywzIDEwLDMuMjIzODU3NjMgMTAsMy41IEwxMCwxMi41IEMxMCwxMi43NzYxNDI0IDkuNzc2MTQyMzcsMTMgOS41LDEzIEM5LjIyMzg1NzYzLDEzIDksMTIuNzc2MTQyNCA5LDEyLjUgTDksMy41IEM5LDMuMjIzODU3NjMgOS4yMjM4NTc2MywzIDkuNSwzIFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==');
|
||||
@ -80,6 +81,7 @@
|
||||
--icon-Repl: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE0LjI5Mjg5MzIsMTIgTDEyLjE0NjQ0NjYsOS44NTM1NTMzOSBDMTEuOTUxMTg0NSw5LjY1ODI5MTI0IDExLjk1MTE4NDUsOS4zNDE3MDg3NiAxMi4xNDY0NDY2LDkuMTQ2NDQ2NjEgQzEyLjM0MTcwODgsOC45NTExODQ0NiAxMi42NTgyOTEyLDguOTUxMTg0NDYgMTIuODUzNTUzNCw5LjE0NjQ0NjYxIEwxNS44NTM1NTM0LDEyLjE0NjQ0NjYgQzE2LjA0ODgxNTUsMTIuMzQxNzA4OCAxNi4wNDg4MTU1LDEyLjY1ODI5MTIgMTUuODUzNTUzNCwxMi44NTM1NTM0IEwxMi44NTM1NTM0LDE1Ljg1MzU1MzQgQzEyLjY1ODI5MTIsMTYuMDQ4ODE1NSAxMi4zNDE3MDg4LDE2LjA0ODgxNTUgMTIuMTQ2NDQ2NiwxNS44NTM1NTM0IEMxMS45NTExODQ1LDE1LjY1ODI5MTIgMTEuOTUxMTg0NSwxNS4zNDE3MDg4IDEyLjE0NjQ0NjYsMTUuMTQ2NDQ2NiBMMTQuMjkyODkzMiwxMyBMMTIuMjk3LDEzIEMxMS4wMTE0MDE0LDEzIDkuNzg3MzEwNDYsMTIuNDUwMDg5NiA4LjkzMzM4NDQzLDExLjQ4OTI4MjEgTDcuOTA0Mzg0NDMsMTAuMzMyMjgyMSBDNy43MjA4NzAwOSwxMC4xMjU5Mzk5IDcuNzM5Mzc1Nyw5LjgwOTg5ODc4IDcuOTQ1NzE3ODgsOS42MjYzODQ0MyBDOC4xNTIwNjAwNiw5LjQ0Mjg3MDA5IDguNDY4MTAxMjIsOS40NjEzNzU3IDguNjUxNjE1NTcsOS42Njc3MTc4OCBMOS42ODA3Mjg4LDEwLjgyNDg0NTIgQzEwLjM0NTAyNTUsMTEuNTcyMjg4NCAxMS4yOTcxMDQsMTIgMTIuMjk3LDEyIEwxNC4yOTI4OTMyLDEyIFogTTAuNSw0IEMwLjIyMzg1NzYyNSw0IC01LjE5NTg0Mzc2ZS0xNCwzLjc3NjE0MjM3IC01LjE5NTg0Mzc2ZS0xNCwzLjUgQy01LjE5NTg0Mzc2ZS0xNCwzLjIyMzg1NzYzIDAuMjIzODU3NjI1LDMgMC41LDMgTDAuNzA0LDMgQzEuOTg5NTk4NTUsMyAzLjIxMzY4OTU0LDMuNTQ5OTEwNDIgNC4wNjc3NzU5NSw0LjUxMDg5ODMgTDUuMDk1Nzc1OTUsNS42Njc4OTgzIEM1LjI3OTE5MDY1LDUuODc0MzI5MDUgNS4yNjA1MzI0Niw2LjE5MDM2MTI0IDUuMDU0MTAxNyw2LjM3Mzc3NTk1IEM0Ljg0NzY3MDk1LDYuNTU3MTkwNjUgNC41MzE2Mzg3Niw2LjUzODUzMjQ2IDQuMzQ4MjI0MDUsNi4zMzIxMDE3IEwzLjMyMDI3MTIsNS4xNzUxNTQ3NiBDMi42NTU5NzQ1Miw0LjQyNzcxMTU5IDEuNzAzODk1OTksNCAwLjcwNCw0IEwwLjUsNCBaIE0xNC4yOTE4OTMyLDIuOTk5IEwxMi4xNDY0NDY2LDAuODUzNTUzMzkxIEMxMS45NTExODQ1LDAuNjU4MjkxMjQ1IDExLjk1MTE4NDUsMC4zNDE3MDg3NTUgMTIuMTQ2NDQ2NiwwLjE0NjQ0NjYwOSBDMTIuMzQxNzA4OCwtMC4wNDg4MTU1MzY1IDEyLjY1ODI5MTIsLTAuMDQ4ODE1NTM2NSAxMi44NTM1NTM0LDAuMTQ2NDQ2NjA5IEwxNS44MzQxOTg2LDMuMTI3MDkxODMgQzE1LjkzMTE4OSwzLjIxNDMwNTE3IDE1Ljk5Mzg4MDQsMy4zMzg5MTI3OCAxNS45OTk1NzU5LDMuNDc4MjE3NzQgQzE1Ljk5OTgzNzEsMy40ODUzNzc4MyAxNS45OTk5OTI4LDMuNDkyNDM5ODEgMTUuOTk5OTk5OCwzLjQ5OTUwMTkyIEMxNS45OTk5ODg0LDMuNTExMDYxMTMgMTUuOTk5NTg0OCwzLjUyMjUyODQ5IDE1Ljk5ODgwMTUsMy41MzM4OTE0MyBDMTUuOTkwOTk0NCwzLjY1MDMzMjU2IDE1Ljk0MjU1OTgsMy43NjQ1NDcwMiAxNS44NTM1NTM0LDMuODUzNTUzMzkgTDEyLjg1MzU1MzQsNi44NTM1NTMzOSBDMTIuNjU4MjkxMiw3LjA0ODgxNTU0IDEyLjM0MTcwODgsNy4wNDg4MTU1NCAxMi4xNDY0NDY2LDYuODUzNTUzMzkgQzExLjk1MTE4NDUsNi42NTgyOTEyNCAxMS45NTExODQ1LDYuMzQxNzA4NzYgMTIuMTQ2NDQ2Niw2LjE0NjQ0NjYxIEwxNC4yOTM4OTMyLDMuOTk5IEwxMi4yOTcsMy45OTkgQzExLjI5NzEwNCwzLjk5OSAxMC4zNDUwMjU1LDQuNDI2NzExNTkgOS42ODA3MTQ0NSw1LjE3NDE3MDkgTDQuMDY3NzI4OCwxMS40ODkxNTQ4IEMzLjIxMzY4OTU0LDEyLjQ1MDA4OTYgMS45ODk1OTg1NSwxMyAwLjcwNCwxMyBMMC41LDEzIEMwLjIyMzg1NzYyNSwxMyAtMi4yNjQ4NTQ5N2UtMTMsMTIuNzc2MTQyNCAtMi4yNjQ4NTQ5N2UtMTMsMTIuNSBDLTIuMjY0ODU0OTdlLTEzLDEyLjIyMzg1NzYgMC4yMjM4NTc2MjUsMTIgMC41LDEyIEwwLjcwNCwxMiBDMS43MDM4OTU5OSwxMiAyLjY1NTk3NDUyLDExLjU3MjI4ODQgMy4zMjAyODU1NSwxMC44MjQ4MjkxIEw4LjkzMzI3MTIsNC41MDk4NDUyNCBDOS43ODczMTA0NiwzLjU0ODkxMDQyIDExLjAxMTQwMTQsMi45OTkgMTIuMjk3LDIuOTk5IEwxNC4yOTE4OTMyLDIuOTk5IFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==');
|
||||
--icon-ResizePanel: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3QgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIiB4PSI0IiB5PSIyIiB3aWR0aD0iMiIgaGVpZ2h0PSIxMiIgcng9IjEiLz48L3N2Zz4=');
|
||||
--icon-RightAlign: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSw4LjUgQzIuMjIzODU3NjMsOC41IDIsOC4yNzYxNDIzNyAyLDggQzIsNy43MjM4NTc2MyAyLjIyMzg1NzYzLDcuNSAyLjUsNy41IEwxMy41LDcuNSBDMTMuNzc2MTQyNCw3LjUgMTQsNy43MjM4NTc2MyAxNCw4IEMxNCw4LjI3NjE0MjM3IDEzLjc3NjE0MjQsOC41IDEzLjUsOC41IEwyLjUsOC41IFogTTIuNSw0IEMyLjIyMzg1NzYzLDQgMiwzLjc3NjE0MjM3IDIsMy41IEMyLDMuMjIzODU3NjMgMi4yMjM4NTc2MywzIDIuNSwzIEwxMy41LDMgQzEzLjc3NjE0MjQsMyAxNCwzLjIyMzg1NzYzIDE0LDMuNSBDMTQsMy43NzYxNDIzNyAxMy43NzYxNDI0LDQgMTMuNSw0IEwyLjUsNCBaIE04LjUsMTMgQzguMjIzODU3NjMsMTMgOCwxMi43NzYxNDI0IDgsMTIuNSBDOCwxMi4yMjM4NTc2IDguMjIzODU3NjMsMTIgOC41LDEyIEwxMy41LDEyIEMxMy43NzYxNDI0LDEyIDE0LDEyLjIyMzg1NzYgMTQsMTIuNSBDMTQsMTIuNzc2MTQyNCAxMy43NzYxNDI0LDEzIDEzLjUsMTMgTDguNSwxMyBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=');
|
||||
--icon-Script: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRhdGFiYXNlIj48cmVjdCB3aWR0aD0iMTkuMzI4IiBoZWlnaHQ9IjE5LjMyOCIgeD0iMi4zMzYiIHk9IjIuMzM2IiByeT0iNC4wMjYiIHJ4PSIzLjc0NSIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PGcgc3Ryb2tlLXdpZHRoPSIxLjU0MyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciI+PHBhdGggZD0iTTE4LjczNDQ0OSA5LjAyMTI1NTZMNS4yNjU1NTEyIDkuMDA5NTM2OE0xOC43MzQ0NDkgMTQuNzM4NTg2TDUuMjY1NTUxMiAxNC43MjY4NjciIHRyYW5zZm9ybT0ibWF0cml4KC45Mzk2OSAwIDAgMS4wMDUzMSAuNzI0IC0uMDYzKSIvPjwvZz48L3N2Zz4=');
|
||||
--icon-Search: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTExLjQzNjIxMjcsMTAuNzI5MTA1OSBMMTQuODUzNTUzNCwxNC4xNDY0NDY2IEMxNS4wNDg4MTU1LDE0LjM0MTcwODggMTUuMDQ4ODE1NSwxNC42NTgyOTEyIDE0Ljg1MzU1MzQsMTQuODUzNTUzNCBDMTQuNjU4MjkxMiwxNS4wNDg4MTU1IDE0LjM0MTcwODgsMTUuMDQ4ODE1NSAxNC4xNDY0NDY2LDE0Ljg1MzU1MzQgTDEwLjcyOTEwNTksMTEuNDM2MjEyNyBDOS41OTIzMzg0OCwxMi40MTEwNDg3IDguMTE0OTQ3NzEsMTMgNi41LDEzIEMyLjkxMDE0OTEzLDEzIDAsMTAuMDg5ODUwOSAwLDYuNSBDMCwyLjkxMDE0OTEzIDIuOTEwMTQ5MTMsMCA2LjUsMCBDMTAuMDg5ODUwOSwwIDEzLDIuOTEwMTQ5MTMgMTMsNi41IEMxMyw4LjExNDk0NzcxIDEyLjQxMTA0ODcsOS41OTIzMzg0OCAxMS40MzYyMTI3LDEwLjcyOTEwNTkgWiBNMTAuNDA5NTc0NywxMC4zNjg0OTIxIEMxMS4zOTI4MzI1LDkuMzc0ODU3OCAxMiw4LjAwODMzNDY4IDEyLDYuNSBDMTIsMy40NjI0MzM4OCA5LjUzNzU2NjEyLDEgNi41LDEgQzMuNDYyNDMzODgsMSAxLDMuNDYyNDMzODggMSw2LjUgQzEsOS41Mzc1NjYxMiAzLjQ2MjQzMzg4LDEyIDYuNSwxMiBDOC4wMDgzMzQ2OCwxMiA5LjM3NDg1NzgsMTEuMzkyODMyNSAxMC4zNjg0OTIxLDEwLjQwOTU3NDcgQzEwLjM3NDkwMDEsMTAuNDAyMzg3OSAxMC4zODE1NTE2LDEwLjM5NTM0MTYgMTAuMzg4NDQ2NiwxMC4zODg0NDY2IEMxMC4zOTUzNDE2LDEwLjM4MTU1MTYgMTAuNDAyMzg3OSwxMC4zNzQ5MDAxIDEwLjQwOTU3NDcsMTAuMzY4NDkyMSBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=');
|
||||
--icon-Settings: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMuOTgwNDcxOSwzLjI3MzM2NTEyIEM0LjgxNDE0NDY1LDIuNTc4NDA2ODQgNS44NTY2MjI5MSwyLjEyNTQ1Njc0IDcsMi4wMjI0MjE1MSBMNywwLjUgQzcsMC4yMjM4NTc2MjUgNy4yMjM4NTc2MywwIDcuNSwwIEM3Ljc3NjE0MjM3LDAgOCwwLjIyMzg1NzYyNSA4LDAuNSBMOCwyLjAyMjQyMTUxIEM5LjE0MzM3NzA5LDIuMTI1NDU2NzQgMTAuMTg1ODU1NCwyLjU3ODQwNjg0IDExLjAxOTUyODEsMy4yNzMzNjUxMiBMMTIuMDk2NDQ2NiwyLjE5NjQ0NjYxIEMxMi4yOTE3MDg4LDIuMDAxMTg0NDYgMTIuNjA4MjkxMiwyLjAwMTE4NDQ2IDEyLjgwMzU1MzQsMi4xOTY0NDY2MSBDMTIuOTk4ODE1NSwyLjM5MTcwODc2IDEyLjk5ODgxNTUsMi43MDgyOTEyNCAxMi44MDM1NTM0LDIuOTAzNTUzMzkgTDExLjcyNjYzNDksMy45ODA0NzE5IEMxMi40MjE1OTMyLDQuODE0MTQ0NjUgMTIuODc0NTQzMyw1Ljg1NjYyMjkxIDEyLjk3NzU3ODUsNyBMMTQuNSw3IEMxNC43NzYxNDI0LDcgMTUsNy4yMjM4NTc2MyAxNSw3LjUgQzE1LDcuNzc2MTQyMzcgMTQuNzc2MTQyNCw4IDE0LjUsOCBMMTIuOTc3NTc4NSw4IEMxMi44NzQ1NDMzLDkuMTQzMzc3MDkgMTIuNDIxNTkzMiwxMC4xODU4NTU0IDExLjcyNjYzNDksMTEuMDE5NTI4MSBMMTIuODAzNTUzNCwxMi4wOTY0NDY2IEMxMi45OTg4MTU1LDEyLjI5MTcwODggMTIuOTk4ODE1NSwxMi42MDgyOTEyIDEyLjgwMzU1MzQsMTIuODAzNTUzNCBDMTIuNjA4MjkxMiwxMi45OTg4MTU1IDEyLjI5MTcwODgsMTIuOTk4ODE1NSAxMi4wOTY0NDY2LDEyLjgwMzU1MzQgTDExLjAxOTUyODEsMTEuNzI2NjM0OSBDMTAuMTg1ODU1NCwxMi40MjE1OTMyIDkuMTQzMzc3MDksMTIuODc0NTQzMyA4LDEyLjk3NzU3ODUgTDgsMTQuNSBDOCwxNC43NzYxNDI0IDcuNzc2MTQyMzcsMTUgNy41LDE1IEM3LjIyMzg1NzYzLDE1IDcsMTQuNzc2MTQyNCA3LDE0LjUgTDcsMTIuOTc3NTc4NSBDNS44NTY2MjI5MSwxMi44NzQ1NDMzIDQuODE0MTQ0NjUsMTIuNDIxNTkzMiAzLjk4MDQ3MTksMTEuNzI2NjM0OSBMMi45MDM1NTMzOSwxMi44MDM1NTM0IEMyLjcwODI5MTI0LDEyLjk5ODgxNTUgMi4zOTE3MDg3NiwxMi45OTg4MTU1IDIuMTk2NDQ2NjEsMTIuODAzNTUzNCBDMi4wMDExODQ0NiwxMi42MDgyOTEyIDIuMDAxMTg0NDYsMTIuMjkxNzA4OCAyLjE5NjQ0NjYxLDEyLjA5NjQ0NjYgTDMuMjczMzY1MTIsMTEuMDE5NTI4MSBDMi41Nzg0MDY4NCwxMC4xODU4NTU0IDIuMTI1NDU2NzQsOS4xNDMzNzcwOSAyLjAyMjQyMTUxLDggTDAuNSw4IEMwLjIyMzg1NzYyNSw4IDAsNy43NzYxNDIzNyAwLDcuNSBDMCw3LjIyMzg1NzYzIDAuMjIzODU3NjI1LDcgMC41LDcgTDIuMDIyNDIxNTEsNyBDMi4xMjU0NTY3NCw1Ljg1NjYyMjkxIDIuNTc4NDA2ODQsNC44MTQxNDQ2NSAzLjI3MzM2NTEyLDMuOTgwNDcxOSBMMi4xOTY0NDY2MSwyLjkwMzU1MzM5IEMyLjAwMTE4NDQ2LDIuNzA4MjkxMjQgMi4wMDExODQ0NiwyLjM5MTcwODc2IDIuMTk2NDQ2NjEsMi4xOTY0NDY2MSBDMi4zOTE3MDg3NiwyLjAwMTE4NDQ2IDIuNzA4MjkxMjQsMi4wMDExODQ0NiAyLjkwMzU1MzM5LDIuMTk2NDQ2NjEgTDMuOTgwNDcxOSwzLjI3MzM2NTEyIFogTTcuNSwxMCBDNi4xMTkyODgxMywxMCA1LDguODgwNzExODcgNSw3LjUgQzUsNi4xMTkyODgxMyA2LjExOTI4ODEzLDUgNy41LDUgQzguODgwNzExODcsNSAxMCw2LjExOTI4ODEzIDEwLDcuNSBDMTAsOC44ODA3MTE4NyA4Ljg4MDcxMTg3LDEwIDcuNSwxMCBaIE03LjUsOSBDOC4zMjg0MjcxMiw5IDksOC4zMjg0MjcxMiA5LDcuNSBDOSw2LjY3MTU3Mjg4IDguMzI4NDI3MTIsNiA3LjUsNiBDNi42NzE1NzI4OCw2IDYsNi42NzE1NzI4OCA2LDcuNSBDNiw4LjMyODQyNzEyIDYuNjcxNTcyODgsOSA3LjUsOSBaIE03LjUsMTIgQzkuOTg1MjgxMzcsMTIgMTIsOS45ODUyODEzNyAxMiw3LjUgQzEyLDUuMDE0NzE4NjMgOS45ODUyODEzNywzIDcuNSwzIEM1LjAxNDcxODYzLDMgMyw1LjAxNDcxODYzIDMsNy41IEMzLDkuOTg1MjgxMzcgNS4wMTQ3MTg2MywxMiA3LjUsMTIgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+');
|
||||
--icon-Share: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYuNzc0MTEwMDUsOS4xNDQxMzAyOSBMOS43NTY4Nzg3NiwxMS4wMDc4NjUxIEMxMC4zMDY0MDY1LDEwLjM4OTU1MjMgMTEuMTA3NzEwOSwxMCAxMiwxMCBDMTMuNjU2ODU0MiwxMCAxNSwxMS4zNDMxNDU4IDE1LDEzIEMxNSwxNC42NTY4NTQyIDEzLjY1Njg1NDIsMTYgMTIsMTYgQzEwLjM0MzE0NTgsMTYgOSwxNC42NTY4NTQyIDksMTMgQzksMTIuNTk0NjU3MiA5LjA4MDM4OTUzLDEyLjIwODA5MDQgOS4yMjYwOTUwNywxMS44NTUzNzMgTDYuMjQzNDc4MjQsOS45OTE3MzMxNyBDNS42OTM5NDA1NSwxMC42MTAyNzkgNC44OTI0ODIzOSwxMSA0LDExIEMyLjM0MzE0NTc1LDExIDEsOS42NTY4NTQyNSAxLDggQzEsNi4zNDMxNDU3NSAyLjM0MzE0NTc1LDUgNCw1IEM0Ljg5MjQ4MjM5LDUgNS42OTM5NDA1NSw1LjM4OTcyMTAzIDYuMjQzNDc4MjQsNi4wMDgyNjY4MyBMOS4yMjYwOTUwNyw0LjE0NDYyNjk2IEM5LjA4MDM4OTUzLDMuNzkxOTA5NjMgOSwzLjQwNTM0MjggOSwzIEM5LDEuMzQzMTQ1NzUgMTAuMzQzMTQ1OCwwIDEyLDAgQzEzLjY1Njg1NDIsMCAxNSwxLjM0MzE0NTc1IDE1LDMgQzE1LDQuNjU2ODU0MjUgMTMuNjU2ODU0Miw2IDEyLDYgQzExLjEwNzcxMDksNiAxMC4zMDY0MDY1LDUuNjEwNDQ3NzMgOS43NTY4Nzg3Niw0Ljk5MjEzNDkzIEw2Ljc3NDExMDA1LDYuODU1ODY5NzEgQzYuOTE5Njg1OTIsNy4yMDg0NTMyNSA3LDcuNTk0ODQ3NDQgNyw4IEM3LDguNDA1MTUyNTYgNi45MTk2ODU5Miw4Ljc5MTU0Njc1IDYuNzc0MTEwMDUsOS4xNDQxMzAyOSBaIE01LjcwNDEyNDg1LDkuMDQ3NDE3MTYgQzUuODkxNzYwNjYsOC43NDI3ODczNCA2LDguMzg0MDM0IDYsOCBDNiw3LjYxNTk2NiA1Ljg5MTc2MDY2LDcuMjU3MjEyNjYgNS43MDQxMjQ4NSw2Ljk1MjU4Mjg0IEM1LjcwMTM1MzQ2LDYuOTQ4NDI1NzcgNS42OTg2MzQ0Miw2Ljk0NDIxNDMyIDUuNjk1OTY5MTgsNi45Mzk5NDg4IEM1LjY5MzQ2MjQ3LDYuOTM1OTM2OTkgNS42OTEwMTk1OSw2LjkzMTkwMzM4IDUuNjg4NjQwMjksNi45Mjc4NDkxNSBDNS4zMzM3NDk5MSw2LjM3MDA2MTY1IDQuNzEwMDkxMjIsNiA0LDYgQzIuODk1NDMwNSw2IDIsNi44OTU0MzA1IDIsOCBDMiw5LjEwNDU2OTUgMi44OTU0MzA1LDEwIDQsMTAgQzQuNzEwMDkxMjIsMTAgNS4zMzM3NDk5MSw5LjYyOTkzODM1IDUuNjg4NjQwMjksOS4wNzIxNTA4NSBDNS42OTEwMTk1OSw5LjA2ODA5NjYyIDUuNjkzNDYyNDcsOS4wNjQwNjMwMSA1LjY5NTk2OTE4LDkuMDYwMDUxMiBDNS42OTg2MzQ0Miw5LjA1NTc4NTY4IDUuNzAxMzUzNDYsOS4wNTE1NzQyMyA1LjcwNDEyNDg1LDkuMDQ3NDE3MTYgWiBNMTAuMzE5OTA5MiwxMS45MTQ1MjgzIEMxMC4zMTUyMjY4LDExLjkyMzA4OTcgMTAuMzEwMjY4MSwxMS45MzE1NjY5IDEwLjMwNTAzMDgsMTEuOTM5OTQ4OCBDMTAuMjk5NTk2LDExLjk0ODY0NjggMTAuMjkzOTM3NiwxMS45NTcxMTk5IDEwLjI4ODA2NzYsMTEuOTY1MzY1NSBDMTAuMTA1MjQsMTIuMjY3MjI4NiAxMCwxMi42MjEzMjQzIDEwLDEzIEMxMCwxNC4xMDQ1Njk1IDEwLjg5NTQzMDUsMTUgMTIsMTUgQzEzLjEwNDU2OTUsMTUgMTQsMTQuMTA0NTY5NSAxNCwxMyBDMTQsMTEuODk1NDMwNSAxMy4xMDQ1Njk1LDExIDEyLDExIEMxMS4yOTU1NzY3LDExIDEwLjY3NjIxMTcsMTEuMzY0MTc3NSAxMC4zMTk5MDkyLDExLjkxNDUyODMgWiBNMTAuMjg4MDY3Niw0LjAzNDYzNDU0IEMxMC4yOTM5Mzc2LDQuMDQyODgwMDkgMTAuMjk5NTk2LDQuMDUxMzUzMjUgMTAuMzA1MDMwOCw0LjA2MDA1MTIgQzEwLjMxMDI2ODEsNC4wNjg0MzMxNSAxMC4zMTUyMjY4LDQuMDc2OTEwMjUgMTAuMzE5OTA5Miw0LjA4NTQ3MTY4IEMxMC42NzYyMTE3LDQuNjM1ODIyNDUgMTEuMjk1NTc2Nyw1IDEyLDUgQzEzLjEwNDU2OTUsNSAxNCw0LjEwNDU2OTUgMTQsMyBDMTQsMS44OTU0MzA1IDEzLjEwNDU2OTUsMSAxMiwxIEMxMC44OTU0MzA1LDEgMTAsMS44OTU0MzA1IDEwLDMgQzEwLDMuMzc4Njc1NzQgMTAuMTA1MjQsMy43MzI3NzEzNyAxMC4yODgwNjc2LDQuMDM0NjM0NTQgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+');
|
||||
|
67
static/ui-icons/UI/Database.svg
Normal file
67
static/ui-icons/UI/Database.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-database"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="Database.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="27.812867"
|
||||
inkscape:cx="11.447057"
|
||||
inkscape:cy="12.185654"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<ellipse
|
||||
cx="12"
|
||||
cy="5"
|
||||
rx="9"
|
||||
ry="3"
|
||||
id="ellipse2"
|
||||
style="stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"
|
||||
id="path6"
|
||||
style="stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
83
static/ui-icons/UI/Script.svg
Normal file
83
static/ui-icons/UI/Script.svg
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-database"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="Script.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="32.000001"
|
||||
inkscape:cx="14.363543"
|
||||
inkscape:cy="10.701744"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
|
||||
id="rect1093"
|
||||
width="19.328373"
|
||||
height="19.328371"
|
||||
x="2.3358135"
|
||||
y="2.3358145"
|
||||
ry="4.0264831"
|
||||
rx="3.7452335" />
|
||||
<g
|
||||
id="g1136"
|
||||
transform="matrix(0.9396903,0,0,1.0053101,0.72371631,-0.06296982)"
|
||||
style="stroke-width:1.5432947;stroke-miterlimit:4;stroke-dasharray:none">
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1107"
|
||||
d="M 18.734449,9.0212556 5.2655512,9.0095368"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.5432947;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1107-3"
|
||||
d="M 18.734449,14.738586 5.2655512,14.726867"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.5432947;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
Loading…
Reference in New Issue
Block a user