@omnsight/osint-entity-components 0.2.4 → 0.2.6

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.
Files changed (60) hide show
  1. package/dist/index.js +8 -8
  2. package/dist/index.mjs +796 -781
  3. package/package.json +15 -2
  4. package/src/App.tsx +397 -141
  5. package/src/avatars/layouts/AvatarDropdown.tsx +1 -1
  6. package/src/avatars/layouts/AvatarSpan.tsx +7 -2
  7. package/src/forms/BaseForm.tsx +138 -0
  8. package/src/forms/EditableAttributes.tsx +131 -0
  9. package/src/forms/EventForm/EditableForm.tsx +78 -0
  10. package/src/forms/EventForm/EditingForm.tsx +401 -0
  11. package/src/forms/EventForm/IconFormSection.tsx +54 -0
  12. package/src/forms/EventForm/StaticForm.tsx +272 -0
  13. package/src/forms/EventForm/index.ts +1 -0
  14. package/src/forms/InsightForm/EditableForm.tsx +70 -0
  15. package/src/forms/InsightForm/EditingForm.tsx +79 -0
  16. package/src/forms/InsightForm/StaticForm.tsx +139 -0
  17. package/src/forms/InsightForm/index.ts +1 -0
  18. package/src/forms/MonitoringSourceForm/EditableForm.tsx +59 -0
  19. package/src/forms/MonitoringSourceForm/EditingForm.tsx +192 -0
  20. package/src/forms/MonitoringSourceForm/StaticForm.tsx +107 -0
  21. package/src/forms/MonitoringSourceForm/index.ts +1 -0
  22. package/src/forms/OrganizationForm/EditableForm.tsx +74 -0
  23. package/src/forms/OrganizationForm/EditingForm.tsx +177 -0
  24. package/src/forms/OrganizationForm/IconFormSection.tsx +60 -0
  25. package/src/forms/OrganizationForm/StaticForm.tsx +209 -0
  26. package/src/forms/OrganizationForm/index.ts +1 -0
  27. package/src/forms/PersonForm/EditableForm.tsx +74 -0
  28. package/src/forms/PersonForm/EditingForm.tsx +187 -0
  29. package/src/forms/PersonForm/IconFormSection.tsx +54 -0
  30. package/src/forms/PersonForm/StaticForm.tsx +202 -0
  31. package/src/forms/PersonForm/index.ts +1 -0
  32. package/src/forms/RelationForm/EditableForm.tsx +74 -0
  33. package/src/forms/RelationForm/EditingForm.tsx +147 -0
  34. package/src/forms/RelationForm/StaticForm.tsx +182 -0
  35. package/src/forms/RelationForm/index.ts +1 -0
  36. package/src/forms/SourceForm/EditableForm.tsx +74 -0
  37. package/src/forms/SourceForm/EditingForm.tsx +199 -0
  38. package/src/forms/SourceForm/IconFormSection.tsx +54 -0
  39. package/src/forms/SourceForm/StaticForm.tsx +209 -0
  40. package/src/forms/SourceForm/index.ts +1 -0
  41. package/src/forms/WebsiteForm/EditableForm.tsx +74 -0
  42. package/src/forms/WebsiteForm/EditingForm.tsx +216 -0
  43. package/src/forms/WebsiteForm/IconFormSection.tsx +54 -0
  44. package/src/forms/WebsiteForm/StaticForm.tsx +225 -0
  45. package/src/forms/WebsiteForm/index.ts +1 -0
  46. package/src/forms/accessLevel.ts +48 -0
  47. package/src/forms/index.ts +8 -0
  48. package/src/icons/Event/Select.tsx +7 -6
  49. package/src/icons/Organization/Select.tsx +7 -6
  50. package/src/icons/Person/Select.tsx +7 -6
  51. package/src/icons/Source/Select.tsx +7 -6
  52. package/src/icons/Website/Select.tsx +9 -8
  53. package/src/inputs/CountrySelect.tsx +45 -0
  54. package/src/inputs/CustomDatePicker.tsx +51 -0
  55. package/src/inputs/CustomDateTimePicker.tsx +51 -0
  56. package/src/inputs/RangeDatePicker.tsx +99 -0
  57. package/src/inputs/TimezoneSelect.tsx +20 -0
  58. package/src/locales/en.json +135 -0
  59. package/src/locales/zh.json +135 -0
  60. package/src/main.tsx +20 -4
