Skip to content

Commit e587567

Browse files
Pollepsjoepio
authored andcommitted
#228 Add folder gridview
1 parent b077911 commit e587567

31 files changed

+889
-87
lines changed

data-browser/index.html

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,19 @@
9292

9393
<!-- Service worker -->
9494
<script>
95-
if ('serviceWorker' in navigator) {
96-
window.addEventListener('load', () => {
97-
navigator.serviceWorker.register('sw.js');
98-
})
99-
}
95+
const registerServiceWorker = async () => {
96+
if ('serviceWorker' in navigator) {
97+
try {
98+
await navigator.serviceWorker.register('/sw.js', {
99+
scope: '/',
100+
});
101+
} catch (error) {
102+
console.error(`Registration failed with ${error}`);
103+
}
104+
}
105+
};
106+
107+
registerServiceWorker();
100108
</script>
101109
</body>
102110

data-browser/public/sw.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
self.addEventListener('install', () => {
2+
// TODO: Do something.
3+
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
datatypes,
3+
JSONValue,
4+
properties,
5+
Resource,
6+
useResource,
7+
useSubject,
8+
useTitle,
9+
} from '@tomic/react';
10+
import React, { useMemo } from 'react';
11+
import styled from 'styled-components';
12+
13+
export interface AllPropsSimpleProps {
14+
resource: Resource;
15+
}
16+
17+
/** Renders a simple list of all properties on the resource. Will not render any link or other interactive element. */
18+
export function AllPropsSimple({ resource }: AllPropsSimpleProps): JSX.Element {
19+
return (
20+
<ul>
21+
{[...resource.getPropVals()].map(([prop, val]) => (
22+
<Row key={prop} prop={prop} val={val} />
23+
))}
24+
</ul>
25+
);
26+
}
27+
28+
interface RowProps {
29+
prop: string;
30+
val: JSONValue;
31+
}
32+
33+
function Row({ prop, val }: RowProps): JSX.Element {
34+
const propResource = useResource(prop);
35+
const [propName] = useTitle(propResource);
36+
const [dataType] = useSubject(propResource, properties.datatype);
37+
38+
const value = useMemo(() => {
39+
if (dataType === datatypes.atomicUrl) {
40+
return <Value val={val as string} />;
41+
}
42+
43+
if (dataType === datatypes.resourceArray) {
44+
return <ResourceArray val={val as string[]} />;
45+
}
46+
47+
return <>{val as string}</>;
48+
}, [val, dataType]);
49+
50+
return (
51+
<List>
52+
<Key>{propName}</Key>: {value}
53+
</List>
54+
);
55+
}
56+
57+
const Key = styled.span`
58+
font-weight: bold;
59+
`;
60+
61+
const List = styled.ul`
62+
list-style: none;
63+
margin: 0;
64+
white-space: nowrap;
65+
overflow: hidden;
66+
text-overflow: ellipsis;
67+
color: ${p => p.theme.colors.textLight};
68+
`;
69+
70+
function ResourceArray({ val }: { val: string[] }): JSX.Element {
71+
return (
72+
<>
73+
{val.map((v, i) => (
74+
<>
75+
<Value val={v} key={v} />
76+
{i === val.length - 1 ? '' : ', '}
77+
</>
78+
))}
79+
</>
80+
);
81+
}
82+
83+
function Value({ val }: { val: string }): JSX.Element {
84+
const valueResource = useResource(val);
85+
const [valueName] = useTitle(valueResource);
86+
87+
return <>{valueName}</>;
88+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React, { useCallback, useId, useState } from 'react';
2+
import styled from 'styled-components';
3+
4+
export interface ButtonGroupOption {
5+
label: string;
6+
icon: React.ReactNode;
7+
value: string;
8+
checked?: boolean;
9+
}
10+
11+
export interface ButtonGroupProps {
12+
options: ButtonGroupOption[];
13+
name: string;
14+
onChange: (value: string) => void;
15+
}
16+
17+
export function ButtonGroup({
18+
options,
19+
name,
20+
onChange,
21+
}: ButtonGroupProps): JSX.Element {
22+
const [selected, setSelected] = useState(
23+
() => options.find(o => o.checked)?.value,
24+
);
25+
26+
const handleChange = useCallback(
27+
(checked: boolean, value: string) => {
28+
if (checked) {
29+
onChange(value);
30+
setSelected(value);
31+
}
32+
},
33+
[onChange],
34+
);
35+
36+
return (
37+
<Group>
38+
{options.map(option => (
39+
<ButtonGroupItem
40+
{...option}
41+
key={option.value}
42+
onChange={handleChange}
43+
checked={selected === option.value}
44+
name={name}
45+
/>
46+
))}
47+
</Group>
48+
);
49+
}
50+
51+
interface ButtonGroupItemProps extends ButtonGroupOption {
52+
onChange: (checked: boolean, value: string) => void;
53+
name: string;
54+
}
55+
56+
function ButtonGroupItem({
57+
onChange,
58+
icon,
59+
label,
60+
name,
61+
value,
62+
checked,
63+
}: ButtonGroupItemProps): JSX.Element {
64+
const id = useId();
65+
66+
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
67+
onChange(event.target.checked, value);
68+
};
69+
70+
return (
71+
<Item>
72+
<Input
73+
id={id}
74+
type='radio'
75+
onChange={handleChange}
76+
name={name}
77+
value={value}
78+
checked={checked}
79+
/>
80+
<Label htmlFor={id} title={label}>
81+
{icon}
82+
</Label>
83+
</Item>
84+
);
85+
}
86+
87+
const Group = styled.form`
88+
display: flex;
89+
height: 2rem;
90+
gap: 0.5rem;
91+
`;
92+
93+
const Item = styled.div`
94+
position: relative;
95+
width: 2rem;
96+
aspect-ratio: 1/1;
97+
`;
98+
99+
const Label = styled.label`
100+
position: absolute;
101+
inset: 0;
102+
width: 100%;
103+
aspect-ratio: 1/1;
104+
display: flex;
105+
align-items: center;
106+
justify-content: center;
107+
border-radius: ${p => p.theme.radius};
108+
color: ${p => p.theme.colors.textLight};
109+
cursor: pointer;
110+
111+
transition: background-color 0.1s ease-in-out, color 0.1s ease-in-out;
112+
113+
input:checked + & {
114+
background-color: ${p => p.theme.colors.bg1};
115+
color: ${p => p.theme.colors.text};
116+
}
117+
118+
:hover {
119+
background-color: ${p => p.theme.colors.bg1};
120+
}
121+
`;
122+
123+
const Input = styled.input`
124+
position: absolute;
125+
inset: 0;
126+
width: 100%;
127+
aspect-ratio: 1/1;
128+
visibility: hidden;
129+
`;

data-browser/src/components/EditableTitle.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
properties,
3-
Resource,
4-
useCanWrite,
5-
useString,
6-
useTitle,
7-
} from '@tomic/react';
1+
import { Resource, useCanWrite, useTitle } from '@tomic/react';
82
import React, { useEffect, useRef, useState } from 'react';
93
import { useHotkeys } from 'react-hotkeys-hook';
104
import { FaEdit } from 'react-icons/fa';
@@ -13,28 +7,26 @@ import styled, { css } from 'styled-components';
137
export interface EditableTitleProps {
148
resource: Resource;
159
/** Uses `name` by default */
16-
propertyURL?: string;
1710
parentRef?: React.RefObject<HTMLInputElement>;
1811
}
1912

