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

[Very WIP] Springy #624

Draft
wants to merge 23 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d3cfc8d
Springy 1
calebeby Aug 28, 2019
13539a3
Use template literals and remove console.log
calebeby Aug 28, 2019
2473980
Set overflow hidden
calebeby Aug 28, 2019
2bcc2d6
Add transitioney
calebeby Aug 28, 2019
5274522
Use date.now
calebeby Aug 28, 2019
c7dd919
Merge remote-tracking branch 'origin/dev' into springy
calebeby Aug 29, 2019
d4b2a17
Merge remote-tracking branch 'origin/dev' into springy
calebeby Sep 7, 2019
730713d
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 2, 2019
6254ec8
eslint disable
calebeby Nov 2, 2019
5d77f09
WIP
calebeby Nov 3, 2019
75c8581
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 3, 2019
59a9da4
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 3, 2019
ef4d5b0
Add computed springs and stuff
calebeby Nov 3, 2019
b08ec72
Rewrite almost everything for really nice compositoin API
calebeby Nov 5, 2019
70e97cd
Empty commit to trigger netlify
calebeby Nov 5, 2019
c19c8d4
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 5, 2019
2144441
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 5, 2019
8b8997c
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 9, 2019
8c35763
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 9, 2019
953cb63
Merge remote-tracking branch 'origin/dev' into springy
calebeby Nov 14, 2019
e66f2f1
Thoughts about createDerivedSpring
calebeby Nov 15, 2019
2f24034
Refactor more things
calebeby Nov 16, 2019
6ee21f9
Merge remote-tracking branch 'origin/dev' into springy
calebeby Jan 28, 2020
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
113 changes: 113 additions & 0 deletions springy-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
- spring should not have all these overloads, creating super-springs should be separate module exports. They aren't actually related to the individual spring configs so it is silly the way it is currently.

```ts
import { initSpring, templateSpring, templateSpringAll, springedObject } from ''
const spring = initSpring()
const x = spring(foo ? 2 : 1) // Springed<number>
const y = foo ? 2 : 1 // number

// Springed<number> - "spring on a spring"
const foo = spring(x)

// Springed<number> - the result of this is directly tied with the x spring, it is not double springed
const bar = createDerivedSpring([x], ([x]) => {
return y / x
})

// Springed<number> - This is double springed. The return value of the createDerivedSpring got springed
const asdf = spring(bar)

// Springed<number> - The return value of createDerivedSpring got springed
const foobar = spring(
createDerivedNumberSpring([x], ([x]) => {
return x > 10 ? 100 : 35
}),
)

const color = (r, g, b) =>
createDerivedSpring([r, g, b].map(spring), ([r, g, b]) => {
return `rgba(${r}, ${g}, ${b})`
})

// The "expected result" would be a color that smoothly transitions
// In this case createDerivedSpring(...).compose() will be called
// We _want_ that to wrap all the input springs with the wrapper springs
// But that conflicts with above where the return value got springed.
// We could use typeof on the return value. That might be the best solution
// NO that won't work because we don't have access to the return value in .compose()
// So we'll need two separate methods for creating derived springs. One that is for numbers and one that is not
const respringedColor = spring(
asdf ? color(235, 220, 130) : color(130, 250, 120),
)

// Spring<{background: 'green', asdf: number}> -- asdf is springed but not background
const css3 = springedObject({
background: foo ? 'green' : 'blue',
asdf: spring(foo ? 1 : 2),
})

// Spring<string> - x is single springed, y is not springed
const css = templateSpring`transform: translateX(${x}px) translateY(${y}px)`

// Spring<string> - both x and y are springed. x is double springed, y is only springed once
const css2 = spring(
templateSpring`transform: translateX(${x}px) translateY(${y}px)`,
)
```

```ts
interface Spring<T> {
[springed]: true
computeValue(state: any): T
compose(outerSpring: CreateSpring): Spring<T>
}
```

- Where should spring state be stored - in Animated is best.

```ts
const Animated = () => {
setInterval(() => {
// springsCache holds values for ONE FRAME so that they do not get computed multiple times
// The values are the resolved/computed values from the springs
const springsCache = Map<Springed<T>, T>
// compute root props spring
// All sub springs when they get computed, get added to springsCache
})
}
```

- What happens if I pass a springed value down into a regular component?

Each component (each element, even) has its own frame loop, so it wouldn't be good (/accurate) if they shared the same cache
_unless_ we have a global frame loop - think about this more in the future
a global thing that Animated components can register themselves into

