@emotion/react#Global TypeScript Examples
The following examples show how to use
@emotion/react#Global.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Layout.tsx From lightning-terminal with MIT License | 6 votes |
Layout: React.FC = ({ children }) => {
const { settingsStore, appView } = useStore();
const { Container, Hamburger, Aside, Content, Fluid } = Styled;
return (
<Background>
<Container fullWidth={appView.fullWidth}>
<Hamburger
collapsed={!settingsStore.sidebarVisible}
onClick={settingsStore.toggleSidebar}
>
<Menu size="large" />
</Hamburger>
<Aside collapsed={!settingsStore.sidebarVisible}>
<Sidebar />
</Aside>
<Content collapsed={!settingsStore.sidebarVisible} fullWidth={appView.fullWidth}>
<Fluid className="container-fluid">{children}</Fluid>
</Content>
</Container>
<Global styles={GlobalStyles} />
</Background>
);
}
Example #2
Source File: index.tsx From react-dev-inspector with MIT License | 6 votes |
HomePage = () => {
return <Inspector data-inspector-line="16" data-inspector-column="4" data-inspector-relative-path="layouts/index.tsx" disableLaunchEditor={!isDev} onClickElement={(inspect: InspectParams) => {
console.debug(inspect);
if (isDev || !inspect.codeInfo?.relativePath) return;
const {
relativePath,
lineNumber
} = inspect.codeInfo;
window.open(`${projectRepo}/blob/master/examples/umi3/${relativePath}#L${lineNumber}`);
}}>
<Global data-inspector-line="32" data-inspector-column="6" data-inspector-relative-path="layouts/index.tsx" styles={S.globalCss} />
<S.Base data-inspector-line="35" data-inspector-column="6" data-inspector-relative-path="layouts/index.tsx">
<S.GithubCorner data-inspector-line="36" data-inspector-column="8" data-inspector-relative-path="layouts/index.tsx" href={projectRepo} />
<Title data-inspector-line="40" data-inspector-column="8" data-inspector-relative-path="layouts/index.tsx">
<span data-inspector-line="41" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx">React Dev Inspector</span>
</Title>
<Slogan data-inspector-line="44" data-inspector-column="8" data-inspector-relative-path="layouts/index.tsx">
<p data-inspector-line="45" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx">Quick jump to local IDE source code directly from browser React component by just a simple click!</p>
<p data-inspector-line="46" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx"><small data-inspector-line="46" data-inspector-column="13" data-inspector-relative-path="layouts/index.tsx">( for this prod online demo page, jump to GitHub file )</small></p>
</Slogan>
<KeyPad data-inspector-line="49" data-inspector-column="8" data-inspector-relative-path="layouts/index.tsx">
<Keypress data-inspector-line="50" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx">Ctrl ⌃</Keypress>
+
<Keypress data-inspector-line="52" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx">Shift ⇧</Keypress>
+
<Keypress data-inspector-line="54" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx">Command ⌘</Keypress>
+
<Keypress data-inspector-line="56" data-inspector-column="10" data-inspector-relative-path="layouts/index.tsx">C</Keypress>
</KeyPad>
</S.Base>
</Inspector>;
}
Example #3
Source File: GlobalStyles.tsx From spacesvr with MIT License | 6 votes |
export default function GlobalStyles() {
return (
<>
<Global styles={globalStyles} />
<Helmet>
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />
</Helmet>
</>
);
}
Example #4
Source File: _app.tsx From rcvr-app with GNU Affero General Public License v3.0 | 6 votes |
RecoverApp: RecoverAppFC = ({
Component,
pageProps: { localeContext, ...pageProps },
}) => {
useA11yFocusRing()
return (
<ThemeProvider theme={theme}>
<QueryClientProvider client={queryClient}>
<LocalesContextProvider value={localeContext}>
<Global styles={globalStyles} />
<AnimateSharedLayout>
<SupportedBrowsersAlert />
<Component {...pageProps} />
</AnimateSharedLayout>
</LocalesContextProvider>
</QueryClientProvider>
</ThemeProvider>
)
}
Example #5
Source File: index.tsx From openchakra with MIT License | 6 votes |
App = () => {
useShortcuts()
return (
<>
<Global
styles={() => ({
html: { minWidth: '860px', backgroundColor: '#1a202c' },
})}
/>
<Metadata />
<Header />
<DndProvider backend={Backend}>
<Flex h="calc(100vh - 3rem)">
<Sidebar />
<EditorErrorBoundary>
<Box bg="white" flex={1} position="relative">
<Editor />
</Box>
</EditorErrorBoundary>
<Box
maxH="calc(100vh - 3rem)"
flex="0 0 15rem"
bg="#f7fafc"
overflowY="auto"
overflowX="visible"
borderLeft="1px solid #cad5de"
>
<InspectorProvider>
<Inspector />
</InspectorProvider>
</Box>
</Flex>
</DndProvider>
</>
)
}
Example #6
Source File: _app.tsx From next-page-tester with MIT License | 6 votes |
MyApp = ({ Component, pageProps }: AppProps) => (
<>
<CacheProvider value={cache}>
<Global
styles={css`
body {
color: red;
}
`}
/>
<Component {...pageProps} />
</CacheProvider>
</>
)
Example #7
Source File: [userId].tsx From crosshare with GNU Affero General Public License v3.0 | 6 votes |
export default function ThemedPage(props: PuzzlePageProps) {
let primary = PRIMARY;
let link = LINK;
let darkMode = false;
let preservePrimary = false;
if ('embedOptions' in props) {
primary = props.embedOptions?.p || PRIMARY;
link = props.embedOptions?.l || LINK;
darkMode = props.embedOptions?.d || false;
preservePrimary = props.embedOptions?.pp || false;
// Just ensure color is parseable, this'll throw if not:
parseToRgba(primary);
}
return (
<>
<Global
styles={{
body: {
backgroundColor: 'transparent !important',
},
'html, body.light-mode, body.dark-mode': colorTheme(
primary,
link,
darkMode,
preservePrimary
),
}}
/>
<EmbedContext.Provider value={true}>
<PuzzlePage {...props} />
</EmbedContext.Provider>
</>
);
}
Example #8
Source File: MantineCssVariables.tsx From mantine with MIT License | 6 votes |
export function MantineCssVariables({ theme }: { theme: MantineTheme }) {
const variables: Record<string, string> = {
'--mantine-color-white': theme.white,
'--mantine-color-black': theme.black,
'--mantine-transition-timing-function': theme.transitionTimingFunction,
'--mantine-line-height': `${theme.lineHeight}`,
'--mantine-font-family': theme.fontFamily,
'--mantine-font-family-monospace': theme.fontFamilyMonospace,
'--mantine-font-family-headings': theme.headings.fontFamily,
'--mantine-heading-font-weight': `${theme.headings.fontWeight}`,
};
assignSizeVariables(variables, theme.shadows, 'shadow');
assignSizeVariables(variables, theme.fontSizes, 'font-size');
assignSizeVariables(variables, theme.radius, 'radius');
assignSizeVariables(variables, theme.spacing, 'spacing');
Object.keys(theme.colors).forEach((color) => {
theme.colors[color].forEach((shade, index) => {
variables[`--mantine-color-${color}-${index}`] = shade;
});
});
const headings = theme.headings.sizes;
Object.keys(headings).forEach((heading) => {
variables[`--mantine-${heading}-font-size`] = `${headings[heading].fontSize}px`;
variables[`--mantine-${heading}-line-height`] = `${headings[heading].lineHeight}`;
});
return (
<Global
styles={{
':root': variables,
}}
/>
);
}
Example #9
Source File: GlobalStyles.tsx From mantine with MIT License | 6 votes |
export function GlobalStyles({ theme }: { theme: MantineTheme }) {
return (
<Global
styles={{
'*, *::before, *::after': {
boxSizing: 'border-box',
},
body: {
...(theme.fn.fontStyles() as any),
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
lineHeight: theme.lineHeight,
fontSize: theme.fontSizes.md,
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
},
}}
/>
);
}
Example #10
Source File: _app.tsx From next-eui-starter with Apache License 2.0 | 6 votes |
EuiApp: FunctionComponent<AppProps> = ({ Component, pageProps }) => (
<>
<Head>
{/* You can override this in other pages - see index.tsx for an example */}
<title>Next.js EUI Starter</title>
</Head>
<Global styles={globalStyes} />
<Theme>
<Chrome>
<EuiErrorBoundary>
<Component {...pageProps} />
</EuiErrorBoundary>
</Chrome>
</Theme>
</>
)
Example #11
Source File: app.tsx From master-frontend-lemoncode with MIT License | 6 votes |
App = () => {
return (
<div>
<Global styles={globalStyles} />
<h1 className="base-background" css={h1Class}>
Hello React !!
</h1>
</div>
);
}
Example #12
Source File: ThemeProvider.tsx From yet-another-generic-startpage with MIT License | 6 votes |
ThemeProvider = ({ children }: PropsWithChildren<unknown>) => {
const [{ font, enableFonts }] = useGeneralSettings()
const globalStyles = getGlobalStyles(font, enableFonts)
return (
<StpgTheme initialTheme={initialTheme} persistTheme={true}>
<ThemeConsumer>
{({ theme }) => (
<EmotionTheme theme={theme}>
<Global styles={globalStyles} />
{children}
</EmotionTheme>
)}
</ThemeConsumer>
</StpgTheme>
)
}
Example #13
Source File: GlobalStyles.tsx From atlas with GNU General Public License v3.0 | 5 votes |
GlobalStyles: React.FC<GlobalStyleProps> = ({ additionalStyles }) => {
const additionalStylesArray = additionalStyles
? Array.isArray(additionalStyles)
? additionalStyles
: [additionalStyles]
: []
return <Global styles={[globalStyles, transitionStyles, ...additionalStylesArray]} />
}
Example #14
Source File: GlobalStyle.tsx From tobira with Apache License 2.0 | 5 votes |
GlobalStyle: React.FC = () => <>
<Global styles={CSS_RESETS} />
<Global styles={GLOBAL_STYLE} />
</>
Example #15
Source File: index.tsx From react-dev-inspector with MIT License | 5 votes |
HomePage = () => {
return (
<Inspector
disableLaunchEditor={!isDev}
onClickElement={(inspect: InspectParams) => {
console.debug(inspect)
if (isDev || !inspect.codeInfo?.relativePath) return
const {
relativePath,
lineNumber,
} = inspect.codeInfo
window.open(
`${projectRepo}/blob/master/examples/umi3/${relativePath}#L${lineNumber}`,
)
}}
>
<Global styles={S.globalCss} />
<S.Base>
<S.GithubCorner
href={projectRepo}
/>
<Title>
<span>React Dev Inspector</span>
</Title>
<Slogan>
<p>Quick jump to local IDE source code directly from browser React component by just a simple click!</p>
<p><small>( for this prod online demo page, jump to GitHub file )</small></p>
</Slogan>
<KeyPad>
<Keypress>Ctrl ⌃</Keypress>
+
<Keypress>Shift ⇧</Keypress>
+
<Keypress>Command ⌘</Keypress>
+
<Keypress>C</Keypress>
</KeyPad>
</S.Base>
</Inspector>
)
}
Example #16
Source File: overlayManager.tsx From atlas with GNU General Public License v3.0 | 5 votes |
OverlayManagerProvider: React.FC = ({ children }) => {
const [scrollLocked, setScrollLocked] = useState(false)
const [scrollbarGap, setScrollbarGap] = useState(0)
const [overlaysSet, setOverlaysSet] = useState(new Set<string>())
const modalContainerRef = useRef<HTMLDivElement>(null)
const anyOverlaysOpen = overlaysSet.size > 0
useEffect(() => {
if (!anyOverlaysOpen && scrollLocked) {
setScrollLocked(false)
setScrollbarGap(0)
enablePageScroll()
} else if (anyOverlaysOpen && !scrollLocked) {
const scrollbarGap = window.innerWidth - document.documentElement.clientWidth
setScrollLocked(true)
setScrollbarGap(scrollbarGap)
disablePageScroll()
}
}, [anyOverlaysOpen, scrollLocked])
return (
<>
<Global styles={[overlayManagerStyles(scrollbarGap), modalTransitions]} />
<OverlayManagerContext.Provider
value={{
scrollLocked,
anyOverlaysOpen,
setOverlaysSet,
modalContainerRef,
}}
>
{children}
<PortalContainer ref={modalContainerRef} />
</OverlayManagerContext.Provider>
</>
)
}
Example #17
Source File: NippleMovement.tsx From spacesvr with MIT License | 5 votes |
NippleMovement = (props: NippleMovementProps) => {
const { direction } = props;
const nipple = useRef<JoystickManager>();
const nippleContainer = useRef<HTMLElement>();
const { containerRef } = useEnvironment();
useEffect(() => {
if (containerRef.current) {
nippleContainer.current = document.createElement("div");
nippleContainer.current.style.position = "fixed";
nippleContainer.current.style.left = "0";
nippleContainer.current.style.bottom = "0";
nippleContainer.current.style.width = "40%";
nippleContainer.current.style.maxWidth = "160px";
nippleContainer.current.style.height = "25%";
nippleContainer.current.style.height = "160px";
nippleContainer.current.style.zIndex = "5";
// add class identifier to nippleContainer to identify touchEvents
nippleContainer.current.classList.add("nipple-container");
containerRef.current.appendChild(nippleContainer.current);
nipple.current = nipplejs.create({
zone: nippleContainer.current,
mode: "static",
position: { left: "50%", top: "50%" },
color: "#fff",
size: 120,
restOpacity: 0.75,
});
nipple.current.on("move", (evt, data) => {
// i kinda pulled 60 out of my ass tbh
const x = (data.distance / 60) * Math.cos(data.angle.radian);
const y = (-data.distance / 60) * Math.sin(data.angle.radian);
direction.current.set(x, y, 0);
});
nipple.current.on("end", () => {
direction.current.set(0, 0, 0);
});
return () => {
if (nipple.current) nipple.current.destroy();
};
}
}, []);
const nippleStyles = css`
.nipple-container > * > .front,
.nipple-container > * > .back {
background: radial-gradient(white, white 64%, black 86%) !important;
}
`;
return <Global styles={nippleStyles} />;
}
Example #18
Source File: canvas-loading-screen.tsx From utopia with MIT License | 5 votes |
CanvasLoadingScreen = React.memo(() => {
const colorTheme = useColorTheme()
return (
<React.Fragment>
<Global
styles={css`
@keyframes placeholderShimmer {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
.shimmer {
color: transparent;
animation-name: placeholderShimmer;
animation-duration: 1.25s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
background: #f6f6f6;
background: linear-gradient(to right, #f6f6f6 8%, #f0f0f0 18%, #f6f6f6 33%);
background-size: 800px 104px;
position: relative;
}
`}
/>
<div
id='canvas-container-loading'
style={{ height: '100%', width: '100%', backgroundColor: colorTheme.bg1.value }}
>
<div
className='shimmer'
style={{
position: 'absolute',
left: BaseCanvasOffsetLeftPane.x,
top: BaseCanvasOffsetLeftPane.y,
width: 375,
height: 812,
}}
></div>
</div>
</React.Fragment>
)
})
Example #19
Source File: NormalizeCSS.tsx From mantine with MIT License | 5 votes |
export function NormalizeCSS() {
return <Global styles={styles} />;
}
Example #20
Source File: EmbeddedView.tsx From atlas with GNU General Public License v3.0 | 5 votes |
EmbeddedGlobalStyles: React.FC<GlobalStyleProps> = ({ additionalStyles }) => {
const additionalStylesArray = additionalStyles
? Array.isArray(additionalStyles)
? additionalStyles
: [additionalStyles]
: []
return <Global styles={[globalStyles, ...additionalStylesArray]} />
}
Example #21
Source File: GlobalStyles.tsx From frontend-v1 with GNU Affero General Public License v3.0 | 5 votes |
GlobalStyles = () => <Global styles={globalStyles} />
Example #22
Source File: Puzzle.tsx From crosshare with GNU Affero General Public License v3.0 | 4 votes |
Puzzle = ({
loadingPlayState,
puzzle,
play,
...props
}: PuzzleProps & AuthPropsOptional) => {
const [state, dispatch] = useReducer(
puzzleReducer,
{
type: 'puzzle',
wasEntryClick: false,
active: { col: 0, row: 0, dir: Direction.Across },
grid: addClues(
fromCells({
mapper: (e) => e,
width: puzzle.size.cols,
height: puzzle.size.rows,
cells: play
? play.g
: puzzle.grid.map((s) => (s === BLOCK ? BLOCK : ' ')),
vBars: new Set(puzzle.vBars),
hBars: new Set(puzzle.hBars),
allowBlockEditing: false,
highlighted: new Set(puzzle.highlighted),
highlight: puzzle.highlight,
hidden: new Set(puzzle.hidden),
}),
puzzle.clues
),
showExtraKeyLayout: false,
answers: puzzle.grid,
alternateSolutions: puzzle.alternateSolutions,
verifiedCells: new Set<number>(play ? play.vc : []),
wrongCells: new Set<number>(play ? play.wc : []),
revealedCells: new Set<number>(play ? play.rc : []),
downsOnly: play?.do || false,
isEnteringRebus: false,
rebusValue: '',
success: play ? play.f : false,
ranSuccessEffects: play ? play.f : false,
filled: false,
autocheck: false,
dismissedKeepTrying: false,
dismissedSuccess: false,
moderating: false,
showingEmbedOverlay: false,
displaySeconds: play ? play.t : 0,
bankedSeconds: play ? play.t : 0,
ranMetaSubmitEffects: false,
...(play &&
play.ct_rv && {
contestRevealed: true,
contestSubmitTime: play.ct_t?.toMillis(),
}),
...(play &&
play.ct_sub && {
ranMetaSubmitEffects: true,
contestPriorSubmissions: play.ct_pr_subs,
contestDisplayName: play.ct_n,
contestSubmission: play.ct_sub,
contestEmail: play.ct_em,
contestSubmitTime: play.ct_t?.toMillis(),
}),
currentTimeWindowStart: 0,
didCheat: play ? play.ch : false,
clueView: false,
cellsUpdatedAt: play ? play.ct : puzzle.grid.map(() => 0),
cellsIterationCount: play ? play.uc : puzzle.grid.map(() => 0),
cellsEverMarkedWrong: new Set<number>(play ? play.we : []),
loadedPlayState: !loadingPlayState,
waitToResize: true,
isEditable(cellIndex) {
return !this.verifiedCells.has(cellIndex) && !this.success;
},
},
advanceActiveToNonBlock
);
const authContext = useContext(AuthContext);
useEffect(() => {
if (!authContext.notifications?.length) {
return;
}
for (const notification of authContext.notifications) {
if (notification.r) {
// shouldn't be possible but be defensive
continue;
}
if (!isNewPuzzleNotification(notification)) {
continue;
}
if (notification.p === puzzle.id) {
App.firestore()
.collection('n')
.doc(notification.id)
.update({ r: true });
return;
}
}
}, [authContext.notifications, puzzle.id]);
useEffect(() => {
if (loadingPlayState === false) {
const action: LoadPlayAction = {
type: 'LOADPLAY',
play: play,
prefs: props.prefs,
isAuthor: props.user ? props.user.uid === puzzle.authorId : false,
};
dispatch(action);
}
}, [loadingPlayState, play, props.user, props.prefs, puzzle.authorId]);
// Every (unpaused) second dispatch a tick action which updates the display time
useEffect(() => {
function tick() {
if (state.currentTimeWindowStart) {
dispatch({ type: 'TICKACTION' });
}
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [state.currentTimeWindowStart, dispatch]);
// Pause when page goes out of focus
function prodPause() {
if (process.env.NODE_ENV !== 'development') {
dispatch({ type: 'PAUSEACTION' });
writePlayToDBIfNeeded();
}
}
useEventListener('blur', prodPause);
const [muted, setMuted] = usePersistedBoolean('muted', false);
const [toggleKeyboard, setToggleKeyboard] = usePersistedBoolean(
'keyboard',
false
);
// Set up music player for success song
const [audioContext, initAudioContext] = useContext(CrosshareAudioContext);
const playSuccess = useRef<(() => void) | null>(null);
useEffect(() => {
if (!audioContext) {
return initAudioContext();
}
if (!playSuccess.current && !muted && audioContext) {
fetch('/success.mp3')
.then((response) => response.arrayBuffer())
.then((buffer) => {
audioContext.decodeAudioData(buffer, (audioBuffer) => {
playSuccess.current = () => {
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();
};
});
});
}
}, [muted, audioContext, initAudioContext]);
const writePlayToDBIfNeeded = useCallback(
async (user?: firebase.User) => {
console.log('doing write play');
if (!state.loadedPlayState) {
return;
}
if (puzzle.contestAnswers?.length) {
// For a meta we need to have run both to skip
if (state.ranSuccessEffects && state.ranMetaSubmitEffects) {
return;
}
} else {
// For a reg puzzle skip if success effects have run
if (state.ranSuccessEffects) {
return;
}
}
const u = user || props.user;
if (!u) {
return;
}
if (!isDirty(u, puzzle.id)) {
return;
}
writePlayToDB(u, puzzle.id)
.then(() => {
console.log('Finished writing play state to db');
})
.catch((reason) => {
console.error('Failed to write play: ', reason);
});
},
[
puzzle.id,
puzzle.contestAnswers,
props.user,
state.ranMetaSubmitEffects,
state.ranSuccessEffects,
state.loadedPlayState,
]
);
const cachePlayForUser = useCallback(
(user: firebase.User | undefined) => {
if (!state.loadedPlayState) {
return;
}
const updatedAt = TimestampClass.now();
const playTime =
state.currentTimeWindowStart === 0
? state.bankedSeconds
: state.bankedSeconds +
(new Date().getTime() - state.currentTimeWindowStart) / 1000;
const playForUser: PlayWithoutUserT = {
c: puzzle.id,
n: puzzle.title,
ua: updatedAt,
g: Array.from(state.grid.cells),
ct: Array.from(state.cellsUpdatedAt),
uc: Array.from(state.cellsIterationCount),
vc: Array.from(state.verifiedCells),
wc: Array.from(state.wrongCells),
we: Array.from(state.cellsEverMarkedWrong),
rc: Array.from(state.revealedCells),
t: playTime,
ch: state.didCheat,
do: state.downsOnly,
f: state.success,
...(state.contestRevealed && {
ct_rv: state.contestRevealed,
ct_t:
state.contestSubmitTime !== undefined
? TimestampClass.fromMillis(state.contestSubmitTime)
: undefined,
ct_n: state.contestDisplayName,
}),
...(state.contestSubmission && {
ct_sub: state.contestSubmission,
ct_pr_subs: state.contestPriorSubmissions || [],
ct_t:
state.contestSubmitTime !== undefined
? TimestampClass.fromMillis(state.contestSubmitTime)
: undefined,
ct_n: state.contestDisplayName,
...(state.contestEmail && {
ct_em: state.contestEmail,
}),
}),
};
cachePlay(user, puzzle.id, playForUser);
},
[
state.downsOnly,
state.loadedPlayState,
puzzle.id,
state.cellsEverMarkedWrong,
state.cellsIterationCount,
state.cellsUpdatedAt,
state.didCheat,
state.grid.cells,
state.revealedCells,
state.success,
state.verifiedCells,
state.wrongCells,
puzzle.title,
state.bankedSeconds,
state.currentTimeWindowStart,
state.contestSubmission,
state.contestSubmitTime,
state.contestEmail,
state.contestDisplayName,
state.contestRevealed,
state.contestPriorSubmissions,
]
);
useEffect(() => {
cachePlayForUser(props.user);
}, [props.user, cachePlayForUser]);
const router = useRouter();
useEffect(() => {
const listener = () => {
writePlayToDBIfNeeded();
};
window.addEventListener('beforeunload', listener);
router.events.on('routeChangeStart', listener);
return () => {
window.removeEventListener('beforeunload', listener);
router.events.off('routeChangeStart', listener);
};
}, [writePlayToDBIfNeeded, router]);
const { addToast } = useSnackbar();
useEffect(() => {
if (
(state.contestSubmission || state.contestRevealed) &&
!state.ranMetaSubmitEffects
) {
const action: RanMetaSubmitEffectsAction = { type: 'RANMETASUBMIT' };
dispatch(action);
if (props.user) {
cachePlayForUser(props.user);
writePlayToDBIfNeeded(props.user);
} else {
signInAnonymously().then((u) => {
cachePlayForUser(u);
writePlayToDBIfNeeded(u);
});
}
}
}, [
cachePlayForUser,
state.contestSubmission,
state.contestRevealed,
state.ranMetaSubmitEffects,
props.user,
writePlayToDBIfNeeded,
]);
useEffect(() => {
if (state.success && !state.ranSuccessEffects) {
const action: RanSuccessEffectsAction = { type: 'RANSUCCESS' };
dispatch(action);
if (props.user) {
cachePlayForUser(props.user);
writePlayToDBIfNeeded(props.user);
} else {
signInAnonymously().then((u) => {
cachePlayForUser(u);
writePlayToDBIfNeeded(u);
});
}
let delay = 0;
if (state.bankedSeconds <= 60) {
addToast('? Solved in under a minute!');
delay += 500;
}
if (!state.didCheat && state.downsOnly) {
addToast('? Solved downs-only!', delay);
} else if (!state.didCheat) {
addToast('? Solved without check/reveal!', delay);
}
if (!muted && playSuccess.current) {
playSuccess.current();
}
}
}, [
addToast,
cachePlayForUser,
muted,
props.user,
state.bankedSeconds,
state.didCheat,
state.downsOnly,
state.ranSuccessEffects,
state.success,
writePlayToDBIfNeeded,
]);
const physicalKeyboardHandler = useCallback(
(e: KeyboardEvent) => {
// Disable keyboard when paused / loading play
if (!(state.success && state.dismissedSuccess)) {
if (loadingPlayState || !state.currentTimeWindowStart) {
return;
}
}
const mkey = fromKeyboardEvent(e);
if (isSome(mkey)) {
const kpa: KeypressAction = { type: 'KEYPRESS', key: mkey.value };
dispatch(kpa);
e.preventDefault();
}
},
[
dispatch,
loadingPlayState,
state.currentTimeWindowStart,
state.success,
state.dismissedSuccess,
]
);
useEventListener('keydown', physicalKeyboardHandler);
const pasteHandler = useCallback(
(e: ClipboardEvent) => {
const tagName = (e.target as HTMLElement)?.tagName?.toLowerCase();
if (tagName === 'textarea' || tagName === 'input') {
return;
}
const pa: PasteAction = {
type: 'PASTE',
content: e.clipboardData?.getData('Text') || '',
};
dispatch(pa);
e.preventDefault();
},
[dispatch]
);
useEventListener('paste', pasteHandler);
let [entry, cross] = entryAndCrossAtPosition(state.grid, state.active);
if (entry === null && cross !== null) {
dispatch({ type: 'CHANGEDIRECTION' });
[entry, cross] = [cross, entry];
}
const keyboardHandler = useCallback(
(key: string) => {
const mkey = fromKeyString(key);
if (isSome(mkey)) {
const kpa: KeypressAction = { type: 'KEYPRESS', key: mkey.value };
dispatch(kpa);
}
},
[dispatch]
);
const { acrossEntries, downEntries } = useMemo(() => {
return {
acrossEntries: state.grid.entries.filter(
(e) => e.direction === Direction.Across
),
downEntries: state.grid.entries.filter(
(e) => e.direction === Direction.Down
),
};
}, [state.grid.entries]);
const isEmbed = useContext(EmbedContext);
/* `clueMap` is a map from ENTRYWORD => '5D: This is the clue' - we use this
* for comment clue tooltips. */
const clueMap = useMemo(() => {
return getEntryToClueMap(state.grid, state.answers);
}, [state.grid, state.answers]);
/* `refs` is a set of referenced entry indexes for each entry in the grid - we use this
* for grid highlights when an entry is selected.
*
* `refPositions` is an array for each entry of [reffedEntry, clueTextStart, clueTextEnd] tuples
*/
const [refs, refPositions] = useMemo(() => {
return getRefs(state.grid);
}, [state.grid]);
const scrollToCross = useMatchMedia(SMALL_AND_UP_RULES);
const overlayBaseProps: PuzzleOverlayBaseProps = {
publishTime: puzzle.isPrivateUntil || puzzle.publishTime,
coverImage: props.coverImage,
profilePicture: props.profilePicture,
downsOnly: state.downsOnly,
clueMap: clueMap,
user: props.user,
nextPuzzle: props.nextPuzzle,
puzzle: puzzle,
isMuted: muted,
solveTime: state.displaySeconds,
didCheat: state.didCheat,
dispatch: dispatch,
};
let puzzleView: ReactNode;
const entryIdx = entryIndexAtPosition(state.grid, state.active);
let refed: Set<number> = new Set();
if (entryIdx !== null) {
refed = refs[entryIdx] || new Set();
}
const shouldConceal =
state.currentTimeWindowStart === 0 &&
!(state.success && state.dismissedSuccess);
if (state.clueView) {
puzzleView = (
<TwoCol
left={
<ClueList
isEnteringRebus={state.isEnteringRebus}
rebusValue={state.rebusValue}
wasEntryClick={state.wasEntryClick}
allEntries={state.grid.entries}
refPositions={refPositions}
refed={refed}
dimCompleted={true}
active={state.active}
grid={state.grid}
showEntries={true}
conceal={shouldConceal}
header={t`Across`}
entries={acrossEntries}
current={entry?.index}
cross={cross?.index}
scrollToCross={scrollToCross}
dispatch={dispatch}
downsOnly={state.downsOnly && !state.success}
/>
}
right={
<ClueList
isEnteringRebus={state.isEnteringRebus}
rebusValue={state.rebusValue}
wasEntryClick={state.wasEntryClick}
allEntries={state.grid.entries}
refPositions={refPositions}
refed={refed}
dimCompleted={true}
active={state.active}
grid={state.grid}
showEntries={true}
conceal={shouldConceal}
header={t`Down`}
entries={downEntries}
current={entry?.index}
cross={cross?.index}
scrollToCross={scrollToCross}
dispatch={dispatch}
downsOnly={state.downsOnly && !state.success}
/>
}
/>
);
} else {
puzzleView = (
<SquareAndCols
leftIsActive={state.active.dir === Direction.Across}
waitToResize={state.waitToResize}
dispatch={dispatch}
aspectRatio={state.grid.width / state.grid.height}
square={(width: number, _height: number) => {
return (
<GridView
isEnteringRebus={state.isEnteringRebus}
rebusValue={state.rebusValue}
squareWidth={width}
grid={state.grid}
active={state.active}
entryRefs={refs}
dispatch={dispatch}
revealedCells={state.revealedCells}
verifiedCells={state.verifiedCells}
wrongCells={state.wrongCells}
showAlternates={state.success ? state.alternateSolutions : null}
answers={state.answers}
/>
);
}}
header={
<div
css={{
height: SQUARE_HEADER_HEIGHT,
fontSize: 18,
lineHeight: '24px',
backgroundColor: 'var(--lighter)',
overflowY: 'scroll',
scrollbarWidth: 'none',
display: 'flex',
}}
>
{entry ? (
<div css={{ margin: 'auto 1em' }}>
<span
css={{
fontWeight: 'bold',
paddingRight: '0.5em',
}}
>
{entry.labelNumber}
{entry.direction === Direction.Across ? 'A' : 'D'}
</span>
<span
css={{
color: shouldConceal ? 'transparent' : 'var(--text)',
textShadow: shouldConceal
? '0 0 1em var(--conceal-text)'
: '',
}}
>
<ClueText
refPositions={refPositions}
entryIndex={entry.index}
allEntries={state.grid.entries}
grid={state.grid}
downsOnly={state.downsOnly && !state.success}
/>
</span>
</div>
) : (
''
)}
</div>
}
left={
<ClueList
wasEntryClick={state.wasEntryClick}
scrollToCross={scrollToCross}
allEntries={state.grid.entries}
refPositions={refPositions}
refed={refed}
dimCompleted={true}
active={state.active}
grid={state.grid}
showEntries={false}
conceal={shouldConceal}
header={t`Across`}
entries={acrossEntries}
current={entry?.index}
cross={cross?.index}
dispatch={dispatch}
downsOnly={state.downsOnly && !state.success}
/>
}
right={
<ClueList
wasEntryClick={state.wasEntryClick}
scrollToCross={scrollToCross}
allEntries={state.grid.entries}
refPositions={refPositions}
refed={refed}
dimCompleted={true}
active={state.active}
grid={state.grid}
showEntries={false}
conceal={shouldConceal}
header={t`Down`}
entries={downEntries}
current={entry?.index}
cross={cross?.index}
dispatch={dispatch}
downsOnly={state.downsOnly && !state.success}
/>
}
/>
);
}
const checkRevealMenus = useMemo(
() => (
<>
<TopBarDropDown icon={<FaEye />} text={t`Reveal`}>
{() => (
<>
<TopBarDropDownLink
icon={<RevealSquare />}
text={t`Reveal Square`}
onClick={() => {
const ca: CheatAction = {
type: 'CHEAT',
unit: CheatUnit.Square,
isReveal: true,
};
dispatch(ca);
}}
/>
<TopBarDropDownLink
icon={<RevealEntry />}
text={t`Reveal Word`}
onClick={() => {
const ca: CheatAction = {
type: 'CHEAT',
unit: CheatUnit.Entry,
isReveal: true,
};
dispatch(ca);
}}
/>
<TopBarDropDownLink
icon={<RevealPuzzle />}
text={t`Reveal Puzzle`}
onClick={() => {
const ca: CheatAction = {
type: 'CHEAT',
unit: CheatUnit.Puzzle,
isReveal: true,
};
dispatch(ca);
}}
/>
</>
)}
</TopBarDropDown>
{!state.autocheck ? (
<TopBarDropDown icon={<FaCheck />} text={t`Check`}>
{() => (
<>
<TopBarDropDownLink
icon={<FaCheckSquare />}
text={t`Autocheck`}
onClick={() => {
const action: ToggleAutocheckAction = {
type: 'TOGGLEAUTOCHECK',
};
dispatch(action);
}}
/>
<TopBarDropDownLink
icon={<CheckSquare />}
text={t`Check Square`}
onClick={() => {
const ca: CheatAction = {
type: 'CHEAT',
unit: CheatUnit.Square,
};
dispatch(ca);
}}
/>
<TopBarDropDownLink
icon={<CheckEntry />}
text={t`Check Word`}
onClick={() => {
const ca: CheatAction = {
type: 'CHEAT',
unit: CheatUnit.Entry,
};
dispatch(ca);
}}
/>
<TopBarDropDownLink
icon={<CheckPuzzle />}
text={t`Check Puzzle`}
onClick={() => {
const ca: CheatAction = {
type: 'CHEAT',
unit: CheatUnit.Puzzle,
};
dispatch(ca);
}}
/>
</>
)}
</TopBarDropDown>
) : (
<TopBarLink
icon={<FaCheckSquare />}
text={t`Autochecking`}
onClick={() => {
const action: ToggleAutocheckAction = { type: 'TOGGLEAUTOCHECK' };
dispatch(action);
}}
/>
)}
</>
),
[state.autocheck]
);
const moreMenu = useMemo(
() => (
<>
<TopBarDropDown icon={<FaEllipsisH />} text={t`More`}>
{() => (
<>
{!state.success ? (
<TopBarDropDownLink
icon={<Rebus />}
text={t`Enter Rebus`}
shortcutHint={<EscapeKey />}
onClick={() => {
const kpa: KeypressAction = {
type: 'KEYPRESS',
key: { k: KeyK.Escape },
};
dispatch(kpa);
}}
/>
) : (
''
)}
{muted ? (
<TopBarDropDownLink
icon={<FaVolumeUp />}
text={t`Unmute`}
onClick={() => setMuted(false)}
/>
) : (
<TopBarDropDownLink
icon={<FaVolumeMute />}
text={t`Mute`}
onClick={() => setMuted(true)}
/>
)}
<TopBarDropDownLink
icon={<FaKeyboard />}
text={t`Toggle Keyboard`}
onClick={() => setToggleKeyboard(!toggleKeyboard)}
/>
{props.isAdmin ? (
<>
<TopBarDropDownLink
icon={<FaGlasses />}
text="Moderate"
onClick={() => dispatch({ type: 'TOGGLEMODERATING' })}
/>
<TopBarDropDownLinkA
href="/admin"
icon={<FaUserLock />}
text="Admin"
/>
</>
) : (
''
)}
{props.isAdmin || props.user?.uid === puzzle.authorId ? (
<>
<TopBarDropDownLinkA
href={`/stats/${puzzle.id}`}
icon={<IoMdStats />}
text={t`Stats`}
/>
<TopBarDropDownLinkA
href={`/edit/${puzzle.id}`}
icon={<FaEdit />}
text={t`Edit`}
/>
{!isEmbed ? (
<TopBarDropDownLink
icon={<ImEmbed />}
text={t`Embed`}
onClick={() => dispatch({ type: 'TOGGLEEMBEDOVERLAY' })}
/>
) : (
''
)}
</>
) : (
''
)}
<TopBarDropDownLinkSimpleA
href={'/api/pdf/' + puzzle.id}
icon={<FaPrint />}
text={t`Print Puzzle`}
/>
{puzzle.hBars.length || puzzle.vBars.length ? (
''
) : (
<TopBarDropDownLinkSimpleA
href={'/api/puz/' + puzzle.id}
icon={<FaRegFile />}
text={t`Download .puz File`}
/>
)}
<TopBarDropDownLinkA
href="/account"
icon={<FaUser />}
text={t`Account / Settings`}
/>
<TopBarDropDownLinkA
href="/construct"
icon={<FaHammer />}
text={t`Construct a Puzzle`}
/>
</>
)}
</TopBarDropDown>
</>
),
[
muted,
props.isAdmin,
props.user?.uid,
puzzle,
setMuted,
state.success,
toggleKeyboard,
setToggleKeyboard,
isEmbed,
]
);
const description = puzzle.blogPost
? puzzle.blogPost.slice(0, 160) + '...'
: puzzle.clues.map(getClueText).slice(0, 10).join('; ');
const locale = router.locale || 'en';
return (
<>
<Global
styles={FULLSCREEN_CSS}
/>
<Head>
<title>{puzzle.title} | Crosshare crossword puzzle</title>
<I18nTags
locale={locale}
canonicalPath={`/crosswords/${puzzle.id}/${slugify(puzzle.title)}`}
/>
<meta key="og:title" property="og:title" content={puzzle.title} />
<meta
key="og:description"
property="og:description"
content={description}
/>
<meta
key="og:image"
property="og:image"
content={'https://crosshare.org/api/ogimage/' + puzzle.id}
/>
<meta key="og:image:width" property="og:image:width" content="1200" />
<meta key="og:image:height" property="og:image:height" content="630" />
<meta
key="og:image:alt"
property="og:image:alt"
content="An image of the puzzle grid"
/>
<meta key="description" name="description" content={description} />
</Head>
<div
css={{
display: 'flex',
flexDirection: 'column',
height: '100%',
}}
>
<div css={{ flex: 'none' }}>
<TopBar title={puzzle.title}>
{!loadingPlayState ? (
!state.success ? (
<>
<TopBarLink
icon={<FaPause />}
hoverText={t`Pause Game`}
text={timeString(state.displaySeconds, true)}
onClick={() => {
dispatch({ type: 'PAUSEACTION' });
writePlayToDBIfNeeded();
}}
keepText={true}
/>
<TopBarLink
icon={state.clueView ? <SpinnerFinished /> : <FaListOl />}
text={state.clueView ? t`Grid` : t`Clues`}
onClick={() => {
const a: ToggleClueViewAction = {
type: 'TOGGLECLUEVIEW',
};
dispatch(a);
}}
/>
{checkRevealMenus}
{moreMenu}
</>
) : (
<>
<TopBarLink
icon={<FaComment />}
text={
puzzle.contestAnswers?.length
? !isMetaSolution(
state.contestSubmission,
puzzle.contestAnswers
) && !state.contestRevealed
? t`Contest Prompt / Submission`
: t`Comments / Leaderboard`
: t`Show Comments`
}
onClick={() => dispatch({ type: 'UNDISMISSSUCCESS' })}
/>
{moreMenu}
</>
)
) : (
moreMenu
)}
</TopBar>
</div>
{state.filled && !state.success && !state.dismissedKeepTrying ? (
<KeepTryingOverlay dispatch={dispatch} />
) : (
''
)}
{state.success && !state.dismissedSuccess ? (
<PuzzleOverlay
{...overlayBaseProps}
overlayType={OverlayType.Success}
contestSubmission={state.contestSubmission}
contestHasPrize={puzzle.contestHasPrize}
contestRevealed={state.contestRevealed}
contestRevealDelay={puzzle.contestRevealDelay}
/>
) : (
''
)}
{state.moderating ? (
<ModeratingOverlay puzzle={puzzle} dispatch={dispatch} />
) : (
''
)}
{state.showingEmbedOverlay && props.user ? (
<EmbedOverlay user={props.user} puzzle={puzzle} dispatch={dispatch} />
) : (
''
)}
{state.currentTimeWindowStart === 0 &&
!state.success &&
!(state.filled && !state.dismissedKeepTrying) ? (
state.bankedSeconds === 0 ? (
<PuzzleOverlay
{...overlayBaseProps}
overlayType={OverlayType.BeginPause}
dismissMessage={t`Begin Puzzle`}
message={t`Ready to get started?`}
loadingPlayState={loadingPlayState || !state.loadedPlayState}
/>
) : (
<PuzzleOverlay
{...overlayBaseProps}
overlayType={OverlayType.BeginPause}
dismissMessage={t`Resume`}
message={t`Your puzzle is paused`}
loadingPlayState={loadingPlayState || !state.loadedPlayState}
/>
)
) : (
''
)}
<div
css={{
flex: '1 1 auto',
overflow: 'scroll',
scrollbarWidth: 'none',
position: 'relative',
}}
>
{puzzleView}
</div>
<div css={{ flex: 'none', width: '100%' }}>
<Keyboard
toggleKeyboard={toggleKeyboard}
keyboardHandler={keyboardHandler}
muted={muted}
showExtraKeyLayout={state.showExtraKeyLayout}
includeBlockKey={false}
/>
</div>
</div>
</>
);
}
Example #23
Source File: CategoryVideos.tsx From atlas with GNU General Public License v3.0 | 4 votes |
CategoryVideos: React.FC<{ categoryId: string }> = ({ categoryId }) => {
const smMatch = useMediaMatch('sm')
const mdMatch = useMediaMatch('md')
const containerRef = useRef<HTMLDivElement>(null)
const scrollWhenFilterChange = useRef(false)
const filtersBarLogic = useFiltersBar()
const {
setVideoWhereInput,
filters: { setIsFiltersOpen, isFiltersOpen, language, setLanguage },
canClearFilters: { canClearAllFilters, clearAllFilters },
videoWhereInput,
} = filtersBarLogic
const [sortVideosBy, setSortVideosBy] = useState<VideoOrderByInput>(VideoOrderByInput.CreatedAtDesc)
const { videoCount } = useVideoCount({
where: { ...videoWhereInput, category: { id_eq: categoryId } },
})
useEffect(() => {
if (scrollWhenFilterChange.current) {
containerRef.current?.scrollIntoView()
}
// account for videoWhereInput initialization
if (!isEqual(videoWhereInput, {})) {
scrollWhenFilterChange.current = true
}
}, [videoWhereInput])
const handleSorting = (value?: VideoOrderByInput | null) => {
if (value) {
setSortVideosBy(value)
}
}
const handleFilterClick = () => {
setIsFiltersOpen((value) => !value)
}
const handleSelectLanguage = useCallback(
(language: string | null | undefined) => {
setLanguage(language)
setVideoWhereInput((value) => ({
...value,
language:
language === 'undefined'
? undefined
: {
iso_eq: language,
},
}))
},
[setLanguage, setVideoWhereInput]
)
const topbarHeight = mdMatch ? 80 : 64
const sortingNode = (
<StyledSelect
size="small"
helperText={null}
value={sortVideosBy}
valueLabel="Sort by: "
items={ADAPTED_SORT_OPTIONS}
onChange={handleSorting}
/>
)
return (
<>
<Global styles={categoryGlobalStyles} />
<Container ref={containerRef}>
<StyledSticky style={{ top: topbarHeight - 1 }}>
<ControlsContainer>
<GridItem colSpan={{ base: 2, sm: 1 }}>
<Text variant={mdMatch ? 'h500' : 'h400'}>
All videos {videoCount !== undefined && `(${videoCount})`}
</Text>
</GridItem>
{smMatch ? (
<StyledSelect
onChange={handleSelectLanguage}
size="small"
value={language}
items={SELECT_LANGUAGE_ITEMS}
/>
) : (
sortingNode
)}
<div>
<Button
badge={canClearAllFilters}
variant="secondary"
icon={<SvgActionFilters />}
onClick={handleFilterClick}
>
Filters
</Button>
</div>
{smMatch && sortingNode}
</ControlsContainer>
<FiltersBar {...filtersBarLogic} activeFilters={['date', 'length', 'other', 'language']} />
</StyledSticky>
<StyledVideoGrid
isFiltersOpen={isFiltersOpen}
emptyFallback={
<FallbackWrapper>
<EmptyFallback
title="No videos found"
subtitle="Please, try changing your filtering criteria"
button={
<Button onClick={clearAllFilters} variant="secondary">
Clear all filters
</Button>
}
/>
</FallbackWrapper>
}
videoWhereInput={{ ...videoWhereInput, category: { id_eq: categoryId } }}
orderBy={sortVideosBy}
onDemandInfinite
/>
</Container>
</>
)
}
Example #24
Source File: _app.tsx From crosshare with GNU Affero General Public License v3.0 | 4 votes |
// `err` is a workaround for https://github.com/vercel/next.js/issues/8592
export default function CrosshareApp({
Component,
pageProps,
err,
}: AppProps & { err: Error }): JSX.Element {
let authStatus = useAuth();
const [loading, setLoading] = useState(false);
if (typeof window === 'undefined') {
authStatus = {
loading: true,
isAdmin: false,
isPatron: false,
notifications: [],
};
}
const [audioContext, setAudioContext] = useState<AudioContext | null>(null);
const initAudioContext = useCallback(() => {
if (!audioContext) {
const constructor =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.AudioContext || (window as any).webkitAudioContext;
setAudioContext(new constructor());
}
}, [audioContext, setAudioContext]);
useEffect(() => {
const handleStart = () => {
setLoading(true);
};
const handleError = () => {
setLoading(false);
};
const handleRouteChange = (url: string) => {
setLoading(false);
gtag.pageview(url);
};
NextJSRouter.events.on('routeChangeStart', handleStart);
NextJSRouter.events.on('routeChangeComplete', handleRouteChange);
NextJSRouter.events.on('routeChangeError', handleError);
return () => {
NextJSRouter.events.off('routeChangeStart', handleStart);
NextJSRouter.events.off('routeChangeComplete', handleRouteChange);
NextJSRouter.events.off('routeChangeError', handleError);
};
}, []);
const { locale } = useRouter();
const firstRender = useRef(true);
if (firstRender.current) {
firstRender.current = false;
if (pageProps.translation) {
i18n.load(locale || 'en', pageProps.translation);
i18n.activate(locale || 'en');
} else {
i18n.activate('en');
}
}
useEffect(() => {
if (pageProps.translation && locale) {
i18n.load(locale, pageProps.translation);
i18n.activate(locale);
}
}, [locale, pageProps.translation]);
useEffect(() => {
const resize = () => {
document.documentElement.style.setProperty(
'--vh',
`${window.innerHeight}px`
);
};
resize();
window.addEventListener('resize', resize);
return () => {
window.removeEventListener('resize', resize);
};
}, []);
return (
<>
<Head>
<title>
Crosshare - Free Crossword Constructor and Daily Mini Crossword
Puzzles
</title>
<meta
key="og:title"
property="og:title"
content="Crosshare Crosswords"
/>
<meta
key="description"
name="description"
content="Crosshare is a community for crossword constructors and solvers. Each day we post a new mini crossword puzzle you can play for free."
/>
<meta
key="og:description"
property="og:description"
content="Crosshare is a community for crossword constructors and solvers. Each day we post a new mini crossword puzzle you can play for free."
/>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, height=device-height"
/>
<meta property="fb:pages" content="100687178303443" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@crosshareapp" />
<meta
key="og:image"
property="og:image"
content="https://crosshare.org/apple-splash-1334-750.png"
/>
<meta property="og:image:type" content="image/png" />
<meta key="og:image:width" property="og:image:width" content="1334" />
<meta key="og:image:height" property="og:image:height" content="750" />
<meta
key="og:image:alt"
property="og:image:alt"
content="The crosshare logo"
/>
</Head>
<Global
styles={{
html: [
colorTheme(PRIMARY, LINK, false, false),
{
'@media (prefers-color-scheme: dark)': colorTheme(
PRIMARY,
LINK,
true,
false
),
},
],
'body.dark-mode': colorTheme(PRIMARY, LINK, true, false),
'body.light-mode': colorTheme(PRIMARY, LINK, false, false),
}}
/>
<CrosshareAudioContext.Provider value={[audioContext, initAudioContext]}>
<AuthContext.Provider value={authStatus}>
<SnackbarProvider>
<BrowserWarning />
<I18nProvider i18n={i18n}>
<Component {...pageProps} err={err} />
</I18nProvider>
</SnackbarProvider>
</AuthContext.Provider>
</CrosshareAudioContext.Provider>
<Snackbar message="Loading..." isOpen={loading} />
</>
);
}
Example #25
Source File: projects-page-component.tsx From utopia with MIT License | 4 votes |
render() {
const hasProjects = this.state.filteredProjects.length > 0
const hasLocalProjects = this.state.filteredLocalProjects.length > 0
const visibleProjectCount =
this.state.filteredProjects.length + this.state.filteredLocalProjects.length
return (
<React.Fragment>
<Global
styles={{
html: {
height: '100%',
},
body: {
overflow: 'hidden',
height: '100%',
margin: 0,
fontFamily: 'Inter, sans-serif',
fontSize: 13,
color: colors.default,
},
}}
/>
<FlexColumn
onMouseDown={this.clearSelectedProject}
style={{
alignItems: 'flex-start',
height: '100vh',
}}
>
<FlexRow
data-label='Navigation'
style={{
width: '100%',
boxShadow: `inset 0px -1px 0px 0px ${colors.default}`,
overflow: 'visible',
cursor: 'pointer',
}}
>
<FlexNavItem selected={this.state.mode === 'projects'} onClick={this.setProjectsMode}>
Projects
</FlexNavItem>
<FlexNavItem selected={this.state.mode === 'filter'} onClick={this.setFilterMode}>
Search
</FlexNavItem>
</FlexRow>
<div
data-label='sticky-header'
style={{
backgroundColor: 'white',
zIndex: 99999,
paddingLeft: layout.margins.wide,
paddingRight: layout.margins.wide,
paddingTop: layout.margins.wide,
paddingBottom: layout.margins.regular,
}}
>
<div>
<H2>
Recent Projects
<span style={{ opacity: 0.3 }}>{visibleProjectCount}</span>
</H2>
</div>
<div style={{ marginTop: layout.margins.regular + 10, fontSize: 12, opacity: 0.7 }}>
<span style={{ marginRight: 60 }}>Last Edited ↧</span>
<span>Public and Private</span>
</div>
</div>
<div
style={{
background: colors.default,
color: 'white',
width: '100%',
}}
>
{this.state.mode === 'filter' ? (
<div style={{ padding: layout.margins.wide, minHeight: '80px' }}>
<input
autoFocus={true}
onChange={this.onFilterChange}
style={{
fontSize: 40,
width: '100%',
minHeight: 60,
border: 'none',
background: 'transparent',
color: 'white',
outline: 'none',
}}
placeholder='Search for project names'
value={this.state.projectTitleFilter || ''}
/>
</div>
) : null}
</div>
<FlexColumn
style={{
overflowY: 'scroll',
flexGrow: 1,
width: '100%',
alignItems: 'stretch',
}}
>
<FlexWrappingList
className='roleProjectsSection'
style={{
flexGrow: 1,
paddingTop: layout.margins.regular,
paddingLeft: layout.margins.regular,
paddingRight: layout.margins.regular,
paddingBottom: layout.margins.wide,
}}
>
{this.newProjectCard}
{hasProjects ? this.state.filteredProjects.map(this.projectComponent) : null}
{hasLocalProjects
? this.state.filteredLocalProjects.map(this.projectComponent)
: null}
</FlexWrappingList>
</FlexColumn>
</FlexColumn>
</React.Fragment>
)
}
Example #26
Source File: Builder.tsx From crosshare with GNU Affero General Public License v3.0 | 4 votes |
GridMode = ({
getMostConstrainedEntry,
reRunAutofill,
state,
dispatch,
setClueMode,
...props
}: GridModeProps) => {
const [muted, setMuted] = usePersistedBoolean('muted', false);
const [toggleKeyboard, setToggleKeyboard] = usePersistedBoolean(
'keyboard',
false
);
const { showSnackbar } = useSnackbar();
const gridRef = useRef<HTMLDivElement | null>(null);
const focusGrid = useCallback(() => {
if (gridRef.current) {
gridRef.current.focus();
}
}, []);
const physicalKeyboardHandler = useCallback(
(e: KeyboardEvent) => {
const mkey = fromKeyboardEvent(e);
if (isSome(mkey)) {
e.preventDefault();
if (mkey.value.k === KeyK.Enter && !state.isEnteringRebus) {
reRunAutofill();
return;
}
if (mkey.value.k === KeyK.Exclamation) {
const entry = getMostConstrainedEntry();
if (entry !== null) {
const ca: ClickedEntryAction = {
type: 'CLICKEDENTRY',
entryIndex: entry,
};
dispatch(ca);
}
return;
}
if (mkey.value.k === KeyK.Octothorp) {
const a: ToggleHiddenAction = { type: 'TOGGLEHIDDEN' };
dispatch(a);
}
const kpa: KeypressAction = { type: 'KEYPRESS', key: mkey.value };
dispatch(kpa);
}
},
[dispatch, reRunAutofill, state.isEnteringRebus, getMostConstrainedEntry]
);
useEventListener(
'keydown',
physicalKeyboardHandler,
gridRef.current || undefined
);
const pasteHandler = useCallback(
(e: ClipboardEvent) => {
const tagName = (e.target as HTMLElement)?.tagName?.toLowerCase();
if (tagName === 'textarea' || tagName === 'input') {
return;
}
const pa: PasteAction = {
type: 'PASTE',
content: e.clipboardData?.getData('Text') || '',
};
dispatch(pa);
e.preventDefault();
},
[dispatch]
);
useEventListener('paste', pasteHandler);
const fillLists = useMemo(() => {
let left = <></>;
let right = <></>;
const [entry, cross] = entryAndCrossAtPosition(state.grid, state.active);
let crossMatches = cross && potentialFill(cross, state.grid);
let entryMatches = entry && potentialFill(entry, state.grid);
if (
crossMatches !== null &&
entryMatches !== null &&
entry !== null &&
cross !== null
) {
/* If we have both entry + cross we now filter for only matches that'd work for both. */
const entryActiveIndex = activeIndex(state.grid, state.active, entry);
const crossActiveIndex = activeIndex(state.grid, state.active, cross);
const entryValidLetters = lettersAtIndex(entryMatches, entryActiveIndex);
const crossValidLetters = lettersAtIndex(crossMatches, crossActiveIndex);
const validLetters = (
entryValidLetters.match(
new RegExp('[' + crossValidLetters + ']', 'g')
) || []
).join('');
entryMatches = entryMatches.filter(([word]) => {
const l = word[entryActiveIndex];
return l && validLetters.indexOf(l) !== -1;
});
crossMatches = crossMatches.filter(([word]) => {
const l = word[crossActiveIndex];
return l && validLetters.indexOf(l) !== -1;
});
}
if (cross && crossMatches !== null) {
if (cross.direction === Direction.Across) {
left = (
<PotentialFillList
selected={false}
gridRef={gridRef}
header="Across"
values={crossMatches}
entryIndex={cross.index}
dispatch={dispatch}
/>
);
} else {
right = (
<PotentialFillList
selected={false}
gridRef={gridRef}
header="Down"
values={crossMatches}
entryIndex={cross.index}
dispatch={dispatch}
/>
);
}
}
if (entry && entryMatches !== null) {
if (entry.direction === Direction.Across) {
left = (
<PotentialFillList
selected={true}
gridRef={gridRef}
header="Across"
values={entryMatches}
entryIndex={entry.index}
dispatch={dispatch}
/>
);
} else {
right = (
<PotentialFillList
selected={true}
gridRef={gridRef}
header="Down"
values={entryMatches}
entryIndex={entry.index}
dispatch={dispatch}
/>
);
}
}
return { left, right };
}, [state.grid, state.active, dispatch]);
const { autofillEnabled, setAutofillEnabled } = props;
const toggleAutofillEnabled = useCallback(() => {
if (autofillEnabled) {
showSnackbar('Autofill Disabled');
}
setAutofillEnabled(!autofillEnabled);
}, [autofillEnabled, setAutofillEnabled, showSnackbar]);
const stats = useMemo(() => {
let totalLength = 0;
const lengthHistogram: Array<number> = new Array(
Math.max(state.grid.width, state.grid.height) - 1
).fill(0);
const lengthHistogramNames = lengthHistogram.map((_, i) =>
(i + 2).toString()
);
state.grid.entries.forEach((e) => {
totalLength += e.cells.length;
lengthHistogram[e.cells.length - 2] += 1;
});
const numEntries = state.grid.entries.length;
const averageLength = totalLength / numEntries;
const lettersHistogram: Array<number> = new Array(26).fill(0);
const lettersHistogramNames = lettersHistogram.map((_, i) =>
String.fromCharCode(i + 65)
);
let numBlocks = 0;
const numTotal = state.grid.width * state.grid.height;
state.grid.cells.forEach((s) => {
if (s === '.') {
numBlocks += 1;
} else {
const index = lettersHistogramNames.indexOf(s);
if (index !== -1) {
lettersHistogram[index] += 1;
}
}
});
return {
numBlocks,
numTotal,
lengthHistogram,
lengthHistogramNames,
numEntries,
averageLength,
lettersHistogram,
lettersHistogramNames,
};
}, [
state.grid.entries,
state.grid.height,
state.grid.width,
state.grid.cells,
]);
const keyboardHandler = useCallback(
(key: string) => {
const mkey = fromKeyString(key);
if (isSome(mkey)) {
const kpa: KeypressAction = { type: 'KEYPRESS', key: mkey.value };
dispatch(kpa);
}
},
[dispatch]
);
const topBarChildren = useMemo(() => {
let autofillIcon = <SpinnerDisabled />;
let autofillReverseIcon = <SpinnerWorking />;
let autofillReverseText = 'Enable Autofill';
let autofillText = 'Autofill disabled';
if (props.autofillEnabled) {
autofillReverseIcon = <SpinnerDisabled />;
autofillReverseText = 'Disable Autofill';
if (props.autofillInProgress) {
autofillIcon = <SpinnerWorking />;
autofillText = 'Autofill in progress';
} else if (props.autofilledGrid.length) {
autofillIcon = <SpinnerFinished />;
autofillText = 'Autofill complete';
} else {
autofillIcon = <SpinnerFailed />;
autofillText = "Couldn't autofill this grid";
}
}
return (
<>
<TopBarDropDown
onClose={focusGrid}
icon={autofillIcon}
text="Autofill"
hoverText={autofillText}
>
{() => (
<>
<TopBarDropDownLink
icon={autofillReverseIcon}
text={autofillReverseText}
onClick={toggleAutofillEnabled}
/>
<TopBarDropDownLink
icon={<FaSignInAlt />}
text="Jump to Most Constrained"
shortcutHint={<ExclamationKey />}
onClick={() => {
const entry = getMostConstrainedEntry();
if (entry !== null) {
const ca: ClickedEntryAction = {
type: 'CLICKEDENTRY',
entryIndex: entry,
};
dispatch(ca);
}
}}
/>
<TopBarDropDownLink
icon={<MdRefresh />}
text="Rerun Autofiller"
shortcutHint={<EnterKey />}
onClick={() => {
reRunAutofill();
}}
/>
</>
)}
</TopBarDropDown>
<TopBarLink
icon={<FaListOl />}
text="Clues"
onClick={() => setClueMode(true)}
/>
<TopBarLink
icon={<FaRegNewspaper />}
text="Publish"
onClick={() => {
const a: PublishAction = {
type: 'PUBLISH',
publishTimestamp: TimestampClass.now(),
};
dispatch(a);
}}
/>
<TopBarDropDown onClose={focusGrid} icon={<FaEllipsisH />} text="More">
{(closeDropdown) => (
<>
<NestedDropDown
onClose={focusGrid}
closeParent={closeDropdown}
icon={<FaRegPlusSquare />}
text="New Puzzle"
>
{() => <NewPuzzleForm dispatch={dispatch} />}
</NestedDropDown>
<NestedDropDown
onClose={focusGrid}
closeParent={closeDropdown}
icon={<FaFileImport />}
text="Import .puz File"
>
{() => <ImportPuzForm dispatch={dispatch} />}
</NestedDropDown>
<TopBarDropDownLink
icon={<FaRegFile />}
text="Export .puz File"
onClick={() => {
const a: SetShowDownloadLink = {
type: 'SETSHOWDOWNLOAD',
value: true,
};
dispatch(a);
}}
/>
<NestedDropDown
onClose={focusGrid}
closeParent={closeDropdown}
icon={<IoMdStats />}
text="Stats"
>
{() => (
<>
<h2>Grid</h2>
<div>
{state.gridIsComplete ? (
<FaRegCheckCircle />
) : (
<FaRegCircle />
)}{' '}
All cells should be filled
</div>
<div>
{state.hasNoShortWords ? (
<FaRegCheckCircle />
) : (
<FaRegCircle />
)}{' '}
All words should be at least three letters
</div>
<div>
{state.repeats.size > 0 ? (
<>
<FaRegCircle /> (
{Array.from(state.repeats).sort().join(', ')})
</>
) : (
<FaRegCheckCircle />
)}{' '}
No words should be repeated
</div>
<h2 css={{ marginTop: '1.5em' }}>Fill</h2>
<div>Number of words: {stats.numEntries}</div>
<div>
Mean word length: {stats.averageLength.toPrecision(3)}
</div>
<div>
Number of blocks: {stats.numBlocks} (
{((100 * stats.numBlocks) / stats.numTotal).toFixed(1)}%)
</div>
<div
css={{
marginTop: '1em',
textDecoration: 'underline',
textAlign: 'center',
}}
>
Word Lengths
</div>
<Histogram
data={stats.lengthHistogram}
names={stats.lengthHistogramNames}
/>
<div
css={{
marginTop: '1em',
textDecoration: 'underline',
textAlign: 'center',
}}
>
Letter Counts
</div>
<Histogram
data={stats.lettersHistogram}
names={stats.lettersHistogramNames}
/>
</>
)}
</NestedDropDown>
<NestedDropDown
onClose={focusGrid}
closeParent={closeDropdown}
icon={<SymmetryIcon type={state.symmetry} />}
text="Change Symmetry"
>
{() => (
<>
<TopBarDropDownLink
icon={<SymmetryRotational />}
text="Use Rotational Symmetry"
onClick={() => {
const a: SymmetryAction = {
type: 'CHANGESYMMETRY',
symmetry: Symmetry.Rotational,
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<SymmetryHorizontal />}
text="Use Horizontal Symmetry"
onClick={() => {
const a: SymmetryAction = {
type: 'CHANGESYMMETRY',
symmetry: Symmetry.Horizontal,
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<SymmetryVertical />}
text="Use Vertical Symmetry"
onClick={() => {
const a: SymmetryAction = {
type: 'CHANGESYMMETRY',
symmetry: Symmetry.Vertical,
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<SymmetryNone />}
text="Use No Symmetry"
onClick={() => {
const a: SymmetryAction = {
type: 'CHANGESYMMETRY',
symmetry: Symmetry.None,
};
dispatch(a);
}}
/>
{state.grid.width === state.grid.height ? (
<>
<TopBarDropDownLink
icon={<SymmetryIcon type={Symmetry.DiagonalNESW} />}
text="Use NE/SW Diagonal Symmetry"
onClick={() => {
const a: SymmetryAction = {
type: 'CHANGESYMMETRY',
symmetry: Symmetry.DiagonalNESW,
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<SymmetryIcon type={Symmetry.DiagonalNWSE} />}
text="Use NW/SE Diagonal Symmetry"
onClick={() => {
const a: SymmetryAction = {
type: 'CHANGESYMMETRY',
symmetry: Symmetry.DiagonalNWSE,
};
dispatch(a);
}}
/>
</>
) : (
''
)}
</>
)}
</NestedDropDown>
<TopBarDropDownLink
icon={<FaSquare />}
text="Toggle Block"
shortcutHint={<PeriodKey />}
onClick={() => {
const a: KeypressAction = {
type: 'KEYPRESS',
key: { k: KeyK.Dot },
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<CgSidebarRight />}
text="Toggle Bar"
shortcutHint={<CommaKey />}
onClick={() => {
const a: KeypressAction = {
type: 'KEYPRESS',
key: { k: KeyK.Comma },
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<FaEyeSlash />}
text="Toggle Cell Visibility"
shortcutHint={<KeyIcon text="#" />}
onClick={() => {
const a: ToggleHiddenAction = {
type: 'TOGGLEHIDDEN',
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={<Rebus />}
text="Enter Rebus"
shortcutHint={<EscapeKey />}
onClick={() => {
const a: KeypressAction = {
type: 'KEYPRESS',
key: { k: KeyK.Escape },
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={
state.grid.highlight === 'circle' ? (
<FaRegCircle />
) : (
<FaFillDrip />
)
}
text="Toggle Square Highlight"
shortcutHint={<BacktickKey />}
onClick={() => {
const a: KeypressAction = {
type: 'KEYPRESS',
key: { k: KeyK.Backtick },
};
dispatch(a);
}}
/>
<TopBarDropDownLink
icon={
state.grid.highlight === 'circle' ? (
<FaFillDrip />
) : (
<FaRegCircle />
)
}
text={
state.grid.highlight === 'circle'
? 'Use Shade for Highlights'
: 'Use Circle for Highlights'
}
onClick={() => {
const a: SetHighlightAction = {
type: 'SETHIGHLIGHT',
highlight:
state.grid.highlight === 'circle' ? 'shade' : 'circle',
};
dispatch(a);
}}
/>
{muted ? (
<TopBarDropDownLink
icon={<FaVolumeUp />}
text="Unmute"
onClick={() => setMuted(false)}
/>
) : (
<TopBarDropDownLink
icon={<FaVolumeMute />}
text="Mute"
onClick={() => setMuted(true)}
/>
)}
<TopBarDropDownLink
icon={<FaKeyboard />}
text="Toggle Keyboard"
onClick={() => setToggleKeyboard(!toggleKeyboard)}
/>
{props.isAdmin ? (
<>
<TopBarDropDownLinkA
href="/admin"
icon={<FaUserLock />}
text="Admin"
/>
</>
) : (
''
)}
<TopBarDropDownLinkA
href="/dashboard"
icon={<FaHammer />}
text="Constructor Dashboard"
/>
<TopBarDropDownLinkA
href="/account"
icon={<FaUser />}
text="Account"
/>
</>
)}
</TopBarDropDown>
</>
);
}, [
focusGrid,
getMostConstrainedEntry,
props.autofillEnabled,
props.autofillInProgress,
props.autofilledGrid.length,
stats,
props.isAdmin,
setClueMode,
setMuted,
state.grid.highlight,
state.grid.width,
state.grid.height,
state.gridIsComplete,
state.hasNoShortWords,
state.repeats,
state.symmetry,
toggleAutofillEnabled,
reRunAutofill,
dispatch,
muted,
toggleKeyboard,
setToggleKeyboard,
]);
return (
<>
<Global styles={FULLSCREEN_CSS} />
<div
css={{
display: 'flex',
flexDirection: 'column',
height: '100%',
}}
>
<div css={{ flex: 'none' }}>
<TopBar>{topBarChildren}</TopBar>
</div>
{state.showDownloadLink ? (
<PuzDownloadOverlay
state={state}
cancel={() => {
const a: SetShowDownloadLink = {
type: 'SETSHOWDOWNLOAD',
value: false,
};
dispatch(a);
}}
/>
) : (
''
)}
{state.toPublish ? (
<PublishOverlay
id={state.id}
toPublish={state.toPublish}
warnings={state.publishWarnings}
user={props.user}
cancelPublish={() => dispatch({ type: 'CANCELPUBLISH' })}
/>
) : (
''
)}
{state.publishErrors.length ? (
<Overlay
closeCallback={() => dispatch({ type: 'CLEARPUBLISHERRORS' })}
>
<>
<div>
Please fix the following errors and try publishing again:
</div>
<ul>
{state.publishErrors.map((s, i) => (
<li key={i}>{s}</li>
))}
</ul>
{state.publishWarnings.length ? (
<>
<div>Warnings:</div>
<ul>
{state.publishWarnings.map((s, i) => (
<li key={i}>{s}</li>
))}
</ul>
</>
) : (
''
)}
</>
</Overlay>
) : (
''
)}
<div
css={{ flex: '1 1 auto', overflow: 'scroll', position: 'relative' }}
>
<SquareAndCols
leftIsActive={state.active.dir === Direction.Across}
ref={gridRef}
aspectRatio={state.grid.width / state.grid.height}
square={(width: number, _height: number) => {
return (
<GridView
isEnteringRebus={state.isEnteringRebus}
rebusValue={state.rebusValue}
squareWidth={width}
grid={state.grid}
active={state.active}
dispatch={dispatch}
allowBlockEditing={true}
autofill={props.autofillEnabled ? props.autofilledGrid : []}
/>
);
}}
left={fillLists.left}
right={fillLists.right}
dispatch={dispatch}
/>
</div>
<div css={{ flex: 'none', width: '100%' }}>
<Keyboard
toggleKeyboard={toggleKeyboard}
keyboardHandler={keyboardHandler}
muted={muted}
showExtraKeyLayout={state.showExtraKeyLayout}
includeBlockKey={true}
/>
</div>
</div>
</>
);
}
Example #27
Source File: vscode-editor-loading-screen.tsx From utopia with MIT License | 4 votes |
VSCodeLoadingScreen = React.memo((): React.ReactElement | null => {
const vscodeLoadingScreenVisible = useEditorState(
(store) => store.editor.vscodeLoadingScreenVisible,
'VSCodeIframeContainer',
)
if (!vscodeLoadingScreenVisible) {
return null
}
return (
<div
style={{
width: '100%',
height: '100%',
fontSize: 13,
display: 'flex',
flexDirection: 'column',
fontFamily: '-apple-system, system-ui, sans-serif',
}}
id={VSCodeLoadingScreenID}
>
<Global
styles={css`
@keyframes placeholderShimmer {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
.shimmer {
color: transparent;
animation-name: placeholderShimmer;
animation-duration: 1.25s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
background: #f6f6f6;
background: linear-gradient(to right, #f6f6f6 8%, #f0f0f0 18%, #f6f6f6 33%);
background-size: 800px 104px;
position: relative;
}
`}
/>
{/* tab row */}
<div
style={{
display: 'flex',
alignItems: 'flex-end',
height: 35,
background: '#f3f3f3',
}}
>
{/* single tab */}
<div
style={{
background: '#FAFAFA',
color: 'rgb(51,51,51)',
borderRight: '1px solid rgb(243, 243, 243)',
height: 35,
display: 'flex',
alignItems: 'center',
paddingLeft: 12,
paddingRight: 12,
width: 140,
}}
>
<div style={{ color: '#b7b73b', display: 'flex' }}>
<JSIcon />
</div>
<span style={{}}>storyboard.js</span>
</div>
</div>
{/* breadcrumbs */}
<div
className='monaco-breadcrumbs'
style={{
paddingLeft: 15,
height: 22,
display: 'flex',
alignItems: 'center',
color: '#888',
}}
>
<div
className='folder monaco-breadcrumb-item'
role='listitem'
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div
className='monaco-icon-label'
style={{
height: 22,
lineHeight: '22px',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div className='monaco-icon-label-container' title='~/utopia'>
<span className='monaco-icon-name-container'>
<a style={{ color: 'rgba(97, 97, 97, 0.8)' }} className='label-name'>
utopia
</a>
</span>
<span className='monaco-icon-description-container'></span>
</div>
</div>
<div style={{ height: 16 }}>
<Chevron />
</div>
</div>
<div
className='file monaco-breadcrumb-item'
role='listitem'
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div
className='monaco-icon-label file-icon storyboard.js-name-file-icon js-ext-file-icon ext-file-icon javascript-lang-file-icon'
style={{
paddingRight: 6,
height: 22,
lineHeight: '22px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div style={{ color: '#b7b73b', display: 'flex', paddingRight: 4 }}>
<JSIcon />
</div>
<div className='monaco-icon-label-container' title='~/utopia/storyboard.js'>
<span className='monaco-icon-name-container'>
<a style={{ color: 'rgba(97, 97, 97, 0.8)' }} className='label-name'>
storyboard.js
</a>
</span>
<span className='monaco-icon-description-container'></span>
</div>
<div style={{ height: 16 }}>
<Chevron />
</div>
</div>
</div>
</div>
{/* code */}
<div
style={{
display: 'grid',
gridTemplateColumns: '66px 500px',
gridTemplateRows: 'repeat(12, 18px)',
alignItems: 'center',
fontSize: 12,
color: '#6d705b',
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
}}
>
{SampleCode.map((line, rowNumber) => (
<React.Fragment key={rowNumber}>
<span
style={{
textAlign: 'right',
paddingRight: 27,
}}
>
{rowNumber}
</span>
<div>
<span
className='shimmer'
style={{
marginLeft: line.indent * 14,
wordWrap: 'normal',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
>
{line.code}
</span>
</div>
</React.Fragment>
))}
</div>
</div>
)
})
Example #28
Source File: preview-page-component.tsx From utopia with MIT License | 4 votes |
render() {
const TopBarHeight = 44
let previewBoxShadow: string
let previewWrapperAlignItems: string
let previewBorderRadius: number
if (this.state.fullscreenViewportOverride) {
previewBoxShadow = ''
previewWrapperAlignItems = 'flex-start'
previewBorderRadius = 0
} else {
previewBoxShadow = '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)'
previewWrapperAlignItems = 'center'
previewBorderRadius = 4
}
const idString = this.props.id !== '' ? ` (${this.props.id})` : null
const displayWidth = this.state.fullscreenViewportOverride ? '100%' : this.state.width
const displayHeight = this.state.fullscreenViewportOverride ? '100%' : this.state.height
const scaledDisplayWidth = this.state.fullscreenViewportOverride
? '100%'
: this.state.width * this.state.scale
const scaledDisplayHeight = this.state.fullscreenViewportOverride
? '100%'
: this.state.height * this.state.scale
return (
<React.Fragment>
<Global
styles={{
html: {
height: '100%',
},
body: {
margin: 0,
height: '100%',
overflow: 'hidden',
},
'@media screen and (max-width: 425px)': {
'.preview-topbar': {
display: 'none !important',
},
},
}}
/>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '100%',
}}
>
<div
className='preview-topbar'
css={{
backgroundColor: '#F5F5F5',
height: TopBarHeight,
width: '100%',
boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontFamily:
'-apple-system, BlinkMacSystemFont, Helvetica, "Segoe UI", Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
fontSize: 12,
lineHeight: '18px',
}}
>
<div
style={{
margin: '0 5px',
}}
>
Untitled{idString}
</div>
<div
style={{
margin: '0 5px',
opacity: 0.25,
}}
>
|
</div>
<div
style={{
margin: '0 5px',
display: 'block',
textAlign: 'center',
height: 18,
}}
>
<img
src='/editor/icons/[email protected]'
width={18}
height={18}
style={{
marginRight: 5,
}}
/>
<div
style={{
display: 'inline-block',
height: 18,
}}
>
{
<PreviewReactSelectDeviceSelector
value={getDeviceReactSelectOption(this.state.deviceInfo.id)}
onChange={this.onDeviceChange}
caratOffset={7}
/>
}
</div>
</div>
<div
style={{
margin: '0 5px',
opacity: 0.25,
}}
>
|
</div>
<div
style={{
margin: '0 5px',
textAlign: 'center',
}}
>
Scale{' '}
{this.state.scale != null ? `(${Number((this.state.scale * 100).toFixed(2))}%)` : ''}
</div>
<div
style={{
margin: '0 5px',
opacity: 0.25,
}}
>
|
</div>
<div
style={{
margin: '0 5px',
}}
>
<img
onClick={this.onRestartClick}
src='/editor/icons/[email protected]'
width={18}
height={18}
style={{
position: 'relative',
top: 3,
}}
/>
</div>
</div>
<div
ref={this.previewWrapperRef}
className='preview-wrapper'
style={{
display: 'flex',
flex: 1,
width: '100%',
height: '100%',
alignItems: previewWrapperAlignItems,
justifyContent: 'center',
overflow: 'hidden',
}}
>
{!(this.state.scale != null) ? null : (
<div
className='preview-box'
style={{
width: scaledDisplayWidth,
height: scaledDisplayHeight,
backgroundColor: '#d6d6d6',
}}
>
<iframe
ref={this.iFrameRef}
width={displayWidth}
height={displayHeight}
src={`/share/${this.props.id}/`}
allow='autoplay'
style={{
width: displayWidth,
height: displayHeight,
borderWidth: 0,
borderRadius: previewBorderRadius,
boxShadow: previewBoxShadow,
zoom: this.state.scale,
transformOrigin: 'top left',
}}
/>
</div>
)}
</div>
</div>
</React.Fragment>
)
}
Example #29
Source File: Plyr.tsx From tobira with Apache License 2.0 | 4 votes |
PlyrPlayer: React.FC<PlyrPlayerProps> = ({ tracks, title, isLive }) => {
// Check if there is any HLS track. If so, we only care about that and
// ignore all other tracks.
// TODO: it's unclear what we want to do in case of multiple HLS tracks. In
// theory, you shouldn't need multiple as the m3u8 playlist can list
// multiple qualities.
const hlsTrack = tracks.find(isHlsTrack);
const source = {
type: "video" as const,
title,
sources: hlsTrack ? [] : tracks.map(track => ({
src: track.uri,
type: track.mimetype ?? undefined,
size: track.resolution?.[1] ?? undefined,
})),
};
// Determine all available qualities. As default quality, we use the largest
// one equal to or below 1080. 1080p is a good default, at least for
// desktops. And once the user changes the quality, it is stored in local
// storage anyway.
//
// When we use a HLS track, this setting is completely ignored, so we can
// still just pass it.
const qualities = Array.from(new Set(
tracks
.map(t => t.resolution?.[1])
.filter((h): h is number => h != null),
));
qualities.sort((a, b) => a - b);
const defaultQuality = Math.max(...qualities.filter(h => h <= 1080));
const aspectRatio = tracks[0].resolution ?? [16, 9];
const options = {
// Compared to the default, "pip" and "airplay" were removed.
controls: [
"play",
"progress",
"current-time",
"mute",
"volume",
"captions",
"settings",
"fullscreen",
],
settings: ["captions", "quality", "speed"],
quality: {
default: defaultQuality,
options: qualities,
},
speed: {
selected: 1,
options: SPEEDS,
},
invertTime: false,
blankVideo: CONFIG.plyr.blankVideo,
iconUrl: CONFIG.plyr.svg,
// Set ratio to avoid visual jumps. I'm slightly uncomfortable doing
// that as the reported resolution could be garbage and the user will
// be stuck with an incorrect aspect ratio. I would like to give the
// video preference once it's loaded. But for not we just assume the
// resolution is correct.
ratio: `${aspectRatio[0]}:${aspectRatio[1]}`,
};
// Unfortunately, `plyr-react` does not seem to offer a way to access the
// `<video>` element via `ref`. So we just use a unique random ID.
const elementId = useId();
// Setup HLS if we have an HLS track.
const hlsRef = useRef<Hls | null>(null);
const loadHls = async () => {
if (hlsTrack !== undefined) {
if (!Hls.isSupported()) {
// TODO: improve this. It's fine for now as browsers that don't
// support hls.js are very rare by now.
throw new Error("HLS is not supported, but required to play this video");
}
const videoElement = document.getElementById(elementId) as HTMLVideoElement;
hlsRef.current = new Hls();
const hls = hlsRef.current;
hls.loadSource(hlsTrack.uri);
hls.attachMedia(videoElement);
// If this is a live event (and not a VOD HLS stream), we want to
// auto-play. Of course, most browsers block that if the user has
// not interacted with the website before. But that's fine.
hls.on(Hls.Events.MANIFEST_PARSED, () => {
if (isLive) {
videoElement.play();
}
});
}
};
useEffect(() => {
loadHls();
return () => {
if (hlsRef.current) {
hlsRef.current.destroy();
}
};
});
return <>
<Global styles={plyrCss} />
<div css={{
"--plyr-color-main": "var(--accent-color)",
"& > div:focus-visible": {
outline: "3px dotted var(--accent-color)",
},
}}>
<Plyr id={elementId} source={source} options={options} />
</div>
</>;
}