13+
const opts = {
14+
commit: true,
15+
validate: false,
16+
};
17+
2018
export function EditableTitle({
2119
resource,
22-
propertyURL,
2320
parentRef,
2421
...props
2522
}: EditableTitleProps): JSX.Element {
26-
propertyURL = propertyURL || properties.name;
27-
const [text, setText] = useString(resource, propertyURL, {
28-
commit: true,
29-
validate: false,
30-
});
23+
const [text, setText] = useTitle(resource, Infinity, opts);
3124
const [isEditing, setIsEditing] = useState(false);
3225

3326
const innerRef = useRef<HTMLInputElement>(null);
3427
const ref = parentRef || innerRef;
3528

3629
const [canEdit] = useCanWrite(resource);
37-
const [starndardTitle] = useTitle(resource);
3830

3931
useHotkeys(
4032
'enter',
@@ -48,7 +40,7 @@ export function EditableTitle({
4840
setIsEditing(true);
4941
}
5042

51-
const placeholder = 'set a title';
43+
const placeholder = canEdit ? 'set a title' : 'Untitled';
5244

5345
useEffect(() => {
5446
ref.current?.focus();
@@ -77,7 +69,7 @@ export function EditableTitle({
7769
subtle={!!canEdit && !text}
7870
>
7971
<>
80-
{text ? text : canEdit ? placeholder : starndardTitle || 'Untitled'}
72+
{text || placeholder}
8173
{canEdit && <Icon />}
8274
</>
8375
</Title>
@@ -86,7 +78,6 @@ export function EditableTitle({
8678

8779
const TitleShared = css`
8880
line-height: 1.1;
89-
width: 100%;
9081
`;
9182

9283
interface TitleProps {
@@ -98,6 +89,7 @@ const Title = styled.h1<TitleProps>`
9889
${TitleShared}
9990
display: flex;
10091
align-items: center;
92+
gap: ${p => p.theme.margin}rem;
10193
justify-content: space-between;
10294
cursor: pointer;
10395
cursor: ${props => (props.canEdit ? 'pointer' : 'initial')};
@@ -129,8 +121,7 @@ const TitleInput = styled.input`
129121

130122
const Icon = styled(FaEdit)`
131123
opacity: 0;
132-
margin-left: auto;
133-
124+
font-size: 0.8em;
134125
${Title}:hover & {
135126
opacity: 0.5;
136127

data-browser/src/components/NewInstanceButton/NewFolderButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function NewFolderButton({
3636

3737
createResourceAndNavigate('Folder', {
3838
[properties.name]: name,
39-
[properties.displayStyle]: 'list',
39+
[properties.displayStyle]: classes.displayStyles.list,
4040
[properties.isA]: [classes.folder],
4141
});
4242
},

data-browser/src/styling.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const zIndex = {
4646
export const animationDuration = 100;
4747

4848
const breadCrumbBarHeight = '2.2rem';
49+
const floatingSearchBarPadding = '4.2rem';
4950

5051
/** Construct a StyledComponents theme object */
5152
export const buildTheme = (darkMode: boolean, mainIn: string): DefaultTheme => {
@@ -78,6 +79,7 @@ export const buildTheme = (darkMode: boolean, mainIn: string): DefaultTheme => {
7879
radius: '9px',
7980
heights: {
8081
breadCrumbBar: breadCrumbBarHeight,
82+
floatingSearchBarPadding: floatingSearchBarPadding,
8183
fullPage: `calc(100% - ${breadCrumbBarHeight})`,
8284
},
8385
colors: {
@@ -130,6 +132,7 @@ declare module 'styled-components' {
130132
heights: {
131133
breadCrumbBar: string;
132134
fullPage: string;
135+
floatingSearchBarPadding: string;
133136
};
134137
colors: {
135138
/** Main accent color, used for links */

data-browser/src/views/BookmarkPage/BookmarkPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,5 @@ const ControlBar = styled.div`
106106
const PreviewWrapper = styled.div`
107107
background-color: ${props => props.theme.colors.bg};
108108
flex: 1;
109+
padding-bottom: ${p => p.theme.heights.floatingSearchBarPadding};
109110
`;

0 commit comments

Comments
 (0)