@@ -0,0 +1,199 @@
1
+ import {
2
+ Group,
3
+ Text,
4
+ Stack,
5
+ ActionIcon,
6
+ Divider,
7
+ UnstyledButton,
8
+ Collapse,
9
+ Title,
10
+ TextInput,
11
+ NumberInput,
12
+ TagsInput,
13
+ Textarea,
14
+ } from '@mantine/core';
15
+ import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
16
+ import { useTranslation } from 'react-i18next';
17
+ import { type Source } from 'omni-osint-crud-client';
18
+ import { EditableAttributes } from '../EditableAttributes';
19
+ import { type CSSProperties, useState } from 'react';
20
+ import { ChevronDownIcon } from '@heroicons/react/24/outline';
21
+ import { Controller } from 'react-hook-form';
22
+ import { BaseForm } from '../BaseForm';
23
+ import { SourceIcon } from '@omnsight/osint-entity-components/icons';
24
+ import { SourceIconFormSection } from './IconFormSection';
25
+
26
+ interface Props {
27
+ source: Source;
28
+ onSubmit?: (data: Source) => void;
29
+ onUpdate?: (data: Partial<Source>) => void;
30
+ onClose?: () => void;
31
+ children?: React.ReactNode;
32
+ style?: CSSProperties;
33
+ }
34
+
35
+ export const EditingForm: React.FC<Props> = ({
36
+ source,
37
+ onSubmit,
38
+ onUpdate,
39
+ onClose,
40
+ children,
41
+ style,
42
+ }) => {
43
+ const { t } = useTranslation();
44
+ const [attributesOpen, setAttributesOpen] = useState(false);
45
+
46
+ const handlClose = () => {
47
+ onClose?.();
48
+ };
49
+
50
+ return (
51
+ <BaseForm<Source>
52
+ style={style}
53
+ icon={<SourceIcon source={source} />}
54
+ title={t('components.forms.SourceForm.title')}
55
+ onClose={handlClose}
56
+ defaultValues={source}
57
+ onSubmit={onSubmit}
58
+ onUpdate={onUpdate}
59
+ onlyShowEditOnDirty={false}
60
+ >
61
+ {({ control, formState: { errors } }) => {
62
+ return (
63
+ <Stack
64
+ pos="relative"
65
+ gap="xs"
66
+ style={{ cursor: 'default' }}
67
+ >
68
+ <Group gap="xs">
69
+ <Text size="sm" fw={500}>
70
+ {t('placeholder.title')}
71
+ </Text>
72
+ <Controller
73
+ name="name"
74
+ control={control}
75
+ rules={{ required: t('common.required') }}
76
+ render={({ field }) => (
77
+ <TextInput
78
+ {...field}
79
+ value={field.value || source.url || ''}
80
+ placeholder={t('placeholder.title')}
81
+ style={{ flex: 'initial' }}
82
+ error={errors.name?.message}
83
+ />
84
+ )}
85
+ />
86
+ {source.url && (
87
+ <ActionIcon
88
+ component="a"
89
+ href={source.url}
90
+ target="_blank"
91
+ variant="subtle"
92
+ size="sm"
93
+ >
94
+ <ArrowTopRightOnSquareIcon style={{ width: '70%', height: '70%' }} />
95
+ </ActionIcon>
96
+ )}
97
+ </Group>
98
+
99
+ <Group gap={4} wrap="nowrap">
100
+ <Text>{t('placeholder.url')}:</Text>
101
+ <Controller
102
+ name="url"
103
+ control={control}
104
+ rules={{ required: t('common.required') }}
105
+ render={({ field }) => (
106
+ <TextInput
107
+ {...field}
108
+ value={field.value || ''}
109
+ placeholder={t('placeholder.url')}
110
+ error={errors.url?.message}
111
+ />
112
+ )}
113
+ />
114
+ </Group>
115
+
116
+ <Text size="sm" fw={500}>
117
+ {t('placeholder.description')}
118
+ </Text>
119
+ <Controller
120
+ name="description"
121
+ control={control}
122
+ render={({ field }) => (
123
+ <Textarea
124
+ {...field}
125
+ value={field.value || ''}
126
+ placeholder={t('components.forms.SourceForm.sourceDescription')}
127
+ />
128
+ )}
129
+ />
130
+
131
+ <Group gap={4}>
132
+ <Text size="sm" c="dimmed">
133
+ {t('placeholder.type')}:
134
+ </Text>
135
+ <SourceIconFormSection data={source} />
136
+ </Group>
137
+
138
+ <Group gap={4}>
139
+ <Text>{t('placeholder.reliability')}:</Text>
140
+ <Controller
141
+ name="reliability"
142
+ control={control}
143
+ render={({ field }) => (
144
+ <NumberInput
145
+ {...field}
146
+ value={field.value || 0}
147
+ placeholder={t('placeholder.reliability')}
148
+ />
149
+ )}
150
+ />
151
+ </Group>
152
+
153
+ <Text size="sm" fw={500}>
154
+ {t('placeholder.tags')}
155
+ </Text>
156
+ <Controller
157
+ name="tags"
158
+ control={control}
159
+ render={({ field }) => (
160
+ <TagsInput
161
+ {...field}
162
+ value={field.value || []}
163
+ placeholder={t('placeholder.tags')}
164
+ />
165
+ )}
166
+ />
167
+
168
+ {children}
169
+
170
+ <Divider my="sm" />
171
+
172
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
173
+ <Group justify="space-between">
174
+ <Title order={5}>{t('placeholder.attributes')}</Title>
175
+ <ChevronDownIcon
176
+ style={{
177
+ width: 16,
178
+ transform: attributesOpen ? 'rotate(180deg)' : 'rotate(0deg)',
179
+ transition: 'transform 200ms ease',
180
+ }}
181
+ />
182
+ </Group>
183
+ </UnstyledButton>
184
+
185
+ <Collapse in={attributesOpen}>
186
+ <Controller
187
+ name="attributes"
188
+ control={control}
189
+ render={({ field }) => (
190
+ <EditableAttributes {...field} value={field.value || {}} isEditing={true} />
191
+ )}
192
+ />
193
+ </Collapse>
194
+ </Stack>
195
+ );
196
+ }}
197
+ </BaseForm>
198
+ );
199
+ };
@@ -0,0 +1,54 @@
1
+ import { Group } from "@mantine/core";
2
+ import {
3
+ SourceColorSelector,
4
+ SourceIconSelector,
5
+ } from "@omnsight/osint-entity-components/icons";
6
+ import type { Source } from "omni-osint-crud-client";
7
+ import { Controller, useFormContext, useWatch } from "react-hook-form";
8
+ import { useTranslation } from "react-i18next";
9
+
10
+ export const SourceIconFormSection = ({ data }: { data: Source }) => {
11
+ const {
12
+ control,
13
+ formState: { errors },
14
+ } = useFormContext<Source>();
15
+ const { t } = useTranslation();
16
+ const type = useWatch({ control, name: "type" });
17
+ const iconColor = useWatch({ control, name: "attributes.icon_color" });
18
+
19
+ const modifiedData = {
20
+ ...data,
21
+ type: type,
22
+ attributes: { ...data.attributes, icon_color: iconColor },
23
+ };
24
+
25
+ return (
26
+ <Group grow>
27
+ <Controller
28
+ name="type"
29
+ control={control}
30
+ rules={{ required: t("common.required") }}
31
+ render={({ field }) => (
32
+ <SourceIconSelector
33
+ {...field}
34
+ data={modifiedData}
35
+ value={field.value}
36
+ error={errors.type?.message}
37
+ />
38
+ )}
39
+ />
40
+ <Controller
41
+ name="attributes.icon_color"
42
+ control={control}
43
+ rules={{ required: t("common.required") }}
44
+ render={({ field }) => (
45
+ <SourceColorSelector
46
+ {...field}
47
+ value={field.value as string | undefined}
48
+ error={errors.attributes?.icon_color?.message}
49
+ />
50
+ )}
51
+ />
52
+ </Group>
53
+ );
54
+ };
@@ -0,0 +1,209 @@
1
+ import {
2
+ Group,
3
+ Text,
4
+ Stack,
5
+ ActionIcon,
6
+ Divider,
7
+ UnstyledButton,
8
+ Collapse,
9
+ Title,
10
+ rem,
11
+ Tooltip,
12
+ Box,
13
+ Select,
14
+ } from '@mantine/core';
15
+ import { ArrowTopRightOnSquareIcon, UserIcon } from '@heroicons/react/24/outline';
16
+ import { useTranslation } from 'react-i18next';
17
+ import { type Source, type Permissive } from 'omni-osint-crud-client';
18
+ import { EditableAttributes } from '../EditableAttributes';
19
+ import { type CSSProperties, useState, type PropsWithChildren } from 'react';
20
+ import { ChevronDownIcon } from '@heroicons/react/24/outline';
21
+ import {
22
+ getAccessLevel,
23
+ getRoles,
24
+ useReadOptions,
25
+ useWriteOptions,
26
+ } from '../accessLevel';
27
+ import { Controller } from 'react-hook-form';
28
+ import { BaseForm } from '../BaseForm';
29
+ import { SourceIcon } from '@omnsight/osint-entity-components/icons';
30
+
31
+ interface Props extends PropsWithChildren {
32
+ source: Source;
33
+ isAdmin?: boolean;
34
+ onUpdate?: (data: Permissive) => void;
35
+ onClose?: () => void;
36
+ onDoubleClick: () => void;
37
+ exitButton?: React.ReactNode;
38
+ style?: CSSProperties;
39
+ editModeEnabled: boolean;
40
+ }
41
+
42
+ export const StaticForm: React.FC<Props> = ({
43
+ source,
44
+ isAdmin = false,
45
+ onUpdate,
46
+ onClose,
47
+ onDoubleClick,
48
+ exitButton,
49
+ children,
50
+ style,
51
+ editModeEnabled,
52
+ }) => {
53
+ const { t } = useTranslation();
54
+ const [attributesOpen, setAttributesOpen] = useState(false);
55
+
56
+ const handlClose = () => {
57
+ onClose?.();
58
+ };
59
+
60
+ const readOptions = useReadOptions(isAdmin);
61
+ const writeOptions = useWriteOptions();
62
+
63
+ return (
64
+ <BaseForm<Source>
65
+ style={style}
66
+ icon={<SourceIcon source={source} />}
67
+ title={source.name || source.url || t('components.forms.SourceForm.title')}
68
+ onClose={handlClose}
69
+ defaultValues={source}
70
+ onUpdate={onUpdate}
71
+ exitButton={exitButton}
72
+ onlyShowEditOnDirty={true}
73
+ >
74
+ {({ control, formState: { errors } }) => (
75
+ <Stack
76
+ pos="relative"
77
+ gap="xs"
78
+ style={{ cursor: editModeEnabled ? 'pointer' : 'default' }}
79
+ onDoubleClick={onDoubleClick}
80
+ >
81
+ <Group gap="xs">
82
+ {source.url && (
83
+ <ActionIcon
84
+ component="a"
85
+ href={source.url}
86
+ target="_blank"
87
+ variant="subtle"
88
+ size="sm"
89
+ >
90
+ <ArrowTopRightOnSquareIcon style={{ width: '70%', height: '70%' }} />
91
+ </ActionIcon>
92
+ )}
93
+ </Group>
94
+
95
+ <Group gap={4} wrap="nowrap">
96
+ <Text>{t('placeholder.url')}:</Text>
97
+ <Text>{source.url}</Text>
98
+ </Group>
99
+
100
+ <Text size="sm">
101
+ {source.description || t('components.forms.SourceForm.sourceDescription')}
102
+ </Text>
103
+
104
+ <Group gap={4}>
105
+ <Text size="sm" c="dimmed">
106
+ {t('placeholder.type')}:
107
+ </Text>
108
+ <Text size="sm">{source.type}</Text>
109
+ </Group>
110
+
111
+ <Group gap={4}>
112
+ <Text>{t('placeholder.reliability')}:</Text>
113
+ <Text>{source.reliability}</Text>
114
+ </Group>
115
+
116
+ <Text>{(source.tags || []).join(', ')}</Text>
117
+
118
+ {children}
119
+
120
+ {onUpdate && (
121
+ <Group gap="xs" w="100%">
122
+ <Box
123
+ style={{
124
+ flex: 1,
125
+ display: "flex",
126
+ alignItems: "center",
127
+ justifyContent: "center",
128
+ }}
129
+ >
130
+ <Tooltip label={source.owner?.toUpperCase()[0] || ""}>
131
+ <UserIcon style={{ width: rem(18), height: rem(18) }} />
132
+ </Tooltip>
133
+ </Box>
134
+ <Box
135
+ style={{
136
+ flex: 3,
137
+ display: "flex",
138
+ }}
139
+ >
140
+ {t("placeholder.accessLabel")}:
141
+ </Box>
142
+ <Controller
143
+ name="read"
144
+ control={control}
145
+ rules={{ required: t("common.required") }}
146
+ render={({ field }) => {
147
+ return (
148
+ <Box style={{ flex: 4 }}>
149
+ <Select
150
+ value={getAccessLevel(field.value ?? [])}
151
+ onChange={(value) => field.onChange(getRoles(value))}
152
+ placeholder={t("placeholder.readAccess")}
153
+ data={readOptions}
154
+ clearable
155
+ error={errors.read?.message}
156
+ />
157
+ </Box>
158
+ );
159
+ }}
160
+ />
161
+ <Controller
162
+ name="write"
163
+ control={control}
164
+ rules={{ required: t("common.required") }}
165
+ render={({ field }) => {
166
+ return (
167
+ <Box style={{ flex: 4 }}>
168
+ <Select
169
+ value={getAccessLevel(field.value ?? [])}
170
+ onChange={(value) => field.onChange(getRoles(value))}
171
+ placeholder={t("placeholder.writeAccess")}
172
+ data={writeOptions}
173
+ clearable
174
+ error={errors.write?.message}
175
+ />
176
+ </Box>
177
+ );
178
+ }}
179
+ />
180
+ </Group>
181
+ )}
182
+
183
+ <Divider my="sm" />
184
+
185
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
186
+ <Group justify="space-between">
187
+ <Title order={5}>{t('placeholder.attributes')}</Title>
188
+ <ChevronDownIcon
189
+ style={{
190
+ width: 16,
191
+ transform: attributesOpen ? 'rotate(180deg)' : 'rotate(0deg)',
192
+ transition: 'transform 200ms ease',
193
+ }}
194
+ />
195
+ </Group>
196
+ </UnstyledButton>
197
+
198
+ <Collapse in={attributesOpen}>
199
+ <EditableAttributes
200
+ value={source.attributes || {}}
201
+ isEditing={false}
202
+ onChange={() => {}}
203
+ />
204
+ </Collapse>
205
+ </Stack>
206
+ )}
207
+ </BaseForm>
208
+ );
209
+ };
@@ -0,0 +1 @@
1
+ export { SourceForm } from './EditableForm';
@@ -0,0 +1,74 @@
1
+ import { useState, type PropsWithChildren, type CSSProperties } from "react";
2
+ import type { Website, Permissive } from "omni-osint-crud-client";
3
+ import { EditingForm } from "./EditingForm";
4
+ import { StaticForm } from "./StaticForm";
5
+
6
+ interface Props extends PropsWithChildren {
7
+ website: Website;
8
+ isAdmin?: boolean;
9
+ onSubmit?: (data: Website) => void;
10
+ onUpdate?: (data: Partial<Website>) => void;
11
+ onUpdatePermissive?: (data: Permissive) => void;
12
+ onClose?: () => void;
13
+ exitButton?: React.ReactNode;
14
+ style?: CSSProperties;
15
+ }
16
+
17
+ export const WebsiteForm: React.FC<Props> = ({
18
+ website,
19
+ isAdmin = false,
20
+ onSubmit,
21
+ onUpdate,
22
+ onUpdatePermissive,
23
+ onClose,
24
+ exitButton,
25
+ children,
26
+ style,
27
+ }) => {
28
+ const [isEditing, setIsEditing] = useState(onSubmit !== undefined);
29
+
30
+ if (
31
+ onSubmit !== undefined &&
32
+ (onUpdate !== undefined || onUpdatePermissive !== undefined)
33
+ ) {
34
+ throw new Error(
35
+ "onSubmit cannot be defined at the same time with onUpdate or onUpdatePermissive",
36
+ );
37
+ }
38
+
39
+ const handlClose = () => {
40
+ if (onUpdate !== undefined) {
41
+ setIsEditing(false);
42
+ }
43
+ onClose?.();
44
+ };
45
+
46
+ const handleDoubleClick = () => {
47
+ if (onUpdate !== undefined) {
48
+ setIsEditing(true);
49
+ }
50
+ };
51
+
52
+ return isEditing ? (
53
+ <EditingForm
54
+ website={website}
55
+ onSubmit={onSubmit}
56
+ onUpdate={onUpdate}
57
+ onClose={handlClose}
58
+ style={style}
59
+ />
60
+ ) : (
61
+ <StaticForm
62
+ website={website}
63
+ isAdmin={isAdmin}
64
+ onUpdate={onUpdatePermissive}
65
+ onClose={handlClose}
66
+ onDoubleClick={handleDoubleClick}
67
+ exitButton={exitButton || <></>}
68
+ style={style}
69
+ editModeEnabled={onUpdate !== undefined}
70
+ >
71
+ {children}
72
+ </StaticForm>
73
+ );
74
+ };