```tsx
const A = () => {
const spring = initSpring()
const x = spring(foo ? 1 : 2)
return (
<Fragment>
<Animated.div style={springedObject({ x })} />
<B x={x}>
</Fragment>
)
}

const B = ({x}) => {
// x is Springed<number>
return <Animated.div style={springedObject({y: x}} />
}
```

- "measuring" oof

```tsx
const A = () => {
const spring = initSpring()
const width = spring(foo ? '50%' : 'auto', (el: HTMLElement) => el.innerWidth)

return <Animated.div style={templateSpring`width: ${width}`} />
}
```
28 changes: 25 additions & 3 deletions src/components/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { logout, useJWT } from '@/jwt'
import { createShadow } from '@/utils/create-shadow'
import { getScrollbarWidth } from '@/utils/get-scrollbar-width'
import { resolveUrl } from '@/utils/resolve-url'
import { initSpring, Animated } from '@/spring/use'
import { useState } from 'preact/hooks'
import clsx from 'clsx'
import { css } from 'linaria'
import { darken, lighten, rgba } from 'polished'
Expand Down Expand Up @@ -89,7 +91,7 @@ const menuStyle = css`
background: white;
box-shadow: ${createShadow(16)};
z-index: 16;
transition: inherit;
/* transition: inherit; */
transition-timing-function: cubic-bezier(1, 0, 0.71, 0.88);
display: flex;
flex-direction: column;
Expand All @@ -103,7 +105,6 @@ const menuStyle = css`
}

