@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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
export function NormalizeCSS() {
  return <Global styles={styles} />;
}
Example #20
Source File: EmbeddedView.tsx    From atlas with GNU General Public License v3.0 5 votes vote down vote up
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 vote down vote up
GlobalStyles = () => <Global styles={globalStyles} />
Example #22
Source File: Puzzle.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
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 vote down vote up
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 vote down vote up
// `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 vote down vote up
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 &nbsp;
                <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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
    </>;
}