@journeyapps-labs/reactor-mod-data-browser 2.0.2 → 2.2.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 +25 -0
- package/dist/@types/core/SchemaModelDefinition.d.ts +15 -1
- package/dist/@types/core/SchemaModelObject.d.ts +5 -0
- package/dist/@types/core/query/Page.d.ts +2 -0
- package/dist/@types/core/query/SimpleQuery.d.ts +4 -8
- package/dist/@types/core/query/filters.d.ts +17 -0
- package/dist/@types/core/query/widgets/BelongsToDisplayWidget.d.ts +14 -0
- package/dist/@types/core/query/widgets/CellDisplayWidget.d.ts +9 -0
- package/dist/@types/core/query/widgets/ColumnDisplayWidget.d.ts +5 -0
- package/dist/@types/core/query/widgets/SmartColumnWidget.d.ts +9 -0
- package/dist/@types/core/query/widgets/SmartFilterWidget.d.ts +9 -0
- package/dist/@types/forms/inputs/LocationInput.d.ts +13 -0
- package/dist/DataBrowserModule.js +1 -1
- package/dist/DataBrowserModule.js.map +1 -1
- package/dist/core/SchemaModelDefinition.js +56 -2
- package/dist/core/SchemaModelDefinition.js.map +1 -1
- package/dist/core/SchemaModelObject.js +72 -11
- package/dist/core/SchemaModelObject.js.map +1 -1
- package/dist/core/query/Page.js +5 -1
- package/dist/core/query/Page.js.map +1 -1
- package/dist/core/query/SimpleQuery.js +49 -83
- package/dist/core/query/SimpleQuery.js.map +1 -1
- package/dist/core/query/filters.js +17 -0
- package/dist/core/query/filters.js.map +1 -0
- package/dist/core/query/widgets/BelongsToDisplayWidget.js +70 -0
- package/dist/core/query/widgets/BelongsToDisplayWidget.js.map +1 -0
- package/dist/core/query/widgets/CellDisplayWidget.js +92 -0
- package/dist/core/query/widgets/CellDisplayWidget.js.map +1 -0
- package/dist/core/query/widgets/ColumnDisplayWidget.js +22 -0
- package/dist/core/query/widgets/ColumnDisplayWidget.js.map +1 -0
- package/dist/core/query/widgets/SmartColumnWidget.js +19 -0
- package/dist/core/query/widgets/SmartColumnWidget.js.map +1 -0
- package/dist/core/query/widgets/SmartFilterWidget.js +56 -0
- package/dist/core/query/widgets/SmartFilterWidget.js.map +1 -0
- package/dist/forms/SchemaModelForm.js +26 -11
- package/dist/forms/SchemaModelForm.js.map +1 -1
- package/dist/forms/inputs/LocationInput.js +74 -0
- package/dist/forms/inputs/LocationInput.js.map +1 -0
- package/dist/panels/model/ModelPanelFactory.js +1 -1
- package/dist/panels/model/ModelPanelFactory.js.map +1 -1
- package/dist/panels/model/ModelPanelWidget.js +21 -3
- package/dist/panels/model/ModelPanelWidget.js.map +1 -1
- package/dist/panels/query/QueryPanelWidget.js +6 -3
- package/dist/panels/query/QueryPanelWidget.js.map +1 -1
- package/dist/panels/query/TableControlsWidget.js +3 -3
- package/dist/panels/query/TableControlsWidget.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist-module/bundle.js +44 -7
- package/dist-module/bundle.js.map +1 -1
- package/package.json +7 -5
- package/src/DataBrowserModule.ts +1 -1
- package/src/core/SchemaModelDefinition.ts +70 -3
- package/src/core/SchemaModelObject.ts +52 -2
- package/src/core/query/Page.ts +9 -1
- package/src/core/query/SimpleQuery.tsx +62 -111
- package/src/core/query/filters.ts +30 -0
- package/src/core/query/widgets/BelongsToDisplayWidget.tsx +107 -0
- package/src/core/query/widgets/CellDisplayWidget.tsx +122 -0
- package/src/core/query/widgets/ColumnDisplayWidget.tsx +27 -0
- package/src/core/query/widgets/SmartColumnWidget.tsx +30 -0
- package/src/core/query/widgets/SmartFilterWidget.tsx +80 -0
- package/src/forms/SchemaModelForm.tsx +28 -9
- package/src/forms/inputs/LocationInput.tsx +104 -0
- package/src/panels/model/ModelPanelFactory.tsx +1 -1
- package/src/panels/model/ModelPanelWidget.tsx +37 -3
- package/src/panels/query/QueryPanelWidget.tsx +6 -3
- package/src/panels/query/TableControlsWidget.tsx +4 -2
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CheckboxWidget,
|
|
3
|
+
ImageMedia,
|
|
4
|
+
MetadataWidget,
|
|
5
|
+
SmartDateDisplayWidget,
|
|
6
|
+
styled
|
|
7
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
8
|
+
import { Attachment, Day, Location, Variable } from '@journeyapps/db';
|
|
9
|
+
import * as _ from 'lodash';
|
|
10
|
+
import * as React from 'react';
|
|
11
|
+
import { PageRow } from '../Page';
|
|
12
|
+
|
|
13
|
+
namespace S {
|
|
14
|
+
export const Empty = styled.div`
|
|
15
|
+
opacity: 0.2;
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const Preview = styled.img`
|
|
19
|
+
max-height: 40px;
|
|
20
|
+
max-width: 40px;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export const pill = styled.div`
|
|
25
|
+
padding: 2px 4px;
|
|
26
|
+
background: ${(p) => p.theme.table.pills};
|
|
27
|
+
border-radius: 3px;
|
|
28
|
+
font-size: 12px;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export const Pills = styled.div`
|
|
32
|
+
display: flex;
|
|
33
|
+
column-gap: 2px;
|
|
34
|
+
row-gap: 2px;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export const Max = styled.div`
|
|
38
|
+
max-width: 500px;
|
|
39
|
+
white-space: pre;
|
|
40
|
+
display: inline;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
text-overflow: ellipsis;
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CellDisplayWidgetProps {
|
|
47
|
+
row: PageRow;
|
|
48
|
+
cell: any;
|
|
49
|
+
variable: Variable;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const MAX_NUMBER_OF_ARR_ITEMS_TO_DISPLAY = 3;
|
|
53
|
+
|
|
54
|
+
export const CellDisplayWidget: React.FC<CellDisplayWidgetProps> = (props) => {
|
|
55
|
+
const { row, cell, variable } = props;
|
|
56
|
+
if (cell == null) {
|
|
57
|
+
return <S.Empty>null</S.Empty>;
|
|
58
|
+
}
|
|
59
|
+
if (_.isString(cell)) {
|
|
60
|
+
if (cell.trim() === '') {
|
|
61
|
+
return <S.Empty>empty</S.Empty>;
|
|
62
|
+
}
|
|
63
|
+
return <S.Max>{cell}</S.Max>;
|
|
64
|
+
}
|
|
65
|
+
if (_.isNumber(cell)) {
|
|
66
|
+
return cell;
|
|
67
|
+
}
|
|
68
|
+
if (_.isArray(cell)) {
|
|
69
|
+
if (cell.length === 0) {
|
|
70
|
+
return <S.Empty>empty array</S.Empty>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let items = _.slice(cell, 0, MAX_NUMBER_OF_ARR_ITEMS_TO_DISPLAY);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<S.Pills>
|
|
77
|
+
{items.map((c) => {
|
|
78
|
+
return <S.pill key={c}>{c}</S.pill>;
|
|
79
|
+
})}
|
|
80
|
+
{items.length !== cell.length ? '...' : null}
|
|
81
|
+
</S.Pills>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (cell instanceof Date) {
|
|
85
|
+
return <SmartDateDisplayWidget date={cell} />;
|
|
86
|
+
}
|
|
87
|
+
if (cell instanceof Day) {
|
|
88
|
+
return <SmartDateDisplayWidget date={cell.toDate()} />;
|
|
89
|
+
}
|
|
90
|
+
if (_.isBoolean(cell)) {
|
|
91
|
+
return <CheckboxWidget checked={cell} onChange={() => {}} />;
|
|
92
|
+
}
|
|
93
|
+
if (cell instanceof Location) {
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<MetadataWidget label={'Lat'} value={`${cell.latitude}`} />
|
|
97
|
+
<MetadataWidget label={'Long'} value={`${cell.longitude}`} />
|
|
98
|
+
</>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (cell instanceof Attachment) {
|
|
102
|
+
if (cell.uploaded()) {
|
|
103
|
+
return (
|
|
104
|
+
<S.Preview
|
|
105
|
+
onClick={() => {
|
|
106
|
+
row.model.getMedia(variable.name).then((media) => {
|
|
107
|
+
if (media instanceof ImageMedia) {
|
|
108
|
+
media.open();
|
|
109
|
+
} else {
|
|
110
|
+
window.open(cell.url(), '_blank');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}}
|
|
114
|
+
src={cell.urls['thumbnail']}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return <S.Empty>Not uploaded</S.Empty>;
|
|
119
|
+
}
|
|
120
|
+
console.log('unknown type', cell);
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
|
|
4
|
+
export interface ColumnDisplayWidgetProps {
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ColumnDisplayWidget: React.FC<ColumnDisplayWidgetProps> = (props) => {
|
|
9
|
+
let parts = (props.label || '').split(' ');
|
|
10
|
+
if (parts.length >= 5) {
|
|
11
|
+
return <S.Width length={150}>{props.label}</S.Width>;
|
|
12
|
+
}
|
|
13
|
+
if (parts.length >= 3) {
|
|
14
|
+
return <S.Width length={80}>{props.label}</S.Width>;
|
|
15
|
+
}
|
|
16
|
+
return <S.Span>{props.label}</S.Span>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
namespace S {
|
|
20
|
+
export const Width = styled.div<{ length: number }>`
|
|
21
|
+
min-width: ${(p) => p.length}px;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export const Span = styled.div`
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { Variable } from '@journeyapps/db';
|
|
4
|
+
import { ColumnDisplayWidget } from './ColumnDisplayWidget';
|
|
5
|
+
import { SmartFilterWidget } from './SmartFilterWidget';
|
|
6
|
+
import { SimpleFilter } from '../filters';
|
|
7
|
+
|
|
8
|
+
export interface SmartColumnWidgetProps {
|
|
9
|
+
variable: Variable;
|
|
10
|
+
filter?: SimpleFilter;
|
|
11
|
+
filterChanged: (filter: SimpleFilter | null) => any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const SmartColumnWidget: React.FC<SmartColumnWidgetProps> = (props) => {
|
|
15
|
+
return (
|
|
16
|
+
<S.Container>
|
|
17
|
+
<ColumnDisplayWidget label={props.variable.label} />
|
|
18
|
+
<SmartFilterWidget filter={props.filter} variable={props.variable} filterChanged={props.filterChanged} />
|
|
19
|
+
</S.Container>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
namespace S {
|
|
24
|
+
export const Container = styled.div`
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: row;
|
|
27
|
+
align-items: center;
|
|
28
|
+
column-gap: 5px;
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { SingleChoiceIntegerType, SingleChoiceType, TextType, Variable } from '@journeyapps/db';
|
|
3
|
+
import { ComboBoxStore, DialogStore, ioc, PanelButtonWidget } from '@journeyapps-labs/reactor-mod';
|
|
4
|
+
import * as _ from 'lodash';
|
|
5
|
+
import { Condition, SimpleFilter } from '../filters';
|
|
6
|
+
|
|
7
|
+
export interface SmartFilterWidgetProps {
|
|
8
|
+
variable: Variable;
|
|
9
|
+
filter?: SimpleFilter;
|
|
10
|
+
filterChanged: (filter: SimpleFilter | null) => any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SmartFilterWidget: React.FC<SmartFilterWidgetProps> = (props) => {
|
|
14
|
+
if (props.variable.type instanceof SingleChoiceIntegerType || props.variable.type instanceof SingleChoiceType) {
|
|
15
|
+
return (
|
|
16
|
+
<PanelButtonWidget
|
|
17
|
+
{...{
|
|
18
|
+
icon: 'filter',
|
|
19
|
+
highlight: !!props.filter,
|
|
20
|
+
action: async (position) => {
|
|
21
|
+
let results = await ioc.get(ComboBoxStore).showMultiSelectComboBox(
|
|
22
|
+
_.map(props.variable.type.options, (option) => {
|
|
23
|
+
return {
|
|
24
|
+
title: `${option.value}`,
|
|
25
|
+
key: `${option.value}`,
|
|
26
|
+
checked: !!props.filter?.statements?.find((s) => s.arg === `${option.value}`)
|
|
27
|
+
};
|
|
28
|
+
}),
|
|
29
|
+
position
|
|
30
|
+
);
|
|
31
|
+
if (results.length > 0) {
|
|
32
|
+
props.filterChanged(
|
|
33
|
+
new SimpleFilter(
|
|
34
|
+
props.variable,
|
|
35
|
+
results.map((r) => {
|
|
36
|
+
return {
|
|
37
|
+
arg: r.key,
|
|
38
|
+
condition: Condition.EQUALS
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
props.filterChanged(null);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (props.variable.type instanceof TextType) {
|
|
52
|
+
return (
|
|
53
|
+
<PanelButtonWidget
|
|
54
|
+
{...{
|
|
55
|
+
icon: 'filter',
|
|
56
|
+
highlight: !!props.filter,
|
|
57
|
+
action: async () => {
|
|
58
|
+
let value = await ioc.get(DialogStore).showInputDialog({
|
|
59
|
+
title: `${props.variable.label}`,
|
|
60
|
+
initialValue: props.filter?.statements[0]?.arg
|
|
61
|
+
});
|
|
62
|
+
if (value) {
|
|
63
|
+
props.filterChanged(
|
|
64
|
+
new SimpleFilter(props.variable, [
|
|
65
|
+
{
|
|
66
|
+
arg: value,
|
|
67
|
+
condition: Condition.EQUALS
|
|
68
|
+
}
|
|
69
|
+
])
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
props.filterChanged(null);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
@@ -2,7 +2,10 @@ import {
|
|
|
2
2
|
BooleanInput,
|
|
3
3
|
DateInput,
|
|
4
4
|
DateTimePickerType,
|
|
5
|
+
FileInput,
|
|
5
6
|
FormModel,
|
|
7
|
+
ImageInput,
|
|
8
|
+
ImageMedia,
|
|
6
9
|
SelectInput,
|
|
7
10
|
TextInput
|
|
8
11
|
} from '@journeyapps-labs/reactor-mod';
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
SingleChoiceType,
|
|
23
26
|
TextType
|
|
24
27
|
} from '@journeyapps/db';
|
|
28
|
+
import { LocationInput } from './inputs/LocationInput';
|
|
25
29
|
|
|
26
30
|
export interface SchemaModelFormOptions {
|
|
27
31
|
definition: SchemaModelDefinition;
|
|
@@ -49,13 +53,25 @@ export class SchemaModelForm extends FormModel {
|
|
|
49
53
|
type: DateTimePickerType.DATE
|
|
50
54
|
});
|
|
51
55
|
}
|
|
52
|
-
if (attribute.type instanceof
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if (attribute.type instanceof SignatureType || attribute.type instanceof PhotoType) {
|
|
57
|
+
let media = new ImageInput({
|
|
58
|
+
name: attribute.name,
|
|
59
|
+
label: attribute.label
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (options.object) {
|
|
63
|
+
options.object.getMedia(attribute.name).then((m) => {
|
|
64
|
+
media.setValue(m as ImageMedia);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return media;
|
|
56
68
|
}
|
|
57
|
-
if (attribute.type instanceof
|
|
58
|
-
|
|
69
|
+
if (attribute.type instanceof AttachmentType) {
|
|
70
|
+
return new FileInput({
|
|
71
|
+
name: attribute.name,
|
|
72
|
+
label: attribute.label,
|
|
73
|
+
value: options.object?.model[attribute.name] || null
|
|
74
|
+
});
|
|
59
75
|
}
|
|
60
76
|
if (attribute.type instanceof BooleanType) {
|
|
61
77
|
return new BooleanInput({
|
|
@@ -65,10 +81,13 @@ export class SchemaModelForm extends FormModel {
|
|
|
65
81
|
});
|
|
66
82
|
}
|
|
67
83
|
if (attribute.type instanceof LocationType) {
|
|
84
|
+
return new LocationInput({
|
|
85
|
+
name: attribute.name,
|
|
86
|
+
label: attribute.label,
|
|
87
|
+
value: options.object?.model[attribute.name]
|
|
88
|
+
});
|
|
68
89
|
}
|
|
69
|
-
if (attribute.type instanceof SingleChoiceIntegerType) {
|
|
70
|
-
}
|
|
71
|
-
if (attribute.type instanceof SingleChoiceType) {
|
|
90
|
+
if (attribute.type instanceof SingleChoiceIntegerType || attribute.type instanceof SingleChoiceType) {
|
|
72
91
|
return new SelectInput({
|
|
73
92
|
name: attribute.name,
|
|
74
93
|
label: attribute.label,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FormInput,
|
|
3
|
+
FormInputGenerics,
|
|
4
|
+
FormInputOptions,
|
|
5
|
+
FormInputRenderOptions,
|
|
6
|
+
InputContainerWidget,
|
|
7
|
+
NumberInput,
|
|
8
|
+
PanelButtonMode,
|
|
9
|
+
PanelButtonWidget
|
|
10
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import { Location } from '@journeyapps/db';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
14
|
+
|
|
15
|
+
export class LocationInput extends FormInput<FormInputGenerics & { VALUE: Location }> {
|
|
16
|
+
latitude: NumberInput;
|
|
17
|
+
longitude: NumberInput;
|
|
18
|
+
|
|
19
|
+
constructor(options: FormInputOptions<Location>) {
|
|
20
|
+
super(options);
|
|
21
|
+
this.latitude = new NumberInput({
|
|
22
|
+
name: 'latitude',
|
|
23
|
+
label: 'Latitude',
|
|
24
|
+
value: options.value?.latitude
|
|
25
|
+
});
|
|
26
|
+
this.longitude = new NumberInput({
|
|
27
|
+
name: 'longitude',
|
|
28
|
+
label: 'Longitude',
|
|
29
|
+
value: options.value?.longitude
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.update();
|
|
33
|
+
|
|
34
|
+
[this.latitude, this.longitude].forEach((l) => {
|
|
35
|
+
l.registerListener({
|
|
36
|
+
valueChanged: () => {
|
|
37
|
+
this.update();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setValue(value: Location) {
|
|
44
|
+
super.setValue(value);
|
|
45
|
+
if (this.latitude.value !== value?.latitude) {
|
|
46
|
+
this.latitude.setValue(value?.latitude);
|
|
47
|
+
}
|
|
48
|
+
if (this.longitude.value !== value?.longitude) {
|
|
49
|
+
this.longitude.setValue(value?.longitude);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
update() {
|
|
54
|
+
let lat = this.latitude.value;
|
|
55
|
+
let long = this.longitude.value;
|
|
56
|
+
if (lat == null || long === null) {
|
|
57
|
+
this.setValue(null);
|
|
58
|
+
} else {
|
|
59
|
+
this.setValue(
|
|
60
|
+
new Location({
|
|
61
|
+
latitude: lat,
|
|
62
|
+
longitude: long,
|
|
63
|
+
horizontal_accuracy: (this.value as Location)?.horizontal_accuracy,
|
|
64
|
+
vertical_accuracy: (this.value as Location)?.vertical_accuracy,
|
|
65
|
+
altitude: (this.value as Location)?.altitude,
|
|
66
|
+
timestamp: new Date()
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
renderControl(options: FormInputRenderOptions): React.JSX.Element {
|
|
73
|
+
return (
|
|
74
|
+
<S.Container>
|
|
75
|
+
<InputContainerWidget error={this.latitude.error} label={this.latitude.label} inline={true}>
|
|
76
|
+
{this.latitude.renderControl({ inline: true })}
|
|
77
|
+
</InputContainerWidget>
|
|
78
|
+
<InputContainerWidget error={this.longitude.error} label={this.longitude.label} inline={true}>
|
|
79
|
+
{this.longitude.renderControl({ inline: true })}
|
|
80
|
+
</InputContainerWidget>
|
|
81
|
+
{this.value ? (
|
|
82
|
+
<PanelButtonWidget
|
|
83
|
+
mode={PanelButtonMode.LINK}
|
|
84
|
+
label="Open in maps"
|
|
85
|
+
icon="map"
|
|
86
|
+
action={() => {
|
|
87
|
+
window.open(`https://www.google.com/maps/?q=${this.latitude.value},${this.longitude.value}`, '_blank');
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
) : null}
|
|
91
|
+
</S.Container>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
namespace S {
|
|
97
|
+
export const Container = styled.div`
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
row-gap: 2px;
|
|
101
|
+
max-width: 250px;
|
|
102
|
+
align-items: flex-start;
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
@@ -24,7 +24,7 @@ export class ModelPanelModel extends ReactorPanelModel {
|
|
|
24
24
|
|
|
25
25
|
constructor(options?: ModelPanelModelOptions) {
|
|
26
26
|
super(ModelPanelFactory.TYPE);
|
|
27
|
-
this.setExpand(
|
|
27
|
+
this.setExpand(false, true);
|
|
28
28
|
this.definition = options?.definition;
|
|
29
29
|
this.model = options?.model;
|
|
30
30
|
}
|
|
@@ -2,7 +2,13 @@ import * as React from 'react';
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
4
|
import styled from '@emotion/styled';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
BorderLayoutWidget,
|
|
7
|
+
LoadingPanelWidget,
|
|
8
|
+
PANEL_CONTENT_PADDING,
|
|
9
|
+
PanelToolbarWidget,
|
|
10
|
+
ScrollableDivCss
|
|
11
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
6
12
|
|
|
7
13
|
import { SchemaModelForm } from '../../forms/SchemaModelForm';
|
|
8
14
|
import { ModelPanelModel } from './ModelPanelFactory';
|
|
@@ -13,7 +19,9 @@ export interface QueryPanelWidgetProps {
|
|
|
13
19
|
|
|
14
20
|
namespace S {
|
|
15
21
|
export const Container = styled.div`
|
|
16
|
-
|
|
22
|
+
overflow: auto;
|
|
23
|
+
padding: ${PANEL_CONTENT_PADDING}px;
|
|
24
|
+
${ScrollableDivCss};
|
|
17
25
|
`;
|
|
18
26
|
}
|
|
19
27
|
|
|
@@ -32,10 +40,36 @@ export const ModelPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
32
40
|
);
|
|
33
41
|
}, [props.model.model, props.model.definition]);
|
|
34
42
|
|
|
43
|
+
let top = null;
|
|
44
|
+
if (props.model.model) {
|
|
45
|
+
top = (
|
|
46
|
+
<PanelToolbarWidget
|
|
47
|
+
btns={
|
|
48
|
+
[
|
|
49
|
+
// {
|
|
50
|
+
// label: 'Delete object',
|
|
51
|
+
// action: () => {}
|
|
52
|
+
// }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
meta={[
|
|
56
|
+
{
|
|
57
|
+
label: 'ID',
|
|
58
|
+
value: props.model?.id
|
|
59
|
+
}
|
|
60
|
+
]}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
35
65
|
return (
|
|
36
66
|
<LoadingPanelWidget loading={!form}>
|
|
37
67
|
{() => {
|
|
38
|
-
return
|
|
68
|
+
return (
|
|
69
|
+
<BorderLayoutWidget top={top}>
|
|
70
|
+
<S.Container>{form.render()}</S.Container>
|
|
71
|
+
</BorderLayoutWidget>
|
|
72
|
+
);
|
|
39
73
|
}}
|
|
40
74
|
</LoadingPanelWidget>
|
|
41
75
|
);
|
|
@@ -7,6 +7,7 @@ import { BorderLayoutWidget, LoadingPanelWidget } from '@journeyapps-labs/reacto
|
|
|
7
7
|
import { Page } from '../../core/query/Page';
|
|
8
8
|
import { PageResultsWidget } from './PageResultsWidget';
|
|
9
9
|
import { TableControlsWidget } from './TableControlsWidget';
|
|
10
|
+
import { autorun } from 'mobx';
|
|
10
11
|
|
|
11
12
|
export interface QueryPanelWidgetProps {
|
|
12
13
|
model: QueryPanelModel;
|
|
@@ -30,9 +31,11 @@ export const QueryPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
30
31
|
}, [props.model.query]);
|
|
31
32
|
|
|
32
33
|
useEffect(() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
return autorun(() => {
|
|
35
|
+
if (props.model.query) {
|
|
36
|
+
setPage(props.model.query.getPage(props.model.current_page));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
36
39
|
}, [props.model.query]);
|
|
37
40
|
|
|
38
41
|
return (
|
|
@@ -46,6 +46,7 @@ export const TableControlsWidget: React.FC<TableControlsWidgetProps> = observer(
|
|
|
46
46
|
/>
|
|
47
47
|
<PanelButtonWidget
|
|
48
48
|
label="Page"
|
|
49
|
+
tooltip="Reload page"
|
|
49
50
|
icon="refresh"
|
|
50
51
|
action={() => {
|
|
51
52
|
props.current_page.load();
|
|
@@ -53,9 +54,10 @@ export const TableControlsWidget: React.FC<TableControlsWidgetProps> = observer(
|
|
|
53
54
|
/>
|
|
54
55
|
<PanelButtonWidget
|
|
55
56
|
label="Query"
|
|
57
|
+
tooltip="Reload Query"
|
|
56
58
|
icon="refresh"
|
|
57
|
-
action={() => {
|
|
58
|
-
props.query.load();
|
|
59
|
+
action={async (event, loading) => {
|
|
60
|
+
await props.query.load();
|
|
59
61
|
}}
|
|
60
62
|
/>
|
|
61
63
|
</S.Container>
|