Skip to content

Commit

Permalink
#228 Add folder gridview
Browse files Browse the repository at this point in the history
  • Loading branch information
Polleps committed Oct 4, 2022
1 parent 82860ed commit df7c6e6
Show file tree
Hide file tree
Showing 31 changed files with 889 additions and 87 deletions.
18 changes: 13 additions & 5 deletions data-browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,19 @@

<!-- Service worker -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js');
})
}
const registerServiceWorker = async () => {
if ('serviceWorker' in navigator) {
try {
await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};

registerServiceWorker();
</script>
</body>

Expand Down
3 changes: 3 additions & 0 deletions data-browser/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
self.addEventListener('install', () => {
// TODO: Do something.
});
88 changes: 88 additions & 0 deletions data-browser/src/components/AllPropsSimple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
datatypes,
JSONValue,
properties,
Resource,
useResource,
useSubject,
useTitle,
} from '@tomic/react';
import React, { useMemo } from 'react';
import styled from 'styled-components';

export interface AllPropsSimpleProps {
resource: Resource;
}

/** Renders a simple list of all properties on the resource. Will not render any link or other interactive element. */
export function AllPropsSimple({ resource }: AllPropsSimpleProps): JSX.Element {
return (
<ul>
{[...resource.getPropVals()].map(([prop, val]) => (
<Row key={prop} prop={prop} val={val} />
))}
</ul>
);
}

interface RowProps {
prop: string;
val: JSONValue;
}

function Row({ prop, val }: RowProps): JSX.Element {
const propResource = useResource(prop);
const [propName] = useTitle(propResource);
const [dataType] = useSubject(propResource, properties.datatype);

const value = useMemo(() => {
if (dataType === datatypes.atomicUrl) {
return <Value val={val as string} />;
}

if (dataType === datatypes.resourceArray) {
return <ResourceArray val={val as string[]} />;
}

return <>{val as string}</>;
}, [val, dataType]);

return (
<List>
<Key>{propName}</Key>: {value}
</List>
);
}

const Key = styled.span`
font-weight: bold;
`;

const List = styled.ul`
list-style: none;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: ${p => p.theme.colors.textLight};
`;

function ResourceArray({ val }: { val: string[] }): JSX.Element {
return (
<>
{val.map((v, i) => (
<>
<Value val={v} key={v} />
{i === val.length - 1 ? '' : ', '}
</>
))}
</>
);
}

function Value({ val }: { val: string }): JSX.Element {
const valueResource = useResource(val);
const [valueName] = useTitle(valueResource);

return <>{valueName}</>;
}
129 changes: 129 additions & 0 deletions data-browser/src/components/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useCallback, useId, useState } from 'react';
import styled from 'styled-components';

export interface ButtonGroupOption {
label: string;
icon: React.ReactNode;
value: string;
checked?: boolean;
}

export interface ButtonGroupProps {
options: ButtonGroupOption[];
name: string;
onChange: (value: string) => void;
}

export function ButtonGroup({
options,
name,
onChange,
}: ButtonGroupProps): JSX.Element {
const [selected, setSelected] = useState(
() => options.find(o => o.checked)?.value,
);

const handleChange = useCallback(
(checked: boolean, value: string) => {
if (checked) {
onChange(value);
setSelected(value);
}
},
[onChange],
);

return (
<Group>
{options.map(option => (
<ButtonGroupItem
{...option}
key={option.value}
onChange={handleChange}
checked={selected === option.value}
name={name}
/>
))}
</Group>
);
}

interface ButtonGroupItemProps extends ButtonGroupOption {
onChange: (checked: boolean, value: string) => void;
name: string;
}

function ButtonGroupItem({
onChange,
icon,
label,
name,
value,
checked,
}: ButtonGroupItemProps): JSX.Element {
const id = useId();

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.checked, value);
};

return (
<Item>
<Input
id={id}
type='radio'
onChange={handleChange}
name={name}
value={value}
checked={checked}
/>
<Label htmlFor={id} title={label}>
{icon}
</Label>
</Item>
);
}

const Group = styled.form`
display: flex;
height: 2rem;
gap: 0.5rem;
`;

const Item = styled.div`
position: relative;
width: 2rem;
aspect-ratio: 1/1;
`;

