Skip to content
Closed
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
8 changes: 4 additions & 4 deletions .github/workflows/deploy-branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
uses: nelonoel/branch-name@v1.0.1
- name: Retrieve build environment if cached # STEP 2
id: build-cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: '/home/runner/.opam/'
path: "/home/runner/.opam/"
key: ${{ runner.os }}-modules-${{ hashFiles('./source/pnp-lock.yaml') }}
#- name: Debugging with ssh
# uses: lhotari/action-upterm@v1
Expand All @@ -29,7 +29,7 @@ jobs:
- name: Use Node.js 21.2.0
uses: actions/setup-node@v3
with:
node-version: '21.2.0'
node-version: "21.2.0"
# yarn global add pnpm@8.10.5
- name: Install dependencies and build nool # STEP 3
run: |
Expand Down Expand Up @@ -66,4 +66,4 @@ jobs:
git add -A
git status
git diff-index --quiet HEAD || (git commit -m "github-deploy-action-${BRANCH_NAME}"; git push)
working-directory: ./server
working-directory: ./server
104 changes: 86 additions & 18 deletions src/Animate.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,91 @@
const blah = (s: string) => `
::view-transition-old(${s}),
::view-transition-new(${s}) {
transition: transform 550ms cubic-bezier(0.68, -0.6, 0.32, 1.6);
}
`;
/**
* Modern View Transitions API - Dynamic Assignment (2025)
* No CSS rule generation, unlimited IDs, proper cleanup
*/

