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.
Files changed (47) hide show
  1. package/.ldo/profile.context.ts +14 -0
  2. package/.ldo/profile.typings.ts +6 -4
  3. package/app/index.tsx +2 -1
  4. package/components/ResourceView.tsx +7 -2
  5. package/components/common/LoadingBar.tsx +27 -0
  6. package/components/common/ProfileAvatar.tsx +28 -0
  7. package/components/nav/DialogProvider.tsx +5 -8
  8. package/components/nav/Layout.tsx +20 -81
  9. package/components/nav/header/AddressBox.tsx +8 -7
  10. package/components/nav/header/AvatarMenu.tsx +54 -57
  11. package/components/nav/header/Header.tsx +18 -2
  12. package/components/nav/header/SignInMenu.tsx +11 -14
  13. package/components/nav/header/ViewMenu.tsx +4 -4
  14. package/components/sharing/AccessDropdown.tsx +95 -0
  15. package/components/sharing/CopyLink.tsx +21 -0
  16. package/components/sharing/PermissionRow.tsx +38 -0
  17. package/components/sharing/SharingModal.tsx +149 -0
  18. package/components/sharing/WacRuleForm.tsx +44 -0
  19. package/components/sharing/agentPermissions/AgentInformation.tsx +37 -0
  20. package/components/sharing/agentPermissions/AgentInput.tsx +126 -0
  21. package/components/sharing/agentPermissions/AgentPermissionRow.tsx +36 -0
  22. package/components/sharing/agentPermissions/AgentPermissions.tsx +56 -0
  23. package/components/sharing/agentPermissions/useContactFilter.ts +35 -0
  24. package/components/ui/button.tsx +52 -5
  25. package/components/ui/dialog.tsx +1 -1
  26. package/components/ui/input-dropdown.tsx +105 -0
  27. package/components/ui/input.tsx +34 -2
  28. package/components/ui/text.tsx +47 -0
  29. package/components/useViewContext.tsx +141 -0
  30. package/components/{nav/utilityResourceViews → utilityResourceViews}/ErrorMessageResourceView.tsx +2 -2
  31. package/lib/icons/Fingerprint.tsx +4 -0
  32. package/lib/icons/Link.tsx +4 -0
  33. package/lib/icons/Loader.tsx +4 -0
  34. package/lib/icons/LogOut.tsx +4 -0
  35. package/lib/icons/Plus.tsx +4 -0
  36. package/lib/icons/Save.tsx +4 -0
  37. package/lib/icons/User.tsx +4 -0
  38. package/lib/icons/UserPlus.tsx +4 -0
  39. package/lib/icons/Users.tsx +4 -0
  40. package/package.json +12 -7
  41. package/resourceViews/Container/ContainerView.tsx +5 -6
  42. package/resourceViews/Profile/ProfileConfig.tsx +20 -0
  43. package/resourceViews/Profile/ProfileKnows.tsx +65 -0
  44. package/resourceViews/Profile/ProfileView.tsx +59 -0
  45. package/resourceViews/RawCode/RawCodeView.tsx +35 -9
  46. package/test-server/server-config.json +1 -1
  47. package/components/nav/useValidView.tsx +0 -51
@@ -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
  }
@@ -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
+ }
@@ -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
- return (
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 };
@@ -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
+ }
@@ -1,7 +1,7 @@
1
1
  import React, { FunctionComponent } from 'react';
2
- import { Text } from '../../ui/text';
2
+ import { Text } from '../ui/text';
3
3
  import { LucideIcon } from 'lucide-react-native';
4
- import { Card } from '../../ui/card';
4
+ import { Card } from '../ui/card';
5
5
  import { View } from 'react-native';
6
6
 
7
7
  interface ErrorMessageResourceViewsProps {
@@ -0,0 +1,4 @@
1
+ import { Fingerprint } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(Fingerprint);
4
+ export { Fingerprint };
@@ -0,0 +1,4 @@
1
+ import { Link } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(Link);
4
+ export { Link };
@@ -0,0 +1,4 @@
1
+ import { Loader } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(Loader);
4
+ export { Loader };
@@ -0,0 +1,4 @@
1
+ import { LogOut } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(LogOut);
4
+ export { LogOut };
@@ -0,0 +1,4 @@
1
+ import { Plus } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(Plus);
4
+ export { Plus };
@@ -0,0 +1,4 @@
1
+ import { Save } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(Save);
4
+ export { Save };
@@ -0,0 +1,4 @@
1
+ import { User } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(User);
4
+ export { User };
@@ -0,0 +1,4 @@
1
+ import { UserPlus } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(UserPlus);
4
+ export { UserPlus };
@@ -0,0 +1,4 @@
1
+ import { Users } from 'lucide-react-native';
2
+ import { iconWithClassName } from './iconWithClassName';
3
+ iconWithClassName(Users);
4
+ export { Users };
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.2",
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-solid": "^1.0.0-alpha.27",
22
- "@ldo/ldo": "^1.0.0-alpha.27",
23
- "@ldo/solid-react": "^1.0.0-alpha.27",
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
- "@ldo/cli": "^1.0.0-alpha.21",
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": "^7.1.7",
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 { useTargetResource } from '../../components/TargetResourceProvider';
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 } = useTargetResource();
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
+ };