-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(page-title): adds base experiments (#15)
* chore(runtime): prepare eperiment providers * feat(testing): add vsc launch config * feat(testing): add coverage provider to jest config * feat(inactive-title): add maqeuee for title * chore(runtime): refactor inner structure of its slice and actions * feat(utils): add multi-byte string funs for split and slice * feat(page-title): update marquee helper to be mb string compatible * feat(page-title): add glitchy and marquee titles * feat(page-title): add array paged title replacing marquee * feat(page-title): disable glitch title for the time being
- Loading branch information
Showing
21 changed files
with
352 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Next.js: debug server-side", | ||
"type": "node-terminal", | ||
"request": "launch", | ||
"command": "npm run dev" | ||
}, | ||
{ | ||
"name": "Next.js: debug client-side", | ||
"type": "chrome", | ||
"request": "launch", | ||
"url": "https://localhost:3000" | ||
}, | ||
{ | ||
"name": "Next.js: debug full stack", | ||
"type": "node-terminal", | ||
"request": "launch", | ||
"command": "npm run dev", | ||
"serverReadyAction": { | ||
"pattern": "- Local:.+(https?://.+)", | ||
"uriFormat": "%s", | ||
"action": "debugWithChrome" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import Head from 'next/head'; | ||
import { useCallback, useEffect, useState } from 'react'; | ||
|
||
import { random } from '@/utils/math'; | ||
|
||
export type RandomBlinkTitleProps = { | ||
duration?: number; | ||
enabled: boolean; | ||
randomRange?: [number, number]; | ||
text?: string; | ||
}; | ||
const GlitchyTitle = ({ | ||
duration = 1000, | ||
enabled, | ||
randomRange = [5000, 1000], | ||
text = glitchyText, | ||
}: RandomBlinkTitleProps) => { | ||
const [blink, setBlink] = useState(false); | ||
// Incrementing this will trigger a rerender/reschedule | ||
const [runCount, setRunCount] = useState(0); | ||
|
||
const schedule = useCallback( | ||
(onComplete: () => void) => { | ||
const rnd = random(Math.min(...randomRange), Math.max(...randomRange)); | ||
const inTimer = setTimeout(() => setBlink(true), rnd); | ||
const outTimer = setTimeout(() => { | ||
setBlink(false); | ||
onComplete(); | ||
}, rnd + duration); | ||
|
||
return () => { | ||
clearTimeout(inTimer); | ||
clearTimeout(outTimer); | ||
}; | ||
}, | ||
[duration, randomRange], | ||
); | ||
|
||
useEffect(() => { | ||
if (!enabled) { | ||
setBlink(false); | ||
setRunCount(0); | ||
return; | ||
} | ||
|
||
const remove = schedule(() => setRunCount((prev) => prev + 1)); | ||
return remove; | ||
}, [enabled, schedule, runCount]); | ||
|
||
return <Head>{enabled && blink && <title>{text}</title>}</Head>; | ||
}; | ||
|
||
// This looks soo funky :D | ||
const glitchyText = 'Ţ̸̳̦̰̦̝͕͐̒̉̉̈́̉͛̃͆ͅh̵̛̜̲̉̋̀̐̒͒͆̕ë̸̦̝̀́̓ ̸̡̯͕̈́̈́͋͂͗M̴͔̘͙̣̞̈́̏͗̈́̾͊̊̇ͅo̵̢̤͚̯̣͕̾̿̑̓͐͝s̶̫͙̳̼̲̔̄͑̒́̓̃̎̕t̵̛̫̅͌̌̓̒͘̕ ̸͓́́̃͠A̵̲͑̅̚ṅ̸̦̗̱͈̳̰̣̿̽͋͊̚ͅn̵͙̭͆o̵̢͎͙̊̓y̷̡̛̯͙͕̞͕̱̖̾̂́͝i̴̤̠͚̯̲̣͛ṅ̸̛̖̗̟͙̻͂̏̉͒ǵ̶̡͈̒͛̌͐̄͘ ̶̢̦͉̩̗̮̬̺̂̑̆̄͆͐Ẃ̶̡̭̪͖̼̟͕́̊́̾̕e̴͓̱͐̈͌̈̔̑̕͠b̸̡̘͍͍̹̹̦͑̒̓s̷͎̥̠̦͕͋́̉̔̏i̸̱̗̻̳̦̓̓̓̍͌̓t̷̤̦̳̯̉̿̉̂̌̚ę̷̣͔͇̇͊͑'; | ||
|
||
export default GlitchyTitle; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Head from 'next/head'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
import string_marquee from '@/features/page_title/utils/string_marquee'; | ||
|
||
type MarqueeTitleProps = { | ||
enabled: boolean; | ||
speedMs?: number; | ||
text: string; | ||
}; | ||
const MarqueeTitle = ({ enabled, speedMs = 1000, text }: MarqueeTitleProps) => { | ||
const [time, setTime] = useState(0); | ||
|
||
useEffect(() => { | ||
if (!enabled) { | ||
setTime(0); | ||
} | ||
|
||
const timer = setInterval(() => setTime((prev) => prev + 1), speedMs); | ||
return () => { | ||
clearInterval(timer); | ||
setTime(0); | ||
}; | ||
}, [enabled, speedMs]); | ||
|
||
return <Head>{enabled && <title>{string_marquee(text, time)}</title>}</Head>; | ||
}; | ||
|
||
export default MarqueeTitle; |
34 changes: 34 additions & 0 deletions
34
src/features/page_title/components/PageTitleExperiment.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { useAppSelector } from '@/redux/hooks'; | ||
import { | ||
selectInteractionUnlocked, | ||
selectIsDocumentVisible, | ||
} from '@/redux/selectors/runtime'; | ||
|
||
import ArrayPagedTitle from './PagedTitle'; | ||
|
||
/** | ||
* Experiments on manipulating the page title. Unfortunatelly the refresh rate | ||
* is quite low and the title is not updated as frequently as I would like. | ||
*/ | ||
const PageTitleExperiment = () => { | ||
const isVisible = useAppSelector(selectIsDocumentVisible); | ||
const hasInteracted = useAppSelector(selectInteractionUnlocked); | ||
|
||
return ( | ||
<> | ||
{/* It works but with the current browser landscape it is super slow. */} | ||
{/* <MarqueeTitle | ||
enabled={hasInteracted && !isVisible} | ||
text="📣 Come back please 🏃♀️🏃 We have candy!! 🚐" | ||
/> */} | ||
{/* It's just not that funny when it show for more than 100ms :( */} | ||
{/* <GlitchyTitle enabled={hasInteracted && isVisible} /> */} | ||
<ArrayPagedTitle | ||
enabled={hasInteracted && !isVisible} | ||
texts={['⭐️ HEY YOU 🫵', '😜 YES YOU 😱', '📣 COME BACK 🏃']} | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
export default PageTitleExperiment; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Head from 'next/head'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
type ArrayPagedTitleProps = { | ||
enabled: boolean; | ||
speedMs?: number; | ||
texts: string[]; | ||
}; | ||
const ArrayPagedTitle = ({ | ||
enabled, | ||
speedMs = 1000, | ||
texts, | ||
}: ArrayPagedTitleProps) => { | ||
const [time, setTime] = useState(0); | ||
|
||
// Every odd iteration will one of the custom lines while every even one | ||
// will keep the original title. | ||
const alternate = time % 2 === 1; | ||
const text = alternate ? undefined : texts[(time / 2) % texts.length]; | ||
|
||
useEffect(() => { | ||
if (!enabled) { | ||
setTime(0); | ||
} | ||
|
||
const timer = setInterval(() => setTime((prev) => prev + 1), speedMs); | ||
return () => { | ||
clearInterval(timer); | ||
setTime(0); | ||
}; | ||
}, [enabled, speedMs]); | ||
|
||
return <Head>{enabled && text && <title>{text}</title>}</Head>; | ||
}; | ||
|
||
export default ArrayPagedTitle; |
36 changes: 36 additions & 0 deletions
36
src/features/page_title/utils/__test__/string_marquee.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
import string_marquee from '@/features/page_title/utils/string_marquee'; | ||
|
||
describe('String Marquee', () => { | ||
const base = { | ||
width: 10, | ||
loopSpace: 5, | ||
separatorChar: ' ', | ||
}; | ||
|
||
it('should return 10 character long output even without content', () => { | ||
expect(string_marquee('', 0, base)).toBe(' '); | ||
}); | ||
|
||
it('should return 10 character no matter the lengt of the input', () => { | ||
expect(string_marquee('Hello', 0, base)).toBe('Hello '); | ||
expect(string_marquee('Hello Bello Chello', 0, base)).toBe('Hello Bell'); | ||
}); | ||
|
||
it('should offset the text by t while keeping 10 character limit and 5 loop space', () => { | ||
expect(string_marquee('Hello', 0, base)).toBe('Hello '); | ||
expect(string_marquee('Hello', 1, base)).toBe('ello H'); | ||
expect(string_marquee('Hello', 2, base)).toBe('llo He'); | ||
expect(string_marquee('Hello', 3, base)).toBe('lo Hel'); | ||
expect(string_marquee('Hello', 4, base)).toBe('o Hell'); | ||
expect(string_marquee('Hello', 5, base)).toBe(' Hello'); | ||
}); | ||
|
||
it('should respect multi-byte characters', () => { | ||
expect(string_marquee('🏃📣', 0, base)).toBe('🏃📣 🏃📣 '); | ||
expect(string_marquee('🏃📣', 1, base)).toBe('📣 🏃📣 '); | ||
expect(string_marquee('🏃📣', 2, base)).toBe(' 🏃📣 '); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { mb_string_to_char_array } from '@/utils/string'; | ||
|
||
type StringMarqueeOpts = { | ||
width?: number; | ||
gapLength?: number; | ||
gapChar?: string; | ||
}; | ||
const string_marquee = ( | ||
text: string, | ||
t = 0, | ||
{ width = 30, gapLength = 5, gapChar = ' ' }: StringMarqueeOpts = {}, | ||
) => { | ||
const source = [ | ||
...mb_string_to_char_array(text), | ||
...new Array(gapLength).fill(gapChar), | ||
]; | ||
const tModulo = t % source.length; | ||
let output = ''; | ||
for (let i = 0; i < width; i++) { | ||
output += source[(tModulo + i) % source.length]; | ||
} | ||
|
||
return output; | ||
}; | ||
|
||
export default string_marquee; |
25 changes: 11 additions & 14 deletions
25
src/hooks/useInFocusMeter.ts → src/hooks/useDocumentVisibilityListener.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,40 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { useCallback, useEffect } from 'react'; | ||
|
||
import { actions as runtimeActions } from '@/redux/slices/runtime'; | ||
import { useAppDispatch } from '@/redux/hooks'; | ||
import { useAppDispatch, useAppSelector } from '@/redux/hooks'; | ||
import { selectIsDocumentVisible } from '@/redux/selectors/runtime'; | ||
|
||
/** | ||
* This will mesaure how long the webpage has been in focus and report it to | ||
* the redux store. Please note that if you change application but the | ||
* browser is still visible it will count as "in focus". | ||
* https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event | ||
*/ | ||
const useInFocusMeter = () => { | ||
const [isInFocus, setIsInFocusInternal] = useState(true); | ||
const useDocumentVisibilityListener = () => { | ||
const isInFocus = useAppSelector(selectIsDocumentVisible); | ||
const dispatch = useAppDispatch(); | ||
|
||
const handleVisibilityChange = () => { | ||
setIsInFocusInternal(!document.hidden); | ||
}; | ||
const handleVisibilityChange = useCallback(() => { | ||
dispatch(runtimeActions.setIsDocumentVisibile(!document.hidden)); | ||
}, [dispatch]); | ||
|
||
useEffect(() => { | ||
document.addEventListener('visibilitychange', handleVisibilityChange); | ||
handleVisibilityChange(); | ||
|
||
return () => | ||
document.removeEventListener('visibilitychange', handleVisibilityChange); | ||
}, []); | ||
|
||
useEffect(() => { | ||
dispatch(runtimeActions.setIsInFocus(isInFocus)); | ||
}, [dispatch, isInFocus]); | ||
}, [handleVisibilityChange]); | ||
|
||
useEffect(() => { | ||
if (!isInFocus) return; | ||
|
||
const interval = setInterval( | ||
() => dispatch(runtimeActions.incrementInFocusSeconds()), | ||
() => dispatch(runtimeActions.incrementVisibilitySeconds()), | ||
1000, | ||
); | ||
return () => clearInterval(interval); | ||
}, [isInFocus, dispatch]); | ||
}; | ||
|
||
export default useInFocusMeter; | ||
export default useDocumentVisibilityListener; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.