mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Import redesign
Summary: New UI design for incremental imports. Test Plan: Updated Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3945
This commit is contained in:
parent
05c15e4ec3
commit
6416994c22
@ -16,7 +16,7 @@ import * as DocConfigTab from 'app/client/components/DocConfigTab';
|
||||
import {Drafts} from "app/client/components/Drafts";
|
||||
import {EditorMonitor} from "app/client/components/EditorMonitor";
|
||||
import * as GridView from 'app/client/components/GridView';
|
||||
import {Importer} from 'app/client/components/Importer';
|
||||
import {importFromFile, selectAndImport} from 'app/client/components/Importer';
|
||||
import {RawDataPage, RawDataPopup} from 'app/client/components/RawDataPage';
|
||||
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
||||
import {ViewLayout} from 'app/client/components/ViewLayout';
|
||||
@ -423,11 +423,11 @@ export class GristDoc extends DisposableWithEvents {
|
||||
const importMenuItems = [
|
||||
{
|
||||
label: t("Import from file"),
|
||||
action: () => Importer.selectAndImport(this, importSourceElems, null, createPreview),
|
||||
action: () => importFromFile(this, createPreview),
|
||||
},
|
||||
...importSourceElems.map(importSourceElem => ({
|
||||
label: importSourceElem.importSource.label,
|
||||
action: () => Importer.selectAndImport(this, importSourceElems, importSourceElem, createPreview)
|
||||
action: () => selectAndImport(this, importSourceElems, importSourceElem, createPreview)
|
||||
}))
|
||||
];
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -68,7 +68,7 @@ export function buildParseOptionsForm(
|
||||
cssModalButtons(
|
||||
dom.domComputed((use) => items.every((item) => use(optionsMap.get(item.name)!) === values[item.name]),
|
||||
(unchanged) => (unchanged ?
|
||||
bigBasicButton('Back to preview', dom.on('click', doCancel), testId('parseopts-back')) :
|
||||
bigBasicButton('Close', dom.on('click', doCancel), testId('parseopts-back')) :
|
||||
bigPrimaryButton('Update preview', dom.on('click', () => doUpdate(collectParseOptions())),
|
||||
testId('parseopts-update'))
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { makeT } from 'app/client/lib/localization';
|
||||
import { bigBasicButton } from 'app/client/ui2018/buttons';
|
||||
import { testId } from 'app/client/ui2018/cssVars';
|
||||
import { testId, theme } from 'app/client/ui2018/cssVars';
|
||||
import { loadingSpinner } from 'app/client/ui2018/loaders';
|
||||
import { cssModalButtons, cssModalTitle, IModalControl, modal } from 'app/client/ui2018/modals';
|
||||
import { cssModalButtons, cssModalTitle, IModalControl, IModalOptions, modal } from 'app/client/ui2018/modals';
|
||||
import { PluginInstance } from 'app/common/PluginInstance';
|
||||
import { RenderTarget } from 'app/plugin/RenderOptions';
|
||||
import { Disposable, dom, DomContents, Observable, styled } from 'grainjs';
|
||||
@ -15,6 +15,7 @@ const t = makeT('PluginScreen');
|
||||
export interface RenderOptions {
|
||||
// Maximizes modal to fill the viewport.
|
||||
fullscreen?: boolean;
|
||||
fullbody?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,6 +25,7 @@ export class PluginScreen extends Disposable {
|
||||
private _openModalCtl: IModalControl | null = null;
|
||||
private _importerContent = Observable.create<DomContents>(this, null);
|
||||
private _fullscreen = Observable.create(this, false);
|
||||
private _fullbody = Observable.create(this, false);
|
||||
|
||||
constructor(private _title: string) {
|
||||
super();
|
||||
@ -46,13 +48,15 @@ export class PluginScreen extends Disposable {
|
||||
}
|
||||
|
||||
public render(content: DomContents, options?: RenderOptions) {
|
||||
this._fullscreen.set(Boolean(options?.fullscreen));
|
||||
this._fullbody.set(Boolean(options?.fullbody));
|
||||
this.showImportDialog();
|
||||
this._importerContent.set(content);
|
||||
this._fullscreen.set(Boolean(options?.fullscreen));
|
||||
}
|
||||
|
||||
// The importer state showing just an error.
|
||||
public renderError(message: string) {
|
||||
this._fullbody.set(false);
|
||||
this.render([
|
||||
this._buildModalTitle(),
|
||||
cssModalBody(t("Import failed: "), message, testId('importer-error')),
|
||||
@ -66,6 +70,7 @@ export class PluginScreen extends Disposable {
|
||||
// The importer state showing just a spinner, when the user has to wait. We don't even let the
|
||||
// user cancel it, because the cleanup can only happen properly once the wait completes.
|
||||
public renderSpinner() {
|
||||
this._fullbody.set(false);
|
||||
this.render([this._buildModalTitle(), cssSpinner(loadingSpinner())]);
|
||||
}
|
||||
|
||||
@ -74,19 +79,28 @@ export class PluginScreen extends Disposable {
|
||||
this._openModalCtl = null;
|
||||
}
|
||||
|
||||
public showImportDialog() {
|
||||
public showImportDialog(options?: IModalOptions) {
|
||||
if (this._openModalCtl) { return; }
|
||||
modal((ctl) => {
|
||||
modal((ctl, ctlOwner) => {
|
||||
this._openModalCtl = ctl;
|
||||
|
||||
// Make sure we are close when parent is closed.
|
||||
this.onDispose(() => {
|
||||
if (ctlOwner.isDisposed()) { return; }
|
||||
ctl.close();
|
||||
});
|
||||
|
||||
return [
|
||||
cssModalOverrides.cls(''),
|
||||
cssModalOverrides.cls('-fullscreen', this._fullscreen),
|
||||
cssModalOverrides.cls('-fullbody', this._fullbody),
|
||||
dom.domComputed(this._importerContent),
|
||||
testId('importer-dialog'),
|
||||
];
|
||||
}, {
|
||||
noClickAway: true,
|
||||
noEscapeKey: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
@ -108,6 +122,11 @@ const cssModalOverrides = styled('div', `
|
||||
height: 100%;
|
||||
margin: 32px;
|
||||
}
|
||||
|
||||
&-fullbody {
|
||||
padding: 0px;
|
||||
background-color: ${theme.importerOutsideBg};
|
||||
}
|
||||
`);
|
||||
|
||||
const cssModalBody = styled('div', `
|
||||
|
@ -10,7 +10,7 @@
|
||||
import * as Mousetrap from 'app/client/lib/Mousetrap';
|
||||
import {arrayRemove} from 'app/common/gutil';
|
||||
import {RefCountMap} from 'app/common/RefCountMap';
|
||||
import {Disposable, dom} from 'grainjs';
|
||||
import {Disposable, dom, DomMethod} from 'grainjs';
|
||||
|
||||
/**
|
||||
* The default focus is organized into layers. A layer determines when focus should move to the
|
||||
@ -136,6 +136,17 @@ export class FocusLayer extends Disposable implements FocusLayerOptions {
|
||||
_focusLayerManager.get(null)?.grabFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new FocusLayer and attaches it to the given element. The layer will be disposed
|
||||
* automatically when the element is removed from the DOM.
|
||||
*/
|
||||
public static attach(options: Partial<FocusLayerOptions>): DomMethod<HTMLElement> {
|
||||
return (element: HTMLElement) => {
|
||||
const layer = FocusLayer.create(null, {defaultFocusElem: element, ...options});
|
||||
dom.autoDisposeElem(element, layer);
|
||||
};
|
||||
}
|
||||
|
||||
public defaultFocusElem: HTMLElement;
|
||||
public allowFocus: (elem: Element) => boolean;
|
||||
public _onDefaultFocus?: () => void;
|
||||
|
@ -37,6 +37,9 @@ export type IconName = "ChartArea" |
|
||||
"GristLogo" |
|
||||
"ThumbPreview" |
|
||||
"AddUser" |
|
||||
"ArrowLeft" |
|
||||
"ArrowRight" |
|
||||
"ArrowRightOutlined" |
|
||||
"BarcodeQR" |
|
||||
"BarcodeQR2" |
|
||||
"Board" |
|
||||
@ -56,6 +59,7 @@ export type IconName = "ChartArea" |
|
||||
"Dropdown" |
|
||||
"DropdownUp" |
|
||||
"Empty" |
|
||||
"Exclamation" |
|
||||
"Expand" |
|
||||
"EyeHide" |
|
||||
"EyeShow" |
|
||||
@ -80,6 +84,7 @@ export type IconName = "ChartArea" |
|
||||
"ImportArrow" |
|
||||
"Info" |
|
||||
"LeftAlign" |
|
||||
"Lighting" |
|
||||
"Lock" |
|
||||
"Log" |
|
||||
"Mail" |
|
||||
@ -180,6 +185,9 @@ export const IconList: IconName[] = ["ChartArea",
|
||||
"GristLogo",
|
||||
"ThumbPreview",
|
||||
"AddUser",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowRightOutlined",
|
||||
"BarcodeQR",
|
||||
"BarcodeQR2",
|
||||
"Board",
|
||||
@ -199,6 +207,7 @@ export const IconList: IconName[] = ["ChartArea",
|
||||
"Dropdown",
|
||||
"DropdownUp",
|
||||
"Empty",
|
||||
"Exclamation",
|
||||
"Expand",
|
||||
"EyeHide",
|
||||
"EyeShow",
|
||||
@ -223,6 +232,7 @@ export const IconList: IconName[] = ["ChartArea",
|
||||
"ImportArrow",
|
||||
"Info",
|
||||
"LeftAlign",
|
||||
"Lighting",
|
||||
"Lock",
|
||||
"Log",
|
||||
"Mail",
|
||||
|
@ -626,6 +626,14 @@ export const theme = {
|
||||
importerSkippedTableOverlay: new CustomProp('theme-importer-skipped-table-overlay', undefined,
|
||||
colors.mediumGrey),
|
||||
importerMatchIcon: new CustomProp('theme-importer-match-icon', undefined, colors.darkGrey),
|
||||
importerOutsideBg: new CustomProp('theme-importer-outside-bg', undefined, colors.lightGrey),
|
||||
importerMainContentBg: new CustomProp('theme-importer-main-content-bg', undefined, '#FFFFFF'),
|
||||
|
||||
// tabs
|
||||
importerActiveFileBg: new CustomProp('theme-importer-active-file-bg', undefined, colors.lightGreen),
|
||||
importerActiveFileFg: new CustomProp('theme-importer-active-file-fg', undefined, colors.light),
|
||||
importerInactiveFileBg: new CustomProp('theme-importer-inactive-file-bg', undefined, colors.mediumGrey),
|
||||
importerInactiveFileFg: new CustomProp('theme-importer-inactive-file-fg', undefined, colors.light),
|
||||
|
||||
/* Menu Toggles */
|
||||
menuToggleFg: new CustomProp('theme-menu-toggle-fg', undefined, colors.slate),
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Command } from 'app/client/components/commands';
|
||||
import { FocusLayer } from 'app/client/lib/FocusLayer';
|
||||
import { makeT } from 'app/client/lib/localization';
|
||||
import { NeedUpgradeError, reportError } from 'app/client/models/errors';
|
||||
import { textButton } from 'app/client/ui2018/buttons';
|
||||
@ -203,6 +204,7 @@ export function multiSelect<T>(selectedOptions: MutableObsArray<T>,
|
||||
return cssMultiSelectMenu(
|
||||
{ tabindex: '-1' }, // Allow menu to be focused.
|
||||
dom.cls(menuCssClass),
|
||||
FocusLayer.attach({pauseMousetrap: true}),
|
||||
dom.onKeyDown({
|
||||
Enter: () => ctl.close(),
|
||||
Escape: () => ctl.close()
|
||||
@ -508,12 +510,6 @@ const cssSelectBtnLink = styled('div', `
|
||||
}
|
||||
`);
|
||||
|
||||
const cssOptionIcon = styled(icon, `
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: ${theme.menuItemIconFg};
|
||||
margin: -3px 8px 0 2px;
|
||||
`);
|
||||
|
||||
export const cssOptionRow = styled('span', `
|
||||
display: flex;
|
||||
@ -521,7 +517,11 @@ export const cssOptionRow = styled('span', `
|
||||
width: 100%;
|
||||
`);
|
||||
|
||||
export const cssOptionRowIcon = styled(cssOptionIcon, `
|
||||
export const cssOptionRowIcon = styled(icon, `
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: var(--icon-color, ${theme.menuItemIconFg});
|
||||
margin: -3px 8px 0 2px;
|
||||
margin: 0 8px 0 0;
|
||||
flex: none;
|
||||
|
||||
|
@ -524,17 +524,35 @@ export const cssModalTooltip = styled(cssMenuElem, `
|
||||
}
|
||||
`);
|
||||
|
||||
export const cssModalTopPadding = styled('div', `
|
||||
padding-top: var(--css-modal-dialog-padding-vertical);
|
||||
`);
|
||||
|
||||
export const cssModalBottomPadding = styled('div', `
|
||||
padding-bottom: var(--css-modal-dialog-padding-vertical);
|
||||
`);
|
||||
|
||||
export const cssModalHorizontalPadding = styled('div', `
|
||||
padding-left: var(--css-modal-dialog-padding-horizontal);
|
||||
padding-right: var(--css-modal-dialog-padding-horizontal);
|
||||
`);
|
||||
|
||||
// For centering, we use 'margin: auto' on the flex item instead of 'justify-content: center' on
|
||||
// the flex container, to ensure the full item can be scrolled in case of overflow.
|
||||
// See https://stackoverflow.com/a/33455342/328565
|
||||
const cssModalDialog = styled('div', `
|
||||
//
|
||||
// If you want to control the padding yourself, use the cssModalTopPadding and other classes above and add -full-body
|
||||
// variant to the modal.
|
||||
export const cssModalDialog = styled('div', `
|
||||
--css-modal-dialog-padding-horizontal: 64px;
|
||||
--css-modal-dialog-padding-vertical: 40px;
|
||||
background-color: ${theme.modalBg};
|
||||
min-width: 428px;
|
||||
color: ${theme.darkText};
|
||||
margin: auto;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 18px 0 ${theme.modalInnerShadow}, 0 0 1px 0 ${theme.modalOuterShadow};
|
||||
padding: 40px 64px;
|
||||
padding: var(--css-modal-dialog-padding-vertical) var(--css-modal-dialog-padding-horizontal);
|
||||
outline: none;
|
||||
|
||||
&-normal {
|
||||
@ -552,9 +570,13 @@ const cssModalDialog = styled('div', `
|
||||
& {
|
||||
width: unset;
|
||||
min-width: unset;
|
||||
padding: 24px 16px;
|
||||
--css-modal-dialog-padding-horizontal: 16px;
|
||||
--css-modal-dialog-padding-vertical: 24px;
|
||||
}
|
||||
}
|
||||
&-full-body {
|
||||
padding: 0;
|
||||
}
|
||||
`);
|
||||
|
||||
export const cssModalTitle = styled('div', `
|
||||
|
@ -41,7 +41,7 @@ export const cssSelectBtn = styled('div', `
|
||||
box-shadow: 0px 0px 2px 2px #5E9ED6;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
&.disabled, &-disabled {
|
||||
--icon-color: ${theme.selectButtonDisabledFg};
|
||||
color: ${theme.selectButtonDisabledFg};
|
||||
cursor: pointer;
|
||||
|
@ -45,21 +45,51 @@ export interface TransformRuleMap {
|
||||
|
||||
// Special values for import destinations; null means "new table", "" means skip table.
|
||||
// Both special options exposed as consts.
|
||||
export type DestId = string | null;
|
||||
export const NEW_TABLE = null;
|
||||
export const SKIP_TABLE = "";
|
||||
export type DestId = string | typeof NEW_TABLE | typeof SKIP_TABLE;
|
||||
|
||||
/**
|
||||
* How to import data into an existing table or a new one.
|
||||
*/
|
||||
export interface TransformRule {
|
||||
/**
|
||||
* The destination table for the transformed data. If null, the data is imported into a new table.
|
||||
*/
|
||||
destTableId: DestId;
|
||||
/**
|
||||
* The list of columns to update (existing or new columns).
|
||||
*/
|
||||
destCols: TransformColumn[];
|
||||
/**
|
||||
* The list of columns to read from the source table (just the headers name).
|
||||
*/
|
||||
sourceCols: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Existing or new column to update. It is created based on the temporary table that was imported.
|
||||
*/
|
||||
export interface TransformColumn {
|
||||
/**
|
||||
* Label of the column to update. For new table it is the same name as the source column.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Column id to update (null for a new table).
|
||||
*/
|
||||
colId: string|null;
|
||||
/**
|
||||
* Type of the column (important for new columns).
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Formula to apply to the target column.
|
||||
*/
|
||||
formula: string;
|
||||
/**
|
||||
* Widget options when we need to create a column (copied from the source).
|
||||
*/
|
||||
widgetOptions: string;
|
||||
}
|
||||
|
||||
|
@ -300,6 +300,12 @@ export const ThemeColors = t.iface([], {
|
||||
"importer-preview-border": "string",
|
||||
"importer-skipped-table-overlay": "string",
|
||||
"importer-match-icon": "string",
|
||||
"importer-outside-bg": "string",
|
||||
"importer-main-content-bg": "string",
|
||||
"importer-active-file-bg": "string",
|
||||
"importer-active-file-fg": "string",
|
||||
"importer-inactive-file-bg": "string",
|
||||
"importer-inactive-file-fg": "string",
|
||||
"menu-toggle-fg": "string",
|
||||
"menu-toggle-hover-fg": "string",
|
||||
"menu-toggle-active-fg": "string",
|
||||
|
@ -392,6 +392,12 @@ export interface ThemeColors {
|
||||
'importer-preview-border': string;
|
||||
'importer-skipped-table-overlay': string;
|
||||
'importer-match-icon': string;
|
||||
'importer-outside-bg': string;
|
||||
'importer-main-content-bg': string;
|
||||
'importer-active-file-bg': string;
|
||||
'importer-active-file-fg': string;
|
||||
'importer-inactive-file-bg': string;
|
||||
'importer-inactive-file-fg': string;
|
||||
|
||||
/* Menu Toggles */
|
||||
'menu-toggle-fg': string;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {delay} from 'app/common/delay';
|
||||
import {BindableValue, DomElementMethod, ISubscribable, Listener, Observable, subscribeElem, UseCB} from 'grainjs';
|
||||
import {BindableValue, DomElementMethod, IKnockoutReadObservable, ISubscribable, Listener, Observable,
|
||||
subscribeElem, UseCB, UseCBOwner} from 'grainjs';
|
||||
import {Observable as KoObservable} from 'knockout';
|
||||
import identity = require('lodash/identity');
|
||||
|
||||
@ -76,7 +77,7 @@ export function clamp(value: number, min: number, max: number): number {
|
||||
/**
|
||||
* Checks if ele is contained within the given bounds.
|
||||
* @param {Number} value
|
||||
* @param {Number} bound1 - does not have to be less than/eqal to bound2
|
||||
* @param {Number} bound1 - does not have to be less than/equal to bound2
|
||||
* @param {Number} bound2
|
||||
* @returns {Boolean} - True/False
|
||||
*/
|
||||
@ -713,7 +714,7 @@ export function cloneFunc(fn: Function): Function { // tslint:disable-line:b
|
||||
|
||||
/**
|
||||
* Generates a random id using a sequence of uppercase alphanumeric characters
|
||||
* preceeded by an optional prefix.
|
||||
* preceded by an optional prefix.
|
||||
*/
|
||||
export function genRandomId(len: number, optPrefix?: string): string {
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
@ -926,6 +927,11 @@ export const unwrap: UseCB = (obs: ISubscribable) => {
|
||||
return (obs as ko.Observable).peek();
|
||||
};
|
||||
|
||||
/**
|
||||
* Use helper for simple boolean negation.
|
||||
*/
|
||||
export const not = (obs: Observable<any>|IKnockoutReadObservable<any>) => (use: UseCBOwner) => !use(obs);
|
||||
|
||||
/**
|
||||
* Get a set of up to `count` distinct values of `values`.
|
||||
*/
|
||||
|
@ -371,6 +371,12 @@ export const GristDark: ThemeColors = {
|
||||
'importer-preview-border': '#69697D',
|
||||
'importer-skipped-table-overlay': 'rgba(111,111,117,0.6)',
|
||||
'importer-match-icon': '#69697D',
|
||||
'importer-outside-bg': '#32323F',
|
||||
'importer-main-content-bg': '#262633',
|
||||
'importer-active-file-bg': '#16B378',
|
||||
'importer-active-file-fg': '#FFFFFF',
|
||||
'importer-inactive-file-bg': '#808080',
|
||||
'importer-inactive-file-fg': '#FFFFFF',
|
||||
|
||||
/* Menu Toggles */
|
||||
'menu-toggle-fg': '#A4A4A4',
|
||||
|
@ -371,6 +371,12 @@ export const GristLight: ThemeColors = {
|
||||
'importer-preview-border': '#D9D9D9',
|
||||
'importer-skipped-table-overlay': 'rgba(217,217,217,0.6)',
|
||||
'importer-match-icon': '#D9D9D9',
|
||||
'importer-outside-bg': '#F7F7F7',
|
||||
'importer-main-content-bg': '#FFFFFF',
|
||||
'importer-active-file-bg': '#16B378',
|
||||
'importer-active-file-fg': '#FFFFFF',
|
||||
'importer-inactive-file-bg': 'rgba(217,217,217,0.6)',
|
||||
'importer-inactive-file-fg': '#FFFFFF',
|
||||
|
||||
/* Menu Toggles */
|
||||
'menu-toggle-fg': '#929299',
|
||||
|
File diff suppressed because one or more lines are too long
@ -447,7 +447,11 @@
|
||||
"Importer": {
|
||||
"Merge rows that match these fields:": "Merge rows that match these fields:",
|
||||
"Select fields to match on": "Select fields to match on",
|
||||
"Update existing records": "Update existing records"
|
||||
"Update existing records": "Update existing records",
|
||||
"{{count}} unmatched field in import_one": "{{count}} unmatched field in import",
|
||||
"{{count}} unmatched field in import_other": "{{count}} unmatched fields in import",
|
||||
"{{count}} unmatched field_one": "{{count}} unmatched field",
|
||||
"{{count}} unmatched field_other": "{{count}} unmatched fields"
|
||||
},
|
||||
"LeftPanelCommon": {
|
||||
"Help Center": "Help Center"
|
||||
|
3
static/ui-icons/UI/ArrowLeft.svg
Normal file
3
static/ui-icons/UI/ArrowLeft.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" preserveAspectRatio="xMidYMid meet">
|
||||
<path d="M 8.7849346,14.670753 2.7846296,8.6213225 c -0.3682084,-0.37123 -0.3682084,-0.9731 0,-1.34432 l 6.000305,-6.049424 c 0.36821,-0.37122276 0.96519,-0.37122276 1.3334004,0 0.36821,0.371223 0.36821,0.973094 0,1.344314 l -4.3907504,4.42669 h 7.7808904 v 1.90115 H 5.7275846 l 4.3907504,4.4267205 c 0.36821,0.3712 0.36821,0.9731 0,1.3443 -0.3682104,0.3712 -0.9651904,0.3712 -1.3334004,0 z" style="clip-rule:evenodd;fill:#000000;fill-rule:evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 599 B |
3
static/ui-icons/UI/ArrowRight.svg
Normal file
3
static/ui-icons/UI/ArrowRight.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" preserveAspectRatio="xMidYMid meet">
|
||||
<path d="M 7.2320137,14.670753 13.232319,8.6213225 c 0.368208,-0.37123 0.368208,-0.9731 0,-1.34432 L 7.2320137,1.2275785 c -0.36821,-0.37122276 -0.96519,-0.37122276 -1.3334004,0 -0.36821,0.371223 -0.36821,0.973094 0,1.344314 l 4.3907507,4.42669 H 2.5084733 v 1.90115 H 10.289364 L 5.8986133,13.326453 c -0.36821,0.3712 -0.36821,0.9731 0,1.3443 0.3682104,0.3712 0.9651904,0.3712 1.3334004,0 z" style="clip-rule:evenodd;fill:#000000;fill-rule:evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 599 B |
3
static/ui-icons/UI/ArrowRightOutlined.svg
Normal file
3
static/ui-icons/UI/ArrowRightOutlined.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 1.50001L15.5 8.00001L7.5 14.5V10.5H3C2.33696 10.5 1.70107 10.2366 1.23223 9.76777C0.763392 9.29893 0.5 8.66305 0.5 8.00001C0.5 7.33697 0.763392 6.70108 1.23223 6.23224C1.70107 5.7634 2.33696 5.50001 3 5.50001H7.5V1.50001Z" stroke="#D9D9D9" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 431 B |
4
static/ui-icons/UI/Exclamation.svg
Normal file
4
static/ui-icons/UI/Exclamation.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 1H6.5L7 10H9L9.5 1Z" fill="#000"/>
|
||||
<path d="M8 15C8.82843 15 9.5 14.3284 9.5 13.5C9.5 12.6716 8.82843 12 8 12C7.17157 12 6.5 12.6716 6.5 13.5C6.5 14.3284 7.17157 15 8 15Z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 307 B |
11
static/ui-icons/UI/Lighting.svg
Normal file
11
static/ui-icons/UI/Lighting.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<g clip-path="url(#clip0_745_18279)">
|
||||
<path d="M14.9999 6.00001H8.39994L9.99994 1.30001C10.2999 0.300011 9.09995 -0.499989 8.29994 0.300011L0.299945 8.30001C-0.300055 8.90001 0.0999446 10 0.999945 10H7.59994L5.99994 14.7C5.69994 15.7 6.89994 16.5 7.69994 15.7L15.6999 7.70001C16.2999 7.10001 15.8999 6.00001 14.9999 6.00001Z" fill="#000" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_745_18279">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 630 B |
Loading…
Reference in New Issue
Block a user