Skip to content

Commit

Permalink
feat: add new dynamic image grid
Browse files Browse the repository at this point in the history
  • Loading branch information
daveschumaker committed Oct 12, 2022
1 parent e10d0c5 commit 77dac33
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 66 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Next.Horde (A front-end for the Stable Horde distrubuted cluster)
# ArtBot for Stable Diffusion

This is a front-end GUI for interacting with the [Stable Horde](https://stablehorde.net/) distributed cluster.
![A painting robot](/public/painting_bot.png)

See it in action: [https://tinybots.net/artbot](https://tinybots.net/artbot)

ArtBot is a front-end GUI for generating images and photos with Stable Diffusion using a distributed computing cluster powered by the [Stable Horde](https://stablehorde.net/).

**To use:**

Expand Down
10 changes: 5 additions & 5 deletions components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@ export default function NavBar() {

return (
<div className="mb-2 text-sm font-medium text-center text-white border-b border-gray-200 w-full">
<ul className="flex flex-wrap">
<li className="text-left w-1/4">
<ul className="flex flex-row">
<li className="text-left">
<Link href="/" passHref>
<a className={isActiveRoute('/')}>
<IconCreate className="inline-block mr-1 pb-1" />
Create
</a>
</Link>
</li>
<li className="text-left w-1/4">
<li className="text-left">
<Link href="/pending" passHref>
<a className={isActiveRoute('/pending')}>
<HourglassIcon className="inline-block mr-[2-px] pb-1" />
Pending
</a>
</Link>
</li>
<li className="text-left w-1/4">
<li className="text-left">
<Link href="/images" passHref>
<a className={isActiveRoute('/images')}>
<PhotoIcon className="inline-block mr-[2-px] pb-1" />
Images
</a>
</Link>
</li>
<li className="text-left w-1/4">
<li className="text-left">
<Link href="/settings" passHref>
<a className={isActiveRoute('/settings')}>
<SettingsIcon className="inline-block mr-[2-px] pb-1" />
Expand Down
34 changes: 34 additions & 0 deletions components/icons/GridIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Modified via: https://tabler-icons.io/

const GridIcon = ({
className,
size = 24,
stroke = 'currentColor'
}: {
className?: string
size?: number
stroke?: string
}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
width={size}
height={size}
viewBox="0 0 24 24"
strokeWidth="1"
stroke={stroke}
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="4" y="4" width="6" height="6" rx="1"></rect>
<rect x="14" y="4" width="6" height="6" rx="1"></rect>
<rect x="4" y="14" width="6" height="6" rx="1"></rect>
<rect x="14" y="14" width="6" height="6" rx="1"></rect>
</svg>
)
}

export default GridIcon
36 changes: 36 additions & 0 deletions components/icons/ListIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Modified via: https://tabler-icons.io/

const ListIcon = ({
className,
size = 24,
stroke = 'currentColor'
}: {
className?: string
size?: number
stroke?: string
}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
width={size}
height={size}
viewBox="0 0 24 24"
strokeWidth="1"
stroke={stroke}
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="9" y1="6" x2="20" y2="6"></line>
<line x1="9" y1="12" x2="20" y2="12"></line>
<line x1="9" y1="18" x2="20" y2="18"></line>
<line x1="5" y1="6" x2="5" y2="6.01"></line>
<line x1="5" y1="12" x2="5" y2="12.01"></line>
<line x1="5" y1="18" x2="5" y2="18.01"></line>
</svg>
)
}

export default ListIcon
15 changes: 15 additions & 0 deletions declarations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
declare module 'react-responsive-masonry' {
import * as React from 'react'

const Masonry: React.FC<{
columnsCount?: number
gutter?: string
children: any
}>

export const ResponsiveMasonry: React.FC<{
columnsCountBreakPoints?: Record<number, number>
}>

export default Masonry
}
5 changes: 5 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-lazyload": "3.2.0",
"react-responsive-masonry": "2.1.6",
"sharp": "0.31.1",
"ssh-deploy-release": "4.0.1"
},
Expand Down
35 changes: 35 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,44 @@ import PollController from '../components/PollController'
import '../styles/globals.css'

import { initDb } from '../utils/db'
import { useEffect } from 'react'
initAppSettings()
initDb()

function MyApp({ Component, pageProps }: AppProps) {
useEffect(() => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').then(
function (registration) {
console.log(
'Service Worker registration successful with scope: ',
registration.scope
)
},
function (err) {
console.log('Service Worker registration failed: ', err)
}
)
})
}
}, [])

