mirror of https://github.com/usememos/memos
feat(frontend): add pixel bird tilemaps
parent
ca2bc4eb84
commit
cf55f11072
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Animations for <Placeholder> piece components.
|
||||
*
|
||||
* Keyframes are wrapped in a prefers-reduced-motion guard so users who opt
|
||||
* out of motion see static ASCII art.
|
||||
*/
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.placeholder-perched-finch-bob {
|
||||
animation: placeholder-gentle-bob 3.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-nesting-sparrow-breathe {
|
||||
animation: placeholder-soft-breathe 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-hovering-swift-flutter {
|
||||
animation: placeholder-quick-flutter 0.75s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-busy-hummingbird-hover {
|
||||
animation: placeholder-humming-hover 0.55s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-searching-owl-look {
|
||||
animation: placeholder-watchful-look 4.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-curious-crow-tilt {
|
||||
animation: placeholder-curious-tilt 3.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-wayward-gull-drift {
|
||||
animation: placeholder-slow-drift 3.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.placeholder-vanished-perch-sway {
|
||||
animation: placeholder-light-sway 4.6s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-gentle-bob {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-soft-breathe {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-quick-flutter {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
50% {
|
||||
transform: translate(2px, -1px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-humming-hover {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-1px, -2px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-watchful-look {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-curious-tilt {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(-1deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-slow-drift {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-light-sway {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
import type { ComponentType } from "react";
|
||||
import BusyHummingbird from "./pieces/BusyHummingbird";
|
||||
import CuriousCrow from "./pieces/CuriousCrow";
|
||||
import HoveringSwift from "./pieces/HoveringSwift";
|
||||
import NestingSparrow from "./pieces/NestingSparrow";
|
||||
import PerchedFinch from "./pieces/PerchedFinch";
|
||||
import SearchingOwl from "./pieces/SearchingOwl";
|
||||
import VanishedPerch from "./pieces/VanishedPerch";
|
||||
import WaywardGull from "./pieces/WaywardGull";
|
||||
|
||||
export type PlaceholderVariant = "empty" | "loading" | "noResults" | "notFound";
|
||||
|
||||
export interface AsciiPiece {
|
||||
id: string;
|
||||
variant: PlaceholderVariant;
|
||||
credit: string;
|
||||
Component: ComponentType;
|
||||
}
|
||||
|
||||
export const ASCII_POOL: AsciiPiece[] = [
|
||||
{
|
||||
id: "memos-perched-finch",
|
||||
variant: "empty",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: PerchedFinch,
|
||||
},
|
||||
{
|
||||
id: "memos-nesting-sparrow",
|
||||
variant: "empty",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: NestingSparrow,
|
||||
},
|
||||
{
|
||||
id: "memos-hovering-swift",
|
||||
variant: "loading",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: HoveringSwift,
|
||||
},
|
||||
{
|
||||
id: "memos-busy-hummingbird",
|
||||
variant: "loading",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: BusyHummingbird,
|
||||
},
|
||||
{
|
||||
id: "memos-searching-owl",
|
||||
variant: "noResults",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: SearchingOwl,
|
||||
},
|
||||
{
|
||||
id: "memos-curious-crow",
|
||||
variant: "noResults",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: CuriousCrow,
|
||||
},
|
||||
{
|
||||
id: "memos-wayward-gull",
|
||||
variant: "notFound",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: WaywardGull,
|
||||
},
|
||||
{
|
||||
id: "memos-vanished-perch",
|
||||
variant: "notFound",
|
||||
credit: "Memos original ASCII art",
|
||||
Component: VanishedPerch,
|
||||
},
|
||||
];
|
||||
|
||||
export function pickPiece(variant: PlaceholderVariant): AsciiPiece {
|
||||
const matches = ASCII_POOL.filter((p) => p.variant === variant);
|
||||
if (matches.length === 0) {
|
||||
throw new Error(`No ASCII piece registered for variant "${variant}"`);
|
||||
}
|
||||
return matches[Math.floor(Math.random() * matches.length)];
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import type { PlaceholderVariant } from "./ascii-pool";
|
||||
|
||||
// Future i18n: swap these for `t("placeholder.<variant>")` lookups via
|
||||
// react-i18next without touching the component.
|
||||
export const DEFAULT_MESSAGES: Record<PlaceholderVariant, string> = {
|
||||
export const DEFAULT_MESSAGES = {
|
||||
empty: "No memos yet",
|
||||
loading: "Loading…",
|
||||
noResults: "Nothing matches that search",
|
||||
notFound: "This page flew the coop",
|
||||
};
|
||||
} as const;
|
||||
|
||||
export type PlaceholderVariant = keyof typeof DEFAULT_MESSAGES;
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
const BusyHummingbird = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-busy-hummingbird-hover"
|
||||
>
|
||||
{String.raw` ,_
|
||||
--=={o )>
|
||||
/ ) )
|
||||
/_/'
|
||||
.- .-`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default BusyHummingbird;
|
||||
@ -1,15 +0,0 @@
|
||||
const CuriousCrow = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-curious-crow-tilt"
|
||||
>
|
||||
{String.raw` __
|
||||
___(o )>
|
||||
/ ._ )
|
||||
/__/ )/
|
||||
\_//
|
||||
^^`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default CuriousCrow;
|
||||
@ -0,0 +1,48 @@
|
||||
# Placeholder Bird Tilemaps
|
||||
|
||||
These SVGs are pixel-art tile strips for the placeholder component. They should read as small game sprites first, not as decorative illustrations.
|
||||
|
||||
## Canvas
|
||||
|
||||
- Each frame is 32 by 32 pixels.
|
||||
- A strip width is `32 * frameCount`; the height stays 32.
|
||||
- The bird should occupy most of the frame. Use the full height when the animal shape supports it.
|
||||
- Keep a transparent background.
|
||||
- Use `shape-rendering="crispEdges"` and integer pixel coordinates.
|
||||
|
||||
## Naming
|
||||
|
||||
- Start with the animal name, for example `Owl` or `Falcon`.
|
||||
- Add the animation name when needed, for example `OwlBlink` or `FalconIdle`.
|
||||
- Do not name assets after UI states or empty-state scenarios.
|
||||
|
||||
## Frame Count
|
||||
|
||||
Frame count is not fixed. Choose it from the animal and animation:
|
||||
|
||||
- short blink: usually 3-5 frames
|
||||
- quiet idle: usually 4-6 frames
|
||||
- hop or walk: usually 4-8 frames
|
||||
- flying, diving, or large body motion: usually 6-10 frames
|
||||
|
||||
Avoid padding an animation with duplicate frames just to hit a standard count. A frame should change the pose, expression, feather shape, or weight.
|
||||
|
||||
## Shared Style
|
||||
|
||||
- Use a strong readable silhouette at 1x.
|
||||
- Prefer chunky pixel clusters over isolated noisy pixels.
|
||||
- Use a limited palette with one dark outline, one or two body colors, one highlight color, and one accent.
|
||||
- Keep eyes readable. At this scale, a 2 by 2 eye or a 1 pixel highlight is often better than a single dark pixel.
|
||||
- Match visual weight between animals. Different species can have different proportions, but the on-screen size should feel comparable.
|
||||
|
||||
## Animation
|
||||
|
||||
- Make idle motion local: breathing, wing settling, ear feather movement, tail flicks, head turns, and blinking.
|
||||
- Avoid moving the entire sprite unless the action is hop, fly, recoil, or collapse.
|
||||
- Preserve the animal identity in every frame. A blink frame should still clearly read as the same bird.
|
||||
- Keep the first frame a stable readable pose because it is what appears in reduced-motion rendering.
|
||||
|
||||
## Current Assets
|
||||
|
||||
- `OwlBlink.svg`: five-frame blink/idle strip with breathing wings, blink, and ear-feather settle.
|
||||
- `FalconIdle.svg`: four-frame idle strip with breathing, blink, alert head shift, and tail flick.
|
||||
@ -0,0 +1,122 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="32" viewBox="0 0 128 32" role="img" aria-labelledby="title desc" shape-rendering="crispEdges">
|
||||
<title id="title">Falcon idle pixel tileset</title>
|
||||
<desc id="desc">A four-frame 32 by 32 pixel falcon idle animation strip.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.outline { fill: #120f0d; }
|
||||
.head { fill: #f2efe7; }
|
||||
.head-shadow { fill: #d4cec2; }
|
||||
.beak { fill: #e7a634; }
|
||||
.beak-light { fill: #f1bf48; }
|
||||
.beak-shadow { fill: #b56e25; }
|
||||
.body { fill: #7a5943; }
|
||||
.body-dark { fill: #4b362d; }
|
||||
.feather { fill: #a77b58; }
|
||||
.wing { fill: #6a4b39; }
|
||||
.tail-tip { fill: #f2efe7; }
|
||||
.claw { fill: #d9972d; }
|
||||
.eye { fill: #cf482b; }
|
||||
.eye-light { fill: #ffd069; }
|
||||
</style>
|
||||
|
||||
<symbol id="falcon-head" viewBox="0 0 32 32">
|
||||
<path class="outline" d="M14 2h10v1h1v12h2v3h-1v1h-4v-2h-3v-3h-3v-2h-2V9H5V8h1V6h5V4h3V2z" />
|
||||
<path class="head" d="M15 4h8v1h1v10h2v2h-3v-2h-3v-3h-3v-2h-2V8h-4V6h4V4z" />
|
||||
<rect class="head-shadow" x="16" y="14" width="3" height="2" />
|
||||
<rect class="head-shadow" x="20" y="16" width="2" height="1" />
|
||||
<rect class="outline" x="18" y="7" width="3" height="1" />
|
||||
<rect class="eye" x="20" y="8" width="2" height="2" />
|
||||
<rect class="eye-light" x="21" y="8" width="1" height="1" />
|
||||
|
||||
<path class="outline" d="M4 9h11v1h2v5h-1v2H6v3H4V9z" />
|
||||
<path class="beak-light" d="M6 10h9v1h1v2H6v-3z" />
|
||||
<path class="beak" d="M5 13h11v2h-1v1H6v3H5v-6z" />
|
||||
<rect class="beak-shadow" x="6" y="15" width="8" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-head-alert" viewBox="0 0 32 32">
|
||||
<use href="#falcon-head" x="-1" />
|
||||
<rect class="outline" x="18" y="7" width="4" height="1" />
|
||||
<rect class="eye" x="19" y="8" width="2" height="2" />
|
||||
<rect class="eye-light" x="20" y="8" width="1" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-body" viewBox="0 0 32 32">
|
||||
<path class="outline" d="M7 15h17v2h2v9h-2v3h-4v2H10v-2H7v-3H6v-9h1v-2z" />
|
||||
<path class="body" d="M9 16h14v2h1v8h-2v2H12v-2H9V16z" />
|
||||
<path class="wing" d="M7 17h3v9H8v-1H7v-8z" />
|
||||
<path class="wing" d="M23 17h3v8h-1v1h-2v-9z" />
|
||||
<path class="body-dark" d="M10 25h3v2h2v2h-5v-2H8v-2h2z" />
|
||||
<rect class="body-dark" x="22" y="25" width="3" height="2" />
|
||||
|
||||
<rect class="feather" x="11" y="18" width="1" height="1" />
|
||||
<rect class="feather" x="15" y="18" width="1" height="1" />
|
||||
<rect class="feather" x="19" y="18" width="1" height="1" />
|
||||
<rect class="feather" x="12" y="20" width="1" height="1" />
|
||||
<rect class="feather" x="16" y="20" width="1" height="1" />
|
||||
<rect class="feather" x="20" y="20" width="1" height="1" />
|
||||
<rect class="feather" x="13" y="22" width="1" height="1" />
|
||||
<rect class="feather" x="18" y="22" width="1" height="1" />
|
||||
<rect class="feather" x="21" y="22" width="1" height="1" />
|
||||
|
||||
<rect class="outline" x="13" y="28" width="2" height="2" />
|
||||
<rect class="outline" x="19" y="28" width="2" height="2" />
|
||||
<path class="claw" d="M14 26h1v3h2v1h-5v-1h2v-3z" />
|
||||
<path class="claw" d="M20 26h1v3h2v1h-5v-1h2v-3z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-body-breathe" viewBox="0 0 32 32">
|
||||
<use href="#falcon-body" />
|
||||
<rect class="outline" x="7" y="16" width="3" height="1" />
|
||||
<rect class="wing" x="7" y="18" width="3" height="8" />
|
||||
<rect class="outline" x="23" y="16" width="3" height="1" />
|
||||
<rect class="wing" x="23" y="18" width="3" height="7" />
|
||||
<rect class="body" x="11" y="17" width="10" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-tail" viewBox="0 0 32 32">
|
||||
<path class="outline" d="M23 23h3v2h3v1h2v3h-2v1h-5v-2h-2v-3h1v-2z" />
|
||||
<path class="body-dark" d="M23 24h3v2h2v1h-4v-1h-1v-2z" />
|
||||
<rect class="tail-tip" x="26" y="25" width="2" height="2" />
|
||||
<rect class="tail-tip" x="24" y="27" width="2" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-tail-flick" viewBox="0 0 32 32">
|
||||
<path class="outline" d="M23 22h3v2h3v1h2v3h-2v1h-5v-2h-2v-3h1v-2z" />
|
||||
<path class="body-dark" d="M23 23h3v2h2v1h-4v-1h-1v-2z" />
|
||||
<rect class="tail-tip" x="26" y="24" width="2" height="2" />
|
||||
<rect class="tail-tip" x="24" y="26" width="2" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-idle-a" viewBox="0 0 32 32">
|
||||
<use href="#falcon-tail" />
|
||||
<use href="#falcon-body" />
|
||||
<use href="#falcon-head" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-idle-b" viewBox="0 0 32 32">
|
||||
<use href="#falcon-tail" />
|
||||
<use href="#falcon-body-breathe" />
|
||||
<use href="#falcon-head" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-idle-blink" viewBox="0 0 32 32">
|
||||
<use href="#falcon-tail" />
|
||||
<use href="#falcon-body" />
|
||||
<use href="#falcon-head" />
|
||||
<rect class="head" x="20" y="8" width="2" height="2" />
|
||||
<rect class="outline" x="19" y="8" width="3" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="falcon-idle-c" viewBox="0 0 32 32">
|
||||
<use href="#falcon-tail-flick" />
|
||||
<use href="#falcon-body" />
|
||||
<use href="#falcon-head-alert" />
|
||||
</symbol>
|
||||
</defs>
|
||||
|
||||
<use href="#falcon-idle-a" x="0" y="0" width="32" height="32" />
|
||||
<use href="#falcon-idle-b" x="32" y="0" width="32" height="32" />
|
||||
<use href="#falcon-idle-blink" x="64" y="0" width="32" height="32" />
|
||||
<use href="#falcon-idle-c" x="96" y="0" width="32" height="32" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@ -1,14 +0,0 @@
|
||||
const HoveringSwift = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-hovering-swift-flutter"
|
||||
>
|
||||
{String.raw` __
|
||||
--<(o )___
|
||||
\ _/
|
||||
'-'
|
||||
. . .`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default HoveringSwift;
|
||||
@ -1,14 +0,0 @@
|
||||
const NestingSparrow = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-nesting-sparrow-breathe"
|
||||
>
|
||||
{String.raw` _
|
||||
__(.)<
|
||||
/___)
|
||||
~\~~~~~~/~
|
||||
\____/`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default NestingSparrow;
|
||||
@ -0,0 +1,84 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="32" viewBox="0 0 160 32" role="img" aria-labelledby="title desc" shape-rendering="crispEdges">
|
||||
<title id="title">Owl blink pixel tileset</title>
|
||||
<desc id="desc">A five-frame 32 by 32 pixel owl idle and blink animation strip.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.outline { fill: #181512; }
|
||||
.body { fill: #70685d; }
|
||||
.body-dark { fill: #4f493f; }
|
||||
.wing { fill: #5f584f; }
|
||||
.face { fill: #b8afa2; }
|
||||
.face-dark { fill: #958c7f; }
|
||||
.eye-light { fill: #f4ead5; }
|
||||
.eye-dark { fill: #2f2a24; }
|
||||
.beak { fill: #bf9a53; }
|
||||
.claw { fill: #d8d1c4; }
|
||||
</style>
|
||||
|
||||
<symbol id="owl-open" viewBox="0 0 32 32">
|
||||
<path class="outline" d="M7 0h6v2h6V0h6v3h2v3h2v19h-2v4h-5v2H10v-2H5v-4H3V6h2V3h2V0z" />
|
||||
<path class="body" d="M8 5h16v2h2v17h-2v3h-5v2h-6v-2H8v-3H6V7h2V5z" />
|
||||
<path class="wing" d="M4 15h6v11H8v-1H5v-3H4v-7z" />
|
||||
<path class="wing" d="M22 15h6v7h-1v3h-3v1h-2V15z" />
|
||||
|
||||
<rect class="face" x="7" y="7" width="8" height="11" />
|
||||
<rect class="face" x="17" y="7" width="8" height="11" />
|
||||
<rect class="face-dark" x="9" y="19" width="14" height="5" />
|
||||
|
||||
<rect class="eye-light" x="8" y="9" width="5" height="5" />
|
||||
<rect class="eye-light" x="19" y="9" width="5" height="5" />
|
||||
<rect class="eye-dark" x="10" y="10" width="2" height="3" />
|
||||
<rect class="eye-dark" x="20" y="10" width="2" height="3" />
|
||||
|
||||
<rect class="beak" x="15" y="15" width="2" height="2" />
|
||||
<rect class="beak" x="14" y="17" width="4" height="2" />
|
||||
<rect class="outline" x="11" y="27" width="2" height="3" />
|
||||
<rect class="outline" x="19" y="27" width="2" height="3" />
|
||||
<rect class="claw" x="10" y="30" width="4" height="1" />
|
||||
<rect class="claw" x="18" y="30" width="4" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="owl-breathe" viewBox="0 0 32 32">
|
||||
<use href="#owl-open" />
|
||||
<rect class="outline" x="3" y="16" width="1" height="6" />
|
||||
<rect class="wing" x="4" y="16" width="6" height="10" />
|
||||
<rect class="outline" x="28" y="16" width="1" height="6" />
|
||||
<rect class="wing" x="22" y="16" width="6" height="10" />
|
||||
<rect class="body" x="11" y="24" width="10" height="2" />
|
||||
<rect class="body-dark" x="10" y="25" width="12" height="1" />
|
||||
<rect class="claw" x="10" y="30" width="5" height="1" />
|
||||
<rect class="claw" x="17" y="30" width="5" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="owl-half" viewBox="0 0 32 32">
|
||||
<use href="#owl-breathe" />
|
||||
<rect class="face" x="8" y="9" width="5" height="5" />
|
||||
<rect class="face" x="19" y="9" width="5" height="5" />
|
||||
<rect class="eye-dark" x="8" y="11" width="5" height="2" />
|
||||
<rect class="eye-dark" x="19" y="11" width="5" height="2" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="owl-closed" viewBox="0 0 32 32">
|
||||
<use href="#owl-breathe" />
|
||||
<rect class="face" x="8" y="9" width="5" height="5" />
|
||||
<rect class="face" x="19" y="9" width="5" height="5" />
|
||||
<rect class="eye-dark" x="8" y="12" width="5" height="1" />
|
||||
<rect class="eye-dark" x="19" y="12" width="5" height="1" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="owl-settle" viewBox="0 0 32 32">
|
||||
<use href="#owl-open" />
|
||||
<rect class="outline" x="6" y="1" width="2" height="3" />
|
||||
<rect class="outline" x="24" y="1" width="2" height="3" />
|
||||
<rect class="body-dark" x="4" y="21" width="1" height="2" />
|
||||
<rect class="body-dark" x="27" y="21" width="1" height="2" />
|
||||
<rect class="face-dark" x="11" y="20" width="10" height="1" />
|
||||
</symbol>
|
||||
</defs>
|
||||
|
||||
<use href="#owl-open" x="0" y="0" width="32" height="32" />
|
||||
<use href="#owl-breathe" x="32" y="0" width="32" height="32" />
|
||||
<use href="#owl-half" x="64" y="0" width="32" height="32" />
|
||||
<use href="#owl-closed" x="96" y="0" width="32" height="32" />
|
||||
<use href="#owl-settle" x="128" y="0" width="32" height="32" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@ -1,15 +0,0 @@
|
||||
const PerchedFinch = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-perched-finch-bob"
|
||||
>
|
||||
{String.raw` .-.
|
||||
(o o)
|
||||
/ V /
|
||||
/( _ )/
|
||||
^^ ^^
|
||||
----| |----`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default PerchedFinch;
|
||||
@ -1,15 +0,0 @@
|
||||
const SearchingOwl = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-searching-owl-look"
|
||||
>
|
||||
{String.raw` ___
|
||||
/ o o /
|
||||
( V )
|
||||
\ - /
|
||||
/| |/
|
||||
/_|___|_/`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default SearchingOwl;
|
||||
@ -1,16 +0,0 @@
|
||||
const VanishedPerch = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-vanished-perch-sway"
|
||||
>
|
||||
{String.raw` . .
|
||||
. .
|
||||
|
||||
\|/
|
||||
------+------
|
||||
|
|
||||
/ /`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default VanishedPerch;
|
||||
@ -1,15 +0,0 @@
|
||||
const WaywardGull = () => (
|
||||
<pre
|
||||
aria-hidden="true"
|
||||
className="font-mono text-xs sm:text-sm leading-tight text-muted-foreground whitespace-pre m-0 placeholder-wayward-gull-drift"
|
||||
>
|
||||
{String.raw` __
|
||||
___ / _)>
|
||||
\ \_/ /
|
||||
\ /
|
||||
'---'
|
||||
. . .`}
|
||||
</pre>
|
||||
);
|
||||
|
||||
export default WaywardGull;
|
||||
@ -0,0 +1,34 @@
|
||||
import FalconIdle from "./pieces/FalconIdle.svg?url";
|
||||
import OwlBlink from "./pieces/OwlBlink.svg?url";
|
||||
|
||||
export interface TileSprite {
|
||||
name: string;
|
||||
src: string;
|
||||
frameWidth: number;
|
||||
frameHeight: number;
|
||||
frames: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
export const TILE_SPRITES: TileSprite[] = [
|
||||
{
|
||||
name: "OwlBlink",
|
||||
src: OwlBlink,
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
frames: 5,
|
||||
duration: 1500,
|
||||
},
|
||||
{
|
||||
name: "FalconIdle",
|
||||
src: FalconIdle,
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
frames: 4,
|
||||
duration: 960,
|
||||
},
|
||||
];
|
||||
|
||||
export function pickTileSprite(): TileSprite {
|
||||
return TILE_SPRITES[Math.floor(Math.random() * TILE_SPRITES.length)];
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import PagedMemoList from "@/components/PagedMemoList";
|
||||
|
||||
vi.mock("@/hooks/useMemoQueries", () => ({
|
||||
useInfiniteMemos: () => ({
|
||||
data: { pages: [{ memos: [], nextPageToken: "" }] },
|
||||
fetchNextPage: vi.fn(async () => undefined),
|
||||
hasNextPage: false,
|
||||
isFetchingNextPage: false,
|
||||
isLoading: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/contexts/MemoFilterContext", () => ({
|
||||
useMemoFilterContext: () => ({ filters: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/i18n", () => ({
|
||||
useTranslate: () => (key: string) => (key === "message.no-data" ? "No data found." : key),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/MemoContent/MentionResolutionContext", () => ({
|
||||
MentionResolutionProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/MemoFilters", () => ({
|
||||
default: () => <div data-testid="memo-filters" />,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/MemoEditor", () => ({
|
||||
default: () => <div data-testid="memo-editor" />,
|
||||
}));
|
||||
|
||||
describe("<PagedMemoList>", () => {
|
||||
it("uses the tile sprite Placeholder for the empty state", () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<PagedMemoList renderer={() => <div />} />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("No data found.")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("placeholder-sprite")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue