Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F10360223
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/admin-panel/collections/collections.jsx b/admin-panel/collections/collections.jsx
index 6766a07..74292be 100644
--- a/admin-panel/collections/collections.jsx
+++ b/admin-panel/collections/collections.jsx
@@ -1,71 +1,79 @@
const React = require('react');
const { useEffect, useState } = React;
import { BrowserRouter as HashRouter, Route, Link } from 'react-router-dom';
import useCollections from './use-collections.js';
import CreateCollectionItem from './create-collection-item.js';
+import Loading from '../loading/loading';
function CollectionList({ match }) {
const collection_name = match.params.collection;
const collection = useCollections(collection_name);
const [items, setItems] = useState([]);
+ const [loading, setLoading] = useState(false);
useEffect(() => {
+ setLoading(true);
fetch(`/api/v1/collections/${collection_name}`)
.then(response => response.json())
- .then(response => setItems(response.items));
+ .then(response => {
+ setLoading(false);
+ setItems(response.items);
+ });
}, []);
- return (
+ return loading ? (
+ <Loading />
+ ) : (
<div>
<Link to={`/collections/${collection_name}/create`}>+create</Link>
{items.map((item, index) => (
<React.Fragment key={index}>
<div>{item.title}</div>
<Link
to={`/collections/${collection_name}/edit/${item.id}`}
>
edit
</Link>
</React.Fragment>
))}
</div>
);
}
function Collection({ match }) {
const collection_name = match.params.collection;
return (
<div>
<h2>{collection_name}</h2>
<Route exact path={match.path} component={CollectionList} />
</div>
);
}
function Collections({ match }) {
return (
<div>
Collections
<Route path={`${match.path}/:collection`} component={Collection} />
<Route
exact
path={`${match.path}/:collection/:mode`}
component={CreateCollectionItem}
/>
<Route
exact
path={`${match.path}/:collection/:mode/:id`}
component={CreateCollectionItem}
/>
<Route
exact
path={match.path}
component={() => <div>Pick a collection</div>}
/>
</div>
);
}
export default Collections;
diff --git a/admin-panel/collections/create-collection-item.js b/admin-panel/collections/create-collection-item.js
index 69a8faa..aacc0fb 100644
--- a/admin-panel/collections/create-collection-item.js
+++ b/admin-panel/collections/create-collection-item.js
@@ -1,104 +1,111 @@
import React, { useState, useEffect } from 'react';
import useCollections from './use-collections.js';
import FormControls from '../form-controls/form-controls.jsx';
+import Loading from '../loading/loading';
export default function({ match }) {
const { id, collection, mode } = match.params;
const _collection = useCollections(collection);
const [values, setValues] = useState({});
+ const [loading, setLoading] = useState(false);
useEffect(() => {
async function fetchData() {
+ setLoading(true);
const query = await fetch(
`/api/v1/collections/${collection}/${id}`
);
const data = await query.json();
setValues({ ...data });
+ setLoading(false);
}
if (mode === 'edit') {
fetchData();
}
}, []);
function setValue(key, value) {
setValues({ ...values, [key]: value });
}
if (!_collection) {
return <div>Loading...</div>;
}
async function save(event) {
event.preventDefault();
let api_endpoint = `/api/v1/collections/${collection}`;
if (mode === 'edit') {
api_endpoint += `/${id}`;
}
//Filter out unnecessary fields from values
const body = {};
Object.keys(values).forEach(key => {
if (_collection.fields[key]) {
body[key] = values[key];
}
});
-
+ setLoading(true);
const query = await fetch(api_endpoint, {
method: mode === 'edit' ? 'PUT' : 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const response = await query.json();
if (response.sealious_error) {
alert(response.message);
console.log(response);
} else {
alert(
`Successfully ${mode === 'edit' ? 'edited' : 'created'} ${
values.title
}`
);
+ setLoading(false);
document.location.hash = `/collections/${collection}`;
}
}
- return (
+ return loading ? (
+ <Loading />
+ ) : (
<div>
<h2>Metadata Editor</h2>
<form onSubmit={save}>
{JSON.stringify(values)}
<ul>
{Object.entries(_collection.fields).map(
([field_name, field]) => (
<label
htmlFor={field_name}
style={{ display: 'block' }}
key={field_name}
>
{field_name}
{FormControls[field.type.name]
? /* eslint-disable indent */
React.createElement(
FormControls[field.type.name],
{
...field,
onChange: value =>
setValue(field_name, value),
value: values[field_name] || '',
}
)
: /* eslint-enable indent */
`: ${field.type.name}`}
</label>
)
)}
</ul>
<input type="submit" />
</form>
</div>
);
}
diff --git a/admin-panel/collections/use-collections.js b/admin-panel/collections/use-collections.js
index 9d9b523..c6119a3 100644
--- a/admin-panel/collections/use-collections.js
+++ b/admin-panel/collections/use-collections.js
@@ -1,43 +1,44 @@
const { useState } = require('react');
let collections_cache = [];
let loaded = false;
let promise = null;
const url = '/api/v1/specifications';
export default function useCollections(collection_name) {
const [collections, setCollections] = useState(collections_cache);
+
if (promise) {
promise.then(setCollections);
} else {
if (loaded) {
setCollections(collections_cache);
} else {
promise = (async function() {
const response = await fetch(url);
const collections = (await response.json()).filter(
collection =>
![
'user-roles',
'users',
'sessions',
'anonymous-sessions',
'formatted-images',
'password-reset-intents',
'registration-intents',
].includes(collection.name)
);
setCollections(collections);
collections_cache = collections;
loaded = true;
return collections;
})();
}
}
return collection_name
? collections.filter(
- collection => collection.name == collection_name
+ collection => collection.name == collection_name
)[0]
: collections;
}
diff --git a/admin-panel/loading/_loading.scss b/admin-panel/loading/_loading.scss
new file mode 100644
index 0000000..ee4eb13
--- /dev/null
+++ b/admin-panel/loading/_loading.scss
@@ -0,0 +1,59 @@
+@keyframes jump {
+ 0% {
+ transform: transl58eY(0);
+ }
+ 7% {
+ transform: scaleY(0.8) scaleX(1.2) translateY(2px);
+ }
+ 10% {
+ transform: scaleY(0.8) scaleX(1.2) translateY(2px);
+ }
+ 19% {
+ transform: scaleX(0.9) scaleY(1.1) translateY(-0.5rem);
+ }
+ 37% {
+ transform: translateY(-1rem);
+ }
+ 55% {
+ transform: scaleX(0.9) scaleY(1.1) translateY(-0.5rem);
+ }
+ 70% {
+ transform: scaleY(0.8) scaleX(1.2) translateY(2px);
+ }
+ 100% {
+ transform: translateY(0);
+ }
+}
+
+.loading-container {
+ text-align: center;
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ padding-left: 1rem;
+}
+
+.loading-box {
+ width: 1rem;
+ height: 1rem;
+ background-color: #624991;
+ display: inline-block;
+ vertical-align: bottom;
+ animation: jump 700ms;
+ animation-iteration-count: infinite;
+}
+
+.loading-shadow {
+ display: inline-block;
+ margin-right: 1rem;
+ width: 1rem;
+ margin-left: -1rem;
+ width: 1rem;
+ box-shadow: 0px 4px 2px 1px rgba(0, 0, 0, 0.16);
+}
+
+.loading-text {
+ margin-top: 1rem;
+ font-size: 1rem;
+ line-height: 2rem;
+ color: hsl(89, 46%, 48%);
+}
diff --git a/admin-panel/loading/loading.jsx b/admin-panel/loading/loading.jsx
new file mode 100644
index 0000000..9fb2c07
--- /dev/null
+++ b/admin-panel/loading/loading.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+const Loading = function(props) {
+ const delay = 65;
+ const boxes_amnt = Math.floor(Math.random() * 4) + 4;
+ const boxes = [];
+ for (var i = 0; i < boxes_amnt; i++) {
+ boxes.push(
+ <div
+ className={'loading-box ' + props.boxClass}
+ style={{ animationDelay: delay * i + 'ms' }}
+ />
+ );
+ boxes.push(<div className="loading-shadow" />);
+ }
+ let loading = null;
+ if (props.text) {
+ loading = <div className="loading-text">{props.text}</div>;
+ }
+ return (
+ <div className="loading-container">
+ <div>{boxes}</div>
+ {loading}
+ </div>
+ );
+};
+
+export default Loading;
diff --git a/admin-panel/styles.scss b/admin-panel/styles.scss
index f268717..f211e7f 100644
--- a/admin-panel/styles.scss
+++ b/admin-panel/styles.scss
@@ -1,22 +1,23 @@
@import './navbar/navbar';
@import './sidebar/sidebar';
+@import './loading/loading';
@import './webfonts.scss';
html {
font-size: 1.5em;
font-family: 'IBM Plex Sans';
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
li {
list-style: none;
}
.app-sealpage {
display: flex;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 8, 05:43 (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1034062
Default Alt Text
(8 KB)
Attached To
Mode
rSEALPAGE Sealpage
Attached
Detach File
Event Timeline
Log In to Comment