return (
<>
<Head>
<title>ArtBot - A Stable Diffusion demo utilizing Stable Horde</title>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@davely" />
<meta name="twitter:title" content="ArtBot for Stable Diffusion" />
<meta
name="twitter:description"
content="Generate AI-created images and photos with Stable Diffusion using a distributed computing cluster powered by Stable Horde."
/>
<meta
name="twitter:image"
content="https://tinybots.net/artbot/painting_bot.png"
/>
<link
rel="apple-touch-icon"
href="/artbot/apple-touch-icon-iphone-60x60.png"
Expand All @@ -37,6 +67,11 @@ function MyApp({ Component, pageProps }: AppProps) {
sizes="144x144"
href="/artbot/apple-touch-icon-ipad-retina-152x152.png"
/>
<meta name="apple-mobile-web-app-title" content="ArtBot"></meta>
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
></meta>
<link rel="icon" type="image/x-icon" href="/artbot/favicon.ico"></link>
</Head>
<PollController />
Expand Down
81 changes: 57 additions & 24 deletions pages/image/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import LazyLoad from 'react-lazyload'
import Masonry from 'react-responsive-masonry'
import ImageDetails from '../../components/ImageDetails'
import ImageSquare from '../../components/ImageSquare'
import PageTitle from '../../components/PageTitle'

import Spinner from '../../components/Spinner'
Expand All @@ -22,7 +23,10 @@ const ImagePage = () => {
const data = await getImageDetails(jobId)
setIsInitialLoad(false)
setImageDetails(data)
findRelatedImages(data.parentJobId)

if (data?.base64String) {
findRelatedImages(data.parentJobId)
}
}

const handleDeleteImageClick = async () => {
Expand All @@ -43,11 +47,36 @@ const ImagePage = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])

const noImageFound = !isInitialLoad && !imageDetails?.base64String

return (
<div>
<div className="inline-block w-1/2">
{!isInitialLoad && noImageFound ? (
<PageTitle>Image not found</PageTitle>
) : (
<PageTitle>Image details</PageTitle>
)}
</div>
{isInitialLoad && <Spinner />}
{!isInitialLoad && noImageFound && (
<>
<div>Oops!</div>
<div className="mt-4">
There is no image at this URL. Perhaps you&apos;ve bookmarked
something that&apos;s been deleted. Or someone shared this link with
you. All images are privately stored inside each user&apos;s
browser.
</div>
<div className="mt-4 mb-2">
<Link href="/">
<a className="text-cyan-400">Why not create something new?</a>
</Link>
</div>
</>
)}
{!isInitialLoad && imageDetails?.base64String && (
<div key={imageDetails.jobId} className="text-center pt-6 pb-6">
<div key={imageDetails.jobId} className="text-center pb-6">
<img
src={'data:image/webp;base64,' + imageDetails.base64String}
className="mx-auto"
Expand All @@ -63,27 +92,31 @@ const ImagePage = () => {
<div className="pt-2 border-0 border-t-2 border-dashed border-slate-500">
<PageTitle>Related images</PageTitle>
<div className="mt-4 flex gap-y-2.5 flex-wrap gap-x-2.5">
{relatedImages.map(
(image: {
jobId: string
base64String: string
prompt: string
timestamp: number
seed: number
}) => {
return (
<Link
href={`/image/${image.jobId}`}
key={image.jobId}
passHref
>
<a>
<ImageSquare imageDetails={image} />
</a>
</Link>
)
}
)}
<Masonry columnsCount={2} gutter="10px">
{relatedImages.map(
(image: {
jobId: string
base64String: string
prompt: string
timestamp: number
seed: number
}) => {
return (
<LazyLoad key={image.jobId} once>
<Link href={`/image/${image.jobId}`} passHref>
<a>
<img
src={'data:image/webp;base64,' + image.base64String}
style={{ width: '100%', display: 'block' }}
alt={image.prompt}
/>
</a>
</Link>
</LazyLoad>
)
}
)}
</Masonry>
</div>
</div>
)}
Expand Down
Loading

0 comments on commit 77dac33

Please sign in to comment.