Compare commits
4 commits
33fab7e655
...
8d24ffec29
Author | SHA1 | Date | |
---|---|---|---|
|
8d24ffec29 | ||
|
fd1c6fae99 | ||
|
15974af792 | ||
|
8588a17bd0 |
6 changed files with 10 additions and 150 deletions
|
@ -10,10 +10,6 @@ You can debug on the Safari on iOS only if you have mac (and run macOS). The cer
|
||||||
- For visual bugs: on you iDevice, redirect the localhost.direct to your dev computer. Now you have the hot reload on you iDevice.
|
- For visual bugs: on you iDevice, redirect the localhost.direct to your dev computer. Now you have the hot reload on you iDevice.
|
||||||
- You can use network debugging apps like "Shadowrocket" to do such thing.
|
- You can use network debugging apps like "Shadowrocket" to do such thing.
|
||||||
|
|
||||||
## Hero Animation won't work (after hot reload)
|
|
||||||
|
|
||||||
That's a known issue. Hot reload won't refresh the module sets the hero cache. Refresh the whole page and it should work.
|
|
||||||
|
|
||||||
## The components don't react to the change as I setting the store, until the page reloaded
|
## The components don't react to the change as I setting the store, until the page reloaded
|
||||||
|
|
||||||
The `WritableAtom<unknwon>.set` might do an equals check. You must set a different object to ensure the atom sending a notify.
|
The `WritableAtom<unknwon>.set` might do an equals check. You must set a different object to ensure the atom sending a notify.
|
||||||
|
@ -47,3 +43,9 @@ Ja, the code is weird, but that's the best we know. Anyway, you need new object
|
||||||
Idk why, but transition on logical directions may not work on WebKit - sometimes they work.
|
Idk why, but transition on logical directions may not work on WebKit - sometimes they work.
|
||||||
|
|
||||||
Use physical directions to avoid trouble, like "margin-top, margin-bottom".
|
Use physical directions to avoid trouble, like "margin-top, margin-bottom".
|
||||||
|
|
||||||
|
## Safe area insets
|
||||||
|
|
||||||
|
For isolating control of the UI effect, we already setup css variables `--safe-area-inset-*`. In components, you should use the variables unless you have reasons to use `env()`.
|
||||||
|
|
||||||
|
Using `--safe-area-inset-*`, you can control the global value in settings (under dev mode).
|
||||||
|
|
|
@ -9,12 +9,10 @@ import {
|
||||||
type ResolvedChildren,
|
type ResolvedChildren,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import "./BottomSheet.css";
|
import "./BottomSheet.css";
|
||||||
import { useHeroSignal } from "../platform/anim";
|
|
||||||
import material from "./material.module.css";
|
import material from "./material.module.css";
|
||||||
import {
|
import {
|
||||||
ANIM_CURVE_ACELERATION,
|
ANIM_CURVE_ACELERATION,
|
||||||
ANIM_CURVE_DECELERATION,
|
ANIM_CURVE_DECELERATION,
|
||||||
ANIM_CURVE_STD,
|
|
||||||
} from "./theme";
|
} from "./theme";
|
||||||
import {
|
import {
|
||||||
animateSlideInFromRight,
|
animateSlideInFromRight,
|
||||||
|
@ -28,32 +26,11 @@ export type BottomSheetProps = {
|
||||||
onClose?(reason: "backdrop"): void;
|
onClose?(reason: "backdrop"): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HERO = Symbol("BottomSheet Hero Symbol");
|
|
||||||
|
|
||||||
function composeAnimationFrame(
|
|
||||||
{
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
}: Record<"top" | "left" | "height" | "width", number>,
|
|
||||||
x: Record<string, unknown>,
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
top: `${top}px`,
|
|
||||||
left: `${left}px`,
|
|
||||||
height: `${height}px`,
|
|
||||||
width: `${width}px`,
|
|
||||||
...x,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const MOVE_SPEED = 1600;
|
const MOVE_SPEED = 1600;
|
||||||
|
|
||||||
const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
||||||
let element: HTMLDialogElement;
|
let element: HTMLDialogElement;
|
||||||
let animation: Animation | undefined;
|
let animation: Animation | undefined;
|
||||||
const [hero, setHero] = useHeroSignal(HERO);
|
|
||||||
const [cache, setCache] = createSignal<ResolvedChildren | undefined>();
|
const [cache, setCache] = createSignal<ResolvedChildren | undefined>();
|
||||||
const ochildren = children(() => props.children);
|
const ochildren = children(() => props.children);
|
||||||
|
|
||||||
|
@ -74,24 +51,11 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
const srcElement = hero();
|
|
||||||
if (srcElement) {
|
|
||||||
srcElement.style.visibility = "unset";
|
|
||||||
}
|
|
||||||
|
|
||||||
element.close();
|
element.close();
|
||||||
setHero();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const animatedClose = () => {
|
const animatedClose = () => {
|
||||||
const srcElement = hero();
|
|
||||||
const endRect = srcElement?.getBoundingClientRect();
|
|
||||||
if (endRect) {
|
|
||||||
const startRect = element.getBoundingClientRect();
|
|
||||||
const animation = animateHero(startRect, endRect, element, true);
|
|
||||||
animation.addEventListener("finish", onClose);
|
|
||||||
animation.addEventListener("cancel", onClose);
|
|
||||||
} else {
|
|
||||||
if (window.innerWidth > 560 && !props.bottomUp) {
|
if (window.innerWidth > 560 && !props.bottomUp) {
|
||||||
onClose();
|
onClose();
|
||||||
return;
|
return;
|
||||||
|
@ -106,21 +70,12 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
||||||
: animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION });
|
: animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION });
|
||||||
animation.addEventListener("finish", onAnimationEnd);
|
animation.addEventListener("finish", onAnimationEnd);
|
||||||
animation.addEventListener("cancel", onAnimationEnd);
|
animation.addEventListener("cancel", onAnimationEnd);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const animatedOpen = () => {
|
const animatedOpen = () => {
|
||||||
element.showModal();
|
element.showModal();
|
||||||
const srcElement = hero();
|
if (props.bottomUp) {
|
||||||
const startRect = srcElement?.getBoundingClientRect();
|
|
||||||
if (!startRect) {
|
|
||||||
console.debug("no source element");
|
|
||||||
}
|
|
||||||
if (startRect) {
|
|
||||||
srcElement!.style.visibility = "hidden";
|
|
||||||
const endRect = element.getBoundingClientRect();
|
|
||||||
animateHero(startRect, endRect, element);
|
|
||||||
} else if (props.bottomUp) {
|
|
||||||
animateSlideInFromBottom(element);
|
animateSlideInFromBottom(element);
|
||||||
} else if (window.innerWidth <= 560) {
|
} else if (window.innerWidth <= 560) {
|
||||||
element.classList.add("animated");
|
element.classList.add("animated");
|
||||||
|
@ -165,41 +120,6 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
||||||
return animation;
|
return animation;
|
||||||
};
|
};
|
||||||
|
|
||||||
const animateHero = (
|
|
||||||
startRect: DOMRect,
|
|
||||||
endRect: DOMRect,
|
|
||||||
element: HTMLElement,
|
|
||||||
reserve?: boolean,
|
|
||||||
) => {
|
|
||||||
const easing = ANIM_CURVE_STD;
|
|
||||||
element.classList.add("animated");
|
|
||||||
// distance_lt = (|top_start - top_end|^2 + |left_end - left_end|^2)^(-2)
|
|
||||||
const distancelt = Math.sqrt(
|
|
||||||
Math.pow(Math.abs(startRect.top - endRect.top), 2) +
|
|
||||||
Math.pow(Math.abs(startRect.left - endRect.left), 2),
|
|
||||||
);
|
|
||||||
const distancerb = Math.sqrt(
|
|
||||||
Math.pow(Math.abs(startRect.bottom - endRect.bottom), 2) +
|
|
||||||
Math.pow(Math.abs(startRect.right - endRect.right), 2),
|
|
||||||
);
|
|
||||||
const distance = distancelt + distancerb;
|
|
||||||
const duration = distance / 1.6;
|
|
||||||
animation = element.animate(
|
|
||||||
[
|
|
||||||
composeAnimationFrame(startRect, { transform: "none" }),
|
|
||||||
composeAnimationFrame(endRect, { transform: "none" }),
|
|
||||||
],
|
|
||||||
{ easing, duration },
|
|
||||||
);
|
|
||||||
const onAnimationEnd = () => {
|
|
||||||
element.classList.remove("animated");
|
|
||||||
animation = undefined;
|
|
||||||
};
|
|
||||||
animation.addEventListener("finish", onAnimationEnd);
|
|
||||||
animation.addEventListener("cancel", onAnimationEnd);
|
|
||||||
return animation;
|
|
||||||
};
|
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (animation) {
|
if (animation) {
|
||||||
animation.cancel();
|
animation.cancel();
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: var(--tutu-zidx-nav, auto);
|
z-index: var(--tutu-zidx-nav, auto);
|
||||||
padding-bottom: var(--safe-area-inset-bottom, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Scaffold {
|
.Scaffold {
|
||||||
|
|
|
@ -1,58 +1,3 @@
|
||||||
import {
|
|
||||||
createContext,
|
|
||||||
createRenderEffect,
|
|
||||||
createSignal,
|
|
||||||
untrack,
|
|
||||||
useContext,
|
|
||||||
type Accessor,
|
|
||||||
type Signal,
|
|
||||||
} from "solid-js";
|
|
||||||
|
|
||||||
export type HeroSource = {
|
|
||||||
[key: string | symbol | number]: HTMLElement | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const HeroSourceContext = createContext<Signal<HeroSource>>(
|
|
||||||
/* __@PURE__ */ undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const HeroSourceProvider = HeroSourceContext.Provider;
|
|
||||||
|
|
||||||
export function useHeroSource() {
|
|
||||||
return useContext(HeroSourceContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use hero value for the {@link key}.
|
|
||||||
*
|
|
||||||
* Note: the setter here won't set the value of the hero source.
|
|
||||||
* To set hero source, use {@link useHeroSource} and set the corresponding key.
|
|
||||||
*/
|
|
||||||
export function useHeroSignal(
|
|
||||||
key: string | symbol | number,
|
|
||||||
): Signal<HTMLElement | undefined> {
|
|
||||||
const source = useHeroSource();
|
|
||||||
if (source) {
|
|
||||||
const [get, set] = createSignal<HTMLElement>();
|
|
||||||
|
|
||||||
createRenderEffect(() => {
|
|
||||||
const value = source[0]();
|
|
||||||
if (value[key]) {
|
|
||||||
set(value[key]);
|
|
||||||
source[1]((x) => {
|
|
||||||
const cpy = Object.assign({}, x);
|
|
||||||
delete cpy[key];
|
|
||||||
return cpy;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [get, set];
|
|
||||||
} else {
|
|
||||||
console.debug("no hero source");
|
|
||||||
return [() => undefined, () => undefined];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function animateRollOutFromTop(
|
export function animateRollOutFromTop(
|
||||||
root: HTMLElement,
|
root: HTMLElement,
|
||||||
|
|
|
@ -98,7 +98,7 @@ const TootVisibilityPickerDialog: Component<{
|
||||||
style={{
|
style={{
|
||||||
"border-top": "1px solid #ddd",
|
"border-top": "1px solid #ddd",
|
||||||
background: "var(--tutu-color-surface)",
|
background: "var(--tutu-color-surface)",
|
||||||
padding: "8px 16px",
|
padding: "8px 16px calc(8px + var(--safe-area-inset-bottom, 0px))",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
"text-align": "end",
|
"text-align": "end",
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -10,8 +10,6 @@ import {
|
||||||
import { type mastodon } from "masto";
|
import { type mastodon } from "masto";
|
||||||
import { vibrate } from "../platform/hardware";
|
import { vibrate } from "../platform/hardware";
|
||||||
import { useDefaultSession } from "../masto/clients";
|
import { useDefaultSession } from "../masto/clients";
|
||||||
import { useHeroSource } from "../platform/anim";
|
|
||||||
import { HERO as BOTTOM_SHEET_HERO } from "../material/BottomSheet";
|
|
||||||
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
|
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
|
||||||
import RegularToot, {
|
import RegularToot, {
|
||||||
findElementActionable,
|
findElementActionable,
|
||||||
|
@ -53,7 +51,6 @@ const TootList: Component<{
|
||||||
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const session = useDefaultSession();
|
const session = useDefaultSession();
|
||||||
const heroSrc = useHeroSource();
|
|
||||||
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
|
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
|
||||||
const { push } = useNavigator();
|
const { push } = useNavigator();
|
||||||
|
|
||||||
|
@ -124,9 +121,6 @@ const TootList: Component<{
|
||||||
console.warn("no account info?");
|
console.warn("no account info?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (heroSrc) {
|
|
||||||
heroSrc[1]((x) => ({ ...x, [BOTTOM_SHEET_HERO]: srcElement }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const acct = `${inf.username}@${p.site}`;
|
const acct = `${inf.username}@${p.site}`;
|
||||||
setTootBottomSheetCache(acct, toot);
|
setTootBottomSheetCache(acct, toot);
|
||||||
|
|
Loading…
Reference in a new issue