@omnsight/osint-entity-components 0.1.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/LICENSE +201 -0
- package/README.md +76 -0
- package/package.json +82 -0
- package/src/App.tsx +245 -0
- package/src/assets/icons/generated/basil-document-solid.tsx +8 -0
- package/src/assets/icons/generated/boxicons-announcement.tsx +8 -0
- package/src/assets/icons/generated/boxicons-book.tsx +8 -0
- package/src/assets/icons/generated/codicon-organization.tsx +8 -0
- package/src/assets/icons/generated/emojione-monotone-ship.tsx +8 -0
- package/src/assets/icons/generated/fa-solid-ship.tsx +8 -0
- package/src/assets/icons/generated/flowbite-truck-solid.tsx +8 -0
- package/src/assets/icons/generated/fluent-emoji-high-contrast-broken-chain.tsx +8 -0
- package/src/assets/icons/generated/fluent-emoji-high-contrast-military-helmet.tsx +8 -0
- package/src/assets/icons/generated/game-icons-bombing-run.tsx +8 -0
- package/src/assets/icons/generated/game-icons-missile-launcher.tsx +8 -0
- package/src/assets/icons/generated/game-icons-pistol-gun.tsx +8 -0
- package/src/assets/icons/generated/gg-website.tsx +8 -0
- package/src/assets/icons/generated/hugeicons-trade-up.tsx +8 -0
- package/src/assets/icons/generated/ic-sharp-oil-barrel.tsx +8 -0
- package/src/assets/icons/generated/ion-train-sharp.tsx +8 -0
- package/src/assets/icons/generated/mdi-account-school.tsx +8 -0
- package/src/assets/icons/generated/mdi-account-tie-hat.tsx +8 -0
- package/src/assets/icons/generated/mdi-account-tie.tsx +19 -0
- package/src/assets/icons/generated/mdi-airplane.tsx +8 -0
- package/src/assets/icons/generated/mdi-factory.tsx +19 -0
- package/src/assets/icons/generated/mdi-forum.tsx +8 -0
- package/src/assets/icons/generated/mdi-handcuffs.tsx +8 -0
- package/src/assets/icons/generated/mdi-tank.tsx +19 -0
- package/src/assets/icons/generated/mingcute-government-line.tsx +25 -0
- package/src/assets/icons/generated/mingcute-phone-call-fill.tsx +8 -0
- package/src/assets/icons/generated/pixel-technology.tsx +8 -0
- package/src/assets/icons/generated/ri-exchange-box-fill.tsx +8 -0
- package/src/assets/icons/generated/ri-seedling-line.tsx +8 -0
- package/src/assets/icons/generated/ri-spy-fill.tsx +8 -0
- package/src/assets/icons/generated/streamline-cyber-newspaper-2.tsx +8 -0
- package/src/assets/icons/generated/streamline-flex-deepfake-technology-1-solid.tsx +8 -0
- package/src/assets/icons/generated/uit-social-media-logo.tsx +8 -0
- package/src/avatars/EmptyAvatar.tsx +12 -0
- package/src/avatars/EventAvatar.tsx +40 -0
- package/src/avatars/OrganizationAvatar.tsx +43 -0
- package/src/avatars/PersonAvatar.tsx +41 -0
- package/src/avatars/SourceAvatar.tsx +67 -0
- package/src/avatars/WebsiteAvatar.tsx +40 -0
- package/src/avatars/index.ts +12 -0
- package/src/avatars/layouts/AvatarDropdown.tsx +48 -0
- package/src/avatars/layouts/AvatarRowList.tsx +23 -0
- package/src/avatars/layouts/AvatarSpan.tsx +11 -0
- package/src/avatars/layouts/EntityStyles.css +10 -0
- package/src/avatars/layouts/RelationTooltip.tsx +75 -0
- package/src/cards/EventCard.tsx +46 -0
- package/src/cards/OrganizationCard.tsx +38 -0
- package/src/cards/PersonCard.tsx +46 -0
- package/src/cards/SourceCard.tsx +63 -0
- package/src/cards/WebsiteCard.tsx +63 -0
- package/src/cards/index.ts +5 -0
- package/src/env.d.ts +2 -0
- package/src/i18n.ts +22 -0
- package/src/icons/Event/Icon.tsx +32 -0
- package/src/icons/Event/Select.tsx +61 -0
- package/src/icons/Event/icons.ts +118 -0
- package/src/icons/Organization/Icon.tsx +41 -0
- package/src/icons/Organization/Select.tsx +61 -0
- package/src/icons/Organization/icons.ts +53 -0
- package/src/icons/Person/Icon.tsx +34 -0
- package/src/icons/Person/Select.tsx +55 -0
- package/src/icons/Person/icons.ts +27 -0
- package/src/icons/Source/Icon.tsx +35 -0
- package/src/icons/Source/Select.tsx +61 -0
- package/src/icons/Source/icons.ts +40 -0
- package/src/icons/Website/Icon.tsx +39 -0
- package/src/icons/Website/Select.tsx +61 -0
- package/src/icons/Website/icons.ts +35 -0
- package/src/icons/index.ts +10 -0
- package/src/index.tsx +3 -0
- package/src/locales/en.json +75 -0
- package/src/locales/zh.json +76 -0
- package/src/main.tsx +22 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ActionIcon, Stack, Popover } from '@mantine/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
children: React.ReactNode[];
|
|
6
|
+
avatarOnOpen: React.ReactNode;
|
|
7
|
+
avatarOnClose: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AvatarDropdown: React.FC<Props> = ({ children, avatarOnOpen, avatarOnClose }) => {
|
|
11
|
+
const [opened, setOpened] = useState(false);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Popover
|
|
15
|
+
opened={opened}
|
|
16
|
+
onChange={setOpened}
|
|
17
|
+
position="bottom"
|
|
18
|
+
withArrow
|
|
19
|
+
shadow="md"
|
|
20
|
+
width={60}
|
|
21
|
+
withinPortal
|
|
22
|
+
>
|
|
23
|
+
<Popover.Target>
|
|
24
|
+
<ActionIcon
|
|
25
|
+
variant="light"
|
|
26
|
+
color="blue"
|
|
27
|
+
size="lg"
|
|
28
|
+
radius="xl"
|
|
29
|
+
onClick={() => setOpened((o) => !o)}
|
|
30
|
+
>
|
|
31
|
+
{opened ? avatarOnOpen : avatarOnClose}
|
|
32
|
+
</ActionIcon>
|
|
33
|
+
</Popover.Target>
|
|
34
|
+
<Popover.Dropdown
|
|
35
|
+
style={{
|
|
36
|
+
padding: 5,
|
|
37
|
+
backgroundColor: 'transparent',
|
|
38
|
+
border: 'none',
|
|
39
|
+
boxShadow: 'none',
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<Stack gap={8} style={{ backgroundColor: 'transparent', padding: 5 }}>
|
|
43
|
+
{children}
|
|
44
|
+
</Stack>
|
|
45
|
+
</Popover.Dropdown>
|
|
46
|
+
</Popover>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Avatar, Text } from '@mantine/core';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: React.ReactNode[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AvatarRowList: React.FC<Props> = ({ children }) => {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Avatar.Group>
|
|
14
|
+
{children.length === 0 ? (
|
|
15
|
+
<Text c="dimmed" size="sm" fs="italic">
|
|
16
|
+
{t('common.notFound')}
|
|
17
|
+
</Text>
|
|
18
|
+
) : (
|
|
19
|
+
children
|
|
20
|
+
)}
|
|
21
|
+
</Avatar.Group>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Avatar } from '@mantine/core';
|
|
3
|
+
import { EmptyAvatar } from '../EmptyAvatar';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: React.ReactNode[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AvatarSpan: React.FC<Props> = ({ children }) => {
|
|
10
|
+
return <Avatar.Group>{children.length === 0 ? <EmptyAvatar /> : children}</Avatar.Group>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Stack, Group, Text, Box, rem, RingProgress } from '@mantine/core';
|
|
2
|
+
import type { Relation } from 'omni-osint-crud-client';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
relation?: Relation;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const RelationTooltip: React.FC<Props> = ({ relation, children }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
if (!relation) return <>{children}</>;
|
|
13
|
+
|
|
14
|
+
const confVal = relation.confidence || 0;
|
|
15
|
+
let confColor = 'red';
|
|
16
|
+
if (confVal >= 80) {
|
|
17
|
+
confColor = 'green';
|
|
18
|
+
} else if (confVal >= 50) {
|
|
19
|
+
confColor = 'yellow';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const formatDate = (ts?: number | null) => {
|
|
23
|
+
if (!ts) return 'N/A';
|
|
24
|
+
try {
|
|
25
|
+
const d = new Date(ts * 1000);
|
|
26
|
+
return d.toLocaleString(undefined, {
|
|
27
|
+
year: 'numeric',
|
|
28
|
+
month: 'numeric',
|
|
29
|
+
day: 'numeric',
|
|
30
|
+
hour: '2-digit',
|
|
31
|
+
minute: '2-digit',
|
|
32
|
+
hour12: false,
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
return 'Invalid Date';
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Stack gap="xs" p={4}>
|
|
41
|
+
<Group justify="space-between" align="center">
|
|
42
|
+
<Text fw={700} size="sm" style={{ lineHeight: 1 }}>
|
|
43
|
+
{relation.label || t('tooltip.relation')}
|
|
44
|
+
</Text>
|
|
45
|
+
<Group gap={4}>
|
|
46
|
+
<Text size="xs" c="dimmed">
|
|
47
|
+
{t('tooltip.confidence')}
|
|
48
|
+
</Text>
|
|
49
|
+
<RingProgress
|
|
50
|
+
size={36}
|
|
51
|
+
thickness={3}
|
|
52
|
+
roundCaps
|
|
53
|
+
sections={[{ value: relation.confidence || 0, color: confColor }]}
|
|
54
|
+
label={
|
|
55
|
+
<Text c={confColor} fw={700} ta="center" size="xs" style={{ fontSize: 9 }}>
|
|
56
|
+
{relation.confidence || 0}
|
|
57
|
+
</Text>
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
</Group>
|
|
61
|
+
</Group>
|
|
62
|
+
|
|
63
|
+
<Box>{children}</Box>
|
|
64
|
+
|
|
65
|
+
<Group justify="space-between" mt={4}>
|
|
66
|
+
<Text size="xs" c="dimmed" style={{ fontSize: rem(10) }}>
|
|
67
|
+
{t('tooltip.createdDate')}: {formatDate(relation.created_at)}
|
|
68
|
+
</Text>
|
|
69
|
+
<Text size="xs" c="dimmed" style={{ fontSize: rem(10) }}>
|
|
70
|
+
{t('tooltip.updatedDate')}: {formatDate(relation.updated_at)}
|
|
71
|
+
</Text>
|
|
72
|
+
</Group>
|
|
73
|
+
</Stack>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Badge, Group, Paper, Stack, Text, Title } from "@mantine/core";
|
|
2
|
+
import { type Event } from "omni-osint-crud-client";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
event: Event;
|
|
8
|
+
background?: string;
|
|
9
|
+
withBorder?: boolean;
|
|
10
|
+
action?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const EventCard: React.FC<Props> = ({
|
|
14
|
+
event,
|
|
15
|
+
background,
|
|
16
|
+
withBorder = true,
|
|
17
|
+
action,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Paper p="xs" bg={background} withBorder={withBorder}>
|
|
23
|
+
<Group justify="space-between" wrap="nowrap" align="flex-start">
|
|
24
|
+
<Stack gap={0}>
|
|
25
|
+
<Title>{event.title || t("event.title")}</Title>
|
|
26
|
+
{event.happened_at && (
|
|
27
|
+
<Text c="dimmed">
|
|
28
|
+
{new Date(event.happened_at * 1000).toLocaleString()}
|
|
29
|
+
</Text>
|
|
30
|
+
)}
|
|
31
|
+
<Text truncate="end">
|
|
32
|
+
{event.description || t("event.description")}
|
|
33
|
+
</Text>
|
|
34
|
+
{event.tags && (
|
|
35
|
+
<Group gap="xs" mt="xs">
|
|
36
|
+
{event.tags.map((tag) => (
|
|
37
|
+
<Badge key={tag}>{tag}</Badge>
|
|
38
|
+
))}
|
|
39
|
+
</Group>
|
|
40
|
+
)}
|
|
41
|
+
</Stack>
|
|
42
|
+
{action}
|
|
43
|
+
</Group>
|
|
44
|
+
</Paper>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Badge, Group, Paper, Stack, Title } from "@mantine/core";
|
|
2
|
+
import { type Organization } from "omni-osint-crud-client";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
organization: Organization;
|
|
8
|
+
withBorder?: boolean;
|
|
9
|
+
action?: React.ReactNode;
|
|
10
|
+
background?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const OrganizationCard: React.FC<Props> = ({
|
|
14
|
+
organization,
|
|
15
|
+
withBorder = true,
|
|
16
|
+
action,
|
|
17
|
+
background,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Paper withBorder={withBorder} p="xs" bg={background}>
|
|
23
|
+
<Group justify="space-between" wrap="nowrap" align="flex-start">
|
|
24
|
+
<Stack gap={0}>
|
|
25
|
+
<Title>{organization.name || t("organization.name")}</Title>
|
|
26
|
+
{organization.tags && (
|
|
27
|
+
<Group gap="xs" mt="xs">
|
|
28
|
+
{organization.tags.map((tag) => (
|
|
29
|
+
<Badge key={tag}>{tag}</Badge>
|
|
30
|
+
))}
|
|
31
|
+
</Group>
|
|
32
|
+
)}
|
|
33
|
+
</Stack>
|
|
34
|
+
{action}
|
|
35
|
+
</Group>
|
|
36
|
+
</Paper>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Badge, Group, Paper, Stack, Text, Title } from "@mantine/core";
|
|
2
|
+
import { type Person } from "omni-osint-crud-client";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
person: Person;
|
|
8
|
+
withBorder?: boolean;
|
|
9
|
+
action?: React.ReactNode;
|
|
10
|
+
background?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const PersonCard: React.FC<Props> = ({
|
|
14
|
+
person,
|
|
15
|
+
withBorder = true,
|
|
16
|
+
action,
|
|
17
|
+
background,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Paper withBorder={withBorder} p="xs" bg={background}>
|
|
23
|
+
<Group justify="space-between" wrap="nowrap" align="flex-start">
|
|
24
|
+
<Stack gap={0}>
|
|
25
|
+
<Title>{person.name || t("person.name")}</Title>
|
|
26
|
+
{person.role && (
|
|
27
|
+
<Text c="dimmed">{person.role || t("person.role")}</Text>
|
|
28
|
+
)}
|
|
29
|
+
{person.aliases && (
|
|
30
|
+
<Text truncate="end">
|
|
31
|
+
{t("person.aliases")}: {person.aliases.join(", ")}
|
|
32
|
+
</Text>
|
|
33
|
+
)}
|
|
34
|
+
{person.tags && (
|
|
35
|
+
<Group gap="xs" mt="xs">
|
|
36
|
+
{person.tags.map((tag) => (
|
|
37
|
+
<Badge key={tag}>{tag}</Badge>
|
|
38
|
+
))}
|
|
39
|
+
</Group>
|
|
40
|
+
)}
|
|
41
|
+
</Stack>
|
|
42
|
+
{action}
|
|
43
|
+
</Group>
|
|
44
|
+
</Paper>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionIcon,
|
|
3
|
+
Badge,
|
|
4
|
+
Group,
|
|
5
|
+
Paper,
|
|
6
|
+
Stack,
|
|
7
|
+
Text,
|
|
8
|
+
Title,
|
|
9
|
+
} from "@mantine/core";
|
|
10
|
+
import { type Source } from "omni-osint-crud-client";
|
|
11
|
+
import { useTranslation } from "react-i18next";
|
|
12
|
+
import React from "react";
|
|
13
|
+
import { LinkIcon } from "@heroicons/react/24/solid";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
source: Source;
|
|
17
|
+
withBorder?: boolean;
|
|
18
|
+
action?: React.ReactNode;
|
|
19
|
+
background?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SourceCard: React.FC<Props> = ({
|
|
23
|
+
source,
|
|
24
|
+
withBorder = true,
|
|
25
|
+
action,
|
|
26
|
+
background,
|
|
27
|
+
}) => {
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Paper withBorder={withBorder} p="xs" bg={background}>
|
|
32
|
+
<Group justify="space-between" wrap="nowrap" align="flex-start">
|
|
33
|
+
<Stack gap={0}>
|
|
34
|
+
<Group>
|
|
35
|
+
<Title>{source.name || t("source.name")}</Title>
|
|
36
|
+
{source.url && (
|
|
37
|
+
<ActionIcon
|
|
38
|
+
variant="subtle"
|
|
39
|
+
size="sm"
|
|
40
|
+
component="a"
|
|
41
|
+
href={source.url}
|
|
42
|
+
target="_blank"
|
|
43
|
+
>
|
|
44
|
+
<LinkIcon />
|
|
45
|
+
</ActionIcon>
|
|
46
|
+
)}
|
|
47
|
+
</Group>
|
|
48
|
+
<Text truncate="end">
|
|
49
|
+
{source.description || t("source.description")}
|
|
50
|
+
</Text>
|
|
51
|
+
{source.tags && (
|
|
52
|
+
<Group gap="xs" mt="xs">
|
|
53
|
+
{source.tags.map((tag) => (
|
|
54
|
+
<Badge key={tag}>{tag}</Badge>
|
|
55
|
+
))}
|
|
56
|
+
</Group>
|
|
57
|
+
)}
|
|
58
|
+
</Stack>
|
|
59
|
+
{action}
|
|
60
|
+
</Group>
|
|
61
|
+
</Paper>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionIcon,
|
|
3
|
+
Badge,
|
|
4
|
+
Group,
|
|
5
|
+
Paper,
|
|
6
|
+
Stack,
|
|
7
|
+
Text,
|
|
8
|
+
Title,
|
|
9
|
+
} from "@mantine/core";
|
|
10
|
+
import { type Website } from "omni-osint-crud-client";
|
|
11
|
+
import { useTranslation } from "react-i18next";
|
|
12
|
+
import React from "react";
|
|
13
|
+
import { LinkIcon } from "@heroicons/react/24/solid";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
website: Website;
|
|
17
|
+
withBorder?: boolean;
|
|
18
|
+
action?: React.ReactNode;
|
|
19
|
+
background?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const WebsiteCard: React.FC<Props> = ({
|
|
23
|
+
website,
|
|
24
|
+
withBorder = true,
|
|
25
|
+
action,
|
|
26
|
+
background,
|
|
27
|
+
}) => {
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Paper withBorder={withBorder} p="xs" bg={background}>
|
|
32
|
+
<Group justify="space-between" wrap="nowrap" align="flex-start">
|
|
33
|
+
<Stack gap={0}>
|
|
34
|
+
<Group>
|
|
35
|
+
<Title>{website.title || t("website.title")}</Title>
|
|
36
|
+
{website.url && (
|
|
37
|
+
<ActionIcon
|
|
38
|
+
variant="subtle"
|
|
39
|
+
size="sm"
|
|
40
|
+
component="a"
|
|
41
|
+
href={website.url}
|
|
42
|
+
target="_blank"
|
|
43
|
+
>
|
|
44
|
+
<LinkIcon />
|
|
45
|
+
</ActionIcon>
|
|
46
|
+
)}
|
|
47
|
+
</Group>
|
|
48
|
+
<Text truncate="end">
|
|
49
|
+
{website.description || t("website.description")}
|
|
50
|
+
</Text>
|
|
51
|
+
{website.tags && (
|
|
52
|
+
<Group gap="xs" mt="xs">
|
|
53
|
+
{website.tags.map((tag) => (
|
|
54
|
+
<Badge key={tag}>{tag}</Badge>
|
|
55
|
+
))}
|
|
56
|
+
</Group>
|
|
57
|
+
)}
|
|
58
|
+
</Stack>
|
|
59
|
+
{action}
|
|
60
|
+
</Group>
|
|
61
|
+
</Paper>
|
|
62
|
+
);
|
|
63
|
+
};
|
package/src/env.d.ts
ADDED
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import i18n from "i18next";
|
|
2
|
+
import { initReactI18next } from "react-i18next";
|
|
3
|
+
import en from "./locales/en.json";
|
|
4
|
+
import zh from "./locales/zh.json";
|
|
5
|
+
|
|
6
|
+
i18n.use(initReactI18next).init({
|
|
7
|
+
resources: {
|
|
8
|
+
en: {
|
|
9
|
+
translation: en,
|
|
10
|
+
},
|
|
11
|
+
zh: {
|
|
12
|
+
translation: zh,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
lng: "en",
|
|
16
|
+
fallbackLng: "en",
|
|
17
|
+
interpolation: {
|
|
18
|
+
escapeValue: false,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export default i18n;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ThemeIcon } from "@mantine/core";
|
|
2
|
+
import { CalendarDaysIcon } from "@heroicons/react/24/solid";
|
|
3
|
+
import { type Event } from "omni-osint-crud-client";
|
|
4
|
+
import { ICON_OPTIONS } from "./icons";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
event: Event;
|
|
8
|
+
size?: number | string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders an icon for an event based on its type.
|
|
14
|
+
* @param event The event object, must contain 'type' and 'attributes'.
|
|
15
|
+
* @param props Additional props to pass to the icon.
|
|
16
|
+
* @returns A component displaying the event icon.
|
|
17
|
+
*/
|
|
18
|
+
export const EventIcon: React.FC<Props> = ({ event, size = "md", ...props }) => {
|
|
19
|
+
const iconColor = String((event.attributes || {}).icon_color || "#0089ff");
|
|
20
|
+
|
|
21
|
+
const selectedType = ICON_OPTIONS.find(
|
|
22
|
+
(option) => option.value === event.type,
|
|
23
|
+
);
|
|
24
|
+
const Icon =
|
|
25
|
+
selectedType && selectedType.icon ? selectedType.icon : CalendarDaysIcon;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ThemeIcon size={size} radius="xl" color={iconColor}>
|
|
29
|
+
<Icon style={{ width: "70%", height: "70%", color: "white" }} {...props} />
|
|
30
|
+
</ThemeIcon>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Event } from "omni-osint-crud-client";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { Group, Select, ColorInput } from "@mantine/core";
|
|
4
|
+
import { ICON_OPTIONS } from "./icons";
|
|
5
|
+
import { EventIcon } from "./Icon";
|
|
6
|
+
|
|
7
|
+
interface EventIconSelectProps {
|
|
8
|
+
value: Event;
|
|
9
|
+
onChange: (value: Event) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const EventIconSelect: React.FC<EventIconSelectProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
}) => {
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const colors = [
|
|
18
|
+
"#0089ff",
|
|
19
|
+
"#ff0000",
|
|
20
|
+
"#00ba21",
|
|
21
|
+
"#c18c17",
|
|
22
|
+
"#be4bdb",
|
|
23
|
+
"#ababab",
|
|
24
|
+
"#7950f2",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const handleTypeChange = (type: string | null) => {
|
|
28
|
+
onChange({ ...value, type: type || undefined });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleColorChange = (color: string) => {
|
|
32
|
+
onChange({
|
|
33
|
+
...value,
|
|
34
|
+
attributes: { ...(value.attributes || {}), icon_color: color },
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const translatedOptions = ICON_OPTIONS.map((option) => ({
|
|
39
|
+
...option,
|
|
40
|
+
label: t(`event.type.${option.label}`),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Group>
|
|
45
|
+
<Select
|
|
46
|
+
leftSection={<EventIcon event={value} />}
|
|
47
|
+
defaultValue={translatedOptions[0].value}
|
|
48
|
+
value={value.type}
|
|
49
|
+
onChange={handleTypeChange}
|
|
50
|
+
data={translatedOptions}
|
|
51
|
+
style={{ flex: 1 }}
|
|
52
|
+
/>
|
|
53
|
+
<ColorInput
|
|
54
|
+
value={String((value.attributes || {}).icon_color || colors[0])}
|
|
55
|
+
onChange={handleColorChange}
|
|
56
|
+
swatches={colors}
|
|
57
|
+
style={{ flex: 1 }}
|
|
58
|
+
/>
|
|
59
|
+
</Group>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { IconMdiHandcuffs } from '@/assets/icons/generated/mdi-handcuffs';
|
|
2
|
+
import { IconHugeiconsTradeUp } from '@/assets/icons/generated/hugeicons-trade-up';
|
|
3
|
+
import { IconGameIconsPistolGun } from '@/assets/icons/generated/game-icons-pistol-gun';
|
|
4
|
+
import { IconIcSharpOilBarrel } from '@/assets/icons/generated/ic-sharp-oil-barrel';
|
|
5
|
+
import { IconRiSpyFill } from '@/assets/icons/generated/ri-spy-fill';
|
|
6
|
+
import { IconFluentEmojiHighContrastMilitaryHelmet } from '@/assets/icons/generated/fluent-emoji-high-contrast-military-helmet';
|
|
7
|
+
import { IconMdiTank } from '@/assets/icons/generated/mdi-tank';
|
|
8
|
+
import { IconFaSolidShip } from '@/assets/icons/generated/fa-solid-ship';
|
|
9
|
+
import { IconMdiAirplane } from '@/assets/icons/generated/mdi-airplane';
|
|
10
|
+
import { IconGameIconsMissileLauncher } from '@/assets/icons/generated/game-icons-missile-launcher';
|
|
11
|
+
import { IconGameIconsBombingRun } from '@/assets/icons/generated/game-icons-bombing-run';
|
|
12
|
+
import { IconIonTrainSharp } from '@/assets/icons/generated/ion-train-sharp';
|
|
13
|
+
import { IconFlowbiteTruckSolid } from '@/assets/icons/generated/flowbite-truck-solid';
|
|
14
|
+
import { IconEmojioneMonotoneShip } from '@/assets/icons/generated/emojione-monotone-ship';
|
|
15
|
+
import { IconFluentEmojiHighContrastBrokenChain } from '@/assets/icons/generated/fluent-emoji-high-contrast-broken-chain';
|
|
16
|
+
import { IconRiExchangeBoxFill } from '@/assets/icons/generated/ri-exchange-box-fill';
|
|
17
|
+
import { IconMingcutePhoneCallFill } from '@/assets/icons/generated/mingcute-phone-call-fill';
|
|
18
|
+
import { IconBoxiconsAnnouncement } from '@/assets/icons/generated/boxicons-announcement';
|
|
19
|
+
|
|
20
|
+
interface IconOption {
|
|
21
|
+
value: string;
|
|
22
|
+
label: string;
|
|
23
|
+
icon?: React.FC<React.ComponentPropsWithoutRef<'svg'>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find icon items in https://icon-sets.iconify.design
|
|
27
|
+
export const ICON_OPTIONS: IconOption[] = [
|
|
28
|
+
{
|
|
29
|
+
value: 'announcement',
|
|
30
|
+
label: 'announcement',
|
|
31
|
+
icon: IconBoxiconsAnnouncement, // icon: boxicons:announcement
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
value: 'conversation',
|
|
35
|
+
label: 'conversation',
|
|
36
|
+
icon: IconMingcutePhoneCallFill, // icon: mingcute:phone-call-fill
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
value: 'trade',
|
|
40
|
+
label: 'trade',
|
|
41
|
+
icon: IconRiExchangeBoxFill, // icon: ri:exchange-box-fill
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
value: 'oil',
|
|
45
|
+
label: 'oil',
|
|
46
|
+
icon: IconIcSharpOilBarrel, // icon: ic:sharp-oil-barrel
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: 'exchange',
|
|
50
|
+
label: 'exchange',
|
|
51
|
+
icon: IconHugeiconsTradeUp, // icon: hugeicons:trade-up
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: 'supplychain-risk',
|
|
55
|
+
label: 'supplychain-risk',
|
|
56
|
+
icon: IconFluentEmojiHighContrastBrokenChain, // icon: fluent-emoji-high-contrast:broken-chain
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: 'ship',
|
|
60
|
+
label: 'ship',
|
|
61
|
+
icon: IconEmojioneMonotoneShip, // icon: emojione-monotone:ship
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: 'truck',
|
|
65
|
+
label: 'truck',
|
|
66
|
+
icon: IconFlowbiteTruckSolid, // icon: flowbite:truck-solid
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: 'train',
|
|
70
|
+
label: 'train',
|
|
71
|
+
icon: IconIonTrainSharp, // icon: ion:train-sharp
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
value: 'crime',
|
|
75
|
+
label: 'crime',
|
|
76
|
+
icon: IconMdiHandcuffs, // icon: mdi:handcuffs
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: 'shot',
|
|
80
|
+
label: 'shot',
|
|
81
|
+
icon: IconGameIconsPistolGun, // icon: game-icons:pistol-gun
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
value: 'bomb',
|
|
85
|
+
label: 'bomb',
|
|
86
|
+
icon: IconGameIconsBombingRun, // icon: game-icons:bombing-run
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
value: 'missile',
|
|
90
|
+
label: 'missile',
|
|
91
|
+
icon: IconGameIconsMissileLauncher, // icon: game-icons:missile-launcher
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
value: 'plane',
|
|
95
|
+
label: 'plane',
|
|
96
|
+
icon: IconMdiAirplane, // icon: mdi:airplane
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: 'naval',
|
|
100
|
+
label: 'naval',
|
|
101
|
+
icon: IconFaSolidShip, // icon: fa-solid:ship
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: 'tank',
|
|
105
|
+
label: 'tank',
|
|
106
|
+
icon: IconMdiTank, // icon: mdi:tank
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: 'infantry',
|
|
110
|
+
label: 'infantry',
|
|
111
|
+
icon: IconFluentEmojiHighContrastMilitaryHelmet, // icon: fluent-emoji-high-contrast:military-helmet
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
value: 'intelligence',
|
|
115
|
+
label: 'intelligence',
|
|
116
|
+
icon: IconRiSpyFill, // icon: ri:spy-fill
|
|
117
|
+
},
|
|
118
|
+
]
|