@journeyapps-labs/reactor-mod-data-browser 3.2.1 → 3.4.0
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/CHANGELOG.md +23 -0
- package/dist/@types/actions/schema-model/ViewHasManyAction.d.ts +12 -0
- package/dist/@types/core/AbstractConnection.d.ts +12 -0
- package/dist/@types/core/SchemaModelDefinition.d.ts +12 -5
- package/dist/@types/core/query/StandardModelFields.d.ts +4 -0
- package/dist/@types/core/query/query-simple/SimpleQueryFilterState.d.ts +4 -6
- package/dist/@types/core/query/widgets/BelongsToDisplayWidget.d.ts +1 -0
- package/dist/@types/core/query/widgets/ColumnDisplayWidget.d.ts +1 -0
- package/dist/@types/core/query/widgets/IDCellDisplayWidget.d.ts +5 -0
- package/dist/@types/core/query/widgets/PeekRelationshipButton.d.ts +1 -0
- package/dist/@types/core/query/widgets/SmartBelongsToDisplayWidget.d.ts +2 -0
- package/dist/@types/core/query/widgets/SmartColumnWidget.d.ts +8 -3
- package/dist/@types/core/query/widgets/SmartFilterWidget.d.ts +6 -0
- package/dist/@types/core/types/ManualConnection.d.ts +1 -0
- package/dist/@types/entities/ConnectionEntityDefinition.d.ts +2 -1
- package/dist/@types/entities/SchemaModelObjectEntityDefinition.d.ts +2 -1
- package/dist/@types/forms/types/belongs-to-filter.d.ts +10 -0
- package/dist/@types/forms/types/shared/type-handler.d.ts +1 -0
- package/dist/@types/forms/types/shared/ui.d.ts +12 -0
- package/dist/@types/panels/query/QueryPanelFactory.d.ts +2 -2
- package/dist/DataBrowserModule.js +2 -0
- package/dist/DataBrowserModule.js.map +1 -1
- package/dist/actions/schema-model/ViewHasManyAction.js +112 -0
- package/dist/actions/schema-model/ViewHasManyAction.js.map +1 -0
- package/dist/core/AbstractConnection.js +58 -5
- package/dist/core/AbstractConnection.js.map +1 -1
- package/dist/core/SchemaModelDefinition.js +70 -12
- package/dist/core/SchemaModelDefinition.js.map +1 -1
- package/dist/core/query/StandardModelFields.js +3 -0
- package/dist/core/query/StandardModelFields.js.map +1 -1
- package/dist/core/query/query-simple/SimpleQueryColumns.js +37 -12
- package/dist/core/query/query-simple/SimpleQueryColumns.js.map +1 -1
- package/dist/core/query/query-simple/SimpleQueryFilterState.js +35 -7
- package/dist/core/query/query-simple/SimpleQueryFilterState.js.map +1 -1
- package/dist/core/query/query-simple/SimpleQuerySortState.js +0 -1
- package/dist/core/query/query-simple/SimpleQuerySortState.js.map +1 -1
- package/dist/core/query/widgets/BelongsToDisplayWidget.js +1 -1
- package/dist/core/query/widgets/BelongsToDisplayWidget.js.map +1 -1
- package/dist/core/query/widgets/ColumnDisplayWidget.js +13 -2
- package/dist/core/query/widgets/ColumnDisplayWidget.js.map +1 -1
- package/dist/core/query/widgets/IDCellDisplayWidget.js +33 -0
- package/dist/core/query/widgets/IDCellDisplayWidget.js.map +1 -0
- package/dist/core/query/widgets/PeekRelationshipButton.js +23 -16
- package/dist/core/query/widgets/PeekRelationshipButton.js.map +1 -1
- package/dist/core/query/widgets/SmartBelongsToDisplayWidget.js +1 -1
- package/dist/core/query/widgets/SmartBelongsToDisplayWidget.js.map +1 -1
- package/dist/core/query/widgets/SmartColumnWidget.js +7 -14
- package/dist/core/query/widgets/SmartColumnWidget.js.map +1 -1
- package/dist/core/query/widgets/SmartFilterWidget.js +10 -7
- package/dist/core/query/widgets/SmartFilterWidget.js.map +1 -1
- package/dist/core/types/ManualConnection.js +3 -0
- package/dist/core/types/ManualConnection.js.map +1 -1
- package/dist/entities/ConnectionEntityDefinition.js +29 -6
- package/dist/entities/ConnectionEntityDefinition.js.map +1 -1
- package/dist/entities/SchemaModelObjectEntityDefinition.js +15 -14
- package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
- package/dist/forms/types/attachment-handler.js +1 -0
- package/dist/forms/types/attachment-handler.js.map +1 -1
- package/dist/forms/types/belongs-to-filter.js +57 -0
- package/dist/forms/types/belongs-to-filter.js.map +1 -0
- package/dist/forms/types/boolean-handler.js +34 -1
- package/dist/forms/types/boolean-handler.js.map +1 -1
- package/dist/forms/types/date-handler.js +1 -0
- package/dist/forms/types/date-handler.js.map +1 -1
- package/dist/forms/types/image-handler.js +1 -0
- package/dist/forms/types/image-handler.js.map +1 -1
- package/dist/forms/types/location-handler.js +1 -0
- package/dist/forms/types/location-handler.js.map +1 -1
- package/dist/forms/types/multiple-choice-handler.js +1 -0
- package/dist/forms/types/multiple-choice-handler.js.map +1 -1
- package/dist/forms/types/multiple-choice-integer-handler.js +1 -0
- package/dist/forms/types/multiple-choice-integer-handler.js.map +1 -1
- package/dist/forms/types/number-handler.js +3 -2
- package/dist/forms/types/number-handler.js.map +1 -1
- package/dist/forms/types/single-choice-handler.js +1 -0
- package/dist/forms/types/single-choice-handler.js.map +1 -1
- package/dist/forms/types/single-choice-integer-handler.js +1 -0
- package/dist/forms/types/single-choice-integer-handler.js.map +1 -1
- package/dist/forms/types/text-handler.js +1 -0
- package/dist/forms/types/text-handler.js.map +1 -1
- package/dist/panels/model-json/ModelJsonPanelWidget.js +4 -3
- package/dist/panels/model-json/ModelJsonPanelWidget.js.map +1 -1
- package/dist/panels/query/QueryPanelFactory.js.map +1 -1
- package/dist/panels/query/table-controls/FilterControlsWidget.js +1 -0
- package/dist/panels/query/table-controls/FilterControlsWidget.js.map +1 -1
- package/dist/stores/ConnectionStore.js +15 -3
- package/dist/stores/ConnectionStore.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist-module/bundle.js +51 -35
- package/dist-module/bundle.js.map +1 -1
- package/package.json +4 -4
- package/src/DataBrowserModule.ts +2 -0
- package/src/actions/schema-model/ViewHasManyAction.ts +114 -0
- package/src/core/AbstractConnection.ts +45 -2
- package/src/core/SchemaModelDefinition.ts +90 -14
- package/src/core/query/StandardModelFields.ts +8 -0
- package/src/core/query/query-simple/SimpleQueryColumns.tsx +50 -14
- package/src/core/query/query-simple/SimpleQueryFilterState.ts +42 -9
- package/src/core/query/query-simple/SimpleQuerySortState.ts +0 -1
- package/src/core/query/widgets/BelongsToDisplayWidget.tsx +2 -1
- package/src/core/query/widgets/ColumnDisplayWidget.tsx +17 -4
- package/src/core/query/widgets/IDCellDisplayWidget.tsx +46 -0
- package/src/core/query/widgets/PeekRelationshipButton.tsx +23 -16
- package/src/core/query/widgets/SmartBelongsToDisplayWidget.tsx +3 -0
- package/src/core/query/widgets/SmartColumnWidget.tsx +27 -23
- package/src/core/query/widgets/SmartFilterWidget.tsx +14 -5
- package/src/core/types/ManualConnection.ts +4 -0
- package/src/entities/ConnectionEntityDefinition.tsx +32 -4
- package/src/entities/SchemaModelObjectEntityDefinition.ts +17 -15
- package/src/forms/types/attachment-handler.tsx +1 -0
- package/src/forms/types/belongs-to-filter.tsx +84 -0
- package/src/forms/types/boolean-handler.tsx +46 -2
- package/src/forms/types/date-handler.tsx +1 -0
- package/src/forms/types/image-handler.tsx +1 -0
- package/src/forms/types/location-handler.tsx +1 -0
- package/src/forms/types/multiple-choice-handler.tsx +1 -0
- package/src/forms/types/multiple-choice-integer-handler.tsx +1 -0
- package/src/forms/types/number-handler.tsx +3 -2
- package/src/forms/types/shared/type-handler.ts +1 -0
- package/src/forms/types/single-choice-handler.tsx +1 -0
- package/src/forms/types/single-choice-integer-handler.tsx +1 -0
- package/src/forms/types/text-handler.tsx +1 -0
- package/src/panels/model-json/ModelJsonPanelWidget.tsx +8 -6
- package/src/panels/query/QueryPanelFactory.tsx +2 -2
- package/src/panels/query/table-controls/FilterControlsWidget.tsx +1 -0
- package/src/stores/ConnectionStore.ts +17 -6
|
@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
|
|
|
3
3
|
|
|
4
4
|
export interface ColumnDisplayWidgetProps {
|
|
5
5
|
label: string;
|
|
6
|
+
secondaryLabel?: string;
|
|
6
7
|
className?: any;
|
|
7
8
|
onClick?: () => any;
|
|
8
9
|
}
|
|
@@ -11,15 +12,16 @@ export const ColumnDisplayWidget: React.FC<ColumnDisplayWidgetProps> = (props) =
|
|
|
11
12
|
const parts = (props.label || '').split(' ');
|
|
12
13
|
const minWidth = parts.length >= 5 ? 150 : parts.length >= 3 ? 80 : undefined;
|
|
13
14
|
return (
|
|
14
|
-
<S.
|
|
15
|
+
<S.Container
|
|
15
16
|
className={props.className}
|
|
16
17
|
minWidth={minWidth}
|
|
17
18
|
clickable={!!props.onClick}
|
|
18
19
|
nowrap={!minWidth}
|
|
19
20
|
onClick={props.onClick}
|
|
20
21
|
>
|
|
21
|
-
{props.label}
|
|
22
|
-
|
|
22
|
+
<S.Label>{props.label}</S.Label>
|
|
23
|
+
{props.secondaryLabel ? <S.SecondaryLabel>{props.secondaryLabel}</S.SecondaryLabel> : null}
|
|
24
|
+
</S.Container>
|
|
23
25
|
);
|
|
24
26
|
};
|
|
25
27
|
|
|
@@ -32,9 +34,20 @@ namespace S {
|
|
|
32
34
|
}
|
|
33
35
|
`;
|
|
34
36
|
|
|
35
|
-
export const
|
|
37
|
+
export const Container = styled.div<{ minWidth?: number; clickable: boolean; nowrap: boolean }>`
|
|
36
38
|
${(p) => (p.minWidth ? `min-width: ${p.minWidth}px;` : '')};
|
|
37
39
|
${(p) => (p.nowrap ? 'white-space: nowrap;' : '')};
|
|
38
40
|
${(p) => (p.clickable ? clickableStyle : '')};
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
align-items: flex-start;
|
|
44
|
+
row-gap: 2px;
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
export const Label = styled.div``;
|
|
48
|
+
|
|
49
|
+
export const SecondaryLabel = styled.div`
|
|
50
|
+
opacity: 0.4;
|
|
51
|
+
font-size: 12px;
|
|
39
52
|
`;
|
|
40
53
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { NotificationStore, NotificationType, TableButtonWidget, ioc, styled } from '@journeyapps-labs/reactor-mod';
|
|
3
|
+
import { copyTextToClipboard } from '@journeyapps-labs/lib-reactor-utils';
|
|
4
|
+
|
|
5
|
+
export interface IDCellDisplayWidgetProps {
|
|
6
|
+
id: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const IDCellDisplayWidget: React.FC<IDCellDisplayWidgetProps> = (props) => {
|
|
10
|
+
const notifications = ioc.get(NotificationStore);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<S.Container>
|
|
14
|
+
<S.Value>{props.id}</S.Value>
|
|
15
|
+
<TableButtonWidget
|
|
16
|
+
icon="copy"
|
|
17
|
+
tooltip="Copy ID"
|
|
18
|
+
action={() => {
|
|
19
|
+
copyTextToClipboard(props.id);
|
|
20
|
+
notifications.showNotification({
|
|
21
|
+
title: 'Copied',
|
|
22
|
+
description: 'ID copied to clipboard',
|
|
23
|
+
type: NotificationType.SUCCESS
|
|
24
|
+
});
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
</S.Container>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
namespace S {
|
|
32
|
+
export const Container = styled.div`
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: row;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
column-gap: 5px;
|
|
38
|
+
width: 100%;
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export const Value = styled.div`
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
text-overflow: ellipsis;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
@@ -21,6 +21,7 @@ import { EmptyValueWidget } from '../../../widgets/EmptyValueWidget';
|
|
|
21
21
|
export interface PeekRelationshipButtonProps {
|
|
22
22
|
object: SchemaModelObject;
|
|
23
23
|
open: (object: SchemaModelObject) => any;
|
|
24
|
+
filterBelongsTo?: (object: SchemaModelObject) => any;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export const PeekRelationshipButton: React.FC<PeekRelationshipButtonProps> = (props) => {
|
|
@@ -89,8 +90,15 @@ export const PeekRelationshipButton: React.FC<PeekRelationshipButtonProps> = (pr
|
|
|
89
90
|
title: 'ID',
|
|
90
91
|
icon: 'copy',
|
|
91
92
|
right: <S.FieldValue>{props.object.id}</S.FieldValue>,
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
group: 'Object',
|
|
94
|
+
action: async () => {
|
|
95
|
+
copyTextToClipboard(props.object.id);
|
|
96
|
+
notifications.showNotification({
|
|
97
|
+
title: 'Copied',
|
|
98
|
+
description: 'Relationship ID copied to clipboard',
|
|
99
|
+
type: NotificationType.SUCCESS
|
|
100
|
+
});
|
|
101
|
+
}
|
|
94
102
|
},
|
|
95
103
|
{
|
|
96
104
|
key: 'meta-updated',
|
|
@@ -114,20 +122,19 @@ export const PeekRelationshipButton: React.FC<PeekRelationshipButtonProps> = (pr
|
|
|
114
122
|
props.open(props.object);
|
|
115
123
|
}
|
|
116
124
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
125
|
+
...(props.filterBelongsTo
|
|
126
|
+
? [
|
|
127
|
+
{
|
|
128
|
+
key: 'filter-belongs-to',
|
|
129
|
+
title: 'Filter belongs to',
|
|
130
|
+
icon: 'filter',
|
|
131
|
+
group: 'Actions',
|
|
132
|
+
action: async () => {
|
|
133
|
+
await props.filterBelongsTo?.(props.object);
|
|
134
|
+
}
|
|
135
|
+
} as ComboBoxItem
|
|
136
|
+
]
|
|
137
|
+
: [])
|
|
131
138
|
];
|
|
132
139
|
const directive = await ioc.get(ComboBoxStore2).show(
|
|
133
140
|
new SimpleComboBoxDirective({
|
|
@@ -4,6 +4,7 @@ import { BelongsToDisplayWidget } from './BelongsToDisplayWidget';
|
|
|
4
4
|
import { PageRow } from '../Page';
|
|
5
5
|
import { ActionSource, styled } from '@journeyapps-labs/reactor-mod';
|
|
6
6
|
import { AbstractConnection } from '../../AbstractConnection';
|
|
7
|
+
import { SchemaModelObject } from '../../SchemaModelObject';
|
|
7
8
|
import { Variable } from '@journeyapps/db';
|
|
8
9
|
import { observer } from 'mobx-react';
|
|
9
10
|
|
|
@@ -11,6 +12,7 @@ export interface SmartBelongsToDisplayWidgetProps {
|
|
|
11
12
|
row: PageRow;
|
|
12
13
|
connection: AbstractConnection;
|
|
13
14
|
variable_id: Variable;
|
|
15
|
+
filterBelongsTo?: (object: SchemaModelObject) => any;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export const SmartBelongsToDisplayWidget: React.FC<SmartBelongsToDisplayWidgetProps> = observer((props) => {
|
|
@@ -35,6 +37,7 @@ export const SmartBelongsToDisplayWidget: React.FC<SmartBelongsToDisplayWidgetPr
|
|
|
35
37
|
relationship={row.model.definition.definition.belongsTo[variable_id.relationship]}
|
|
36
38
|
connection={connection}
|
|
37
39
|
id={value}
|
|
40
|
+
filterBelongsTo={props.filterBelongsTo}
|
|
38
41
|
/>
|
|
39
42
|
);
|
|
40
43
|
});
|
|
@@ -1,38 +1,48 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import styled from '@emotion/styled';
|
|
3
|
-
import {
|
|
3
|
+
import { Variable } from '@journeyapps/db';
|
|
4
4
|
import { ColumnDisplayWidget } from './ColumnDisplayWidget';
|
|
5
|
-
import {
|
|
5
|
+
import { SmartFilterWidget, SmartTypeEngineFilterWidget } from './SmartFilterWidget';
|
|
6
6
|
import { SimpleFilter } from '../filters';
|
|
7
|
-
import {
|
|
8
|
-
import type { SortDirection } from '../query-simple/SimpleQuery';
|
|
7
|
+
import { SortDirection } from '../query-simple/SimpleQuery';
|
|
9
8
|
|
|
10
9
|
export interface SmartColumnWidgetProps {
|
|
11
10
|
variable: Variable;
|
|
12
|
-
|
|
11
|
+
typeLabel?: string;
|
|
13
12
|
filter?: SimpleFilter;
|
|
14
13
|
sortDirection?: SortDirection;
|
|
15
14
|
onToggleSort?: () => Promise<any> | any;
|
|
15
|
+
setupFilter?: (event: {
|
|
16
|
+
variable: Variable;
|
|
17
|
+
filter?: SimpleFilter;
|
|
18
|
+
position?: MouseEvent;
|
|
19
|
+
}) => Promise<SimpleFilter | null>;
|
|
16
20
|
filterChanged: (filter: SimpleFilter | null) => any;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export const SmartColumnWidget: React.FC<SmartColumnWidgetProps> = (props) => {
|
|
20
24
|
const baseLabel = props.variable.label || props.variable.name;
|
|
21
|
-
const displayLabel = props.sortDirection
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
display = (
|
|
25
|
-
<S.TypeGroup>
|
|
26
|
-
{display}
|
|
27
|
-
<S.Type label={props.type.label} />
|
|
28
|
-
</S.TypeGroup>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
25
|
+
const displayLabel = props.sortDirection
|
|
26
|
+
? `${baseLabel} ${props.sortDirection === SortDirection.ASC ? '↓' : '↑'}`
|
|
27
|
+
: baseLabel;
|
|
31
28
|
return (
|
|
32
29
|
<S.Container>
|
|
33
30
|
<S.TopRow>
|
|
34
|
-
{
|
|
35
|
-
|
|
31
|
+
<ColumnDisplayWidget label={displayLabel} secondaryLabel={props.typeLabel} onClick={props.onToggleSort} />
|
|
32
|
+
{props.setupFilter ? (
|
|
33
|
+
<SmartFilterWidget
|
|
34
|
+
filter={props.filter}
|
|
35
|
+
variable={props.variable}
|
|
36
|
+
setupFilter={props.setupFilter}
|
|
37
|
+
filterChanged={props.filterChanged}
|
|
38
|
+
/>
|
|
39
|
+
) : (
|
|
40
|
+
<SmartTypeEngineFilterWidget
|
|
41
|
+
filter={props.filter}
|
|
42
|
+
variable={props.variable}
|
|
43
|
+
filterChanged={props.filterChanged}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
36
46
|
</S.TopRow>
|
|
37
47
|
</S.Container>
|
|
38
48
|
);
|
|
@@ -53,12 +63,6 @@ namespace S {
|
|
|
53
63
|
column-gap: 5px;
|
|
54
64
|
`;
|
|
55
65
|
|
|
56
|
-
export const Type = styled(ColumnDisplayWidget)`
|
|
57
|
-
opacity: 0.5;
|
|
58
|
-
`;
|
|
59
|
-
|
|
60
|
-
export const TypeGroup = styled.div``;
|
|
61
|
-
|
|
62
66
|
export const FilterMetaRow = styled.div`
|
|
63
67
|
display: flex;
|
|
64
68
|
align-items: center;
|
|
@@ -15,6 +15,11 @@ import { SimpleFilter, StatementMatch } from '../filters';
|
|
|
15
15
|
export interface SmartFilterWidgetProps {
|
|
16
16
|
variable: Variable;
|
|
17
17
|
filter?: SimpleFilter;
|
|
18
|
+
setupFilter: (event: {
|
|
19
|
+
variable: Variable;
|
|
20
|
+
filter?: SimpleFilter;
|
|
21
|
+
position?: MouseEvent;
|
|
22
|
+
}) => Promise<SimpleFilter | null>;
|
|
18
23
|
filterChanged: (filter: SimpleFilter | null) => any;
|
|
19
24
|
}
|
|
20
25
|
|
|
@@ -63,10 +68,6 @@ export const SmartFilterMetadataWidget: React.FC<SmartFilterMetadataWidgetProps>
|
|
|
63
68
|
|
|
64
69
|
export const SmartFilterWidget: React.FC<SmartFilterWidgetProps> = (props) => {
|
|
65
70
|
const isActive = (props.filter?.statements?.length || 0) > 0;
|
|
66
|
-
const handler = ioc.get(TypeEngine).getHandler(props.variable.type);
|
|
67
|
-
if (!handler?.setupFilter) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
71
|
return (
|
|
71
72
|
<S.FilterButton
|
|
72
73
|
active={isActive}
|
|
@@ -75,7 +76,7 @@ export const SmartFilterWidget: React.FC<SmartFilterWidgetProps> = (props) => {
|
|
|
75
76
|
tooltipPos: TooltipPosition.BOTTOM
|
|
76
77
|
})}
|
|
77
78
|
onClick={async (event) => {
|
|
78
|
-
const filter = await
|
|
79
|
+
const filter = await props.setupFilter({
|
|
79
80
|
variable: props.variable,
|
|
80
81
|
filter: props.filter,
|
|
81
82
|
position: event.nativeEvent
|
|
@@ -91,6 +92,14 @@ export const SmartFilterWidget: React.FC<SmartFilterWidgetProps> = (props) => {
|
|
|
91
92
|
);
|
|
92
93
|
};
|
|
93
94
|
|
|
95
|
+
export const SmartTypeEngineFilterWidget: React.FC<Omit<SmartFilterWidgetProps, 'setupFilter'>> = (props) => {
|
|
96
|
+
const setupFilter = ioc.get(TypeEngine).getHandler(props.variable.type)?.setupFilter;
|
|
97
|
+
if (!setupFilter) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return <SmartFilterWidget {...props} setupFilter={setupFilter} />;
|
|
101
|
+
};
|
|
102
|
+
|
|
94
103
|
namespace S {
|
|
95
104
|
export const MetaList = styled.div`
|
|
96
105
|
display: flex;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DescendantEntityProviderComponent,
|
|
3
|
+
DescendantLoadingEntityProviderComponent,
|
|
3
4
|
EntityCardsPresenterComponent,
|
|
4
5
|
EntityDefinition,
|
|
5
6
|
EntityDescriberComponent,
|
|
@@ -7,13 +8,17 @@ import {
|
|
|
7
8
|
inject,
|
|
8
9
|
InlineTreePresenterComponent,
|
|
9
10
|
SearchableTreeSearchScope,
|
|
10
|
-
SimpleEntitySearchEngineComponent
|
|
11
|
+
SimpleEntitySearchEngineComponent,
|
|
12
|
+
TreeBadgeWidget,
|
|
13
|
+
ThemeStore
|
|
11
14
|
} from '@journeyapps-labs/reactor-mod';
|
|
15
|
+
import * as React from 'react';
|
|
12
16
|
import { DataBrowserEntities } from '../entities';
|
|
13
17
|
import { ConnectionStore } from '../stores/ConnectionStore';
|
|
14
18
|
import { AbstractConnection } from '../core/AbstractConnection';
|
|
15
19
|
import { AddConnectionAction } from '../actions/connections/AddConnectionAction';
|
|
16
20
|
import { SavedQueryEntity, SavedQueryStore } from '../stores/SavedQueryStore';
|
|
21
|
+
import { SchemaModelDefinition } from '../core/SchemaModelDefinition';
|
|
17
22
|
|
|
18
23
|
export class ConnectionEntityDefinition extends EntityDefinition<AbstractConnection> {
|
|
19
24
|
@inject(ConnectionStore)
|
|
@@ -22,6 +27,9 @@ export class ConnectionEntityDefinition extends EntityDefinition<AbstractConnect
|
|
|
22
27
|
@inject(SavedQueryStore)
|
|
23
28
|
accessor savedQueryStore: SavedQueryStore;
|
|
24
29
|
|
|
30
|
+
@inject(ThemeStore)
|
|
31
|
+
accessor themeStore: ThemeStore;
|
|
32
|
+
|
|
25
33
|
constructor() {
|
|
26
34
|
super({
|
|
27
35
|
type: DataBrowserEntities.CONNECTION,
|
|
@@ -56,14 +64,30 @@ export class ConnectionEntityDefinition extends EntityDefinition<AbstractConnect
|
|
|
56
64
|
new InlineTreePresenterComponent<AbstractConnection>({
|
|
57
65
|
loadChildrenAsNodesAreOpened: true,
|
|
58
66
|
cacheTreeEntities: true,
|
|
59
|
-
searchScope: SearchableTreeSearchScope.VISIBLE_ONLY
|
|
67
|
+
searchScope: SearchableTreeSearchScope.VISIBLE_ONLY,
|
|
68
|
+
augmentTreeNodeProps: (entity) => {
|
|
69
|
+
if (!entity.isOnline) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const theme = this.themeStore.getCurrentTheme();
|
|
73
|
+
return {
|
|
74
|
+
rightChildren: (
|
|
75
|
+
<TreeBadgeWidget
|
|
76
|
+
icon="bolt"
|
|
77
|
+
background={'transparent'}
|
|
78
|
+
iconColor={theme.status.success}
|
|
79
|
+
tooltip="Connection online"
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
60
84
|
})
|
|
61
85
|
);
|
|
62
86
|
|
|
63
87
|
this.registerComponent(new EntityCardsPresenterComponent<AbstractConnection>());
|
|
64
88
|
|
|
65
89
|
this.registerComponent(
|
|
66
|
-
new
|
|
90
|
+
new DescendantLoadingEntityProviderComponent<AbstractConnection, SchemaModelDefinition>({
|
|
67
91
|
descendantType: DataBrowserEntities.SCHEMA_MODEL_DEFINITION,
|
|
68
92
|
generateOptions: (parent) => {
|
|
69
93
|
return {
|
|
@@ -71,7 +95,11 @@ export class ConnectionEntityDefinition extends EntityDefinition<AbstractConnect
|
|
|
71
95
|
label: 'Models',
|
|
72
96
|
icon: 'cube'
|
|
73
97
|
},
|
|
74
|
-
descendants: parent.schema_models.items
|
|
98
|
+
descendants: parent.schema_models.items,
|
|
99
|
+
loading: () => parent.isLoadingOnline,
|
|
100
|
+
refreshDescendants: async () => {
|
|
101
|
+
await parent.ensureOnline();
|
|
102
|
+
}
|
|
75
103
|
};
|
|
76
104
|
}
|
|
77
105
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Action,
|
|
2
3
|
EntityDefinition,
|
|
3
4
|
EntityDescriberComponent,
|
|
4
5
|
inject,
|
|
@@ -10,6 +11,8 @@ import { DataBrowserEntities } from '../entities';
|
|
|
10
11
|
import { ConnectionStore } from '../stores/ConnectionStore';
|
|
11
12
|
import { SchemaModelObject } from '../core/SchemaModelObject';
|
|
12
13
|
import { SchemaModelDefinition } from '../core/SchemaModelDefinition';
|
|
14
|
+
import { validate as validateUUID } from 'uuid';
|
|
15
|
+
import { ViewHasManyAction } from '../actions/schema-model/ViewHasManyAction';
|
|
13
16
|
|
|
14
17
|
export interface SchemaModelObjectEntityDefinitionEncoded {
|
|
15
18
|
connection_id: string;
|
|
@@ -42,21 +45,6 @@ export class SchemaModelObjectEntityDefinition extends EntityDefinition<SchemaMo
|
|
|
42
45
|
})
|
|
43
46
|
);
|
|
44
47
|
|
|
45
|
-
// this.registerComponent(
|
|
46
|
-
// new SimpleParentEntitySearchEngine<SchemaModelDefinition,SchemaModelObject>({
|
|
47
|
-
// label: 'ID',
|
|
48
|
-
// filterResultsWithMatcher: false,
|
|
49
|
-
// type: DataBrowserEntities.SCHEMA_MODEL_DEFINITION,
|
|
50
|
-
// getEntities: async (event) => {
|
|
51
|
-
// let object = await event.parameters.parent.resolve(event.value);
|
|
52
|
-
// if(object){
|
|
53
|
-
// return [object]
|
|
54
|
-
// }
|
|
55
|
-
// return []
|
|
56
|
-
// }
|
|
57
|
-
// })
|
|
58
|
-
// );
|
|
59
|
-
|
|
60
48
|
this.registerComponent(
|
|
61
49
|
new SimpleParentEntitySearchEngine<SchemaModelDefinition, SchemaModelObject>({
|
|
62
50
|
label: 'Label',
|
|
@@ -66,6 +54,13 @@ export class SchemaModelObjectEntityDefinition extends EntityDefinition<SchemaMo
|
|
|
66
54
|
if (!event.value) {
|
|
67
55
|
return [];
|
|
68
56
|
}
|
|
57
|
+
if (validateUUID(event.value)) {
|
|
58
|
+
const object = await event.parameters.parent.resolve(event.value);
|
|
59
|
+
if (object) {
|
|
60
|
+
return [object];
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
69
64
|
return await event.parameters.parent.search(event.value);
|
|
70
65
|
}
|
|
71
66
|
})
|
|
@@ -101,4 +96,11 @@ export class SchemaModelObjectEntityDefinition extends EntityDefinition<SchemaMo
|
|
|
101
96
|
getEntityUID(t: SchemaModelObject) {
|
|
102
97
|
return t.model.id;
|
|
103
98
|
}
|
|
99
|
+
|
|
100
|
+
isActionAllowedForEntity(action: Action, entity: SchemaModelObject) {
|
|
101
|
+
if (action.id === ViewHasManyAction.ID) {
|
|
102
|
+
return Object.keys(entity.definition.definition.hasMany || {}).length > 0;
|
|
103
|
+
}
|
|
104
|
+
return super.isActionAllowedForEntity(action, entity);
|
|
105
|
+
}
|
|
104
106
|
}
|
|
@@ -5,6 +5,7 @@ import { TypeHandler } from './shared/type-handler';
|
|
|
5
5
|
|
|
6
6
|
export const attachmentHandler: TypeHandler = {
|
|
7
7
|
matches: (type) => type instanceof AttachmentType,
|
|
8
|
+
getTypeLabel: () => 'Attachment',
|
|
8
9
|
encode: async (value: File) => {
|
|
9
10
|
return Attachment.create({
|
|
10
11
|
data: await value.arrayBuffer(),
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Variable } from '@journeyapps/db';
|
|
2
|
+
import { ArrayInput, DialogStore2, EntityInput, FormModel, ioc } from '@journeyapps-labs/reactor-mod';
|
|
3
|
+
import { Relationship } from '@journeyapps/parser-schema';
|
|
4
|
+
import { SchemaModelDefinition } from '../../core/SchemaModelDefinition';
|
|
5
|
+
import { SchemaModelObject } from '../../core/SchemaModelObject';
|
|
6
|
+
import { Condition, SimpleFilter, Statement, StatementMatch } from '../../core/query/filters';
|
|
7
|
+
import { DataBrowserEntities } from '../../entities';
|
|
8
|
+
import { ClearableFilterFormDialogDirective } from './filters/ClearableFilterFormDialogDirective';
|
|
9
|
+
|
|
10
|
+
interface BelongsToFilterFormValue {
|
|
11
|
+
values: (SchemaModelObject | null)[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class BelongsToFilterForm extends FormModel<BelongsToFilterFormValue> {
|
|
15
|
+
constructor(private foreignDefinition: SchemaModelDefinition) {
|
|
16
|
+
super();
|
|
17
|
+
|
|
18
|
+
this.addInput(
|
|
19
|
+
new ArrayInput({
|
|
20
|
+
name: 'values',
|
|
21
|
+
label: 'Matches any of',
|
|
22
|
+
value: [null],
|
|
23
|
+
generate: () => {
|
|
24
|
+
return new EntityInput({
|
|
25
|
+
name: 'value',
|
|
26
|
+
label: foreignDefinition.definition.label || foreignDefinition.definition.name,
|
|
27
|
+
entityType: DataBrowserEntities.SCHEMA_MODEL_OBJECT,
|
|
28
|
+
parent: foreignDefinition,
|
|
29
|
+
value: null
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async hydrateFromFilter(filter?: SimpleFilter) {
|
|
37
|
+
const values = await Promise.all(
|
|
38
|
+
(filter?.statements || [])
|
|
39
|
+
.map((statement) => `${statement.arg || ''}`)
|
|
40
|
+
.filter((id) => !!id)
|
|
41
|
+
.map((id) => this.foreignDefinition.resolve(id))
|
|
42
|
+
);
|
|
43
|
+
this.setValues({
|
|
44
|
+
values: values.filter((value) => !!value).length > 0 ? values.filter((value) => !!value) : [null]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toFilter(variable: Variable): SimpleFilter | null {
|
|
49
|
+
const values = (this.value()?.values || []).filter((value) => !!value?.id);
|
|
50
|
+
if (values.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return new SimpleFilter(
|
|
54
|
+
variable,
|
|
55
|
+
values.map((value) => new Statement(Condition.EQUALS, value.id)),
|
|
56
|
+
StatementMatch.ANY
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const setupBelongsToFilter = async (options: {
|
|
62
|
+
definition: SchemaModelDefinition;
|
|
63
|
+
relationship: Relationship;
|
|
64
|
+
variable: Variable;
|
|
65
|
+
filter?: SimpleFilter;
|
|
66
|
+
}) => {
|
|
67
|
+
const foreignDefinition = options.definition.connection.getSchemaModelDefinitionByName(
|
|
68
|
+
options.relationship.foreignType.name
|
|
69
|
+
);
|
|
70
|
+
const form = new BelongsToFilterForm(foreignDefinition);
|
|
71
|
+
await form.hydrateFromFilter(options.filter);
|
|
72
|
+
const result = await ioc.get(DialogStore2).showDialog(
|
|
73
|
+
new ClearableFilterFormDialogDirective({
|
|
74
|
+
title: `Filter ${options.relationship.name}`,
|
|
75
|
+
form,
|
|
76
|
+
filter: options.filter,
|
|
77
|
+
handler: async () => {}
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
if (!result) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return form.toFilter(options.variable);
|
|
84
|
+
};
|
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
import { BooleanType } from '@journeyapps/db';
|
|
2
|
-
import { BooleanInput, CheckboxWidget } from '@journeyapps-labs/reactor-mod';
|
|
1
|
+
import { BooleanType, Variable } from '@journeyapps/db';
|
|
2
|
+
import { BooleanInput, CheckboxWidget, DialogStore2, FormModel, ioc } from '@journeyapps-labs/reactor-mod';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { TypeHandler } from './shared/type-handler';
|
|
5
|
+
import { ClearableFilterFormDialogDirective } from './filters/ClearableFilterFormDialogDirective';
|
|
6
|
+
import { Condition, SimpleFilter, Statement } from '../../core/query/filters';
|
|
7
|
+
|
|
8
|
+
interface BooleanFilterFormValue {
|
|
9
|
+
value: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class BooleanFilterForm extends FormModel<BooleanFilterFormValue> {
|
|
13
|
+
constructor(value?: boolean) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this.addInput(
|
|
17
|
+
new BooleanInput({
|
|
18
|
+
name: 'value',
|
|
19
|
+
label: 'Value',
|
|
20
|
+
value: value === true
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static fromFilter(filter?: SimpleFilter): boolean {
|
|
26
|
+
return filter?.statements?.[0]?.arg === true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
toFilter(variable: Variable): SimpleFilter {
|
|
30
|
+
return new SimpleFilter(variable, [new Statement(Condition.EQUALS, this.value().value === true)]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
5
33
|
|
|
6
34
|
export const booleanHandler: TypeHandler = {
|
|
7
35
|
matches: (type) => type instanceof BooleanType,
|
|
36
|
+
getTypeLabel: () => 'Boolean',
|
|
8
37
|
encode: async (value: boolean) => value,
|
|
9
38
|
decode: async (value: boolean) => value,
|
|
10
39
|
encodeToScalar: async (value: boolean) => value,
|
|
@@ -24,5 +53,20 @@ export const booleanHandler: TypeHandler = {
|
|
|
24
53
|
}}
|
|
25
54
|
/>
|
|
26
55
|
);
|
|
56
|
+
},
|
|
57
|
+
setupFilter: async ({ variable, filter }) => {
|
|
58
|
+
const form = new BooleanFilterForm(BooleanFilterForm.fromFilter(filter));
|
|
59
|
+
const result = await ioc.get(DialogStore2).showDialog(
|
|
60
|
+
new ClearableFilterFormDialogDirective({
|
|
61
|
+
title: `Filter ${variable.label || variable.name}`,
|
|
62
|
+
form,
|
|
63
|
+
filter,
|
|
64
|
+
handler: async () => {}
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
if (!result) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return form.toFilter(variable);
|
|
27
71
|
}
|
|
28
72
|
};
|
|
@@ -86,6 +86,7 @@ const toDateValue = (value: unknown): Date | undefined => {
|
|
|
86
86
|
|
|
87
87
|
export const dateHandler: TypeHandler = {
|
|
88
88
|
matches: (type) => type instanceof DatetimeType || type instanceof DateType,
|
|
89
|
+
getTypeLabel: (type) => (type instanceof DatetimeType ? 'Datetime' : 'Date'),
|
|
89
90
|
encode: async (value: Date) => new Day(value),
|
|
90
91
|
decode: async (value: Day | Date) => toDateValue(value),
|
|
91
92
|
encodeToScalar: async (value: Date) => value?.toISOString() || null,
|
|
@@ -30,6 +30,7 @@ export const imageHandler = (context: TypeHandlerContext): TypeHandler => {
|
|
|
30
30
|
|
|
31
31
|
return {
|
|
32
32
|
matches: (type) => type instanceof SignatureType || type instanceof PhotoType,
|
|
33
|
+
getTypeLabel: (type) => (type instanceof SignatureType ? 'Signature' : 'Photo'),
|
|
33
34
|
encode: async (value: ImageMedia) => {
|
|
34
35
|
return Attachment.create({
|
|
35
36
|
data: await value.toArrayBuffer()
|
|
@@ -6,6 +6,7 @@ import { TypeHandler } from './shared/type-handler';
|
|
|
6
6
|
|
|
7
7
|
export const locationHandler: TypeHandler = {
|
|
8
8
|
matches: (type) => type instanceof LocationType,
|
|
9
|
+
getTypeLabel: () => 'Location',
|
|
9
10
|
encode: async (value: Location) => value,
|
|
10
11
|
decode: async (value: Location) => value,
|
|
11
12
|
encodeToScalar: async (value: Location) => {
|
|
@@ -6,6 +6,7 @@ import { TypeHandler, TypeHandlerContext } from './shared/type-handler';
|
|
|
6
6
|
export const multipleChoiceHandler = (context: TypeHandlerContext): TypeHandler => {
|
|
7
7
|
return {
|
|
8
8
|
matches: (type) => type instanceof MultipleChoiceType,
|
|
9
|
+
getTypeLabel: () => 'Multiple choice',
|
|
9
10
|
encode: async (value: string[]) => value,
|
|
10
11
|
decode: async (value: string[]) => value,
|
|
11
12
|
encodeToScalar: async (value: string[]) => JSON.stringify(value || []),
|
|
@@ -6,6 +6,7 @@ import { TypeHandler, TypeHandlerContext } from './shared/type-handler';
|
|
|
6
6
|
export const multipleChoiceIntegerHandler = (context: TypeHandlerContext): TypeHandler => {
|
|
7
7
|
return {
|
|
8
8
|
matches: (type) => type instanceof MultipleChoiceIntegerType,
|
|
9
|
+
getTypeLabel: () => 'Multiple choice integer',
|
|
9
10
|
encode: async (value: string[]) => value.map((v) => parseInt(v)),
|
|
10
11
|
decode: async (value: number[]) => value.map((v) => `${v}`),
|
|
11
12
|
encodeToScalar: async (value: string[]) => JSON.stringify(value || []),
|