@kanaries/graphic-walker 0.3.13 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/viewQuery.worker-03404216.js.map +1 -1
- package/dist/components/dataTable/index.d.ts +2 -2
- package/dist/components/timeoutImg.d.ts +5 -0
- package/dist/dataSource/utils.d.ts +1 -1
- package/dist/graphic-walker.es.js +24450 -24270
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +142 -136
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/interfaces.d.ts +11 -0
- package/dist/lib/inferMeta.d.ts +1 -1
- package/dist/lib/viewQuery.d.ts +2 -2
- package/dist/services.d.ts +3 -3
- package/dist/store/index.d.ts +3 -8
- package/dist/store/visualSpecStore.d.ts +428 -2
- package/dist/utils/dataPrep.d.ts +2 -2
- package/dist/utils/is-plain-object.d.ts +2 -0
- package/dist/utils/save.d.ts +2 -2
- package/package.json +1 -1
- package/src/assets/kanaries.png +0 -0
- package/src/components/dataTable/index.tsx +154 -74
- package/src/components/dataTable/pagination.tsx +1 -1
- package/src/components/timeoutImg.tsx +29 -0
- package/src/components/toolbar/components.tsx +1 -0
- package/src/dataSource/utils.ts +15 -13
- package/src/fields/filterField/slider.tsx +1 -1
- package/src/fields/filterField/tabs.tsx +67 -18
- package/src/interfaces.ts +16 -0
- package/src/lib/inferMeta.ts +7 -4
- package/src/lib/viewQuery.ts +2 -2
- package/src/services.ts +3 -3
- package/src/store/index.tsx +46 -45
- package/src/store/visualSpecStore.ts +57 -5
- package/src/utils/dataPrep.ts +21 -28
- package/src/utils/is-plain-object.ts +33 -0
- package/src/utils/save.ts +2 -2
- package/src/visualSettings/index.tsx +21 -27
- package/src/assets/kanaries-logo.svg +0 -1
package/src/lib/inferMeta.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IAnalyticType, IMutField, IRow, ISemanticType, IUncertainMutField } from '../interfaces';
|
|
2
|
+
import { getValueByKeyPath } from '../utils/dataPrep';
|
|
2
3
|
|
|
3
4
|
const COMMON_TIME_FORMAT: RegExp[] = [
|
|
4
5
|
/^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD
|
|
@@ -55,12 +56,12 @@ function inferAnalyticTypeFromSemanticType(semanticType: ISemanticType): IAnalyt
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
export function inferSemanticType(data: IRow[],
|
|
59
|
-
const values = data.map((row) => row
|
|
59
|
+
export function inferSemanticType(data: IRow[], path: string[]): ISemanticType {
|
|
60
|
+
const values = data.map((row) => getValueByKeyPath(row, path));
|
|
60
61
|
|
|
61
62
|
let st: ISemanticType = isNumericArray(values) ? 'quantitative' : 'nominal';
|
|
62
63
|
if (st === 'nominal') {
|
|
63
|
-
if (isDateTimeArray(data.map((row) => `${row
|
|
64
|
+
if (isDateTimeArray(data.map((row) => `${getValueByKeyPath(row, path)}`))) st = 'temporal';
|
|
64
65
|
}
|
|
65
66
|
return st;
|
|
66
67
|
}
|
|
@@ -70,7 +71,7 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
|
|
|
70
71
|
const finalFieldMetas: IMutField[] = [];
|
|
71
72
|
for (let field of fields) {
|
|
72
73
|
let semanticType: ISemanticType =
|
|
73
|
-
field.semanticType === '?' ? inferSemanticType(dataSource, field.
|
|
74
|
+
field.semanticType === '?' ? inferSemanticType(dataSource, field.path) : field.semanticType;
|
|
74
75
|
let analyticType: IAnalyticType = inferAnalyticTypeFromSemanticType(semanticType);
|
|
75
76
|
|
|
76
77
|
finalFieldMetas.push({
|
|
@@ -79,6 +80,8 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
|
|
|
79
80
|
name: field.name ? field.name : field.fid,
|
|
80
81
|
analyticType,
|
|
81
82
|
semanticType,
|
|
83
|
+
basename: field.basename || field.name || field.fid,
|
|
84
|
+
path: field.path,
|
|
82
85
|
});
|
|
83
86
|
}
|
|
84
87
|
return finalFieldMetas;
|
package/src/lib/viewQuery.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IField, IRow } from "../interfaces";
|
|
2
2
|
import { aggregate } from "./op/aggregate";
|
|
3
3
|
import { fold } from "./op/fold";
|
|
4
4
|
import { IAggQuery, IBinQuery, IFoldQuery, IRawQuery } from "./interfaces";
|
|
@@ -6,7 +6,7 @@ import { bin } from "./op/bin";
|
|
|
6
6
|
|
|
7
7
|
export type IViewQuery = IAggQuery | IFoldQuery | IBinQuery | IRawQuery;
|
|
8
8
|
|
|
9
|
-
export function queryView (rawData: IRow[], metas:
|
|
9
|
+
export function queryView (rawData: IRow[], metas: IField[], query: IViewQuery) {
|
|
10
10
|
switch (query.op) {
|
|
11
11
|
case 'aggregate':
|
|
12
12
|
return aggregate(rawData, query);
|
package/src/services.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toJS } from 'mobx';
|
|
2
|
-
import { IRow, IMutField, IFilterField, Specification } from './interfaces';
|
|
2
|
+
import { IRow, IMutField, IField, IFilterField, Specification } from './interfaces';
|
|
3
3
|
/* eslint import/no-webpack-loader-syntax:0 */
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
// eslint-disable-next-line
|
|
@@ -140,7 +140,7 @@ export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]
|
|
|
140
140
|
}
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
-
export const transformDataService = async (data: IRow[], columns:
|
|
143
|
+
export const transformDataService = async (data: IRow[], columns: IField[]): Promise<IRow[]> => {
|
|
144
144
|
if (columns.length === 0 || data.length === 0) return data;
|
|
145
145
|
const worker = new TransformDataWorker();
|
|
146
146
|
try {
|
|
@@ -156,7 +156,7 @@ export const transformDataService = async (data: IRow[], columns: IMutField[]):
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export const applyViewQuery = async (data: IRow[], metas:
|
|
159
|
+
export const applyViewQuery = async (data: IRow[], metas: IField[], query: IViewQuery): Promise<IRow[]> => {
|
|
160
160
|
const worker = new ViewQueryWorker();
|
|
161
161
|
try {
|
|
162
162
|
const res: IRow[] = await workerService(worker, {
|
package/src/store/index.tsx
CHANGED
|
@@ -1,62 +1,63 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
2
|
-
import { CommonStore } from './commonStore'
|
|
3
|
-
import { VizSpecStore } from './visualSpecStore'
|
|
1
|
+
import React, { useContext, useMemo, useEffect } from 'react';
|
|
2
|
+
import { CommonStore } from './commonStore';
|
|
3
|
+
import { VizSpecStore } from './visualSpecStore';
|
|
4
4
|
|
|
5
5
|
export interface IGlobalStore {
|
|
6
6
|
commonStore: CommonStore;
|
|
7
7
|
vizStore: VizSpecStore;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
const StoreDict: Record<string, IGlobalStore> = {};
|
|
11
|
+
const createStore = () => {
|
|
12
|
+
const commonStore = new CommonStore();
|
|
13
|
+
const vizStore = new VizSpecStore(commonStore);
|
|
14
|
+
return {
|
|
15
|
+
commonStore,
|
|
16
|
+
vizStore,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const getStore = (key?: string): IGlobalStore => {
|
|
20
|
+
if (key) {
|
|
21
|
+
if (!StoreDict[key]) StoreDict[key] = createStore();
|
|
22
|
+
return StoreDict[key];
|
|
23
|
+
} else {
|
|
24
|
+
return createStore();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
17
27
|
|
|
18
28
|
const StoreContext = React.createContext<IGlobalStore>(null!);
|
|
19
|
-
|
|
20
|
-
export function destroyGWStore() {
|
|
21
|
-
initStore.commonStore.destroy();
|
|
22
|
-
initStore.vizStore.destroy();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function rebootGWStore() {
|
|
26
|
-
const cs = new CommonStore();
|
|
27
|
-
const vs = new VizSpecStore(cs);
|
|
28
|
-
initStore.commonStore = cs;
|
|
29
|
-
initStore.vizStore = vs;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
29
|
interface StoreWrapperProps {
|
|
33
|
-
keepAlive?: boolean;
|
|
30
|
+
keepAlive?: boolean | string;
|
|
34
31
|
storeRef?: React.MutableRefObject<IGlobalStore | null>;
|
|
32
|
+
children?: React.ReactNode;
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
|
|
35
|
+
const noop = () => {};
|
|
36
|
+
|
|
37
|
+
export const StoreWrapper = (props: StoreWrapperProps) => {
|
|
38
|
+
const storeKey = props.keepAlive ? `${props.keepAlive}` : '';
|
|
39
|
+
const store = useMemo(() => getStore(storeKey), [storeKey]);
|
|
40
|
+
useEffect(() => {
|
|
39
41
|
if (props.storeRef) {
|
|
40
|
-
props.storeRef
|
|
42
|
+
const ref = props.storeRef;
|
|
43
|
+
ref.current = store;
|
|
44
|
+
return () => {
|
|
45
|
+
ref.current = null;
|
|
46
|
+
};
|
|
41
47
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
destroyGWStore();
|
|
48
|
+
return noop;
|
|
49
|
+
}, [props.storeRef]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!storeKey) {
|
|
52
|
+
return () => {
|
|
53
|
+
store.commonStore.destroy();
|
|
54
|
+
store.vizStore.destroy();
|
|
55
|
+
};
|
|
52
56
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</StoreContext.Provider>
|
|
58
|
-
}
|
|
59
|
-
}
|
|
57
|
+
return noop;
|
|
58
|
+
}, [storeKey]);
|
|
59
|
+
return <StoreContext.Provider value={store}>{props.children}</StoreContext.Provider>;
|
|
60
|
+
};
|
|
60
61
|
|
|
61
62
|
export function useGlobalStore() {
|
|
62
63
|
return useContext(StoreContext);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from "mobx";
|
|
2
2
|
import produce from "immer";
|
|
3
|
-
import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisualConfig, Specification } from "../interfaces";
|
|
3
|
+
import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisSpecForExport, IFilterFieldForExport, IVisualConfig, Specification } from "../interfaces";
|
|
4
4
|
import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
|
|
5
5
|
import { VisSpecWithHistory } from "../models/visSpecHistory";
|
|
6
6
|
import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
|
|
@@ -88,7 +88,7 @@ type DeepReadonly<T extends Record<keyof any, any>> = {
|
|
|
88
88
|
readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]):
|
|
91
|
+
const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpecForExport[] => {
|
|
92
92
|
return backwards.map((content) => ({
|
|
93
93
|
...content,
|
|
94
94
|
config: {
|
|
@@ -336,6 +336,7 @@ export class VizSpecStore {
|
|
|
336
336
|
dragId: uniqueId(),
|
|
337
337
|
fid: f.fid,
|
|
338
338
|
name: f.name || f.fid,
|
|
339
|
+
basename: f.basename || f.name || f.fid,
|
|
339
340
|
semanticType: f.semanticType,
|
|
340
341
|
analyticType: f.analyticType,
|
|
341
342
|
}));
|
|
@@ -345,6 +346,7 @@ export class VizSpecStore {
|
|
|
345
346
|
dragId: uniqueId(),
|
|
346
347
|
fid: f.fid,
|
|
347
348
|
name: f.name || f.fid,
|
|
349
|
+
basename: f.basename || f.name || f.fid,
|
|
348
350
|
analyticType: f.analyticType,
|
|
349
351
|
semanticType: f.semanticType,
|
|
350
352
|
aggName: "sum",
|
|
@@ -701,15 +703,15 @@ export class VizSpecStore {
|
|
|
701
703
|
return stringifyGWContent({
|
|
702
704
|
datasets: toJS(this.commonStore.datasets),
|
|
703
705
|
dataSources: this.commonStore.dataSources,
|
|
704
|
-
specList: pureVisList,
|
|
706
|
+
specList: this.visSpecEncoder(pureVisList),
|
|
705
707
|
});
|
|
706
708
|
}
|
|
707
709
|
public exportViewSpec() {
|
|
708
710
|
const pureVisList = dumpsGWPureSpec(this.visList);
|
|
709
|
-
return pureVisList
|
|
711
|
+
return this.visSpecEncoder(pureVisList);
|
|
710
712
|
}
|
|
711
713
|
public importStoInfo (stoInfo: IStoInfo) {
|
|
712
|
-
this.visList = parseGWPureSpec(forwardVisualConfigs(stoInfo.specList));
|
|
714
|
+
this.visList = parseGWPureSpec(this.visSpecDecoder(forwardVisualConfigs(stoInfo.specList)));
|
|
713
715
|
this.visIndex = 0;
|
|
714
716
|
this.commonStore.datasets = stoInfo.datasets;
|
|
715
717
|
this.commonStore.dataSources = stoInfo.dataSources;
|
|
@@ -719,4 +721,54 @@ export class VizSpecStore {
|
|
|
719
721
|
const content = parseGWContent(raw);
|
|
720
722
|
this.importStoInfo(content);
|
|
721
723
|
}
|
|
724
|
+
|
|
725
|
+
private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
|
|
726
|
+
const updatedVisList = visList.map((visSpec) => {
|
|
727
|
+
const updatedFilters = visSpec.encodings.filters.map((filter) => {
|
|
728
|
+
if (filter.rule?.type === "one of") {
|
|
729
|
+
const rule = {
|
|
730
|
+
...filter.rule,
|
|
731
|
+
value: Array.from(filter.rule.value)
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
...filter,
|
|
735
|
+
rule
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return filter as IFilterFieldForExport;
|
|
739
|
+
});
|
|
740
|
+
return {
|
|
741
|
+
...visSpec,
|
|
742
|
+
encodings: {
|
|
743
|
+
...visSpec.encodings,
|
|
744
|
+
filters: updatedFilters
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
return updatedVisList;
|
|
749
|
+
}
|
|
750
|
+
private visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
|
|
751
|
+
const updatedVisList = visList.map((visSpec) => {
|
|
752
|
+
const updatedFilters = visSpec.encodings.filters.map((filter) => {
|
|
753
|
+
if (filter.rule?.type === "one of" && Array.isArray(filter.rule.value)) {
|
|
754
|
+
return {
|
|
755
|
+
...filter,
|
|
756
|
+
rule: {
|
|
757
|
+
...filter.rule,
|
|
758
|
+
value: new Set(filter.rule.value)
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return filter;
|
|
763
|
+
})
|
|
764
|
+
return {
|
|
765
|
+
...visSpec,
|
|
766
|
+
encodings: {
|
|
767
|
+
...visSpec.encodings,
|
|
768
|
+
filters: updatedFilters
|
|
769
|
+
}
|
|
770
|
+
} as IVisSpec;
|
|
771
|
+
});
|
|
772
|
+
return updatedVisList;
|
|
773
|
+
}
|
|
722
774
|
}
|
package/src/utils/dataPrep.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { IMutField, IRow } from "../interfaces";
|
|
2
|
+
import { isPlainObject } from "./is-plain-object";
|
|
2
3
|
|
|
3
|
-
function updateRowKeys(data: IRow[], keyEncodeList: {from: string; to: string}[]): IRow[] {
|
|
4
|
+
function updateRowKeys(data: IRow[], keyEncodeList: { from: string[]; to: string }[]): IRow[] {
|
|
4
5
|
return data.map((row) => {
|
|
5
6
|
const newRow: IRow = {};
|
|
6
7
|
for (let k in keyEncodeList) {
|
|
7
8
|
const { from, to } = keyEncodeList[k];
|
|
8
|
-
newRow[to] = row
|
|
9
|
+
newRow[to] = getValueByKeyPath(row, from);
|
|
9
10
|
}
|
|
10
11
|
return newRow;
|
|
11
12
|
});
|
|
@@ -13,9 +14,9 @@ function updateRowKeys(data: IRow[], keyEncodeList: {from: string; to: string}[]
|
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* parse column id(key) to a safe string
|
|
16
|
-
* @param metas
|
|
17
|
+
* @param metas
|
|
17
18
|
*/
|
|
18
|
-
function parseColumnMetas
|
|
19
|
+
function parseColumnMetas(metas: IMutField[]) {
|
|
19
20
|
return metas.map((meta, i) => {
|
|
20
21
|
const safeKey = `gwc_${i}`;
|
|
21
22
|
return {
|
|
@@ -26,42 +27,34 @@ function parseColumnMetas (metas: IMutField[]) {
|
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export function guardDataKeys
|
|
30
|
+
export function guardDataKeys(
|
|
31
|
+
data: IRow[],
|
|
32
|
+
metas: IMutField[]
|
|
33
|
+
): {
|
|
30
34
|
safeData: IRow[];
|
|
31
35
|
safeMetas: IMutField[];
|
|
32
36
|
} {
|
|
33
|
-
const safeMetas = parseColumnMetas(metas)
|
|
37
|
+
const safeMetas = parseColumnMetas(metas);
|
|
34
38
|
const keyEncodeList = safeMetas.map((f, i) => ({
|
|
35
|
-
from: metas[i].fid,
|
|
36
|
-
to: f.fid
|
|
39
|
+
from: metas[i].path ?? [metas[i].fid],
|
|
40
|
+
to: f.fid,
|
|
37
41
|
}));
|
|
38
42
|
const safeData = updateRowKeys(data, keyEncodeList);
|
|
39
43
|
return {
|
|
40
44
|
safeData,
|
|
41
|
-
safeMetas
|
|
42
|
-
}
|
|
45
|
+
safeMetas,
|
|
46
|
+
};
|
|
43
47
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let flatColKeys: string[] = [];
|
|
49
|
-
for (let key of keys) {
|
|
50
|
-
if (typeof object[key] === 'object') {
|
|
51
|
-
const subKeys = flatNestKeys(object[key]);
|
|
52
|
-
flatColKeys = flatColKeys.concat(subKeys.map(k => `${key}${SPLITOR}${k}`));
|
|
53
|
-
} else {
|
|
54
|
-
flatColKeys.push(key)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return flatColKeys;
|
|
48
|
+
export function flatKeys(obj: Object, prefixKeys: string[] = []): string[][] {
|
|
49
|
+
return Object.keys(obj).flatMap((k) =>
|
|
50
|
+
isPlainObject(obj[k]) ? flatKeys(obj[k], prefixKeys.concat(k)) : [prefixKeys.concat(k)]
|
|
51
|
+
);
|
|
58
52
|
}
|
|
59
53
|
|
|
60
|
-
export function getValueByKeyPath
|
|
61
|
-
const keys = keyPath.split(SPLITOR);
|
|
54
|
+
export function getValueByKeyPath(object: any, keyPath: string[]): any {
|
|
62
55
|
let value = object;
|
|
63
|
-
for (let key of
|
|
56
|
+
for (let key of keyPath) {
|
|
64
57
|
value = value[key];
|
|
65
58
|
}
|
|
66
59
|
return value;
|
|
67
|
-
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2014-2017, Jon Schlinkert.
|
|
5
|
+
* Released under the MIT License.
|
|
6
|
+
*/
|
|
7
|
+
function isObject(o: any) {
|
|
8
|
+
return Object.prototype.toString.call(o) === '[object Object]';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isPlainObject(o: any) {
|
|
12
|
+
var ctor, prot;
|
|
13
|
+
|
|
14
|
+
if (isObject(o) === false) return false;
|
|
15
|
+
|
|
16
|
+
// If has modified constructor
|
|
17
|
+
ctor = o.constructor;
|
|
18
|
+
if (ctor === undefined) return true;
|
|
19
|
+
|
|
20
|
+
// If has modified prototype
|
|
21
|
+
prot = ctor.prototype;
|
|
22
|
+
if (isObject(prot) === false) return false;
|
|
23
|
+
|
|
24
|
+
// If constructor does not have an Object-specific method
|
|
25
|
+
if (prot.hasOwnProperty('isPrototypeOf') === false) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Most likely a plain Object
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { isPlainObject };
|
package/src/utils/save.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IDataSet, IDataSource, IVisSpec } from "../interfaces";
|
|
1
|
+
import { IDataSet, IDataSource, IVisSpec, IVisSpecForExport } from "../interfaces";
|
|
2
2
|
import { VisSpecWithHistory } from "../models/visSpecHistory";
|
|
3
3
|
|
|
4
4
|
export function dumpsGWPureSpec(list: VisSpecWithHistory[]): IVisSpec[] {
|
|
@@ -12,7 +12,7 @@ export function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[] {
|
|
|
12
12
|
export interface IStoInfo {
|
|
13
13
|
datasets: IDataSet[];
|
|
14
14
|
specList: {
|
|
15
|
-
[K in keyof
|
|
15
|
+
[K in keyof IVisSpecForExport]: K extends "config" ? Partial<IVisSpecForExport[K]> : IVisSpecForExport[K];
|
|
16
16
|
}[];
|
|
17
17
|
dataSources: IDataSource[];
|
|
18
18
|
}
|
|
@@ -33,7 +33,8 @@ import Toolbar, { ToolbarItemProps } from '../components/toolbar';
|
|
|
33
33
|
import { ButtonWithShortcut } from './menubar';
|
|
34
34
|
import { useCurrentMediaTheme } from '../utils/media';
|
|
35
35
|
import throttle from '../utils/throttle';
|
|
36
|
-
import KanariesLogo from '../assets/kanaries
|
|
36
|
+
import KanariesLogo from '../assets/kanaries.png';
|
|
37
|
+
import { ImageWithFallback } from '../components/timeoutImg';
|
|
37
38
|
|
|
38
39
|
const Invisible = styled.div`
|
|
39
40
|
clip: rect(1px, 1px, 1px, 1px);
|
|
@@ -101,30 +102,8 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
101
102
|
|
|
102
103
|
const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
|
|
103
104
|
|
|
104
|
-
console.log('kanaries logo', KanariesLogo);
|
|
105
|
-
|
|
106
105
|
const items = useMemo<ToolbarItemProps[]>(() => {
|
|
107
106
|
const builtInItems = [
|
|
108
|
-
{
|
|
109
|
-
key: 'kanaries',
|
|
110
|
-
label: 'kanaries',
|
|
111
|
-
icon: () => (
|
|
112
|
-
// Kanaries brand info is not allowed to be removed or changed unless you are granted with special permission.
|
|
113
|
-
<a href="https://kanaries.net" target="_blank">
|
|
114
|
-
<img
|
|
115
|
-
id="kanaries-logo"
|
|
116
|
-
className="m-1"
|
|
117
|
-
src="https://imagedelivery.net/tSvh1MGEu9IgUanmf58srQ/b6bc899f-a129-4c3a-d08f-d406166d0c00/public"
|
|
118
|
-
alt="kanaries"
|
|
119
|
-
onError={(e) => {
|
|
120
|
-
// @ts-ignore
|
|
121
|
-
e.target.src = KanariesLogo;
|
|
122
|
-
}}
|
|
123
|
-
/>
|
|
124
|
-
</a>
|
|
125
|
-
),
|
|
126
|
-
},
|
|
127
|
-
'-',
|
|
128
107
|
{
|
|
129
108
|
key: 'undo',
|
|
130
109
|
label: 'undo (Ctrl + Z)',
|
|
@@ -512,14 +491,29 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
512
491
|
commonStore.setShowCodeExportPanel(true);
|
|
513
492
|
},
|
|
514
493
|
},
|
|
494
|
+
...(extra.length === 0 ? [] : ['-', ...extra]),
|
|
495
|
+
'-',
|
|
496
|
+
{
|
|
497
|
+
key: 'kanaries',
|
|
498
|
+
label: 'kanaries',
|
|
499
|
+
icon: () => (
|
|
500
|
+
// Kanaries brand info is not allowed to be removed or changed unless you are granted with special permission.
|
|
501
|
+
<a href="https://docs.kanaries.net" target="_blank">
|
|
502
|
+
<ImageWithFallback
|
|
503
|
+
id="kanaries-logo"
|
|
504
|
+
className="p-1.5 opacity-70 hover:opacity-100"
|
|
505
|
+
src="https://imagedelivery.net/tSvh1MGEu9IgUanmf58srQ/b6bc899f-a129-4c3a-d08f-d406166d0c00/public"
|
|
506
|
+
fallbackSrc={KanariesLogo}
|
|
507
|
+
timeout={1000}
|
|
508
|
+
alt="kanaries"
|
|
509
|
+
/>
|
|
510
|
+
</a>
|
|
511
|
+
),
|
|
512
|
+
},
|
|
515
513
|
] as ToolbarItemProps[];
|
|
516
514
|
|
|
517
515
|
const items = builtInItems.filter((item) => typeof item === 'string' || !exclude.includes(item.key));
|
|
518
516
|
|
|
519
|
-
if (extra.length > 0) {
|
|
520
|
-
items.push('-', ...extra);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
517
|
return items;
|
|
524
518
|
}, [
|
|
525
519
|
vizStore,
|