Use placeholders to prevent layout flow disruptions
Diff
chartered-frontend/package-lock.json | 15 +++++++++++++++
chartered-frontend/package.json | 1 +
chartered-frontend/src/index.sass | 1 +
chartered-frontend/src/util.tsx | 17 +++++++++++++++++
chartered-frontend/src/pages/crate/CrateView.tsx | 8 +++++---
chartered-frontend/src/pages/crate/Members.tsx | 12 +++++++-----
chartered-frontend/src/pages/crate/OrganisationView.tsx | 61 +++++++++++++++++++++++++++++++++++++++++++++++++------------
chartered-frontend/src/pages/organisations/ListOrganisations.tsx | 7 ++-----
8 files changed, 85 insertions(+), 37 deletions(-)
@@ -18,6 +18,7 @@
"react-dom": "^17.0.2",
"react-human-time": "^1.2.0",
"react-markdown": "^7.0.1",
"react-placeholder": "^4.1.0",
"react-router-dom": "^5.3.0",
"react-syntax-highlighter": "^15.4.4",
"remark-gfm": "^2.0.0",
@@ -9081,6 +9082,14 @@
"react-dom": ">=16.3.0"
}
},
"node_modules/react-placeholder": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/react-placeholder/-/react-placeholder-4.1.0.tgz",
"integrity": "sha512-z1HGD86NWJTYTQumHsmGH9jkozv4QHa9dju/vHVUd4f1svu23pf5v7QoBLBfs3kA1S9GLJaCeRMHLbO2SCdz5A==",
"peerDependencies": {
"react": "^16.8.0 || ^17"
}
},
"node_modules/react-popper": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz",
@@ -18803,6 +18812,12 @@
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
}
},
"react-placeholder": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/react-placeholder/-/react-placeholder-4.1.0.tgz",
"integrity": "sha512-z1HGD86NWJTYTQumHsmGH9jkozv4QHa9dju/vHVUd4f1svu23pf5v7QoBLBfs3kA1S9GLJaCeRMHLbO2SCdz5A==",
"requires": {}
},
"react-popper": {
"version": "1.3.11",
@@ -32,6 +32,7 @@
"react-dom": "^17.0.2",
"react-human-time": "^1.2.0",
"react-markdown": "^7.0.1",
"react-placeholder": "^4.1.0",
"react-router-dom": "^5.3.0",
"react-syntax-highlighter": "^15.4.4",
"remark-gfm": "^2.0.0",
@@ -1,7 +1,8 @@
$primary: #0d6efd
$font-family-monospace: "Source Code Pro", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace
$enable-cssgrid: true
@import "../node_modules/react-placeholder/lib/reactPlaceholder.css"
@import "~bootstrap/scss/bootstrap.scss"
@import "../node_modules/source-code-pro/source-code-pro.css"
@@ -1,4 +1,5 @@
import React = require("react");
import ReactPlaceholder from "react-placeholder";
import { AuthContext } from "./useAuth";
export const BASE_URL = "http://localhost:8888";
@@ -37,4 +38,20 @@
}, reloadOn);
return { response, error };
}
export function RoundedPicture({ src, height, width, className }: { src: string, height: string, width: string, className?: string }) {
const [imageLoaded, setImageLoaded] = React.useState(false);
return (
<div className={`position-relative d-inline-block ${className || ''}`} style={{height, width}}>
<ReactPlaceholder showLoadingAnimation type="round" style={{height, width, position: "absolute"}} ready={imageLoaded}><></></ReactPlaceholder>
<img
style={{visibility: imageLoaded ? "visible" : "hidden", height, width}}
src={src}
onLoad={() => setImageLoaded(true)}
className="rounded-circle"
/>
</div>
);
}
@@ -19,7 +19,7 @@
Square,
} from "react-bootstrap-icons";
import { useParams, NavLink, Redirect, Link } from "react-router-dom";
import { authenticatedEndpoint, useAuthenticatedRequest } from "../../util";
import { authenticatedEndpoint, RoundedPicture, RoundedPicture, useAuthenticatedRequest } from "../../util";
import Prism from "react-syntax-highlighter/dist/cjs/prism";
import ReactMarkdown from "react-markdown";
@@ -374,9 +374,11 @@
<div>
<div className="d-inline-block">
By
<img
<RoundedPicture
src="http://placekitten.com/22/22"
className="rounded-circle ms-1 me-1"
height="22px"
width="22px"
className="ms-1 me-1"
/>
{version.uploader}
</div>
@@ -7,12 +7,13 @@
Save,
PlusLg,
} from "react-bootstrap-icons";
import { authenticatedEndpoint, useAuthenticatedRequest } from "../../util";
import { authenticatedEndpoint, RoundedPicture, RoundedPicture, useAuthenticatedRequest } from "../../util";
import { useAuth } from "../../useAuth";
import { Button, Modal } from "react-bootstrap";
import { AsyncTypeahead } from "react-bootstrap-typeahead";
import { debounce } from "lodash";
import _ = require("lodash");
import ReactPlaceholder from "react-placeholder";
interface Member {
uuid: string;
@@ -203,7 +204,7 @@
<tr>
<td className="align-middle fit">
<img src="http://placekitten.com/48/48" className="rounded-circle" />
<RoundedPicture src="http://placekitten.com/48/48" height="48px" width="48px" />
</td>
<td className="align-middle">
@@ -310,10 +311,11 @@
ref={searchRef}
renderMenuItemChildren={(option, props) => (
<>
<img
alt={option.username}
<RoundedPicture
src="http://placekitten.com/24/24"
className="rounded-circle me-2"
height="24px"
width="24px"
className="me-2"
/>
<span>{option.username}</span>
</>
@@ -1,10 +1,10 @@
import React = require("react");
import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
import { useAuthenticatedRequest, authenticatedEndpoint } from "../../util";
import { useAuthenticatedRequest, authenticatedEndpoint, RoundedPicture } from "../../util";
import { BoxSeam, Plus, Trash } from "react-bootstrap-icons";
import {
@@ -19,6 +19,7 @@
import ErrorPage from "../ErrorPage";
import Loading from "../Loading";
import Members from "./Members";
import ReactPlaceholder from "react-placeholder";
interface OrganisationDetails {
possible_permissions?: string[];
@@ -60,10 +61,11 @@
if (error) {
return <ErrorPage message={error} />;
} else if (!organisationDetails) {
return <Loading />;
}
const ready = !!organisationDetails;
const [imageLoaded, setImageLoaded] = useState(false);
return (
<div className="text-white">
<Nav />
@@ -74,14 +76,13 @@
<div className="card border-0 shadow-sm text-black h-100">
<div className="card-body">
<div className="d-flex flex-row align-items-center">
<img
src="http://placekitten.com/96/96"
className="rounded-circle"
/>
<RoundedPicture src="http://placekitten.com/96/96" height="96px" width="96px" />
<div className="px-2">
<h1 className="text-primary my-0">{organisation}</h1>
<p className="m-0">{organisationDetails.description}</p>
<ReactPlaceholder showLoadingAnimation type="text" rows={1} ready={ready} style={{height: "1.4rem"}}>
<p className="m-0">{organisationDetails?.description || <i>No description given.</i>}</p>
</ReactPlaceholder>
</div>
</div>
</div>
@@ -113,23 +114,35 @@
</ul>
</div>
{activeTab == "crates" ? (
<ListCrates
organisation={organisation}
crates={organisationDetails.crates}
/>
) : (
<></>
)}
{activeTab == "members" ? (
<ListMembers
organisation={organisation}
members={organisationDetails.members}
possiblePermissions={organisationDetails.possible_permissions}
reload={() => setReload(reload + 1)}
/>
{!ready ? (
<div className="card-body">
<div className="d-flex justify-content-center align-items-center">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
) : (
<></>
<>
{activeTab == "crates" ? (
<ListCrates
organisation={organisation}
crates={organisationDetails.crates}
/>
) : (
<></>
)}
{activeTab == "members" ? (
<ListMembers
organisation={organisation}
members={organisationDetails.members}
possiblePermissions={organisationDetails.possible_permissions}
reload={() => setReload(reload + 1)}
/>
) : (
<></>
)}
</>
)}
</div>
</div>
@@ -1,9 +1,9 @@
import React = require("react");
import { Link } from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
import { useAuthenticatedRequest } from "../../util";
import { RoundedPicture, useAuthenticatedRequest } from "../../util";
import ErrorPage from "../ErrorPage";
import Loading from "../Loading";
@@ -48,10 +48,7 @@
{list.organisations.map((v, i) => (
<tr key={i}>
<td className="align-middle fit">
<img
src="http://placekitten.com/48/48"
className="rounded-circle"
/>
<RoundedPicture src="http://placekitten.com/48/48" height="48px" width="48px" />
</td>
<td className="align-middle" style={{ lineHeight: "1.1" }}>