.${scrimHiddenClass} & {
transform: translateX(100%);
box-shadow: none;
}
`
Expand All @@ -126,12 +127,18 @@ const logoutHandler = () => {
export const Menu = ({ onHide, visible }: Props) => {
const { jwt } = useJWT()
const isAdmin = jwt?.peregrineRoles.isAdmin
// const spring = initSpring()
const isLoggedIn = jwt
const savedReports = useSavedReports()

return (
<Scrim visible={visible} onClickOutside={onHide}>
<aside class={menuStyle}>
<aside
class={menuStyle}
style={{
transform: `translateX(${visible ? 0 : 100}%)`,
}}
>
<IconButton
aria-label="Close Menu"
icon={closeIcon}
Expand Down Expand Up @@ -175,3 +182,18 @@ export const Menu = ({ onHide, visible }: Props) => {
</Scrim>
)
}

const TextAnimated = () => {
const spring = initSpring()
const [counter, setCounter] = useState(0)
const increment = () => setCounter(c => c + 1)
const decrement = () => setCounter(c => c - 1)

return (
<div>
<button onClick={increment}>+</button>
<Animated.div>{spring(counter)}</Animated.div>
<button onClick={decrement}>-</button>
</div>
)
}
8 changes: 8 additions & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ const routes = [
path: '/leaderboard',
component: () => import('./routes/leaderboard'),
},
{
path: '/springy',
component: () => import('./routes/springy'),
},
{
path: '/transitioney',
component: () => import('./routes/transitioney'),
},
{
path: '/saved-reports',
component: () => import('./routes/saved-reports'),
Expand Down
62 changes: 62 additions & 0 deletions src/routes/event-team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ import { usePromise } from '@/utils/use-promise'
import { nextIncompleteMatch } from '@/utils/next-incomplete-match'
import { ChartCard } from '@/components/chart'
import { useEventMatches } from '@/cache/event-matches/use'
import { useState } from 'preact/hooks'
import {
initSpring,
Animated,
Springed,
tweenColor,
tweenLength,
springedObject,
templateSpring,
measure,
} from '@/spring/use'
import { useSchema } from '@/cache/schema/use'

const sectionStyle = css`
Expand Down Expand Up @@ -86,6 +97,7 @@ const EventTeam = ({ eventKey, teamNum }: Props) => {
/>
</Fragment>
)}
<TestComponent />
<InfoGroupCard
info={[
{
Expand Down Expand Up @@ -133,3 +145,53 @@ const EventTeam = ({ eventKey, teamNum }: Props) => {
}

export default EventTeam

const TestComponent = () => {
const [toggle, setToggle] = useState(false)
const spring = initSpring({ mass: 0.0007 })

const styles = springedObject({
padding: '0.5rem',
'border-radius': '0.2rem',
// transform: spring(templateSpring`translateX(${toggle ? 200 : -200}px)`),
'font-family': '"Dank Mono", "Fira Code", "Source Code Pro"',
// left: spring(templateSpring`${spring(toggle ? 10 : 100)}px`),
left: toggle ? 0 : '',
right: toggle ? '' : 0,
position: 'absolute',
transform: spring(
templateSpring`translateX(${measure(elSnapshot => {
console.log('hi', elSnapshot.offsetLeft)
return -elSnapshot.offsetLeft
})}px)`,
),
// background: tweenColor(spring, toggle ? '#282828' : 'black'),
// color: tweenColor(spring, toggle ? '#b16286' : '#994cc3'),
// width: tweenLength(spring, toggle ? '20vw' : '100%', el => el.offsetWidth),
// ...(toggle
// ? {
// background: tweenColor(spring, '#282828'),
// color: tweenColor(spring, '#b16286'),
// }
// : {
// background: tweenColor(spring, 'black'),
// color: tweenColor(spring, '#994cc3'),
// }),
})

return (
<div
class={css`
width: 50vw;
display: flex;
flex-direction: column;
align-items: center;
`}
>
<Animated.pre style={styles}>
{toggle ? 'hi' : 'hiya long\n\nhi again'}
</Animated.pre>
<button onClick={() => setToggle(t => !t)}>clickme</button>
</div>
)
}
117 changes: 117 additions & 0 deletions src/routes/springy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { h, FunctionComponent } from 'preact'
import {
initSpring,
Animated,
templateSpring,
createDerivedNumberSpring,
} from '@/spring/use'
import { useState } from 'preact/hooks'
import { css } from 'linaria'

const wrapperStyle = css`
width: 100%;
height: 100vh;
overflow: hidden;
background: black;
`

const width = 50

const boxStyle = css`
position: absolute;
width: ${width}px;
height: ${width}px;
background: purple;
will-change: transform;
border-radius: 10%;

&::before {
content: '';
display: block;
width: ${width / 4}px;
height: ${width / 4}px;
border-radius: 50%;
background: red;
margin: 0 auto;
transform: translateY(${width / 15}px);
}
`

const Springy: FunctionComponent = () => {
const spring = initSpring({
friction: 0.01,
mass: 0.003,
springStrength: 0.02,
})
const heavySpring = initSpring({
friction: 0.013,
mass: 0.007,
springStrength: 0.02,
})
const [x, setX] = useState(0)
const [y, setY] = useState(0)
const targetX = x - width / 2
const targetY = y - width / 2
const offsetX = spring(targetX)
const offsetY = spring(targetY)
const angle = createDerivedNumberSpring(
[offsetX, offsetY],
([offsetX, offsetY]) =>
-Math.atan2(offsetY - targetY, targetX - offsetX) + Math.PI / 2,
)
const offsetX2 = spring(offsetX)
const offsetY2 = spring(offsetY)
const angle2 = createDerivedNumberSpring(
[offsetX2, offsetY2],
([offsetX, offsetY]) =>
-Math.atan2(offsetY - targetY, targetX - offsetX) + Math.PI / 2,
)
const offsetX3 = spring(offsetX2)
const offsetY3 = spring(offsetY2)
const angle3 = createDerivedNumberSpring(
[offsetX3, offsetY3],
([offsetX, offsetY]) =>
-Math.atan2(offsetY - targetY, targetX - offsetX) + Math.PI / 2,
)

return (
// eslint-disable-next-line caleb/jsx-a11y/no-static-element-interactions, caleb/jsx-a11y/click-events-have-key-events
<div
class={wrapperStyle}
onMouseMove={(e: MouseEvent) => {
setX(e.x)
setY(e.y)
}}
// onClick={(e: MouseEvent) => {
// setX(e.x)
// setY(e.y)
// }}
>
<Animated.div
class={boxStyle}
style={templateSpring`transform: translate(${offsetX}px, ${offsetY}px) rotate(${spring(
angle,
)}rad)`}
/>
{/* <Animated.div
class={boxStyle}
data-asdf={JSON.stringify(angle)}
style={templateSpring`transform: translate(${angle}px, ${0}px) rotate(${0}rad)`}
/> */}
<Animated.div
class={boxStyle}
style={templateSpring`transform: translate(${offsetX2}px, ${offsetY2}px) rotate(${spring(
angle2,
)}rad)`}
/>
<Animated.div
class={boxStyle}
style={templateSpring`transform: translate(${offsetX3}px, ${offsetY3}px) rotate(${spring(
angle3,
)}rad)`}
/>
</div>
)
}

export default Springy
Loading