export const init = ():void => {
//TODO: unhardcode id max
var style = document.createElement("style");
for (let id = 0; id < 100; id++) {
style.innerHTML += `#node-${id}.animate { view-transition-name: flip-node-${id}; }\n`;
style.innerHTML += `#main.unsetSelections #sym-${id}, #main.setSelect #sym-${id}, #main.moveStage #sym-${id} { view-transition-name: flip-sym-${id}; }\n`;
export const assignTransitionNames = (rootElement: HTMLElement | null, actionType: string) => {
if (!rootElement) return;

// Find all nodes that should animate
const nodes = rootElement.querySelectorAll('[id^="node-"]') as NodeListOf<HTMLElement>;
const syms = rootElement.querySelectorAll('[id^="sym-"]') as NodeListOf<HTMLElement>;

nodes.forEach(el => {
const id = el.id.split('-')[1];
const hasAnimateClass = el.classList.contains('animate');
// Only assign transition names to nodes that should animate
if (hasAnimateClass) {
el.style.viewTransitionName = `flip-node-${id}`;
}
});

// Assign symbol transition names for specific action types
if (['unsetSelections', 'setSelect', 'moveStage'].includes(actionType)) {
syms.forEach(el => {
const id = el.id.split('-')[1];
el.style.viewTransitionName = `flip-sym-${id}`;
});
}

// Handle selected node transition
if (['setSelect', 'unsetSelections'].includes(actionType)) {
const selected = rootElement.querySelector('.selected') as HTMLElement;
if (selected) {
selected.style.viewTransitionName = 'flip-node-selected';
}

//style.innerHTML += `#main.setSelect #pat-${id} { view-transition-name: flip-pat-${id}; }`;
//style.innerHTML += blah(`flip-node-${id}`);
//style.innerHTML += blah(`flip-pat-${id}`);
const logo = rootElement.querySelector('.logo') as HTMLElement;
const seed = rootElement.querySelector('#seed') as HTMLElement;
if (logo) logo.style.viewTransitionName = 'setSelect-logo';
if (seed) seed.style.viewTransitionName = 'setSelect-seed';
}
};

export const cleanupTransitionNames = (rootElement: HTMLElement | null) => {
if (!rootElement) return;

// Clean up root element transition name
if (rootElement.style.viewTransitionName) {
rootElement.style.viewTransitionName = '';
}
style.innerHTML += `#main.setSelect .logo, #main.unsetSelections .logo { view-transition-name: setSelect-logo }\n`;
style.innerHTML += `#main.setSelect #seed, #main.unsetSelections #seed { view-transition-name: setSelect-seed }\n`;
//style.innerHTML += `#node.selected { view-transition-name: flip-node-selected; }\n`;

// Check nodes and syms specifically since we know we assigned to them
const nodes = rootElement.querySelectorAll('[id^="node-"]') as NodeListOf<HTMLElement>;
const syms = rootElement.querySelectorAll('[id^="sym-"]') as NodeListOf<HTMLElement>;

nodes.forEach(el => {
if (el.style.viewTransitionName) {
el.style.viewTransitionName = '';
}
});

syms.forEach(el => {
if (el.style.viewTransitionName) {
el.style.viewTransitionName = '';
}
});
};

// Minimal init - just add base CSS for transition styling
export const init = (): void => {
const style = document.createElement("style");
style.innerHTML = `
/* Base transition timing for all view transitions */
::view-transition-group(*) {
animation-duration: calc(var(--anim-factor) * 0.25s);
animation-fill-mode: both;
animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6);
}

::view-transition-new(*) {
height: 100%;
width: 100%;
}

::view-transition-old(*) {
height: 100%;
width: 100%;
}
`;
document.getElementsByTagName("head")[0].appendChild(style);
};
55 changes: 45 additions & 10 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,58 @@ import * as Animate from "./Animate";

export type SetModel = SetStoreFunction<Model.t>;


const App: Component = () => {
const [model, setModel] = createStore({ ...Model.init });
let activeTransition: ViewTransition | null = null;
let pendingHoverAction: Action.t | null = null;

Animate.init();
const inject = (a: Action.t) => {
console.log(a);
if (a.t === "setHover" || !document.startViewTransition) {
console.log("sethover dont transition:" + a.t);
// CRITICAL: setHover actions must be deferred during view transitions.
// Transform actions trigger hover changes when tool sides flip (mouse position changes),
// causing DOM updates that interfere with ongoing view transitions and break animations.
// Solution: defer hover actions until transition completes, then apply the final hover state.
if (a.t === "setHover") {
if (activeTransition) {
pendingHoverAction = a;
return;
}
go(model, setModel, a);
return;
}

if (!document.startViewTransition) {
go(model, setModel, a);
return;
}
const guy2 = document.getElementById("main");
guy2 ? guy2.classList.add(a.t) : console.log("no guy r add");
let v = document.startViewTransition(() => go(model, setModel, a));
v.finished.then(() =>
guy2 ? guy2.classList.remove(a.t) : console.log("no guy 2 rm")
);

const main = document.getElementById("main");
main?.classList.add(a.t);

// Assign transition names just before transition
Animate.assignTransitionNames(main, a.t);

activeTransition = document.startViewTransition(() => {
go(model, setModel, a);
// Re-assign transition names to NEW elements after DOM update
const mainAfter = document.getElementById("main");
// Force style recomputation before reassigning transition names
mainAfter?.offsetHeight;
Animate.assignTransitionNames(mainAfter, a.t);
});

activeTransition.finished.then(() => {
main?.classList.remove(a.t);
Animate.cleanupTransitionNames(main);
activeTransition = null;

// Process any pending hover action
if (pendingHoverAction) {
const deferredAction = pendingHoverAction;
pendingHoverAction = null;
go(model, setModel, deferredAction);
}
});
};
document.addEventListener("keydown", Keyboard.keydown(inject), false);
document.addEventListener("keyup", Keyboard.keyup(inject), false);
Expand Down
8 changes: 3 additions & 5 deletions src/Update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import * as Path from "./syntax/Path";
import * as Animate from "./Animate";
import * as Util from "./Util";


export type result = Model.t | "NoChange";

export const of_theme = (theme: Settings.theme): [number, number] => {
Expand Down Expand Up @@ -206,7 +205,8 @@ export const update = (model: Model.t, action: Action.t): result => {
},
};
case "wheelNumTools":
const clamp = (x:number, a:number, b:number) => Math.max( a, Math.min(x, b) );
const clamp = (x: number, a: number, b: number) =>
Math.max(a, Math.min(x, b));
console.log("wheelNumTools:" + action.offset + ":" + model.tools.size);
return {
...model,
Expand All @@ -215,9 +215,7 @@ export const update = (model: Model.t, action: Action.t): result => {
size: clamp(
model.tools.size + action.offset,
1,
model.tools.transforms.length,


model.tools.transforms.length
),
},
};
Expand Down
55 changes: 1 addition & 54 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1114,51 +1114,12 @@ body:has(#main.Dark) {
color: black !important;
}

::view-transition-old(flip-node-selected),
::view-transition-new(flip-node-selected) {
/*transition: none;
transition: transform 250ms cubic-bezier(0.68, -0.6, 0.32, 1.6) !important;*/
/*animation-duration: 200ms !important;
transition: transform 250ms cubic-bezier(0.68, -0.6, 0.32, 1.6) !important;
height: auto;
right: 0;
left: auto;
transform-origin: right center;*/
}
.selected {
/* needs some tlc. dont want internals to show during transition
*/
view-transition-name: flip-node-selected;
}
#main.setSelect .selected,
#main.unsetSelections .selected {
/* needs some tlc. dont want internals to show during transition
*/
view-transition-name: flip-node-selected !important;
}

#seed .icon {
view-transition-name: flip-seed-icon;
}
#seed #noolbox {
view-transition-name: flip-noolbox;
}
#seed .previews {
/* view-transition-name: flip-previews; */
}

::view-transition-group(flip-node-selected) {
view-transition-name: selected;
animation-timing-function: easeInBack; /*easeInBack; easeOutBounce;*/
animation-timing-function: easeInBack;
animation-duration: calc(var(--anim-factor) * 0.2s);
}
::view-transition-old(flip-node-selected) {
}
::view-transition-new(flip-node-selected) {
}

::view-transition-group(*) {
/*pointer-events: none;*/
animation-duration: calc(var(--anim-factor) * 0.25s);
animation-fill-mode: both;
animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6);
Expand All @@ -1167,24 +1128,10 @@ body:has(#main.Dark) {
::view-transition-new(*) {
height: 100%;
width: 100%;
/*animation-name: none !important;*/
/*animation-name: none !important;
animation-fill-mode: forwards;
opacity: 0;*/
/*animation-name: none;*/
}
::view-transition-old(*) {
height: 100%;
width: 100%;
/*animation-name: none !important;*/
/*animation-name: none !important;*/

/*pointer-events: none;*/
/*animation-name: none;*/
}

::view-transition {
/*pointer-events: none;*/
}

.selected-transition * {
Expand Down