linked-data-browser 0.0.2 → 0.0.4
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/.ldo/profile.context.ts +14 -0
- package/.ldo/profile.typings.ts +6 -4
- package/app/index.tsx +2 -1
- package/components/ResourceView.tsx +7 -2
- package/components/common/LoadingBar.tsx +27 -0
- package/components/common/ProfileAvatar.tsx +28 -0
- package/components/nav/DialogProvider.tsx +5 -8
- package/components/nav/Layout.tsx +20 -81
- package/components/nav/header/AddressBox.tsx +8 -7
- package/components/nav/header/AvatarMenu.tsx +54 -57
- package/components/nav/header/Header.tsx +18 -2
- package/components/nav/header/SignInMenu.tsx +11 -14
- package/components/nav/header/ViewMenu.tsx +4 -4
- package/components/sharing/AccessDropdown.tsx +95 -0
- package/components/sharing/CopyLink.tsx +21 -0
- package/components/sharing/PermissionRow.tsx +38 -0
- package/components/sharing/SharingModal.tsx +149 -0
- package/components/sharing/WacRuleForm.tsx +44 -0
- package/components/sharing/agentPermissions/AgentInformation.tsx +37 -0
- package/components/sharing/agentPermissions/AgentInput.tsx +126 -0
- package/components/sharing/agentPermissions/AgentPermissionRow.tsx +36 -0
- package/components/sharing/agentPermissions/AgentPermissions.tsx +56 -0
- package/components/sharing/agentPermissions/useContactFilter.ts +35 -0
- package/components/ui/button.tsx +52 -5
- package/components/ui/dialog.tsx +1 -1
- package/components/ui/input-dropdown.tsx +105 -0
- package/components/ui/input.tsx +34 -2
- package/components/ui/text.tsx +47 -0
- package/components/useViewContext.tsx +141 -0
- package/components/{nav/utilityResourceViews → utilityResourceViews}/ErrorMessageResourceView.tsx +2 -2
- package/lib/icons/Fingerprint.tsx +4 -0
- package/lib/icons/Link.tsx +4 -0
- package/lib/icons/Loader.tsx +4 -0
- package/lib/icons/LogOut.tsx +4 -0
- package/lib/icons/Plus.tsx +4 -0
- package/lib/icons/Save.tsx +4 -0
- package/lib/icons/User.tsx +4 -0
- package/lib/icons/UserPlus.tsx +4 -0
- package/lib/icons/Users.tsx +4 -0
- package/package.json +12 -7
- package/resourceViews/Container/ContainerView.tsx +5 -6
- package/resourceViews/Profile/ProfileConfig.tsx +20 -0
- package/resourceViews/Profile/ProfileKnows.tsx +65 -0
- package/resourceViews/Profile/ProfileView.tsx +59 -0
- package/resourceViews/RawCode/RawCodeView.tsx +35 -9
- package/test-server/server-config.json +1 -1
- package/components/nav/useValidView.tsx +0 -51
package/components/ui/button.tsx
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { Pressable } from 'react-native';
|
|
4
|
-
import { TextClassContext } from '../ui/text';
|
|
3
|
+
import { Pressable, View } from 'react-native';
|
|
4
|
+
import { Text, TextClassContext } from '../ui/text';
|
|
5
5
|
import { cn } from '../../lib/utils';
|
|
6
|
+
import { LucideIcon } from 'lucide-react-native';
|
|
7
|
+
import { CircleSnail } from 'react-native-progress';
|
|
8
|
+
import { useTheme } from '@react-navigation/native';
|
|
6
9
|
|
|
7
10
|
const buttonVariants = cva(
|
|
8
11
|
'group flex items-center justify-center rounded-md web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2',
|
|
@@ -60,9 +63,32 @@ const buttonTextVariants = cva(
|
|
|
60
63
|
);
|
|
61
64
|
|
|
62
65
|
type ButtonProps = React.ComponentProps<typeof Pressable> &
|
|
63
|
-
VariantProps<typeof buttonVariants
|
|
66
|
+
VariantProps<typeof buttonVariants> & {
|
|
67
|
+
iconLeft?: React.ReactElement;
|
|
68
|
+
iconRight?: React.ReactElement;
|
|
69
|
+
isLoading?: boolean;
|
|
70
|
+
text?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function Button({
|
|
74
|
+
ref,
|
|
75
|
+
className,
|
|
76
|
+
variant,
|
|
77
|
+
size,
|
|
78
|
+
children,
|
|
79
|
+
iconLeft,
|
|
80
|
+
iconRight,
|
|
81
|
+
isLoading,
|
|
82
|
+
text,
|
|
83
|
+
...props
|
|
84
|
+
}: ButtonProps) {
|
|
85
|
+
const theme = useTheme();
|
|
86
|
+
|
|
87
|
+
const loadColor =
|
|
88
|
+
!variant || variant === 'default'
|
|
89
|
+
? theme.colors.background
|
|
90
|
+
: theme.colors.primary;
|
|
64
91
|
|
|
65
|
-
function Button({ ref, className, variant, size, ...props }: ButtonProps) {
|
|
66
92
|
return (
|
|
67
93
|
<TextClassContext.Provider
|
|
68
94
|
value={buttonTextVariants({
|
|
@@ -73,13 +99,34 @@ function Button({ ref, className, variant, size, ...props }: ButtonProps) {
|
|
|
73
99
|
>
|
|
74
100
|
<Pressable
|
|
75
101
|
className={cn(
|
|
102
|
+
'justify-between flex-row gap-2 items-center',
|
|
76
103
|
props.disabled && 'opacity-50 web:pointer-events-none',
|
|
77
104
|
buttonVariants({ variant, size, className }),
|
|
78
105
|
)}
|
|
79
106
|
ref={ref}
|
|
80
107
|
role="button"
|
|
81
108
|
{...props}
|
|
82
|
-
|
|
109
|
+
>
|
|
110
|
+
{isLoading ? (
|
|
111
|
+
<View>
|
|
112
|
+
<CircleSnail size={20} color={loadColor} />
|
|
113
|
+
</View>
|
|
114
|
+
) : iconLeft ? (
|
|
115
|
+
<Text>
|
|
116
|
+
{React.cloneElement(iconLeft, {
|
|
117
|
+
size: (iconLeft.props as any).size || 16,
|
|
118
|
+
} as any)}
|
|
119
|
+
</Text>
|
|
120
|
+
) : undefined}
|
|
121
|
+
{text ? <Text>{text}</Text> : (children as React.ReactNode)}
|
|
122
|
+
{iconRight ? (
|
|
123
|
+
<Text>
|
|
124
|
+
{React.cloneElement(iconRight, {
|
|
125
|
+
size: (iconRight.props as any).size || 16,
|
|
126
|
+
} as any)}
|
|
127
|
+
</Text>
|
|
128
|
+
) : undefined}
|
|
129
|
+
</Pressable>
|
|
83
130
|
</TextClassContext.Provider>
|
|
84
131
|
);
|
|
85
132
|
}
|
package/components/ui/dialog.tsx
CHANGED
|
@@ -82,7 +82,7 @@ function DialogContent({
|
|
|
82
82
|
<DialogOverlay>
|
|
83
83
|
<DialogPrimitive.Content
|
|
84
84
|
className={cn(
|
|
85
|
-
'max-w-lg gap-4 border border-border web:cursor-default bg-background p-6 shadow-lg web:duration-200 rounded-lg',
|
|
85
|
+
'max-w-lg gap-4 border border-border web:cursor-default bg-background p-6 shadow-lg web:duration-200 rounded-lg overflow-hidden',
|
|
86
86
|
open
|
|
87
87
|
? 'web:animate-in web:fade-in-0 web:zoom-in-95'
|
|
88
88
|
: 'web:animate-out web:fade-out-0 web:zoom-out-95',
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useMemo,
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
useEffect,
|
|
7
|
+
ReactNode,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { View, type TextInputProps } from 'react-native';
|
|
10
|
+
import { Input } from './input';
|
|
11
|
+
import { Card } from './card';
|
|
12
|
+
import { ButtonProps } from './button';
|
|
13
|
+
import { LoadingBar } from '../common/LoadingBar';
|
|
14
|
+
import { cn } from 'lib/utils';
|
|
15
|
+
|
|
16
|
+
interface InputDropdownProps<T> extends TextInputProps {
|
|
17
|
+
onChangeText: (text: string) => void;
|
|
18
|
+
items: T[];
|
|
19
|
+
renderItem: (item: T, onSelect: (item: T) => void) => ReactNode;
|
|
20
|
+
filterItems: (items: T[], searchText: string) => T[];
|
|
21
|
+
onItemSelect?: (item: T) => void;
|
|
22
|
+
buttonRight?: ButtonProps;
|
|
23
|
+
isLoading?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function InputDropdown<T>({
|
|
27
|
+
items,
|
|
28
|
+
renderItem,
|
|
29
|
+
filterItems,
|
|
30
|
+
onItemSelect,
|
|
31
|
+
buttonRight,
|
|
32
|
+
onChangeText,
|
|
33
|
+
onFocus,
|
|
34
|
+
value,
|
|
35
|
+
isLoading,
|
|
36
|
+
className,
|
|
37
|
+
...inputProps
|
|
38
|
+
}: InputDropdownProps<T>) {
|
|
39
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
40
|
+
const inputRef = useRef<any>(null);
|
|
41
|
+
|
|
42
|
+
const filteredItems = useMemo(() => {
|
|
43
|
+
return filterItems(items, value || '');
|
|
44
|
+
}, [items, value, filterItems]);
|
|
45
|
+
|
|
46
|
+
const handleInputChange = useCallback(
|
|
47
|
+
(text: string) => {
|
|
48
|
+
onChangeText(text);
|
|
49
|
+
setIsDropdownOpen(true);
|
|
50
|
+
},
|
|
51
|
+
[onChangeText],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const handleInputFocus = useCallback(
|
|
55
|
+
(event: any) => {
|
|
56
|
+
setIsDropdownOpen(true);
|
|
57
|
+
onFocus?.(event);
|
|
58
|
+
},
|
|
59
|
+
[onFocus],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const handleItemSelect = useCallback(
|
|
63
|
+
(item: T) => {
|
|
64
|
+
onItemSelect?.(item);
|
|
65
|
+
setIsDropdownOpen(false);
|
|
66
|
+
},
|
|
67
|
+
[onItemSelect],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Close dropdown when clicking outside
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const handleClickOutside = (event: any) => {
|
|
73
|
+
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
|
74
|
+
setIsDropdownOpen(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (isDropdownOpen) {
|
|
79
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
80
|
+
return () => {
|
|
81
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}, [isDropdownOpen]);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<View className={cn('relative', className)} ref={inputRef}>
|
|
88
|
+
<Input
|
|
89
|
+
{...inputProps}
|
|
90
|
+
onChangeText={handleInputChange}
|
|
91
|
+
onFocus={handleInputFocus}
|
|
92
|
+
buttonRight={buttonRight}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
{isDropdownOpen && (filteredItems.length > 0 || isLoading) && (
|
|
96
|
+
<Card className="absolute top-full left-0 right-0 mt-1 max-h-60 overflow-y-auto border border-border shadow-lg bg-background">
|
|
97
|
+
{isLoading && <LoadingBar isLoading={isLoading} />}
|
|
98
|
+
{filteredItems.map((item, index) => (
|
|
99
|
+
<View key={index}>{renderItem(item, handleItemSelect)}</View>
|
|
100
|
+
))}
|
|
101
|
+
</Card>
|
|
102
|
+
)}
|
|
103
|
+
</View>
|
|
104
|
+
);
|
|
105
|
+
}
|
package/components/ui/input.tsx
CHANGED
|
@@ -1,25 +1,57 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { TextInput, type TextInputProps } from 'react-native';
|
|
2
|
+
import { TextInput, View, type TextInputProps } from 'react-native';
|
|
3
3
|
import { cn } from '../../lib/utils';
|
|
4
|
+
import { Text } from './text';
|
|
5
|
+
import { Button, ButtonProps } from './button';
|
|
4
6
|
|
|
5
7
|
function Input({
|
|
6
8
|
className,
|
|
7
9
|
placeholderClassName,
|
|
10
|
+
label,
|
|
11
|
+
buttonRight,
|
|
8
12
|
...props
|
|
9
13
|
}: TextInputProps & {
|
|
10
14
|
ref?: React.RefObject<TextInput>;
|
|
15
|
+
label?: string;
|
|
16
|
+
buttonRight?: ButtonProps;
|
|
11
17
|
}) {
|
|
12
|
-
|
|
18
|
+
let textInput = (
|
|
13
19
|
<TextInput
|
|
14
20
|
className={cn(
|
|
15
21
|
'web:flex h-10 native:h-12 web:w-full rounded-md border border-input bg-background px-3 web:py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground placeholder:text-muted-foreground web:ring-offset-background file:border-0 file:bg-transparent file:font-medium web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2',
|
|
16
22
|
props.editable === false && 'opacity-50 web:cursor-not-allowed',
|
|
23
|
+
buttonRight ? 'rounded-br-none rounded-tr-none' : '',
|
|
17
24
|
className,
|
|
18
25
|
)}
|
|
19
26
|
placeholderClassName={cn('text-muted-foreground', placeholderClassName)}
|
|
20
27
|
{...props}
|
|
21
28
|
/>
|
|
22
29
|
);
|
|
30
|
+
|
|
31
|
+
if (buttonRight) {
|
|
32
|
+
textInput = (
|
|
33
|
+
<View className="flex-row">
|
|
34
|
+
{textInput}
|
|
35
|
+
<Button
|
|
36
|
+
{...buttonRight}
|
|
37
|
+
className={cn(
|
|
38
|
+
'rounded-tl-none rounded-bl-none',
|
|
39
|
+
buttonRight.className,
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (label) {
|
|
47
|
+
textInput = (
|
|
48
|
+
<View className="gap-1">
|
|
49
|
+
<Text variant="label">{label}</Text>
|
|
50
|
+
{textInput}
|
|
51
|
+
</View>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return textInput;
|
|
23
55
|
}
|
|
24
56
|
|
|
25
57
|
export { Input };
|
package/components/ui/text.tsx
CHANGED
|
@@ -5,21 +5,68 @@ import { cn } from '../../lib/utils';
|
|
|
5
5
|
|
|
6
6
|
const TextClassContext = React.createContext<string | undefined>(undefined);
|
|
7
7
|
|
|
8
|
+
export type TextVariant = 'default' | 'h1' | 'h2' | 'h3' | 'label';
|
|
9
|
+
export type TextSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl';
|
|
10
|
+
|
|
8
11
|
function Text({
|
|
9
12
|
className,
|
|
10
13
|
asChild = false,
|
|
14
|
+
variant,
|
|
15
|
+
size,
|
|
16
|
+
muted,
|
|
17
|
+
bold,
|
|
11
18
|
...props
|
|
12
19
|
}: React.ComponentProps<typeof RNText> & {
|
|
13
20
|
ref?: React.RefObject<RNText>;
|
|
14
21
|
asChild?: boolean;
|
|
22
|
+
variant?: TextVariant;
|
|
23
|
+
size?: TextSize;
|
|
24
|
+
muted?: boolean;
|
|
25
|
+
bold?: boolean;
|
|
15
26
|
}) {
|
|
16
27
|
const textClass = React.useContext(TextClassContext);
|
|
17
28
|
const Component = asChild ? Slot.Text : RNText;
|
|
29
|
+
|
|
30
|
+
let isMuted = false;
|
|
31
|
+
let isBold = false;
|
|
32
|
+
let textSize: TextSize = 'base';
|
|
33
|
+
|
|
34
|
+
if (variant) {
|
|
35
|
+
switch (variant) {
|
|
36
|
+
case 'h1':
|
|
37
|
+
isBold = true;
|
|
38
|
+
textSize = '3xl';
|
|
39
|
+
break;
|
|
40
|
+
case 'h2':
|
|
41
|
+
isBold = true;
|
|
42
|
+
textSize = '2xl';
|
|
43
|
+
break;
|
|
44
|
+
case 'h3':
|
|
45
|
+
isBold = true;
|
|
46
|
+
textSize = 'xl';
|
|
47
|
+
break;
|
|
48
|
+
case 'label':
|
|
49
|
+
isBold = true;
|
|
50
|
+
textSize = 'sm';
|
|
51
|
+
break;
|
|
52
|
+
case 'default':
|
|
53
|
+
default:
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (muted !== undefined) isMuted = muted;
|
|
59
|
+
if (bold !== undefined) isBold = bold;
|
|
60
|
+
if (size !== undefined) textSize = size;
|
|
61
|
+
|
|
18
62
|
return (
|
|
19
63
|
<Component
|
|
20
64
|
className={cn(
|
|
21
65
|
'text-base text-foreground web:select-text',
|
|
22
66
|
textClass,
|
|
67
|
+
isBold ? 'font-semibold' : '',
|
|
68
|
+
isMuted ? 'text-muted-foreground' : '',
|
|
69
|
+
`text-${textSize}`,
|
|
23
70
|
className,
|
|
24
71
|
)}
|
|
25
72
|
{...props}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import createContainer from 'constate';
|
|
2
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useDataBrowserConfig } from './DataBrowser';
|
|
4
|
+
import { useTargetResource } from './TargetResourceProvider';
|
|
5
|
+
import { ResourceViewConfig } from './ResourceView';
|
|
6
|
+
import { EyeOff } from '../lib/icons/EyeOff';
|
|
7
|
+
import { OctagonX } from '../lib/icons/OctagonX';
|
|
8
|
+
import { CircleSlash } from '../lib/icons/CircleSlash';
|
|
9
|
+
import { TextCursorInput } from '../lib/icons/TextCursorInput';
|
|
10
|
+
import { ShieldX } from '../lib/icons/ShieldX';
|
|
11
|
+
import { CircleX } from '../lib/icons/CircleX';
|
|
12
|
+
import { Loader } from '../lib/icons/Loader';
|
|
13
|
+
import { ErrorMessageResourceView } from './utilityResourceViews/ErrorMessageResourceView';
|
|
14
|
+
import { SolidContainer, SolidLeaf } from '@ldo/connected-solid';
|
|
15
|
+
import { LucideIcon } from 'lucide-react-native';
|
|
16
|
+
import { useLdo } from '@ldo/solid-react';
|
|
17
|
+
|
|
18
|
+
export const [ViewContextProvider, useViewContext] = createContainer(() => {
|
|
19
|
+
const { targetUri, targetResource, refresh, navigateTo } =
|
|
20
|
+
useTargetResource();
|
|
21
|
+
const { views } = useDataBrowserConfig();
|
|
22
|
+
const { dataset } = useLdo();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Calculate Valid Views
|
|
26
|
+
*/
|
|
27
|
+
const validViews = useMemo(() => {
|
|
28
|
+
if (!targetResource || !targetUri) {
|
|
29
|
+
return [
|
|
30
|
+
constructErrorView(
|
|
31
|
+
'Enter a URI in the address bar to view a resource.',
|
|
32
|
+
TextCursorInput,
|
|
33
|
+
),
|
|
34
|
+
];
|
|
35
|
+
} else if (targetResource.type === 'InvalidIdentifierResource') {
|
|
36
|
+
return [
|
|
37
|
+
constructErrorView(
|
|
38
|
+
`${targetResource.uri} is an invalid URI.`,
|
|
39
|
+
CircleSlash,
|
|
40
|
+
),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const errorViews = getErrorViews(targetResource);
|
|
45
|
+
if (errorViews) return errorViews;
|
|
46
|
+
|
|
47
|
+
const potentialViews = views.filter((view) =>
|
|
48
|
+
view.canDisplay(targetUri!, targetResource!, dataset),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return potentialViews.length > 0
|
|
52
|
+
? potentialViews
|
|
53
|
+
: [constructErrorView(`No valid view for ${targetUri}`, OctagonX)];
|
|
54
|
+
}, [targetResource, targetUri, views, dataset]);
|
|
55
|
+
|
|
56
|
+
const [curViewConfig, setCurViewConfig] = useState<ResourceViewConfig>(
|
|
57
|
+
validViews[0],
|
|
58
|
+
);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setCurViewConfig(validViews[0]);
|
|
61
|
+
}, [targetUri, validViews]);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
validViews,
|
|
65
|
+
curViewConfig,
|
|
66
|
+
setCurViewConfig,
|
|
67
|
+
targetResource,
|
|
68
|
+
targetUri,
|
|
69
|
+
refresh,
|
|
70
|
+
navigateTo,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function constructErrorView(
|
|
75
|
+
message: string,
|
|
76
|
+
icon: LucideIcon,
|
|
77
|
+
): ResourceViewConfig {
|
|
78
|
+
return {
|
|
79
|
+
name: 'noValidView',
|
|
80
|
+
displayName: 'No Valid View',
|
|
81
|
+
displayIcon: EyeOff,
|
|
82
|
+
view: () => <ErrorMessageResourceView icon={icon} message={message} />,
|
|
83
|
+
canDisplay: () => true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getErrorViews(
|
|
88
|
+
targetResource: SolidLeaf | SolidContainer,
|
|
89
|
+
): ResourceViewConfig[] | undefined {
|
|
90
|
+
// Handle Edge cases
|
|
91
|
+
if (targetResource.status.type === 'unfetched') {
|
|
92
|
+
return [constructErrorView(`Loading`, Loader)];
|
|
93
|
+
} else if (targetResource.isAbsent()) {
|
|
94
|
+
return [
|
|
95
|
+
constructErrorView(
|
|
96
|
+
`${targetResource.uri} either doesn't exist or you don't have read access to it.`,
|
|
97
|
+
CircleSlash,
|
|
98
|
+
),
|
|
99
|
+
];
|
|
100
|
+
} else if (targetResource.status.isError) {
|
|
101
|
+
switch (targetResource.status.type) {
|
|
102
|
+
case 'noncompliantPodError':
|
|
103
|
+
return [
|
|
104
|
+
constructErrorView(
|
|
105
|
+
`${targetResource.uri} returned a response that is not compliant with the Linked Web Storage specification: ${targetResource.status.message}`,
|
|
106
|
+
OctagonX,
|
|
107
|
+
),
|
|
108
|
+
];
|
|
109
|
+
case 'serverError':
|
|
110
|
+
return [
|
|
111
|
+
constructErrorView(
|
|
112
|
+
`${targetResource.uri} encountered an internal server error: ${targetResource.status.message}`,
|
|
113
|
+
OctagonX,
|
|
114
|
+
),
|
|
115
|
+
];
|
|
116
|
+
case 'unauthenticatedError':
|
|
117
|
+
return [
|
|
118
|
+
constructErrorView(
|
|
119
|
+
`${targetResource.uri} requires you to log in to view.`,
|
|
120
|
+
ShieldX,
|
|
121
|
+
),
|
|
122
|
+
];
|
|
123
|
+
case 'unauthorizedError':
|
|
124
|
+
return [
|
|
125
|
+
constructErrorView(
|
|
126
|
+
`You don't have access to ${targetResource.uri}.`,
|
|
127
|
+
ShieldX,
|
|
128
|
+
),
|
|
129
|
+
];
|
|
130
|
+
case 'unexpectedHttpError':
|
|
131
|
+
case 'unexpectedResourceError':
|
|
132
|
+
default:
|
|
133
|
+
return [
|
|
134
|
+
constructErrorView(
|
|
135
|
+
`An unexpected error occurred: ${targetResource.status.message}.`,
|
|
136
|
+
CircleX,
|
|
137
|
+
),
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
package/components/{nav/utilityResourceViews → utilityResourceViews}/ErrorMessageResourceView.tsx
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { FunctionComponent } from 'react';
|
|
2
|
-
import { Text } from '
|
|
2
|
+
import { Text } from '../ui/text';
|
|
3
3
|
import { LucideIcon } from 'lucide-react-native';
|
|
4
|
-
import { Card } from '
|
|
4
|
+
import { Card } from '../ui/card';
|
|
5
5
|
import { View } from 'react-native';
|
|
6
6
|
|
|
7
7
|
interface ErrorMessageResourceViewsProps {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-data-browser",
|
|
3
3
|
"main": "index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev:ios": "expo start -c --ios",
|
|
7
7
|
"dev:web": "concurrently \"npm run solid-server\" \"expo start -c --web\"",
|
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
"postinstall": "npx tailwindcss -i ./global.css -o ./node_modules/.cache/nativewind/global.css",
|
|
12
12
|
"build:standalone": "expo export -p web --output-dir dist-standalone && node scripts/adjust-standalone-paths.js",
|
|
13
13
|
"build:server": "EXPO_PUBLIC_IS_SERVER_HOSTED=true expo export -p web --output-dir dist-server && node scripts/adjust-server-paths.js",
|
|
14
|
-
"build:server:watch": "npx chokidar '**/*' -i 'dist-standalone/**' -i 'dist-server/**' -i 'node_modules/**' -c 'npm run build:server'",
|
|
14
|
+
"build:server:watch": "npx chokidar '**/*' -i 'dist-standalone/**' -i 'dist-server/**' -i 'node_modules/**' -i 'data/**' -c 'npm run build:server'",
|
|
15
15
|
"solid-server": "community-solid-server -c ./test-server/server-config.json -f ./data --seed-config ./test-server/solid-css-seed.json",
|
|
16
16
|
"build:ldo": "ldo build --input .shapes --output .ldo",
|
|
17
17
|
"build": "npm run build:standalone && npm run build:server"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@inrupt/solid-client-authn-browser": "^3.0.0",
|
|
21
|
-
"@ldo/connected
|
|
22
|
-
"@ldo/
|
|
23
|
-
"@ldo/
|
|
21
|
+
"@ldo/connected": "^1.0.0-alpha.32",
|
|
22
|
+
"@ldo/connected-solid": "^1.0.0-alpha.32",
|
|
23
|
+
"@ldo/ldo": "^1.0.0-alpha.32",
|
|
24
|
+
"@ldo/react": "^1.0.0-alpha.33",
|
|
25
|
+
"@ldo/solid-react": "^1.0.0-alpha.33",
|
|
24
26
|
"@monaco-editor/react": "^4.7.0",
|
|
25
27
|
"@radix-ui/react-dialog": "^1.1.14",
|
|
26
28
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
@@ -67,12 +69,14 @@
|
|
|
67
69
|
"expo-splash-screen": "~0.30.8",
|
|
68
70
|
"expo-status-bar": "~2.2.3",
|
|
69
71
|
"expo-system-ui": "~5.0.7",
|
|
72
|
+
"lodash": "^4.17.21",
|
|
70
73
|
"lucide-react-native": "^0.511.0",
|
|
71
74
|
"nativewind": "^4.1.23",
|
|
72
75
|
"react": "19.0.0",
|
|
73
76
|
"react-dom": "19.0.0",
|
|
74
77
|
"react-native": "0.79.2",
|
|
75
78
|
"react-native-notifier": "^2.0.0",
|
|
79
|
+
"react-native-progress": "^5.0.1",
|
|
76
80
|
"react-native-reanimated": "~3.17.5",
|
|
77
81
|
"react-native-safe-area-context": "^5.4.0",
|
|
78
82
|
"react-native-screens": "~4.10.0",
|
|
@@ -84,9 +88,10 @@
|
|
|
84
88
|
},
|
|
85
89
|
"devDependencies": {
|
|
86
90
|
"@babel/core": "^7.26.0",
|
|
87
|
-
"@
|
|
91
|
+
"@expo/metro-runtime": "~5.0.4",
|
|
92
|
+
"@ldo/cli": "^1.0.0-alpha.32",
|
|
88
93
|
"@react-native-community/eslint-config": "^3.2.0",
|
|
89
|
-
"@solid/community-server": "^
|
|
94
|
+
"@solid/community-server": "^8.0.0-alpha.0",
|
|
90
95
|
"@types/jsonld": "^1.5.15",
|
|
91
96
|
"@types/react": "~19.0.14",
|
|
92
97
|
"@types/shexj": "^2.1.7",
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
DropdownMenuContent,
|
|
9
9
|
DropdownMenuItem,
|
|
10
10
|
} from '../../components/ui/dropdown-menu';
|
|
11
|
-
import {
|
|
12
|
-
import { ErrorMessageResourceView } from '../../components/nav/utilityResourceViews/ErrorMessageResourceView';
|
|
11
|
+
import { ErrorMessageResourceView } from '../../components/utilityResourceViews/ErrorMessageResourceView';
|
|
13
12
|
import { CircleX } from '../../lib/icons/CircleX';
|
|
14
13
|
import { Folder } from '../../lib/icons/Folder';
|
|
15
14
|
import { Code } from '../../lib/icons/Code';
|
|
16
15
|
import { File } from '../../lib/icons/File';
|
|
17
16
|
import { Trash } from '../../lib/icons/Trash';
|
|
17
|
+
import { Plus } from '../../lib/icons/Plus';
|
|
18
18
|
import { Separator } from '../../components/ui/separator';
|
|
19
19
|
import { useDialog } from '../../components/nav/DialogProvider';
|
|
20
20
|
import {
|
|
@@ -23,9 +23,10 @@ import {
|
|
|
23
23
|
SolidLeaf,
|
|
24
24
|
} from '@ldo/connected-solid';
|
|
25
25
|
import { Notifier } from 'react-native-notifier';
|
|
26
|
+
import { useViewContext } from '../../components/useViewContext';
|
|
26
27
|
|
|
27
28
|
export const ContainerView: FunctionComponent = () => {
|
|
28
|
-
const { targetResource, navigateTo } =
|
|
29
|
+
const { targetResource, navigateTo } = useViewContext();
|
|
29
30
|
const { prompt } = useDialog();
|
|
30
31
|
|
|
31
32
|
const onCreateContainer = useCallback(async () => {
|
|
@@ -87,9 +88,7 @@ export const ContainerView: FunctionComponent = () => {
|
|
|
87
88
|
<View className="max-w-[200px] flex-1 p-4">
|
|
88
89
|
<DropdownMenu>
|
|
89
90
|
<DropdownMenuTrigger asChild>
|
|
90
|
-
<Button
|
|
91
|
-
<Text>Create</Text>
|
|
92
|
-
</Button>
|
|
91
|
+
<Button text='Create' iconLeft={<Plus />} />
|
|
93
92
|
</DropdownMenuTrigger>
|
|
94
93
|
<DropdownMenuContent>
|
|
95
94
|
<DropdownMenuItem onPress={onCreateContainer}>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SolidProfileShapeShapeType } from '.ldo/profile.shapeTypes';
|
|
2
|
+
import { ResourceViewConfig } from '../../components/ResourceView';
|
|
3
|
+
import { User } from '../../lib/icons/User';
|
|
4
|
+
import { ProfileView } from './ProfileView';
|
|
5
|
+
|
|
6
|
+
export const ProfileConfig: ResourceViewConfig = {
|
|
7
|
+
name: 'profile',
|
|
8
|
+
displayName: 'Profile',
|
|
9
|
+
displayIcon: User,
|
|
10
|
+
view: ProfileView,
|
|
11
|
+
canDisplay: (targetUri, targetResource, dataset) => {
|
|
12
|
+
const profile = dataset
|
|
13
|
+
.usingType(SolidProfileShapeShapeType)
|
|
14
|
+
.fromSubject(targetUri);
|
|
15
|
+
|
|
16
|
+
return !!profile?.type?.some?.(
|
|
17
|
+
(val) => val['@id'] === 'Person' || val['@id'] === 'Person2',
|
|
18
|
+
);
|
|
19
|
+
},
|
|
20
|
+
};
|