# 🎨 UI **🗺 Contents – [ℹ️ About](#about) · [🦾 Technology](#technology) · [🏗 Structure](#structure) · [🤝 Conventions](#conventions)** **[⬆️ Top-Level README](../../README.md)** ![ManStandingBeforeUI](../../misc/ManStandingBeforeUI.png)
# ℹ️ About _Welcome to the UI package for StableStudio!_ **If you're ready to dive in and make changes, be sure to read about [technology](#technology), [structure](#structure), [conventions](#conventions), and [plugins](../stablestudio-plugin/README.md) first.** ## 📜 History StableStudio represents a new direction for [DreamStudio](https://dreamstudio.ai) with renewed focus on open-source and community-driven development, but the project has an interesting backstory... - **Pre-Stable Diffusion Launch** _Summer 2022_ [DreamStudio](https://dreamstudio.ai) began as passion-project by [@nin](https://twitter.com/nin_artificial) with the goal of creating an all-in-one creative studio for generative animations using Disco Diffusion. He and several collaborators wrote the original UI using Vue.js and a local Python back-end. With the imminent arrival of Stable Diffusion, focus shifted toward creating an easy-to-use interface for image generation so the release of the model would be as accessible as possible. - **Post-Stable Diffusion Launch** _August 2022 – November 2022_ Following the release of Stable Diffusion, [DreamStudio](https://dreamstudio.ai) served as a vehicle for new models and features shipped by [Stability AI](http://stability.ai). This period saw the introduction of [in-painting](https://platform.stability.ai/docs/features/inpainting?tab=typescript), [out-painting](https://platform.stability.ai/docs/features/inpainting?tab=typescript), and [image-to-image](https://platform.stability.ai/docs/features/image-to-image?tab=typescript). - **React** _December 2022 – April 2023_ In early December, work began on a new infinite-canvas version of the existing editor. Given the need to manage complicated state and side effects declaratively, the decision was made to switch the editor to React. Once a working editor MVP was finished, the opportunity was taken to rewrite the entire app while also shipping a re-imagined UX. - **StableStudio** _Present – Future_ The breakneck speed of the Stable Diffusion community has proven distributing generative AI is better achieved through open-source development. We're hopeful StableStudio can serve as a hub for community collaboration and a home for exciting new features. # 🦾 Technology ![SorcererWithReact](../../misc/SorcererWithReact.png) - **[TypeScript](https://www.typescriptlang.org/)** – Type safety ftw! - **[Vite](https://vitejs.dev/)** – Bundler and live-development tool. You can see the full config in [`vite.config.ts`](./vite.config.ts) - **[React](https://reactjs.org/)** –  We're using React with modern hooks and functional components - **[Zustand](https://docs.pmnd.rs/zustand/getting-started/introduction)** –  Extremely fast and easy-to-use state management, it's powerful and tiny! - **[Tailwind](https://tailwindcss.com/)** – Our preferred method of styling components, the config is at [`tailwind.config.js`](./tailwind.config.cjs) - **[Emotion](https://emotion.sh/docs/introduction)** – For when we need to break free of Tailwind and write "native CSS" # 🏗 Structure ![CyberPyramid](../../misc/CyberPyramid.png) ## 🧱 Domain-Driven Design **The most important aspect of this codebase's structure is adherence to domain-driven design.** This means we organize code around the _concepts_ rather than technical implementation details. For example, if we identify a _concept_, let's say `User`, we would create a `User` "domain." The `User` domain would _own_ all user-related code, including visual representation, state, hooks, etc. Most domains are _composed_ of smaller domains; a hypothetical `User` domain might be composed of `User.State`, `User.Avatar`, `User.Preferences`, `User.Details`, etc. _This structure is fractal in nature,_ meaning `User.Details` might itself be composed of `User.Details.SocialMedia`, `User.Details.ContactInformation`, `User.Details.UpdateRequest`, etc. ### \*️⃣ Domain Syntax Here's an example of how we might represent a `User` domain in code... ```tsx import { Avatar } from "./Avatar"; import { Preferences } from "./Preferences"; import { Details } from "./Details"; export type User = { id: ID; avatar?: User.Avatar; preferences?: User.Preferences; details?: User.Details; }; export function User({ id }: User.Props) { const user = User.use(id); return ( <> ); } export declare namespace User { export { Avatar, Preferences, Details }; } export namespace User { User.Avatar = Avatar; User.Preferences = Preferences; User.Details = Details; export type Props = { id?: ID; }; export const use = (id: ID) => { // Make an API call for the user or something }; } ``` This syntax takes advantage of TypeScript's declaration merging feature to enable nice fluent-style APIs for domains... ```tsx import { User } from "./User"; // You can use `User` as a type const bob: User = { id: "bob" }; function App(id: ID) { // You can use `User` as a namespace const user = User.use(id); const userPreferences = User.Preferences.use(id); // You can use `User` as a component return ( <> ); } ``` Here is an example of a real domain ([`Editor.Camera`](./src/Editor/Camera/index.tsx)) you'll find in the app... ```tsx // Owner of all things camera-related in the editor Editor.Camera; // Centers the camera in the editor Editor.Camera.Center; // The hand tool is used for panning around Editor.Camera.Hand; // For resetting the camera to the default view Editor.Camera.Reset; // Specifies keyboard shortcuts for camera actions Editor.Camera.Shortcuts; // Controls the zoom level of the camera and mouse wheel controls Editor.Camera.Zoom; ``` Quick note about the `export declare namespace User` syntax from the first example... This is needed when a namespace exports a type. It's an unfortunate bit of syntax which might [not be needed](https://github.com/microsoft/TypeScript/issues/39865) in the future. If you want to discuss this further, please check out [this issue](https://github.com/Stability-AI/StableStudio/issues/7). ### 📁 Directories and Files _Domains are usually correlated one-to-one with file/folder structure._ For example, the [`Editor.Camera`](./src/Editor/Camera/index.tsx) domain mentioned above is composed of the following files... ``` ./src/Editor/Camera/ ├── Center.tsx ├── Hand.tsx ├── Reset.tsx ├── Shortcut.tsx ├── Zoom.tsx └── index.tsx ``` This means if you see a domain like [`Generation.Image.Download`](./src/Generation/Image/Download/index.tsx), you can expect to find it at `./src/Generation/Image/Download`. All root-level domains located at the top-level of the [`./src`](./src) folder and can be imported using the shorthand syntax... ```ts import { ExampleDomain } from "~/ExampleDomain"; import { ExampleDomain } from "../../../../../ExampleDomain"; // ^ If you were deep in the folder tree, it would look like this without the `~` alias ``` ### 📋 Structuring Tips - **Look for repeated references to a word or concept** For example, if you're working in a `User` domain and find yourself using the word "avatar" a lot, you might want to create a `User.Avatar` domain - **Compound words are structuring hints** Domains like `DeviceInformation` might be better represented as `Device.Information`. - **Plural domains are very important** The singular [`Generation.Image.Input`](./src/Generation/Image/Input/index.tsx) domain owns everything related to the concept of what's being fed to an image generation, but [`Generation.Image.Inputs`](./src/Generation/Image/index.tsx) owns the state of all inputs as a collective. Plural domains are usually exported at the same level as their singular counterparts, such as [`Editor.Dream`](./src/Editor/Dream/index.tsx) and [`Editor.Dreams`](./src/Editor/Dream/Dreams.tsx). ### ⭐️ Important Domains - **[`App`](./src/App/index.tsx)** – Owner of app-level functionality such as the React root, sidebars, providers, etc. - **[`Generation.Image`](./src/Generation/Image/index.tsx)** – Likely the largest domain, owns all things related to image generation - **[`Generation.Image.Session`](./src/Generation/Image/Session/index.tsx)** – Domain which was poorly named and grew too large as a result, manages the active "image generation session" - **[`Editor`](./src/GlobalVariables/index.tsd)** – Home of all editor functionality - **[`Plugin`](./src/Plugin/index.ts)** – Owner of plugin setup and access, contains the important `Plugin.use` hook - **[`Theme`](./src/Theme/index.ts)** – Contains the StableStudio's design system and common components such as `Theme.Icon`, `Theme.Button`, etc. - **[`Shortcut`](./src/Shortcut/index.ts)** – Contains the StableStudio's keyboard shortcut system and menu - **[`GlobalState`](./src/GlobalState/index.ts)** – Mostly a pass-through wrapper around [Zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) - **[`GlobalVariables`](./src/GlobalVariables/index.ts)** – Extremely useful functions and types available across the StableStudio without needing any imports (`css`, `classes`, `useEffect`, etc.) # 🤝 Conventions ![RobotsHoldingHands](../../misc/RobotsHoldingHands.png) ## 💅 Styles and CSS Most styling in StableStudio is implemented with [Tailwind CSS](https://tailwindcss.com/). The [`Theme`](./src/Theme/index.ts) domain contains a bunch of useful pre-made components, make sure to check there first if you need something "off-the-shelf." You can use the [`classes`](#global-variables) function anywhere without imports if you need conditional styling or to break apart class names... ```tsx function Example({ isEmphasized, isDisabled, children, }: PropsWithChildren<{ isEmphasized?: boolean; isDisabled: boolean; }>) { return (
{children}
); } ``` You can always "break glass" and use raw CSS via the globally-available `css` function if you need to, but please try to avoid it... ```tsx function Example() { return (
Hello, World!
); } ``` ## 🏛 State Management via [Zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) StableStudio uses [Zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) for state management, which is honestly super cool. It's fast and small, you can read through the whole codebase in a few minutes. We originally used [Recoil](https://recoiljs.org/) it couldn't handle "hot" paths nearly as well. Whenever you need globally-available state, you can use the [`GlobalState`](./src/GlobalState/index.ts) domain, which wraps Zustand... ```tsx import { GlobalState } from "~/GlobalState"; function Count() { const count = Count.use(); const setCount = Count.useSet(); return (
{count}
); } export namespace Count { export const use = State.use(({ count }) => count); export const useSet = () => State.use(({ setCount }) => setCount, GlobalState.shallow); } type State = { count: number; setCount: (count: number) => void; }; namespace State { const store = GlobalState.create((set) => ({ count: 0, setCount: (count) => set({ count }), })); export const use = store; } ``` Notice how the `Count` domain exports `Count.use` and `Count.useSet` instead of exporting its state directly. This allows us to change the state implementation without breaking any code that uses it. Finally, you can see `GlobalState.shallow` used to [limit rerenders](https://github.com/pmndrs/zustand#selecting-multiple-state-slices).