Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-2195] Add market discovery rain #304

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/typescript/frontend/src/app/launch/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import ClientLaunchEmojicoinPage from "../../components/pages/launch-emojicoin/ClientLaunchEmojicoinPage";
import { fetchRandomSymbols } from "@/queries/launch";
import { type Metadata } from "next";
import { emoji } from "utils";

export const dynamic = "force-static";

export const metadata: Metadata = {
title: "launch",
description: `Launch your own emojicoins using emojicoin.fun ${emoji("party popper")}`,
};

export default async function LaunchEmojicoinPage() {
return <ClientLaunchEmojicoinPage />;
const randomSymbols = await fetchRandomSymbols({});
return <ClientLaunchEmojicoinPage randomSymbols={randomSymbols} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
} from "@aptos-labs/ts-sdk";
import { symbolBytesToEmojis } from "@sdk/emoji_data";
import MemoizedLaunchAnimation from "./memoized-launch";
import { EmojiRain } from "./EmojiRain";
import { useMatchBreakpoints } from "@hooks/index";

const LOADING_TIME = 2000;
type Stage = "initial" | "loading" | "coding";
Expand All @@ -32,7 +34,9 @@ const lastMarketRegistration = (
return undefined;
};

const ClientLaunchEmojicoinPage = () => {
const ClientLaunchEmojicoinPage: React.FC<{
randomSymbols: ReturnType<typeof symbolBytesToEmojis>[];
}> = ({ randomSymbols }) => {
const searchParams = useSearchParams();
const emojiParams = searchParams.get("emojis");
const setEmojis = useEmojiPicker((state) => state.setEmojis);
Expand All @@ -48,6 +52,8 @@ const ClientLaunchEmojicoinPage = () => {
const isLoadingRegisteredMarket = useEmojiPicker((state) => state.isLoadingRegisteredMarket);
const [stage, setStage] = useState<Stage>(isLoadingRegisteredMarket ? "loading" : "initial");

const { isMobile } = useMatchBreakpoints();

useEffect(() => {
if (emojiParams !== null) {
setEmojis(getEmojisInString(emojiParams));
Expand Down Expand Up @@ -136,13 +142,32 @@ const ClientLaunchEmojicoinPage = () => {
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [status]);

const backgroundDivClasses =
"absolute w-[200vw] h-[123%] top-[-15%] " +
"left-[-100vw] backdrop-blur-lg border-y-[1px] border-dark-gray " +
"border-solid sm:border-x-[1px] sm:!w-[130%] sm:left-[-15%] rounded-[3px]";

return (
<div className="flex flex-col grow">
<div className="flex flex-col grow relative h-[100%]">
<div
className="absolute w-[100%] overflow-hidden"
style={{ height: isMobile ? "calc(100% + 12px)" : "calc(100% + 36px)" }}
>
{randomSymbols.length > 0 && (
<EmojiRain
randomSymbols={randomSymbols}
onClick={(name) => setEmojis(name.emojis.map((e) => e.emoji))}
/>
)}
</div>
<TextCarousel />

<div className="flex justify-center items-center h-full px-6">
<div className="relative flex flex-col w-full max-w-[414px]">
<MemoizedLaunchAnimation loading={isLoadingRegisteredMarket} />
<div className="relative flex flex-col w-full max-w-[414px] z-50">
<div className={backgroundDivClasses}></div>
<div className="z-50">
<MemoizedLaunchAnimation loading={isLoadingRegisteredMarket} />
</div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { type symbolBytesToEmojis } from "@sdk/emoji_data";
import { type AnimationPlaybackControls, useAnimate } from "framer-motion";
import { useEffect, useMemo, useState } from "react";
import { Emoji } from "utils/emoji";

const DROP_FALL_TIME = 7.5;
const DROP_INTERVAL = 1;

export function EmojiRainDrop({
name,
onClick,
index,
emojis,
}: {
name: ReturnType<typeof symbolBytesToEmojis>;
onClick?: (data: ReturnType<typeof symbolBytesToEmojis>) => void;
index: number;
emojis: number;
}) {
// Position all emojis above the visible space.
const initialY = -200;

// Calculate a random position on the X axis.
const x = useMemo(() => Math.random() * 90 + 5, []);

// Calculate a random delay for the emojis to be staggered.
const delay = useMemo(() => (Math.random() + index) * DROP_INTERVAL, [index]);

const [scope, animate] = useAnimate();
const [controls, setControls] = useState<AnimationPlaybackControls>();

useEffect(() => {
const controls = animate(
scope.current,
{ top: "110vh" },
{
duration: DROP_FALL_TIME,
delay,
ease: [],
repeat: Infinity,
repeatDelay: emojis * DROP_INTERVAL - DROP_FALL_TIME, // Repeat after all the other emojis have fallen.
}
);

setControls(controls);

return () => controls.complete();
}, [scope, animate, delay, emojis]);

return (
<div
onClick={() => onClick && onClick(name)}
onMouseOver={() => {
if (controls) {
controls.time += delay;
controls.pause();
}
}}
onMouseOut={() => controls?.play()}
className="absolute emoji-rain-drop z-10 flex flex-col select-none cursor-pointer"
style={{
left: `${x}vw`,
fontSize: "1em",
top: initialY,
}}
ref={scope}
>
{name.emojis.map((e) => (
<span key={`rain-drop-emoji-${e.hex}`}>
<Emoji emojis={e.emoji} />
</span>
))}
</div>
);
}

export function EmojiRain({
randomSymbols,
onClick,
}: {
randomSymbols: ReturnType<typeof symbolBytesToEmojis>[];
onClick?: (symbol: ReturnType<typeof symbolBytesToEmojis>) => void;
}) {
return (
<>
<div
className="absolute top-[-1.1%] w-[100%] h-[1%] bg-black z-20"
style={{
boxShadow: "0px 0px 8px 8px black",
}}
></div>
{randomSymbols.map((name, i) => {
const emojiRainDrop = (
<EmojiRainDrop
key={`rain-drop-${name.emojis.map((e) => e.hex).reduce((a, b) => `${a}-${b}`, "")}`}
name={name}
onClick={onClick}
index={i}
emojis={randomSymbols.length}
/>
);
return emojiRainDrop;
})}
<div
className="absolute bottom-0 w-[100%] h-[1%] bg-black z-20"
style={{
boxShadow: "0px 0px 8px 8px black",
}}
></div>
</>
);
}
1 change: 1 addition & 0 deletions src/typescript/sdk/src/indexer-v2/queries/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./home";
export * from "./market";
export * from "./pools";
export * from "./launch";
16 changes: 16 additions & 0 deletions src/typescript/sdk/src/indexer-v2/queries/app/launch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
if (process.env.NODE_ENV !== "test") {
require("server-only");
}

import { DatabaseRpc } from "../../types/json-types";
import { postgrest } from "../client";
import { queryHelper } from "../utils";
import { DatabaseTypeConverter } from "../../types";

const selectRandomSymbols = () =>
postgrest.rpc(DatabaseRpc.RandomSymbols, undefined, { get: true });

export const fetchRandomSymbols = queryHelper(
selectRandomSymbols,
DatabaseTypeConverter[DatabaseRpc.RandomSymbols]
);
12 changes: 11 additions & 1 deletion src/typescript/sdk/src/indexer-v2/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ import {
type ProcessedFields,
DatabaseRpc,
} from "./json-types";
import { type MarketEmojiData, type SymbolEmoji, toMarketEmojiData } from "../../emoji_data";
import {
type MarketEmojiData,
symbolBytesToEmojis,
type SymbolEmoji,
toMarketEmojiData,
} from "../../emoji_data";
import { toPeriod, toTrigger, type Period, type Trigger } from "../../const";
import { toAccountAddressString } from "../../utils";
import Big from "big.js";
Expand Down Expand Up @@ -596,6 +601,10 @@ export const toPriceFeed = (data: DatabaseJsonType["price_feed"]) => {
};
};

/// Replace `\x` with `0x` then convert to market symbol data.
export const toRandomSymbolsRPCResponse = (data: DatabaseJsonType["random_symbols"]) =>
symbolBytesToEmojis("0" + data.emojis.substring(1));
xbtmatt marked this conversation as resolved.
Show resolved Hide resolved

export const DatabaseTypeConverter = {
[TableName.GlobalStateEvents]: toGlobalStateEventModel,
[TableName.PeriodicStateEvents]: toPeriodicStateEventModel,
Expand All @@ -611,6 +620,7 @@ export const DatabaseTypeConverter = {
[TableName.ProcessorStatus]: toProcessorStatus,
[TableName.PriceFeed]: toPriceFeed,
[DatabaseRpc.UserPools]: toUserPoolsRPCResponse,
[DatabaseRpc.RandomSymbols]: toRandomSymbolsRPCResponse,
};

export type DatabaseModels = {
Expand Down
5 changes: 3 additions & 2 deletions src/typescript/sdk/src/indexer-v2/types/json-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export enum TableName {

export enum DatabaseRpc {
UserPools = "user_pools",
PriceFeed = "price_feed",
RandomSymbols = "random_symbols",
}

// Fields that only exist after being processed by a processor.
Expand Down Expand Up @@ -365,6 +365,7 @@ export type DatabaseJsonType = {
ProcessedFields &
UserLPCoinBalance & { daily_volume: Uint128String }
>;
[DatabaseRpc.RandomSymbols]: { emojis: `\\x${string}` };
};

type Columns = DatabaseJsonType[TableName.GlobalStateEvents] &
Expand All @@ -381,6 +382,6 @@ type Columns = DatabaseJsonType[TableName.GlobalStateEvents] &
DatabaseJsonType[TableName.PriceFeed] &
DatabaseJsonType[TableName.ProcessorStatus] &
DatabaseJsonType[DatabaseRpc.UserPools] &
DatabaseJsonType[DatabaseRpc.PriceFeed];
DatabaseJsonType[DatabaseRpc.RandomSymbols];

export type AnyColumnName = keyof Columns;
Loading