Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement skeleton loader (forms, query builder, attachment) #3334

Merged
merged 27 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c2ef61c
Implement skeleton loader for forms - query builder - attachment plug…
CarolineDenis Apr 11, 2023
f3d6699
Lint code with ESLint and Prettier
CarolineDenis Apr 11, 2023
0e09e7a
Create a skeleton wrapper
CarolineDenis Apr 12, 2023
261d3fc
Lint code with ESLint and Prettier
CarolineDenis Apr 12, 2023
c5a45de
Change skeleton colors for light mode
CarolineDenis Apr 12, 2023
133f9b4
Merge branch 'issue-2998' of https://github.com/specify/specify7 into…
CarolineDenis Apr 12, 2023
b132e2e
Lint code with ESLint and Prettier
CarolineDenis Apr 12, 2023
252a69e
Replace skeleton library with custom skeleton
CarolineDenis Apr 12, 2023
c7ff847
Merge branch 'issue-2998' of https://github.com/specify/specify7 into…
CarolineDenis Apr 12, 2023
a7a5de5
Delete unnecessary lines
CarolineDenis Apr 12, 2023
ac8468c
Erase skeleton library
CarolineDenis Apr 12, 2023
4fa777c
Fix unnecessary scroll in form skeleton
CarolineDenis Apr 13, 2023
8040d9c
Lint code with ESLint and Prettier
CarolineDenis Apr 13, 2023
78f592d
Improve form skeleton UI, delete unnecessary loading gif in form dialogs
CarolineDenis Apr 13, 2023
6e23353
Merge branch 'issue-2998' of https://github.com/specify/specify7 into…
CarolineDenis Apr 13, 2023
d4e5deb
Add some gaps in QueryBuilder skeleton
CarolineDenis Apr 13, 2023
eca377a
Fixe failing tests
CarolineDenis Apr 13, 2023
1744445
Display a skeleton for query list dialog and record set dialog
CarolineDenis Apr 13, 2023
9658aa2
Lint code with ESLint and Prettier
CarolineDenis Apr 13, 2023
11d2496
Replace skeleton component with wrap
CarolineDenis Apr 24, 2023
228fe00
Lint code with ESLint and Prettier
CarolineDenis Apr 24, 2023
47fff8c
Create a Skeleton.Root
CarolineDenis Apr 28, 2023
086808e
Lint code with ESLint and Prettier
CarolineDenis Apr 28, 2023
f16a15b
Add key prop to component
CarolineDenis Apr 28, 2023
14d4ca6
Add key prop
CarolineDenis Apr 28, 2023
fde145b
Merge remote-tracking branch 'origin/production' into issue-2998
CarolineDenis May 3, 2023
fd98bd7
Add flex col to form skeleton
CarolineDenis May 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { fetchResource } from '../DataModel/resource';
import type { SpAppResource, SpViewSetObj } from '../DataModel/types';
import { NotFoundView } from '../Router/NotFoundView';
import { locationToState, useStableLocation } from '../Router/RouterState';
import { AppResourceSkeleton } from '../SkeletonLoaders/AppResource';
import { findAppResourceDirectory } from './Create';
import { AppResourceEditor } from './Editor';
import type { AppResourceMode } from './helpers';
import { getAppResourceMode } from './helpers';
import type { AppResources } from './hooks';
import { useResourcesTree } from './hooks';
import type { AppResourcesOutlet } from './index';
import { ScopedAppResourceDir } from './types';
import type { ScopedAppResourceDir } from './types';

