mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
Add skeleton loading indicators
This commit is contained in:
37
src/app/components/generator/FileView.tsx
Normal file
37
src/app/components/generator/FileView.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { DocAndNode } from '@spyglassmc/core'
|
||||
import { JsonFileNode } from '@spyglassmc/json'
|
||||
import { useErrorBoundary } from 'preact/hooks'
|
||||
import { useDocAndNode, useSpyglass } from '../../contexts/Spyglass.jsx'
|
||||
import { message } from '../../Utils.js'
|
||||
import { ErrorPanel } from '../ErrorPanel.jsx'
|
||||
import { JsonFileView } from './JsonFileView.jsx'
|
||||
|
||||
type FileViewProps = {
|
||||
docAndNode: DocAndNode | undefined,
|
||||
}
|
||||
export function FileView({ docAndNode: original }: FileViewProps) {
|
||||
const { serviceLoading } = useSpyglass()
|
||||
|
||||
const [error, errorRetry] = useErrorBoundary()
|
||||
if (error) {
|
||||
return <ErrorPanel error={`Error viewing the file: ${message(error)}`} onDismiss={errorRetry} />
|
||||
}
|
||||
|
||||
const docAndNode = useDocAndNode(original)
|
||||
if (!docAndNode || serviceLoading) {
|
||||
return <div class="file-view flex flex-col gap-1">
|
||||
<div class="skeleton rounded-md h-[34px] w-[200px]"></div>
|
||||
<div class="skeleton rounded-md h-[34px] w-[240px]"></div>
|
||||
<div class="skeleton rounded-md h-[34px] w-[190px] ml-[18px]"></div>
|
||||
<div class="skeleton rounded-md h-[34px] w-[130px] ml-[18px]"></div>
|
||||
<div class="skeleton rounded-md h-[34px] w-[290px]"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
const fileNode = docAndNode?.node.children[0]
|
||||
if (JsonFileNode.is(fileNode)) {
|
||||
return <JsonFileView docAndNode={docAndNode} node={fileNode.children[0]} />
|
||||
}
|
||||
|
||||
return <ErrorPanel error={`Cannot view file ${docAndNode.doc.uri}`} />
|
||||
}
|
||||
@@ -2,35 +2,19 @@ import type { DocAndNode, Range } from '@spyglassmc/core'
|
||||
import { dissectUri } from '@spyglassmc/java-edition/lib/binder/index.js'
|
||||
import type { JsonNode } from '@spyglassmc/json'
|
||||
import { JsonFileNode } from '@spyglassmc/json'
|
||||
import { useCallback, useErrorBoundary, useMemo } from 'preact/hooks'
|
||||
import { useLocale } from '../../contexts/index.js'
|
||||
import { useDocAndNode, useSpyglass } from '../../contexts/Spyglass.jsx'
|
||||
import { useCallback, useMemo } from 'preact/hooks'
|
||||
import { useSpyglass } from '../../contexts/Spyglass.jsx'
|
||||
import { getRootType, simplifyType } from './McdocHelpers.js'
|
||||
import type { McdocContext } from './McdocRenderer.jsx'
|
||||
import { McdocRoot } from './McdocRenderer.jsx'
|
||||
|
||||
type TreePanelProps = {
|
||||
type JsonFileViewProps = {
|
||||
docAndNode: DocAndNode,
|
||||
onError: (message: string) => unknown,
|
||||
node: JsonNode,
|
||||
}
|
||||
export function Tree({ docAndNode: original, onError }: TreePanelProps) {
|
||||
const { lang } = useLocale()
|
||||
export function JsonFileView({ docAndNode, node }: JsonFileViewProps) {
|
||||
const { service } = useSpyglass()
|
||||
|
||||
if (lang === 'none') return <></>
|
||||
|
||||
const docAndNode = useDocAndNode(original)
|
||||
const fileChild = docAndNode.node.children[0]
|
||||
if (!JsonFileNode.is(fileChild)) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const [error] = useErrorBoundary(e => {
|
||||
onError(`Error rendering the tree: ${e.message}`)
|
||||
console.error(e)
|
||||
})
|
||||
if (error) return <></>
|
||||
|
||||
const makeEdit = useCallback((edit: (range: Range) => JsonNode | undefined) => {
|
||||
if (!service) {
|
||||
return
|
||||
@@ -62,15 +46,15 @@ export function Tree({ docAndNode: original, onError }: TreePanelProps) {
|
||||
}, [docAndNode, service, makeEdit])
|
||||
|
||||
const resourceType = useMemo(() => {
|
||||
if (original.doc.uri.endsWith('/pack.mcmeta')) {
|
||||
if (docAndNode.doc.uri.endsWith('/pack.mcmeta')) {
|
||||
return 'pack_mcmeta'
|
||||
}
|
||||
if (ctx === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const res = dissectUri(original.doc.uri, ctx)
|
||||
const res = dissectUri(docAndNode.doc.uri, ctx)
|
||||
return res?.category
|
||||
}, [original, ctx])
|
||||
}, [docAndNode, ctx])
|
||||
|
||||
const mcdocType = useMemo(() => {
|
||||
if (!ctx || !resourceType) {
|
||||
@@ -81,8 +65,8 @@ export function Tree({ docAndNode: original, onError }: TreePanelProps) {
|
||||
return type
|
||||
}, [resourceType, ctx])
|
||||
|
||||
return <div class="tree node-root" data-category={getCategory(resourceType)}>
|
||||
{(ctx && mcdocType) && <McdocRoot type={mcdocType} node={fileChild.children[0]} ctx={ctx} />}
|
||||
return <div class="file-view tree node-root" data-category={getCategory(resourceType)}>
|
||||
{(ctx && mcdocType) && <McdocRoot type={mcdocType} node={node} ctx={ctx} />}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -144,9 +144,15 @@ export function ProjectPanel() {
|
||||
{project.name !== DRAFT_PROJECT.name && <Btn icon="trashcan" label={locale('project.delete')} onClick={onDeleteProject} />}
|
||||
</BtnMenu>
|
||||
</div>
|
||||
<div class="file-view">
|
||||
<div class="project-files">
|
||||
{entries === undefined
|
||||
? <></>
|
||||
? <div class="p-2 flex flex-col gap-2">
|
||||
<div class="skeleton-2 rounded h-4 w-24"></div>
|
||||
<div class="skeleton-2 rounded h-4 w-32 ml-4"></div>
|
||||
<div class="skeleton-2 rounded h-4 w-24 ml-8"></div>
|
||||
<div class="skeleton-2 rounded h-4 w-36 ml-8"></div>
|
||||
<div class="skeleton-2 rounded h-4 w-28"></div>
|
||||
</div>
|
||||
: entries.length === 0
|
||||
? <span>{locale('project.no_files')}</span>
|
||||
: <TreeView entries={entries} split={path => path.split('/')} group={FolderEntry} leaf={FileEntry} />}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { checkVersion, fetchDependencyMcdoc, fetchPreset, fetchRegistries, getSn
|
||||
import { DEPENDENCY_URI } from '../../services/Spyglass.js'
|
||||
import { Store } from '../../Store.js'
|
||||
import { cleanUrl, genPath } from '../../Utils.js'
|
||||
import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, Footer, HasPreview, Octicon, PreviewPanel, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js'
|
||||
import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileView, Footer, HasPreview, Octicon, PreviewPanel, ProjectPanel, SearchList, SourcePanel, TextInput, VersionSwitcher } from '../index.js'
|
||||
import { getRootDefault } from './McdocHelpers.js'
|
||||
|
||||
export const SHARE_KEY = 'share'
|
||||
@@ -59,7 +59,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY)
|
||||
const ignoreChange = useRef(false)
|
||||
|
||||
const { value: docAndNode } = useAsync(async () => {
|
||||
const { value: docAndNode, loading: docLoading } = useAsync(async () => {
|
||||
let text: string | undefined = undefined
|
||||
if (currentPreset && sharedSnippetId) {
|
||||
setSharedSnippetId(undefined)
|
||||
@@ -386,7 +386,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
</BtnMenu>
|
||||
</div>
|
||||
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
|
||||
{docAndNode && <Tree docAndNode={docAndNode} onError={setError} />}
|
||||
<FileView docAndNode={docLoading ? undefined : docAndNode} />
|
||||
<Footer donate={!gen.tags?.includes('partners')} />
|
||||
</main>
|
||||
<div class="popup-actions right-actions" style={`--offset: -${8 + actionsShown * 50}px;`}>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
export * from './FileCreation.js'
|
||||
export * from './FileRenaming.js'
|
||||
export * from './FileView.jsx'
|
||||
export * from './GeneratorCard.jsx'
|
||||
export * from './GeneratorList.jsx'
|
||||
export * from './JsonFileView.jsx'
|
||||
export * from './PreviewPanel.js'
|
||||
export * from './ProjectCreation.js'
|
||||
export * from './ProjectDeletion.js'
|
||||
export * from './ProjectPanel.js'
|
||||
export * from './SourcePanel.js'
|
||||
export * from './Tree.js'
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useVersion } from './Version.jsx'
|
||||
interface SpyglassContext {
|
||||
client: SpyglassClient
|
||||
service: SpyglassService | undefined
|
||||
serviceLoading: boolean
|
||||
}
|
||||
|
||||
const SpyglassContext = createContext<SpyglassContext | undefined>(undefined)
|
||||
@@ -59,13 +60,14 @@ export function SpyglassProvider({ children }: { children: ComponentChildren })
|
||||
const { version } = useVersion()
|
||||
const [client] = useState(new SpyglassClient())
|
||||
|
||||
const { value: service } = useAsync(() => {
|
||||
const { value: service, loading: serviceLoading } = useAsync(() => {
|
||||
return client.createService(version)
|
||||
}, [client, version])
|
||||
|
||||
const value: SpyglassContext = {
|
||||
client,
|
||||
service,
|
||||
serviceLoading,
|
||||
}
|
||||
|
||||
return <SpyglassContext.Provider value={value}>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
:root {
|
||||
--background-1: #fafafa;
|
||||
--background-2: #e2e2e2;
|
||||
--background-2-shimmer: #d9d9d9;
|
||||
--background-3: #d4d3d3;
|
||||
--background-3-shimmer: #cbcbcb;
|
||||
--background-4: #b8b8b8;
|
||||
--background-5: #bdbdbd;
|
||||
--background-6: #cecece;
|
||||
@@ -53,8 +55,10 @@
|
||||
:root.dark {
|
||||
--background-1: #1b1b1b;
|
||||
--background-2: #252525;
|
||||
--background-2-shimmer: #2c2c2c;
|
||||
--background-3: #222222;
|
||||
--background-4: #3d3d3d;
|
||||
--background-4-shimmer: #424242;
|
||||
--background-5: #383838;
|
||||
--background-6: #575757;
|
||||
--text-1: #ffffff;
|
||||
@@ -361,16 +365,48 @@ main > .controls {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tree {
|
||||
.file-view {
|
||||
margin-top: -40px;
|
||||
overflow-x: auto;
|
||||
padding: 8px 16px 50vh;
|
||||
}
|
||||
|
||||
.error + .tree {
|
||||
.error + .file-view {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
opacity: 1;
|
||||
background-image: linear-gradient(90deg, var(--background-2) 0px, var(--background-2-shimmer) 38px, var(--background-2-shimmer) 42px, var(--background-2) 80px);
|
||||
background-size: 600px;
|
||||
animation: skeleton-shimmer 2.5s infinite linear, skeleton-fade-in 1s forwards linear;
|
||||
}
|
||||
|
||||
.skeleton-2 {
|
||||
opacity: 1;
|
||||
background-image: linear-gradient(90deg, var(--background-4) 0px, var(--background-4-shimmer) 38px, var(--background-4-shimmer) 42px, var(--background-4) 80px);
|
||||
background-size: 600px;
|
||||
animation: skeleton-shimmer 2.5s infinite linear, skeleton-fade-in 1s forwards linear;
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
background-position: -110px;
|
||||
}
|
||||
40%, 100% {
|
||||
background-position: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton-fade-in {
|
||||
0%, 20% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-source {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
@@ -1434,7 +1470,7 @@ main.has-project {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
[data-modals] .tree {
|
||||
[data-modals] .file-view {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1786,7 +1822,7 @@ hr {
|
||||
content: '\200b';
|
||||
}
|
||||
|
||||
.file-view {
|
||||
.project-files {
|
||||
background-color: var(--background-2);
|
||||
color: var(--text-2);
|
||||
overflow: hidden;
|
||||
@@ -1795,7 +1831,7 @@ hr {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.file-view > span {
|
||||
.project-files > span {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
@@ -1943,8 +1979,10 @@ hr {
|
||||
}
|
||||
|
||||
.ea-content {
|
||||
opacity: 1;
|
||||
margin: 0 !important;
|
||||
background: var(--background-2) !important;
|
||||
animation: skeleton-fade-in 0.5s forwards linear;
|
||||
}
|
||||
|
||||
.ea-content span {
|
||||
@@ -2978,13 +3016,13 @@ hr {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1300px) {
|
||||
main.has-preview .tree {
|
||||
main.has-preview .file-view {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
main .tree {
|
||||
main .file-view {
|
||||
margin-top: 4px !important;
|
||||
}
|
||||
}
|
||||
@@ -3015,7 +3053,7 @@ hr {
|
||||
top: 64px
|
||||
}
|
||||
|
||||
.tree {
|
||||
.file-view {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user