const Label = styled.label`
position: absolute;
inset: 0;
width: 100%;
aspect-ratio: 1/1;
display: flex;
align-items: center;
justify-content: center;
border-radius: ${p => p.theme.radius};
color: ${p => p.theme.colors.textLight};
cursor: pointer;
transition: background-color 0.1s ease-in-out, color 0.1s ease-in-out;
input:checked + & {
background-color: ${p => p.theme.colors.bg1};
color: ${p => p.theme.colors.text};
}
:hover {
background-color: ${p => p.theme.colors.bg1};
}
`;

const Input = styled.input`
position: absolute;
inset: 0;
width: 100%;
aspect-ratio: 1/1;
visibility: hidden;
`;
31 changes: 11 additions & 20 deletions data-browser/src/components/EditableTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
properties,
Resource,
useCanWrite,
useString,
useTitle,
} from '@tomic/react';
import { Resource, useCanWrite, useTitle } from '@tomic/react';
import React, { useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaEdit } from 'react-icons/fa';
Expand All @@ -13,28 +7,26 @@ import styled, { css } from 'styled-components';
export interface EditableTitleProps {
resource: Resource;
/** Uses `name` by default */
propertyURL?: string;
parentRef?: React.RefObject<HTMLInputElement>;
}

const opts = {
commit: true,
validate: false,
};

export function EditableTitle({
resource,
propertyURL,
parentRef,
...props
}: EditableTitleProps): JSX.Element {
propertyURL = propertyURL || properties.name;
const [text, setText] = useString(resource, propertyURL, {
commit: true,
validate: false,
});
const [text, setText] = useTitle(resource, Infinity, opts);
const [isEditing, setIsEditing] = useState(false);

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

const [canEdit] = useCanWrite(resource);
const [starndardTitle] = useTitle(resource);

useHotkeys(
'enter',
Expand All @@ -48,7 +40,7 @@ export function EditableTitle({
setIsEditing(true);
}

const placeholder = 'set a title';
const placeholder = canEdit ? 'set a title' : 'Untitled';

useEffect(() => {
ref.current?.focus();
Expand Down Expand Up @@ -77,7 +69,7 @@ export function EditableTitle({
subtle={!!canEdit && !text}
>
<>
{text ? text : canEdit ? placeholder : starndardTitle || 'Untitled'}
{text || placeholder}
{canEdit && <Icon />}
</>
</Title>
Expand All @@ -86,7 +78,6 @@ export function EditableTitle({

const TitleShared = css`
line-height: 1.1;
width: 100%;
`;

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

const Icon = styled(FaEdit)`
opacity: 0;
margin-left: auto;
font-size: 0.8em;
${Title}:hover & {
opacity: 0.5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function NewFolderButton({

createResourceAndNavigate('Folder', {
[properties.name]: name,
[properties.displayStyle]: 'list',
[properties.displayStyle]: classes.displayStyles.list,
[properties.isA]: [classes.folder],
});
},
Expand Down
3 changes: 3 additions & 0 deletions data-browser/src/styling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const zIndex = {
export const animationDuration = 100;

const breadCrumbBarHeight = '2.2rem';
const floatingSearchBarPadding = '4.2rem';

/** Construct a StyledComponents theme object */
export const buildTheme = (darkMode: boolean, mainIn: string): DefaultTheme => {
Expand Down Expand Up @@ -78,6 +79,7 @@ export const buildTheme = (darkMode: boolean, mainIn: string): DefaultTheme => {
radius: '9px',
heights: {
breadCrumbBar: breadCrumbBarHeight,
floatingSearchBarPadding: floatingSearchBarPadding,
fullPage: `calc(100% - ${breadCrumbBarHeight})`,
},
colors: {
Expand Down Expand Up @@ -130,6 +132,7 @@ declare module 'styled-components' {
heights: {
breadCrumbBar: string;
fullPage: string;
floatingSearchBarPadding: string;
};
colors: {
/** Main accent color, used for links */
Expand Down
1 change: 1 addition & 0 deletions data-browser/src/views/BookmarkPage/BookmarkPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,5 @@ const ControlBar = styled.div`
const PreviewWrapper = styled.div`
background-color: ${props => props.theme.colors.bg};
flex: 1;
padding-bottom: ${p => p.theme.heights.floatingSearchBarPadding};
`;
Loading

0 comments on commit df7c6e6

Please sign in to comment.