export function AppResourceView(): JSX.Element {
return <Wrapper mode="appResources" />;
Expand Down Expand Up @@ -43,8 +44,9 @@ export function Wrapper({
const baseHref = `/specify/resources/${
mode === 'appResources' ? 'app-resource' : 'view-set'
}`;
return initialData === undefined ? null : resource === undefined ||
directory === undefined ? (
return initialData === undefined ? (
<AppResourceSkeleton />
) : resource === undefined || directory === undefined ? (
<NotFoundView container={false} />
) : (
<AppResourceEditor
Expand Down Expand Up @@ -121,7 +123,7 @@ function useInitialData(
),
[initialDataFrom]
),
true
false
)[0];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { Attachment } from '../DataModel/types';
import { raise } from '../Errors/Crash';
import { ErrorBoundary } from '../Errors/ErrorBoundary';
import { ResourceView } from '../Forms/ResourceView';
import { loadingGif } from '../Molecules';
import { AttachmentGallerySkeleton } from '../SkeletonLoaders/AttachmentGallery';
import { AttachmentCell } from './Cell';
import { AttachmentDialog } from './Dialog';

Expand Down Expand Up @@ -93,9 +93,11 @@ export function AttachmentGallery({
}
/>
))}
{isComplete
? attachments.length === 0 && <p>{attachmentsText.noAttachments()}</p>
: loadingGif}
{isComplete ? (
attachments.length === 0 && <p>{attachmentsText.noAttachments()}</p>
) : (
<AttachmentGallerySkeleton />
)}
</Container.Base>
{typeof viewRecord === 'object' && (
<ErrorBoundary dismissible>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { loadingBar } from '../Molecules';
import { Dialog } from '../Molecules/Dialog';
import { FilePicker } from '../Molecules/FilePicker';
import { ProtectedTable } from '../Permissions/PermissionDenied';
import { AttachmentPluginSkeleton } from '../SkeletonLoaders/AttachmentPlugin';
import { attachmentSettingsPromise, uploadFile } from './attachments';
import { AttachmentViewer } from './Viewer';

Expand Down Expand Up @@ -52,7 +53,7 @@ export function useAttachment(
false,
[resource]
),
true
false
);
}

Expand All @@ -64,13 +65,16 @@ function ProtectedAttachmentsPlugin({
readonly mode: FormMode;
}): JSX.Element | null {
const [attachment, setAttachment] = useAttachment(resource);

useErrorContext('attachment', attachment);

const filePickerContainer = React.useRef<HTMLDivElement | null>(null);
const related = useTriggerState(
resource?.specifyModel.name === 'Attachment' ? undefined : resource
);
return attachment === undefined ? null : (
return attachment === undefined ? (
<AttachmentPluginSkeleton />
) : (
<div
className="h-full overflow-x-auto"
ref={filePickerContainer}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { SortConfig } from '../Molecules/Sorting';
import { SortIndicator } from '../Molecules/Sorting';
import { hasTablePermission } from '../Permissions/helpers';
import { userPreferences } from '../Preferences/userPreferences';
import { AttachmentPluginSkeleton } from '../SkeletonLoaders/AttachmentPlugin';
import { relationshipIsToMany } from '../WbPlanView/mappingHelpers';
import { FormCell } from './index';

Expand Down Expand Up @@ -481,6 +482,6 @@ function Attachment({
) : attachment === false ? (
<p>{formsText.noData()}</p>
) : (
loadingGif
<AttachmentPluginSkeleton />
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { attachmentView } from '../FormParse/webOnlyViews';
import { loadingGif } from '../Molecules';
import { userPreferences } from '../Preferences/userPreferences';
import { unsafeTriggerNotFound } from '../Router/Router';
import { FormSkeleton } from '../SkeletonLoaders/Form';

const FormLoadingContext = React.createContext<boolean>(false);
FormLoadingContext.displayName = 'FormLoadingContext';
Expand Down Expand Up @@ -111,7 +112,7 @@ export function SpecifyForm<SCHEMA extends AnySchema>({
{loadingGif}
</div>
)}
{formIsLoaded && (
{formIsLoaded ? (
CarolineDenis marked this conversation as resolved.
Show resolved Hide resolved
<DataEntry.Grid
aria-hidden={showLoading}
className={`${showLoading ? 'pointer-events-none opacity-50' : ''}`}
Expand Down Expand Up @@ -147,6 +148,8 @@ export function SpecifyForm<SCHEMA extends AnySchema>({
</React.Fragment>
))}
</DataEntry.Grid>
) : (
<FormSkeleton />
)}
</div>
</FormLoadingContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { isTreeModel, treeRanksPromise } from '../InitialContext/treeRanks';
import { useTitle } from '../Molecules/AppTitle';
import { hasPermission, hasToolPermission } from '../Permissions/helpers';
import { userPreferences } from '../Preferences/userPreferences';
import { QueryBuilderSkeleton } from '../SkeletonLoaders/QueryBuilder';
import { getMappedFields, mappingPathIsComplete } from '../WbPlanView/helpers';
import { getMappingLineProps } from '../WbPlanView/LineComponents';
import { MappingView } from '../WbPlanView/MapperComponents';
Expand Down Expand Up @@ -92,7 +93,7 @@ export function QueryBuilder({
'queryBuilder',
queryResource.isNew() ? 'create' : 'update'
);
const [treeRanksLoaded = false] = useAsyncState(fetchTreeRanks, true);
const [treeRanksLoaded = false] = useAsyncState(fetchTreeRanks, false);

const [query, setQuery] = useResource(queryResource);
useErrorContext('query', query);
Expand Down Expand Up @@ -557,5 +558,7 @@ export function QueryBuilder({
)}
</Form>
</Container.Full>
) : null;
) : (
<QueryBuilderSkeleton />
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import { Skeleton } from './Skeleton';

export function AppResourceSkeleton() {
return (
<Skeleton>
<rect height="8" rx="3" ry="3" width="141" x="28" y="1" />
<rect height="10" rx="2" ry="2" width="10" x="10" y="0" />
<rect height="10" rx="2" ry="2" width="10" x="190" y="0" />
<rect height="10" rx="2" ry="2" width="15" x="203" y="0" />
<rect height="10" rx="2" ry="2" width="15" x="220.5" y="0" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="28" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="48" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="68" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="88" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="108" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="128" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="148" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="168" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="188" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="208" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="228" />
<rect height="8" rx="2" ry="2" width="195" x="28" y="248" />
</Skeleton>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import { DEFAULT_FETCH_LIMIT } from '../DataModel/collection';
import { Skeleton } from './Skeleton';

export function AttachmentGallerySkeleton() {
return <Skeleton viewBox="0 0 115 150">{createRectangles()}</Skeleton>;
}

function createRectangles() {
const rectWidth = 22;
const rectHeight = 22;
const rectRadius = 2;
return Array.from({ length: DEFAULT_FETCH_LIMIT }, (_, index) => (
<rect
height={rectHeight}
rx={rectRadius}
ry={rectRadius}
width={rectWidth}
x={rectWidth * index + 5}
CarolineDenis marked this conversation as resolved.
Show resolved Hide resolved
y={rectHeight * index + Math.floor(index / 4) * 10}
/>
));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import { Skeleton } from './Skeleton';

export function AttachmentPluginSkeleton() {
return (
<Skeleton viewBox="0 0 220 400">
<rect height="140" rx="2" ry="2" width="120" x="7" y="0" />
<rect height="7" rx="2" ry="2" width="36" x="135" y="0" />
<rect height="12" rx="2" ry="2" width="12" x="191" y="0" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="18" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="28" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="38" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="48" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="58" />
<rect height="5" rx="2" ry="2" width="33" x="135" y="68" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="78" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="88" />
<rect height="5" rx="2" ry="2" width="68" x="135" y="98" />
<rect height="15" rx="2" ry="2" width="30" x="135" y="125" />
<rect height="15" rx="2" ry="2" width="30" x="173" y="125" />
</Skeleton>
);
}
42 changes: 42 additions & 0 deletions specifyweb/frontend/js_src/lib/components/SkeletonLoaders/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

import { Skeleton } from './Skeleton';

export function FormSkeleton() {
return (
<Skeleton className="h-full w-[120vh]">
<rect height="3" rx="2" ry="2" width="22" x="9" y="6" />
<rect height="5" rx="2" ry="2" width="41" x="33" y="5" />
<rect height="3" rx="2" ry="2" width="22" x="107" y="6" />
<rect height="5" rx="2" ry="2" width="25" x="131" y="5" />
<rect height="5" rx="2" ry="2" width="25" x="157" y="5" />

<rect height="5" rx="2" ry="2" width="52" x="9" y="18" />
<circle cx="66" cy="20.5" r="3" />
<circle cx="74" cy="20.5" r="3" />
<rect height="3" rx="2" ry="2" width="22" x="9" y="25" />

<rect height="3" rx="2" ry="2" width="22" x="28" y="35" />
<rect height="5" rx="2" ry="2" width="100" x="54" y="34" />
<circle cx="158" cy="36.5" r="2" />
<circle cx="163" cy="36.5" r="2" />
<circle cx="168" cy="36.5" r="2" />

<rect height="5" rx="2" ry="2" width="52" x="9" y="53" />
<circle cx="66" cy="55.5" r="3" />
<rect height="3" rx="2" ry="2" width="22" x="9" y="60" />

<rect height="20" rx="2" ry="2" width="96" x="86" y="47.5" />

<rect height="5" rx="2" ry="2" width="52" x="9" y="73" />
<circle cx="66" cy="75.5" r="3" />

<rect height="3" rx="2" ry="2" width="22" x="9" y="93" />
<rect height="7" rx="2" ry="2" width="25" x="9" y="98" />
<rect height="7" rx="2" ry="2" width="25" x="40" y="98" />
<rect height="7" rx="2" ry="2" width="25" x="125" y="98" />

<rect height="5" rx="2" ry="2" width="130" x="9" y="120" />
</Skeleton>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';

import { Skeleton } from './Skeleton';

export function QueryBuilderSkeleton() {
return (
<Skeleton viewBox="0 0 240 400">
<rect height="5" rx="2" ry="2" width="5" x="9" y="5" />
<rect height="5" rx="2" ry="2" width="41" x="20" y="5" />
<rect height="5" rx="2" ry="2" width="25" x="132" y="5" />
<rect height="5" rx="2" ry="2" width="25" x="161" y="5" />

<rect height="5" rx="2" ry="2" width="52" x="9" y="20" />
<rect height="38" rx="2" ry="2" width="52" x="9" y="27" />
<rect height="38" rx="2" ry="2" width="52" x="65" y="27" />
<rect height="38" rx="2" ry="2" width="5" x="121" y="27" />

<rect height="5" rx="2" ry="2" width="185" x="11" y="80" />
<rect height="5" rx="2" ry="2" width="5" x="198" y="80" />
<rect height="5" rx="2" ry="2" width="5" x="204" y="80" />
<rect height="5" rx="2" ry="2" width="5" x="210" y="80" />
<rect height="5" rx="2" ry="2" width="5" x="216" y="80" />

<rect height="5" rx="2" ry="2" width="5" x="13" y="190" />
<rect height="5" rx="2" ry="2" width="45" x="22" y="190" />

<rect height="5" rx="2" ry="2" width="5" x="122" y="190" />
<rect height="7" rx="2" ry="2" width="30" x="168" y="188.5" />
<rect height="7" rx="2" ry="2" width="30" x="132" y="188.5" />
</Skeleton>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ReactNode } from 'react';
import React from 'react';
import ContentLoader from 'react-content-loader';

import { userPreferences } from '../Preferences/userPreferences';

type SkeletonProps = {
readonly speed?: number;
readonly viewBox?: string;
readonly className?: string;
readonly children: ReactNode;
};
maxpatiiuk marked this conversation as resolved.
Show resolved Hide resolved

export function Skeleton({
speed = 3,
viewBox = '0 0 200 400',
className = 'h-full w-full',
children,
}: SkeletonProps): JSX.Element {
const [motionPref] = userPreferences.use('general', 'ui', 'reduceMotion');
return (
<div className={className}>
<ContentLoader
animate={motionPref !== 'reduce'}
backgroundColor="#333"
foregroundColor="#999"
speed={speed}
viewBox={viewBox}
>
{children}
</ContentLoader>
</div>
);
}
18 changes: 18 additions & 0 deletions specifyweb/frontend/js_src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading