From 10b9f60d83ab2e7f16331335e2d5a08fe622f1f1 Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:15:27 +0000 Subject: [PATCH] many things idk idc --- TODO.md | 1 - project/app/src/app/layout/Layout.ts | 77 +++++++++------ project/app/src/app/layout/Main.ts | 99 ++++++++++--------- .../src/features/feed/components/FeedForm.ts | 5 +- .../FeedGroupAddFormPopoverButton.ts | 2 +- .../features/feed/components/FeedScroller.ts | 24 +++-- .../components/{FeedPage.ts => FeedView.ts} | 0 .../features/feed/components/PostThread.ts | 47 +++++++-- project/app/src/features/post/PostView.ts | 6 +- .../app/src/features/profile/ProfileView.ts | 31 ++---- project/app/src/features/profile/routes.ts | 31 +++++- project/app/src/shared/router/index.ts | 14 ++- 12 files changed, 210 insertions(+), 127 deletions(-) rename project/app/src/features/feed/components/{FeedPage.ts => FeedView.ts} (100%) diff --git a/TODO.md b/TODO.md index 6858692..b564da8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ # TODO -- [ ] PostForm should have parts, not just text like on X or Facebook or Instagram. It should be more like a page builder. - [x] Instead of using tx.origin, Sender contract should give the sender address to the Indexer contract, and there should be permissions on the indexer contract that allows or disallows Sender contracts to index post in behalf of you. - [x] On the left side of header we should have feed list similar to X lists, at the top there is the default home list for your home feed. - [x] We should be able to add any feed we want to the list. So we should have a add to feed popover and button something. diff --git a/project/app/src/app/layout/Layout.ts b/project/app/src/app/layout/Layout.ts index 3382ce2..d098d50 100644 --- a/project/app/src/app/layout/Layout.ts +++ b/project/app/src/app/layout/Layout.ts @@ -13,15 +13,13 @@ const { div } = tags; const documentScroller = document.scrollingElement ?? document.body; export function Layout() { - console.log("hello"); const mainElement = Main().element; - - console.log(mainElement); + const headerElement = Header().effect(usePart("header")).element; return div() .id("app") .effect(useScope(LayoutCss)) - .children(Header().effect(usePart("header")), mainElement) + .children(headerElement, mainElement) .effect((appElement) => { function scroll(isOpen: boolean, behavior: ScrollBehavior) { const left = isOpen ? 0 : Number.MAX_SAFE_INTEGER; @@ -53,15 +51,30 @@ export function Layout() { let isStaticCache = true; function updateStaticState() { const scrollProgress = getScrollProgress(); - const isStatic = - scrollProgress === 1 || appElement.scrollWidth === appElement.clientWidth; + + const isScrollable = appElement.scrollWidth !== appElement.clientWidth; + + // TODO: Need something like this becuase having overflow: auto; on the app breaks sticky content + // Basically if im gonna make the app scrollable it should be when im scrolling already somehow + // worse case i might make the whole thing not scroll based and would use pointer events + /* console.log("isScrollable", isScrollable); + if (!isScrollable) { + appElement.style.removeProperty("overflow"); + headerElement.style.removeProperty("display"); + } else { + appElement.style.overflow = "unset"; + headerElement.style.display = "none"; + mainElement.style.inlineSize = "calc(100% + 1px)"; + } */ + + const isStatic = scrollProgress === 1 || !isScrollable; if (isStatic === isStaticCache) return isStatic; if (isStatic) { const scrollY = mainElement.scrollTop; - mainElement.style.overflow = "visible"; - mainElement.style.blockSize = ""; + mainElement.style.removeProperty("overflow"); + mainElement.style.removeProperty("block-size"); documentScroller.scrollTop = scrollY; isStaticCache = true; } else { @@ -94,6 +107,7 @@ export function Layout() { appElement.addEventListener("scroll", handleScroll); function handleScroll() { + console.log("scroll"); updateStaticState(); } @@ -114,28 +128,6 @@ const LayoutCss = css` align-items: start; grid-template-columns: minmax(0, 20em) 1fr; grid-template-rows: auto; - - @container body (inline-size < ${layoutBrakpoint}) { - overflow: overlay; - grid-template-columns: 100% 100%; - - &::-webkit-scrollbar { - display: none; - } - - [data-part="header"] { - animation: hide-header-scroll linear; - animation-timeline: scroll(x); - z-index: -1; - } - - @keyframes hide-header-scroll { - to { - scale: 0.95; - translate: 100% 0; - } - } - } } [data-part="header"] { @@ -145,4 +137,29 @@ const LayoutCss = css` overflow: clip; } + + @container body (inline-size < ${layoutBrakpoint}) { + :scope { + overflow: auto; + position: relative; + grid-template-columns: 100% 100%; + } + + &::-webkit-scrollbar { + display: none; + } + + [data-part="header"] { + animation: hide-header-scroll linear; + animation-timeline: scroll(x); + z-index: -1; + } + + @keyframes hide-header-scroll { + to { + scale: 0.95; + translate: 100% 0; + } + } + } `; diff --git a/project/app/src/app/layout/Main.ts b/project/app/src/app/layout/Main.ts index 2ab3cc9..4118da9 100644 --- a/project/app/src/app/layout/Main.ts +++ b/project/app/src/app/layout/Main.ts @@ -10,30 +10,30 @@ import { BackSvg } from "~/shared/svgs/BackSvg"; import { CloseSvg } from "~/shared/svgs/CloseSvg"; import { css, useScope } from "~/shared/utils/css"; +// TODO: seperate route and post section into two different files later. + const { section, main, header, a, strong } = tags; export function Main() { return main() .effect(useScope(MainCss)) .children( - section({ class: "route" }) - .ariaLabel(router.route.derive((route) => route?.title() ?? null)) - .children( - header().children( - a({ class: "icon back" }) - .ariaHidden("true") - .href(menuSearchParam.toHref("open")) - .children(BackSvg()), - router.route.derive((route) => { - if (!route) return null; - return strong().textContent(route.title()); - }), - ), - router.route.derive((route) => { - return route?.render() ?? null; - }), - ), - + router.route.derive((route) => { + if (!route) return null; + return section({ class: "route" }) + .ariaLabel(route.title) + .children( + header().children( + a({ class: "icon back" }) + .ariaHidden("true") + .href(menuSearchParam.toHref("open")) + .children(BackSvg()), + strong().textContent(route.title), + route.renderHeaderEnd(), + ), + route.render(), + ); + }), computed(() => ({ searchParam: postSearchParam.val, config: config.val })).derive( ({ searchParam, config }) => awaited( @@ -49,10 +49,7 @@ export function Main() { .ariaHidden("true") .href(postSearchParam.toHref(null)) .children(CloseSvg()), - router.route.derive((route) => { - if (!route) return null; - return strong().textContent("Post"); - }), + strong().textContent("Post"), ), PostThread(post), ); @@ -69,8 +66,6 @@ export const MainCss = css` display: block flex; align-content: start; - gap: 0.5em; - background-color: var(--base); min-block-size: 100dvb; } @@ -80,18 +75,41 @@ export const MainCss = css` gap: 1em; align-content: start; - background-color: color-mix(in srgb, var(--base), var(--pop) 5%); + header { + position: sticky; + inset-block-start: 0; + + display: block flex; + align-items: center; + gap: 1em; + + background-color: color-mix(in srgb, var(--base), var(--pop) 2.5%); + padding-inline: 1em; + padding-block: 1.25em; + + .icon { + inline-size: 1.5em; + border-radius: 50%; + aspect-ratio: 1; + color: color-mix(in srgb, var(--base), var(--pop) 50%); + } + + strong { + font-size: 1.1em; + } + } &.route { flex: 1.5; - &:has(a svg) { - @container body (inline-size >= ${layoutBrakpoint}) { - grid-template-columns: auto; + header { + margin-inline: 0.5em; + margin-block-start: 0.5em; + } - a:has(svg) { - display: none; - } + @container body (inline-size >= ${layoutBrakpoint}) { + header .icon { + display: none; } } } @@ -106,6 +124,8 @@ export const MainCss = css` position: sticky; inset-block-start: 0; + background-color: color-mix(in srgb, var(--base), var(--pop) 1%); + @container main (inline-size < 60em) { position: fixed; inset-block: 0; @@ -115,21 +135,4 @@ export const MainCss = css` } } } - - section header { - display: block grid; - grid-template-columns: 1.5em auto; - align-items: center; - gap: 1em; - - a:has(svg) { - border-radius: 50%; - aspect-ratio: 1; - color: color-mix(in srgb, var(--base), var(--pop) 50%); - } - - strong { - font-size: 1.1em; - } - } `; diff --git a/project/app/src/features/feed/components/FeedForm.ts b/project/app/src/features/feed/components/FeedForm.ts index 32f408b..d9f4336 100644 --- a/project/app/src/features/feed/components/FeedForm.ts +++ b/project/app/src/features/feed/components/FeedForm.ts @@ -153,7 +153,8 @@ export function FeedForm(feedIds: readonly FeedId[]) { const PostFormCss = css` :scope { display: block grid; - background-color: color-mix(in srgb, var(--base), var(--pop) 7.5%); + background-color: color-mix(in srgb, transparent, var(--base) 90%); + backdrop-filter: blur(3px); padding-inline: 1em; padding-block: 0.5em; @@ -177,7 +178,7 @@ const PostFormCss = css` gap: 0.5em; padding: 0.5em; - background-color: color-mix(in srgb, var(--base), var(--pop) 10%); + background-color: color-mix(in srgb, var(--base), var(--pop) 5%); .actions { align-self: start; diff --git a/project/app/src/features/feed/components/FeedGroupAddFormPopoverButton.ts b/project/app/src/features/feed/components/FeedGroupAddFormPopoverButton.ts index de3970a..83bcb56 100644 --- a/project/app/src/features/feed/components/FeedGroupAddFormPopoverButton.ts +++ b/project/app/src/features/feed/components/FeedGroupAddFormPopoverButton.ts @@ -38,7 +38,7 @@ export function FeedGroupAddFormPopoverButton(params: { }), ); - return button() + return button({ class: "button" }) .effect(() => { document.body.append(popover.element); return () => { diff --git a/project/app/src/features/feed/components/FeedScroller.ts b/project/app/src/features/feed/components/FeedScroller.ts index e9fa84c..e507cd2 100644 --- a/project/app/src/features/feed/components/FeedScroller.ts +++ b/project/app/src/features/feed/components/FeedScroller.ts @@ -7,7 +7,7 @@ import { ReloadSvg } from "~/shared/svgs/ReloadSvg"; import { css, useScope } from "~/shared/utils/css"; import { useOnVisible } from "~/shared/utils/effects/useOnVisible"; -const { div, section, button } = tags; +const { ul, li, section, button } = tags; export function FeedScroller(feed: Feed) { const host = section() @@ -17,7 +17,7 @@ export function FeedScroller(feed: Feed) { }) .ariaLabel("Feed"); - const posts = div({ class: "posts" }); + const posts = ul(); let nextGenerator: ReturnType | undefined; @@ -39,7 +39,9 @@ export function FeedScroller(feed: Feed) { // Give animation time to play. await new Promise((resolve) => setTimeout(resolve, Math.max(0, 1000 - passed))); - const newFragment = fragment(result.value?.map((post) => PostView(post)) ?? null); + const newFragment = fragment( + result.value?.map((post) => li().children(PostView(post))) ?? null, + ); if (clear) { posts.element.replaceChildren(newFragment); @@ -107,14 +109,24 @@ export function FeedScroller(feed: Feed) { const FeedScrollerCss = css` :scope { display: block grid; - gap: 2em; + gap: 0.5em; align-content: start; } - .posts { + ul { display: block grid; - gap: 1em; align-content: start; + + gap: 0.5em; + + list-style: none; + } + + li { + padding-inline: 1.5em; + padding-block: 2em; + + background-color: color-mix(in srgb, transparent, currentColor 5%); } button.load { diff --git a/project/app/src/features/feed/components/FeedPage.ts b/project/app/src/features/feed/components/FeedView.ts similarity index 100% rename from project/app/src/features/feed/components/FeedPage.ts rename to project/app/src/features/feed/components/FeedView.ts diff --git a/project/app/src/features/feed/components/PostThread.ts b/project/app/src/features/feed/components/PostThread.ts index 7186a3b..ca33e43 100644 --- a/project/app/src/features/feed/components/PostThread.ts +++ b/project/app/src/features/feed/components/PostThread.ts @@ -5,17 +5,46 @@ import { Feed } from "~/features/feed/Feed"; import { Post } from "~/features/post/Post"; import { PostView } from "~/features/post/PostView"; import { config } from "~/shared/config"; +import { css, useScope } from "~/shared/utils/css"; +import { usePart } from "~/shared/utils/effects/usePart"; -const { div, strong } = tags; +const { div, section, strong } = tags; export function PostThread(post: Post) { const repliesFeed = config.derive((config) => Feed.PostReplies({ post, config })); - return div().children( - PostView(post), - strong().textContent("Replies"), - repliesFeed.derive((repliesFeed) => [ - FeedForm([repliesFeed.id]), - FeedScroller(repliesFeed), - ]), - ); + return div() + .effect(useScope(PostThreadCss)) + .children( + PostView(post).effect(usePart("post")), + section({ class: "replies" }) + .ariaLabel("replies") + .children( + strong().textContent("Replies"), + repliesFeed.derive((repliesFeed) => + FeedScroller(repliesFeed).effect(usePart("scroller")), + ), + ), + repliesFeed.derive((repliesFeed) => FeedForm([repliesFeed.id]).effect(usePart("form"))), + ); } + +const PostThreadCss = css` + .replies { + padding: 0.75em; + font-size: 0.85em; + } + + [data-part="post"] { + padding: 1.5em; + font-size: 0.9em; + } + + [data-part="scroller"] { + min-block-size: 100dvb; + } + + [data-part="form"] { + position: sticky; + inset-block-end: 0; + } +`; diff --git a/project/app/src/features/post/PostView.ts b/project/app/src/features/post/PostView.ts index bc0cb6f..05ad817 100644 --- a/project/app/src/features/post/PostView.ts +++ b/project/app/src/features/post/PostView.ts @@ -2,10 +2,10 @@ import { tags } from "@purifyjs/core"; import { Post } from "~/features/post/Post"; import { PostContent } from "~/features/post/PostContent"; import { postSearchParam } from "~/features/post/routes"; -import { WalletAddress } from "~/shared/wallet/components/WalletAddress"; -import { WalletAvatarSvg } from "~/shared/wallet/components/WalletAvatarSvg"; import { getRelativeTimeSignal } from "~/shared/language/time"; import { css, useScope } from "~/shared/utils/css"; +import { WalletAddress } from "~/shared/wallet/components/WalletAddress"; +import { WalletAvatarSvg } from "~/shared/wallet/components/WalletAvatarSvg"; const { article, footer, address, time, div, a } = tags; @@ -48,7 +48,7 @@ const FeedItemCss = css` "avatar . content" "avatar . ." "avatar . footer"; - grid-template-columns: 1.1em 0.25em 1fr; + grid-template-columns: 2em 0.5em 1fr; grid-template-rows: auto 0.5em auto; } diff --git a/project/app/src/features/profile/ProfileView.ts b/project/app/src/features/profile/ProfileView.ts index d63db19..0327191 100644 --- a/project/app/src/features/profile/ProfileView.ts +++ b/project/app/src/features/profile/ProfileView.ts @@ -1,43 +1,32 @@ import { tags } from "@purifyjs/core"; import { FeedForm } from "~/features/feed/components/FeedForm"; -import { FeedGroupAddFormPopoverButton } from "~/features/feed/components/FeedGroupAddFormPopoverButton"; import { FeedScroller } from "~/features/feed/components/FeedScroller"; import { Feed } from "~/features/feed/Feed"; -import { Config } from "~/shared/config"; import { Address } from "~/shared/schemas/primatives"; import { css, useScope } from "~/shared/utils/css"; import { usePart } from "~/shared/utils/effects/usePart"; const { div } = tags; -export function ProfileView(params: { address: Address; config: Config }) { - const { config, address } = params; - - const profileFeed = Feed.ProfilePosts({ address, config }); +export function ProfileView(params: { address: Address; postsFeed: Feed }) { + const { postsFeed } = params; return div() .effect(useScope(ProfileViewCss)) .children( - FeedGroupAddFormPopoverButton({ - values: { - feedId: profileFeed.id, - style: { - type: "profile", - address, - label: "posts", - }, - }, - }) - .attributes({ class: "button" }) - .textContent("add to group"), - FeedScroller(profileFeed).effect(usePart("scroller")), - FeedForm([profileFeed.id]).effect(usePart("form")), + div({ class: "content" }).children(FeedScroller(postsFeed)), + FeedForm([postsFeed.id]).effect(usePart("form")), ); } const ProfileViewCss = css` - [data-part="scroller"] { + .content { min-block-size: 100dvb; + padding-inline: 0.75em; + + display: block grid; + grid-template-columns: minmax(0, 60em); + justify-content: center; } [data-part="form"] { diff --git a/project/app/src/features/profile/routes.ts b/project/app/src/features/profile/routes.ts index 206b592..ce9ed32 100644 --- a/project/app/src/features/profile/routes.ts +++ b/project/app/src/features/profile/routes.ts @@ -1,24 +1,49 @@ +import { FeedGroupAddFormPopoverButton } from "~/features/feed/components/FeedGroupAddFormPopoverButton"; +import { Feed } from "~/features/feed/Feed"; import { ProfileView } from "~/features/profile/ProfileView"; import { config } from "~/shared/config"; import { Router } from "~/shared/router"; import { Address } from "~/shared/schemas/primatives"; +import { css, useScope } from "~/shared/utils/css"; export const profileRoutes = { profile: new Router.Route({ fromPathname(pathname) { return Address() - .transform((address) => ({ address })) + .transform((address) => ({ + address, + postsFeed: config.derive((config) => Feed.ProfilePosts({ address, config })), + })) .parse(pathname); }, toPathname(data) { return data.address; }, render(data) { - const { address } = data; - return config.derive((config) => ProfileView({ address, config })); + const { address, postsFeed } = data; + return postsFeed.derive((postsFeed) => ProfileView({ address, postsFeed })); + }, + renderHeaderEnd(data) { + const { address, postsFeed } = data; + return postsFeed.derive((postsFeed) => + FeedGroupAddFormPopoverButton({ + values: { + feedId: postsFeed.id, + style: { type: "profile", address, label: "posts" }, + }, + }) + .effect(useScope(profilePageHeaderEndCss)) + .textContent("add to group"), + ); }, title() { return "Profile"; }, }), }; + +const profilePageHeaderEndCss = css` + :scope { + font-size: 0.6em; + } +`; diff --git a/project/app/src/shared/router/index.ts b/project/app/src/shared/router/index.ts index b88b605..1d44a18 100644 --- a/project/app/src/shared/router/index.ts +++ b/project/app/src/shared/router/index.ts @@ -42,18 +42,20 @@ export namespace Router { return { name, data, + title: route.title(data), render() { return route.render(data); }, - title() { - return route.title(data); + renderHeaderEnd() { + return route.renderHeaderEnd(data); }, } as { [K in keyof TRoutes]: { name: K; - title(): ReturnType; data: Exclude, undefined>; + title: ReturnType; render(): ReturnType; + renderHeaderEnd(): ReturnType; }; }[keyof TRoutes]; } @@ -75,6 +77,7 @@ export namespace Router { toPathname(data: TData): string; fromPathname(pathname: string): TData; render(data: TData): TRender; + renderHeaderEnd?(data: TData): MemberOf; title(data: TData): TTitle; }; } @@ -89,6 +92,7 @@ export namespace Router { toPathname(data: TData): string; toHref(data: TData): string; render(data: TData): TRender; + renderHeaderEnd(data: TData): MemberOf; title(data: TData): TTitle; } Router.Route = class< @@ -118,6 +122,10 @@ export namespace Router { return this.#init.render(data); } + public renderHeaderEnd(data: TData) { + return this.#init.renderHeaderEnd?.(data) ?? null; + } + public title(data: TData): TTitle { return this.#init.title(data); }