> => {\n switch (layout) {\n case Layout.DEFAULT:\n return (props) => (\n \n {props.children}\n \n );\n case Layout.NONE:\n return NoneLayout;\n default:\n throw new TypeError(`unrecognized layout: '${layout}'`);\n }\n};\n\nexport const withLayout = (layout: Layout, opts: LayoutOptions = DEFAULT_OPTIONS) => {\n const LayoutComponent = getLayout(layout, opts);\n return function(Component: React.ComponentType
) {\n return (props: P) => (\n \n \n \n );\n };\n};\n","import { useEffect, useState } from 'react';\n\n// from https://github.com/rehooks/online-status\n\nconst getOnlineStatus = () => {\n return typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean' ? navigator.onLine : true;\n};\n\nexport const useOnlineStatus = () => {\n const [onlineStatus, setOnlineStatus] = useState(getOnlineStatus);\n\n useEffect(() => {\n const goOnline = () => setOnlineStatus(true);\n const goOffline = () => setOnlineStatus(false);\n\n window.addEventListener('online', goOnline);\n window.addEventListener('offline', goOffline);\n\n return () => {\n window.removeEventListener('online', goOnline);\n window.removeEventListener('offline', goOffline);\n };\n }, []);\n\n return onlineStatus;\n};\n","import { CompassOutlined, MenuOutlined, UploadOutlined, UserOutlined } from '@ant-design/icons';\nimport { Avatar, Button, Col, Modal, Row, Space } from 'antd';\nimport React, { useState } from 'react';\nimport { Link, NavLink } from 'react-router-dom';\nimport styled from 'styled-components';\nimport { isLoggedInSelector, useAuth } from '../../ctx/auth';\nimport { useViewport } from '../../ctx/viewport/useViewport';\nimport { eqAdmin, gtEqTeacher } from '../../domain';\n\nconst StyledRow = styled(Row)`\n svg {\n color: ${(props) => props.theme['@muted']};\n }\n\n .active-link svg {\n color: ${(props) => props.theme['@primary-color']};\n }\n`;\n\nconst StyledUploadOutlined = styled(UploadOutlined)`\n font-size: 22px;\n`;\n\nconst StyledCompassOutlined = styled(CompassOutlined)`\n font-size: 22px;\n`;\n\nconst StyledMenuOutlined = styled(MenuOutlined)`\n font-size: 22px;\n`;\n\nconst Username = styled.div`\n font-size: 14px;\n`;\n\nconst Role = styled.div`\n font-size: 12px;\n font-weight: lighter;\n color: ${(props) => props.theme['@muted']};\n`;\n\ninterface Props {}\n\nexport const Menu: React.FC = (props) => {\n const [authState, authApi] = useAuth();\n const isLoggedIn = isLoggedInSelector(authState);\n const isAuthPending = authState.isPending;\n const user = authState.user;\n const { xs, sm, md } = useViewport();\n const isLtEqMd = xs || sm || md;\n\n const [isModalVisible, setModalVisible] = useState(false);\n const isGtEqTeacher = gtEqTeacher(user.role);\n const isAdmin = eqAdmin(user.role);\n const settingsButtonClassName = isModalVisible ? 'active-link' : '';\n\n const showModal = () => setModalVisible(true);\n const hideModal = () => setModalVisible(false);\n const onLogoutClick = () => {\n authApi.logout();\n hideModal();\n };\n\n const gutterPx = isLoggedIn ? 16 : 8;\n const isLibraryVisible = !isAuthPending && !isLtEqMd && isLoggedIn;\n const isUploadVisible = !isAuthPending && !isLtEqMd && isLoggedIn && isGtEqTeacher;\n const isLoginVisible = !isAuthPending && !isLoggedIn;\n const isSignupVisible = !isAuthPending && !isLoggedIn;\n const isSettingsVisible = !isAuthPending && isLoggedIn;\n const isAdminActionsVisible = !isAuthPending && isAdmin;\n\n return (\n <>\n \n {isLibraryVisible ? (\n \n \n \n ) : null}\n\n {isUploadVisible ? (\n \n \n \n ) : null}\n\n {isSettingsVisible ? (\n \n \n \n ) : null}\n\n {isLoginVisible ? (\n \n \n \n \n \n ) : null}\n\n {isSignupVisible ? (\n \n \n \n \n \n ) : null}\n \n\n {isLoggedIn ? (\n \n \n } />{' '}\n \n \n \n {user.username}\n {user.role.toLowerCase()}\n
\n \n \n }\n visible={isModalVisible}\n onCancel={hideModal}\n footer={null}\n >\n \n {isAdminActionsVisible && (\n <>\n \n \n \n >\n )}\n\n {isAdminActionsVisible && (\n <>\n \n \n \n >\n )}\n\n \n \n \n ) : null}\n >\n );\n};\n","import { Button, Col, Layout, Row } from 'antd';\nimport React, { PropsWithChildren } from 'react';\nimport { Link, useLocation } from 'react-router-dom';\nimport styled from 'styled-components';\nimport { Logo } from '../../components/Logo';\nimport { Wordmark } from '../../components/Wordmark';\nimport { useMeta } from '../../ctx/meta/useMeta';\nimport { useViewport } from '../../ctx/viewport/useViewport';\nimport { useOnlineStatus } from '../../hooks/useOnlineStatus';\nimport { Menu } from './Menu';\n\nexport const HEADER_HEIGHT_PX = 64;\n\nconst StyledLayout = styled(Layout)`\n && {\n min-height: 100vh;\n }\n`;\n\nconst StyledHeader = styled(Layout.Header)`\n && {\n background: #ffffff;\n border-bottom: 1px solid #e8e8e8;\n padding: 0 16px;\n display: flex;\n align-items: center;\n height: ${HEADER_HEIGHT_PX}px;\n }\n`;\n\nconst StyledFooter = styled(Layout.Footer)`\n text-align: center;\n color: ${(props) => props.theme['@muted-color']};\n`;\n\nconst Lane = styled.div`\n max-width: 1200px;\n width: 100%;\n margin: 0 auto;\n`;\n\nconst Version = styled.div`\n font-size: 0.6em;\n`;\n\nconst Offline = styled.em`\n font-weight: lighter;\n color: ${(props) => props.theme['@muted']};\n`;\n\ntype Props = PropsWithChildren<{\n lanes: boolean;\n footer: boolean;\n}>;\n\nexport const DefaultLayout: React.FC = (props) => {\n const { version } = useMeta();\n\n const location = useLocation();\n const { lg, xl, xxl } = useViewport();\n const isGtMd = lg || xl || xxl;\n\n const isOnline = useOnlineStatus();\n const isWordmarkVisible = isOnline && isGtMd;\n const isOfflineVisible = !isOnline;\n const isVersionVisible = !isWordmarkVisible;\n const logoLinkTo = location.pathname.startsWith('/library') ? '/' : '/library';\n\n return (\n \n \n \n \n \n \n \n \n \n \n
\n \n {isWordmarkVisible && (\n \n \n \n )}\n {isVersionVisible && (\n \n {version}\n \n )}\n {isOfflineVisible && (\n \n offline\n \n )}\n
\n \n \n \n \n \n
\n \n \n {props.lanes ? {props.children} : props.children}\n {props.footer && isGtMd && (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n {version}\n \n \n )}\n \n );\n};\n","import React, { PropsWithChildren } from 'react';\n\nexport const NoneLayout: React.FC> = (props) => {\n return {props.children}
;\n};\n","export interface Range {\n readonly start: T;\n readonly end: T;\n readonly size: T;\n readonly midpoint: T;\n contains(value: T): boolean;\n eq(range: Range): boolean;\n toString(): string;\n}\n\nclass LeftBoundedNumberRange implements Range {\n start;\n end = Number.POSITIVE_INFINITY;\n size = Number.POSITIVE_INFINITY;\n midpoint = Number.POSITIVE_INFINITY;\n\n constructor(start: number) {\n this.start = start;\n }\n\n to(end: number) {\n return new NumberRange(this.start, end);\n }\n\n contains(value: number) {\n return this.start <= value && value <= this.end;\n }\n\n eq(range: NumberRange) {\n return this.start === range.start && this.end === range.end;\n }\n\n toString() {\n return `[${this.start}, +∞)`;\n }\n}\n\nclass RightBoundedNumberRange implements Range {\n start = Number.NEGATIVE_INFINITY;\n end;\n size = Number.POSITIVE_INFINITY;\n midpoint = Number.NEGATIVE_INFINITY;\n\n constructor(end: number) {\n this.end = end;\n }\n\n from(start: number) {\n return new NumberRange(start, this.end);\n }\n\n contains(value: number) {\n return this.start <= value && value <= this.end;\n }\n\n eq(range: NumberRange) {\n return this.start === range.start && this.end === range.end;\n }\n\n toString() {\n return `(-∞, ${this.end}]`;\n }\n}\n\nexport class NumberRange implements Range {\n static unsorted(n1: number, n2: number) {\n return n1 < n2 ? NumberRange.from(n1).to(n2) : NumberRange.from(n2).to(n1);\n }\n\n static from(start: number) {\n return new LeftBoundedNumberRange(start);\n }\n\n static to(end: number) {\n return new RightBoundedNumberRange(end);\n }\n\n start: number;\n end: number;\n\n constructor(start: number, end: number) {\n if (start > end) {\n throw new RangeError('start must be <= end');\n }\n\n this.start = start;\n this.end = end;\n }\n\n get size() {\n return this.end - this.start;\n }\n\n get midpoint() {\n return this.start + this.size / 2;\n }\n\n contains(value: number) {\n return this.start <= value && value <= this.end;\n }\n\n eq(range: NumberRange) {\n return this.start === range.start && this.end === range.end;\n }\n\n toString() {\n const start = this.start === Number.NEGATIVE_INFINITY ? '(-∞' : `[${this.start.toString()}`;\n const end = this.end === Number.POSITIVE_INFINITY ? '+∞)' : `${this.end.toString()}]`;\n return `${start}, ${end}`;\n }\n}\n","import { deviceType, primaryInput } from 'detect-it';\nimport { Device } from './types';\n\n// src and tests at https://github.com/kaimallea/isMobile\n\nconst appleIphone = /iPhone/i;\nconst appleIpod = /iPod/i;\nconst appleTablet = /iPad/i;\nconst androidPhone = /\\bAndroid(?:.+)Mobile\\b/i; // Match 'Android' AND 'Mobile'\nconst androidTablet = /Android/i;\nconst amazonPhone = /(?:SD4930UR|\\bSilk(?:.+)Mobile\\b)/i; // Match 'Silk' AND 'Mobile'\nconst amazonTablet = /Silk/i;\nconst windowsPhone = /Windows Phone/i;\nconst windowsTablet = /\\bWindows(?:.+)ARM\\b/i; // Match 'Windows' AND 'ARM'\nconst otherBlackBerry = /BlackBerry/i;\nconst otherBlackBerry10 = /BB10/i;\nconst otherOpera = /Opera Mini/i;\nconst otherChrome = /\\b(CriOS|Chrome)(?:.+)Mobile/i;\nconst otherFirefox = /Mobile(?:.+)Firefox\\b/i; // Match 'Mobile' AND 'Firefox'\n\nconst match = (regex: RegExp, userAgent: string): boolean => {\n return regex.test(userAgent);\n};\n\nexport const getDevice = (userAgent: string): Device => {\n // Facebook mobile app's integrated browser adds a bunch of strings that\n // match everything. Strip it out if it exists.\n let tmp = userAgent.split('[FBAN');\n if (typeof tmp[1] !== 'undefined') {\n userAgent = tmp[0];\n }\n\n // Twitter mobile app's integrated browser on iPad adds a \"Twitter for\n // iPhone\" string. Same probably happens on other tablet platforms.\n // This will confuse detection so strip it out if it exists.\n tmp = userAgent.split('Twitter');\n if (typeof tmp[1] !== 'undefined') {\n userAgent = tmp[0];\n }\n\n const device: Device = {\n primaryInput: primaryInput,\n inputType: deviceType,\n apple: {\n phone: match(appleIphone, userAgent) && !match(windowsPhone, userAgent),\n ipod: match(appleIpod, userAgent),\n tablet: !match(appleIphone, userAgent) && match(appleTablet, userAgent) && !match(windowsPhone, userAgent),\n device:\n (match(appleIphone, userAgent) || match(appleIpod, userAgent) || match(appleTablet, userAgent)) &&\n !match(windowsPhone, userAgent),\n },\n amazon: {\n phone: match(amazonPhone, userAgent),\n tablet: !match(amazonPhone, userAgent) && match(amazonTablet, userAgent),\n device: match(amazonPhone, userAgent) || match(amazonTablet, userAgent),\n },\n android: {\n phone:\n (!match(windowsPhone, userAgent) && match(amazonPhone, userAgent)) ||\n (!match(windowsPhone, userAgent) && match(androidPhone, userAgent)),\n tablet:\n !match(windowsPhone, userAgent) &&\n !match(amazonPhone, userAgent) &&\n !match(androidPhone, userAgent) &&\n (match(amazonTablet, userAgent) || match(androidTablet, userAgent)),\n device:\n (!match(windowsPhone, userAgent) &&\n (match(amazonPhone, userAgent) ||\n match(amazonTablet, userAgent) ||\n match(androidPhone, userAgent) ||\n match(androidTablet, userAgent))) ||\n match(/\\bokhttp\\b/i, userAgent),\n },\n windows: {\n phone: match(windowsPhone, userAgent),\n tablet: match(windowsTablet, userAgent),\n device: match(windowsPhone, userAgent) || match(windowsTablet, userAgent),\n },\n other: {\n blackberry: match(otherBlackBerry, userAgent),\n blackberry10: match(otherBlackBerry10, userAgent),\n opera: match(otherOpera, userAgent),\n firefox: match(otherFirefox, userAgent),\n chrome: match(otherChrome, userAgent) && !match(androidPhone, userAgent),\n device:\n match(otherBlackBerry, userAgent) ||\n match(otherBlackBerry10, userAgent) ||\n match(otherOpera, userAgent) ||\n match(otherFirefox, userAgent) ||\n (match(otherChrome, userAgent) && !match(androidPhone, userAgent)),\n },\n mobile: false,\n phone: false,\n tablet: false,\n };\n\n device.mobile = device.apple.device || device.android.device || device.windows.device || device.other.device;\n // excludes 'other' devices and ipods, targeting touchscreen phones\n device.phone = device.apple.phone || device.android.phone || device.windows.phone;\n device.tablet = device.apple.tablet || device.android.tablet || device.windows.tablet;\n\n return device;\n};\n","import { createAction, createReducer } from '@reduxjs/toolkit';\nimport React, { PropsWithChildren, useReducer } from 'react';\nimport { useEffectOnce } from '../../hooks/useEffectOnce';\nimport { getDevice } from './getDevice';\nimport { Device } from './types';\n\nconst DEVICE_ACTIONS = {\n setDevice: createAction<{ device: Device }>('setDevice'),\n};\n\nconst INITIAL_USER_AGENT = navigator.userAgent || '';\n\nconst getInitialState = (): Device => getDevice(INITIAL_USER_AGENT);\n\nconst deviceReducer = createReducer(getInitialState(), (builder) => {\n builder.addCase(DEVICE_ACTIONS.setDevice, (state, action) => {\n return action.payload.device;\n });\n});\n\nexport const DeviceCtx = React.createContext(getInitialState());\n\nexport const DeviceProvider: React.FC> = (props) => {\n const [state, dispatch] = useReducer(deviceReducer, getInitialState());\n\n useEffectOnce(() => {\n const device = getDevice(navigator.userAgent || '');\n dispatch(DEVICE_ACTIONS.setDevice({ device }));\n });\n\n return {props.children};\n};\n","import { useContext } from 'react';\nimport { DeviceCtx } from './DeviceCtx';\n\nexport const useDevice = () => {\n const state = useContext(DeviceCtx);\n return state;\n};\n","import styled from 'styled-components';\n\nexport const Box = styled.div`\n background: white;\n border: 1px solid ${(props) => props.theme['@border-color']};\n border-radius: 4px;\n padding: 24px;\n`;\n","const theme = {\n '@primary-color': '#FC354C',\n '@secondary-color': '#6CABBA',\n '@tertiary-color': '#F4F4F4',\n '@highlight-color': '#FF9EA9',\n '@background-color-base': '#F0F2F5',\n '@muted': '#999999',\n '@border-color': '#D9D9D9',\n};\n\nmodule.exports = { theme };\n","export const identity = (x: T) => x;\n","import { identity } from './identity';\n// https://github.com/acdlite/recompose/blob/master/src/packages/recompose/compose.js\n\nexport const compose = (...funcs: Function[]) => funcs.reduce((a, b) => (...args: any[]) => a(b(...args)), identity);\n","import { message, Modal, ModalFuncProps, notification } from 'antd';\nimport { ArgsProps as MessageArgsProps } from 'antd/lib/message';\nimport { ArgsProps as NotificationArgsProps } from 'antd/lib/notification';\nimport { isNull } from 'lodash';\nimport { Duration } from '../../util/Duration';\nimport { MessageConfig, ModalConfig, Notify, PopupConfig } from './types';\n\ntype AntdSendMessage = (props: Omit) => any;\ntype AntdMakePopup = (props: NotificationArgsProps) => any;\ntype AntdShowModal = (props: ModalFuncProps) => any;\n\nconst MESSAGE_DEFAULT_DURATION = Duration.sec(3);\n\nexport class AntdNotify implements Notify {\n private static message = (sendMessage: AntdSendMessage) => (config: MessageConfig) => {\n sendMessage({\n key: config.key,\n content: config.content,\n duration: isNull(config.duration)\n ? config.duration || undefined\n : (config.duration || MESSAGE_DEFAULT_DURATION).sec,\n onClick: config.onClick,\n });\n };\n\n private static popup = (makePopup: AntdMakePopup) => (config: PopupConfig) => {\n makePopup({\n message: config.title,\n description: config.content,\n placement: config.placement,\n closeIcon: config.closeIcon,\n btn: config.button,\n duration: config.duration?.sec,\n });\n };\n\n private static modal = (showModal: AntdShowModal) => (config: ModalConfig) => {\n showModal({\n title: config.title,\n content: config.content,\n maskClosable: true,\n });\n };\n\n message = {\n info: AntdNotify.message(message.info),\n success: AntdNotify.message(message.success),\n warn: AntdNotify.message(message.warn),\n error: AntdNotify.message(message.error),\n loading: AntdNotify.message(message.loading),\n };\n popup = {\n info: AntdNotify.popup(notification.info),\n success: AntdNotify.popup(notification.success),\n warn: AntdNotify.popup(notification.warn),\n error: AntdNotify.popup(notification.error),\n };\n modal = {\n info: AntdNotify.modal(Modal.info),\n success: AntdNotify.modal(Modal.success),\n warn: AntdNotify.modal(Modal.warn),\n error: AntdNotify.modal(Modal.error),\n };\n}\n","import { noop } from 'lodash';\nimport { Notify } from './types';\n\nexport class NoopNotify implements Notify {\n message = {\n info: noop,\n success: noop,\n warn: noop,\n error: noop,\n loading: noop,\n };\n popup = {\n info: noop,\n success: noop,\n warn: noop,\n error: noop,\n };\n modal = {\n info: noop,\n success: noop,\n warn: noop,\n error: noop,\n };\n}\n","import { REACT_SNAP_ACTIVE } from '../../constants';\nimport { AntdNotify } from './AntdNotify';\nimport { NoopNotify } from './NoopNotify';\nimport { Notify } from './types';\n\nexport const notify: Notify = REACT_SNAP_ACTIVE ? new NoopNotify() : new AntdNotify();\n","export type ReqInit = Omit;\n\nexport type Req = (input: RequestInfo, init?: ReqInit) => void;\n\nexport type Cancel = () => void;\n\nexport type Reset = () => void;\n\nexport type Parse = (res: Response) => T | Promise;\n\nexport enum Status {\n Init,\n Pending,\n Success,\n Error,\n Cancelled,\n}\n\nexport type Res =\n | { status: Status.Init }\n | { status: Status.Pending }\n | { status: Status.Success; result: T }\n | { status: Status.Error; error: Error }\n | { status: Status.Cancelled };\n","import { noop } from 'lodash';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport * as xhr from '../lib/xhr';\n\nenum CancelType {\n None,\n Internal,\n External,\n Supplant,\n}\n\nexport const useReq = (\n parse: xhr.Parse\n): [req: xhr.Req, res: xhr.Res, cancel: xhr.Cancel, reset: xhr.Reset] => {\n const [res, setRes] = useState>(() => ({ status: xhr.Status.Init }));\n const externalCancelRef = useRef(noop);\n const internalCancelRef = useRef(noop);\n const supplantCancelRef = useRef(noop);\n\n // If any of the args change, cancel the res so that any stale in-flight requests don't get erroneously applied.\n useEffect(() => {\n internalCancelRef.current();\n }, [parse]);\n\n const req = useCallback(\n (input: RequestInfo, init?: xhr.ReqInit) => {\n supplantCancelRef.current();\n\n let done = false;\n setRes({ status: xhr.Status.Pending });\n\n // We communicate via memory because the AbortController API does not offer a way to pass messages via\n // AbortController.prototype.abort. In other words, without these variables, we have no way to tell if the\n // AbortController.prototype.abort call was triggered internally or externally.\n let cancelled = false;\n let cancelType = CancelType.None;\n const abortController = new AbortController();\n const cancel = (nextCancelType: CancelType) => () => {\n if (!cancelled && !done) {\n cancelled = true;\n cancelType = nextCancelType;\n abortController.abort();\n }\n };\n internalCancelRef.current = cancel(CancelType.Internal);\n externalCancelRef.current = cancel(CancelType.External);\n supplantCancelRef.current = cancel(CancelType.Supplant);\n\n fetch(input, { ...init, signal: abortController.signal })\n .then(parse)\n .then((parsed) => {\n if (cancelled) {\n setRes({ status: xhr.Status.Cancelled });\n } else {\n setRes({ status: xhr.Status.Success, result: parsed });\n }\n })\n .catch((error) => {\n if (done) {\n return;\n } else if (cancelled && cancelType === CancelType.Internal) {\n setRes({ status: xhr.Status.Init });\n } else if (cancelled && cancelType === CancelType.External) {\n setRes({ status: xhr.Status.Cancelled });\n } else if (cancelled && cancelType === CancelType.Supplant) {\n // The supplanted call will handle state changes.\n return;\n } else {\n setRes({ status: xhr.Status.Error, error });\n }\n })\n .finally(() => {\n done = true;\n });\n },\n [parse]\n );\n\n const cancel = useCallback(() => {\n externalCancelRef.current();\n }, []);\n\n const reset = useCallback(() => {\n cancel();\n setRes({ status: xhr.Status.Init });\n }, [cancel]);\n\n return [req, res, cancel, reset];\n};\n","import { useCallback, useState } from 'react';\nimport { MissingDataError } from '../lib/errors';\nimport * as graphql from '../lib/graphql';\nimport * as xhr from '../lib/xhr';\nimport { useReq } from './useReq';\nimport { useResHandler } from './useResHandler';\n\nconst GRAPHQL_URI = `${window.location.origin}/graphql`;\n\nexport type Exec = (variables: graphql.VariablesOf) => void;\n\n// The reason why we use this instead of $gql.GqlResponseOf, is because the server can return data and populate errors.\n// Callers can use the status discriminant to determine what the response looks like instead of testing the presence\n// of the data and errors properties.\nexport enum GqlStatus {\n Init,\n Pending,\n Success,\n Errors,\n Cancelled,\n}\n\nexport type GqlRes =\n | {\n status: GqlStatus.Init;\n }\n | {\n status: GqlStatus.Pending;\n }\n | {\n status: GqlStatus.Success;\n data: graphql.SuccessfulResponse['data'];\n }\n | {\n status: GqlStatus.Errors;\n errors: string[];\n }\n | {\n status: GqlStatus.Cancelled;\n };\n\nexport const useGql = (\n gql: G\n): [exec: Exec, res: GqlRes, cancel: xhr.Cancel, reset: xhr.Reset] => {\n const [req, res, cancel, reset] = useReq(graphql.$gql.toGqlResponse);\n\n const exec = useCallback(\n (variables: graphql.VariablesOf) => {\n req(GRAPHQL_URI, gql.toRequestInit(variables));\n },\n [req, gql]\n );\n\n const [gqlRes, setGqlRes] = useState>({ status: GqlStatus.Init });\n useResHandler(xhr.Status.Init, res, (res) => {\n setGqlRes({ status: GqlStatus.Init });\n });\n useResHandler(xhr.Status.Pending, res, (res) => {\n setGqlRes({ status: GqlStatus.Pending });\n });\n useResHandler(xhr.Status.Success, res, (res) => {\n const { data, errors } = res.result;\n if (errors) {\n setGqlRes({ status: GqlStatus.Errors, errors: errors.map((error) => error.message) });\n } else if (!data) {\n setGqlRes({ status: GqlStatus.Errors, errors: [new MissingDataError().message] });\n } else {\n setGqlRes({ status: GqlStatus.Success, data });\n }\n });\n useResHandler(xhr.Status.Error, res, (res) => {\n setGqlRes({ status: GqlStatus.Errors, errors: [res.error.message] });\n });\n useResHandler(xhr.Status.Cancelled, res, (res) => {\n setGqlRes({ status: GqlStatus.Cancelled });\n });\n\n return [exec, gqlRes, cancel, reset];\n};\n","import React, { useCallback, useEffect } from 'react';\nimport * as xhr from '../lib/xhr';\n\nexport type ResHandler = (res: Extract, { status: S }>) => void;\n\nexport const useResHandler = (\n status: S,\n res: xhr.Res,\n handler: ResHandler,\n deps: React.DependencyList = []\n) => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const callback = useCallback(handler, deps);\n\n useEffect(() => {\n if (res.status === status) {\n callback(res as Extract, { status: S }>);\n }\n }, [status, res, callback]);\n};\n","import React, { useState } from 'react';\nimport { useTimeout } from '../hooks/useTimeout';\nimport { Duration } from '../util/Duration';\n\nexport const withRenderDelay = (delay: Duration) => {\n return function(Component: React.ComponentType
): React.FC
{\n return (props) => {\n const [visible, setVisible] = useState(false);\n useTimeout(() => setVisible(true), delay.ms);\n return visible ? : null;\n };\n };\n};\n","var _style, _g, _g2;\n\nvar _excluded = [\"title\", \"titleId\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport * as React from \"react\";\n\nfunction SvgSonarSleep(_ref, svgRef) {\n var title = _ref.title,\n titleId = _ref.titleId,\n props = _objectWithoutProperties(_ref, _excluded);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n xmlns: \"http://www.w3.org/2000/svg\",\n xmlnsXlink: \"http://www.w3.org/1999/xlink\",\n x: \"0px\",\n y: \"0px\",\n viewBox: \"0 0 648 432\",\n style: {\n enableBackground: \"new 0 0 648 432\"\n },\n xmlSpace: \"preserve\",\n ref: svgRef,\n \"aria-labelledby\": titleId\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", {\n id: titleId\n }, title) : null, _style || (_style = /*#__PURE__*/React.createElement(\"style\", {\n type: \"text/css\"\n }, \"\\n\\t.st0{fill:#FFFFFF;}\\n\\t.st1{fill:#333333;}\\n\\t.st2{fill:none;stroke:#440606;stroke-width:3.7456;stroke-miterlimit:10;}\\n\\t.st3{fill:#D1D3D4;}\\n\\t.st4{fill:#FC354C;}\\n\\t.st5{fill:#440606;}\\n\\t.st6{opacity:0.88;fill:#FFFFFF;}\\n\\t.st7{opacity:0.6;fill:#6ECCE5;}\\n\\t.st8{fill:none;stroke:#440606;stroke-width:3;stroke-miterlimit:10;}\\n\\t.st9{fill:none;stroke:#440606;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}\\n\\t.st10{fill:#FD818F;}\\n\\t.st11{fill:none;stroke:#440606;stroke-width:2;stroke-miterlimit:10;}\\n\\t.st12{fill:none;stroke:#440606;stroke-width:3.2006;stroke-miterlimit:10;}\\n\")), _g || (_g = /*#__PURE__*/React.createElement(\"g\", {\n id: \"color\"\n }, /*#__PURE__*/React.createElement(\"path\", {\n className: \"st0\",\n d: \"M463.3,251.7c2.3,1.7,4.7,3.4,7,5c0.9,0.6,1.7,1.3,2.7,1.8c5.7,3.1,13.6-0.4,18.6-3.4 c5.7-3.5,10.7-8.2,15.2-13.2c2-2.2,4-4.5,5.9-6.8c7.8-9.7,12.2-21.2,19.4-31.2c3.8-5.2,8.3-10,13.7-13.5c5.4-3.5,12.5-5,16.2-10.8 c0.5-0.8,0.5-1.8,0-2.6c-9.8-14.1-20-28.5-31-41.8c-11.4-13.7-24.7-25.6-36.3-39.1c-0.2-0.3-0.5-0.6-0.7-0.9 c-4.6-5.4-8.6-11.1-12.5-17c-3.1-4.7-5.7-10.6-9.9-14.4c-3.6-3.2-12.6-1.1-17.2-0.9c-13,0.7-26.2,2.4-38.5,7.2 c-11.9,4.7-22.6,12.2-34.7,16.3c-8.8,3-18.3,4.2-26.1,9.2c-3.4,2.2-8.7,7.3-9.5,11.6c-0.9,5,6.8,10.8,9.9,14.2 c9.4,10.6,18.8,21.2,28.2,31.8c3.1,3.6,6.3,7.1,8.9,11.1c2.5,3.9,4.5,8,7,11.9c5,7.6,12,13.6,17.6,20.8c4.6,5.9,8.3,12.4,12.1,18.8 c4.4,7.4,9,17.4,15.3,23.4C449.6,244,457.4,247.6,463.3,251.7z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st1\",\n d: \"M381,286.8C381,286.8,381.1,286.8,381,286.8c0.6-0.6,1.3-0.8,2-0.9c3.9-0.6,7.5-2.2,11.3-3.2 c4.4-1.2,9.4-1.8,13.1,1.5c5.2,4.6,4,13.4,8.7,18.6c2.7,3.1,7,4.2,10.5,6.2c12.8,7,15.9,26.8,6,37.5c-20.9,1.8-37.9,11.5-56.6,20 c-17.4,7.9-35.2,1.8-53.3,1.9c-15.7,0.1-30.5,6.1-45.9,8.4c-11.3,1.7-22.9,2.7-34.4,3.9c-7.1,0.7-13.9,1-20.7,1 c-13.7,0-27.1-1-40.8-1c-26.7,0-31,9.6-48.4,9.6c-8.6,0-10.6-4.2-17.1-6.9c-2.2-0.9-4.4-2-6.6-3.2c-17.6-9.5-26.3-26.6-27.4-46.2 c-0.5-8.9,2.2-16.1,5.8-23c7.7-14.6,15.4-22.3,15.4-32.8c0-4.8-1.6-10.1-5.5-16.9c-1.7-3-3.2-6.1-3.6-9.9c-1-9.2,0.9-18.5,3.3-27.3 c3.8-13.6,9.2-28.9,18.2-40c5-6.1,11.9-10.3,17.5-15.8c5.7-5.7,8.7-13.6,14.8-18.8c4.6-4,10.7-5.7,16.7-7.3 c20-5.4,39.9-10.9,60.2-16.4c3.2-0.9,6.9-2,10.7-2.8c12.6-2.9,28.2-10,42.5-10c16,0,25.3,9.6,25.3,20.6c0,4.9-1.7,9.7-1.7,14.5 c0,9.6,7.5,13.7,7.6,21.9c0,0,0,0.1,0,0.1c0,1.4-0.4,3.5-1.9,3.9c0.3-0.1,0.7-0.2,1.1-0.3c-1.2,0.6-1.6,0.8-2.8,0.9 c1.2,2.4,3.5,4.7,5.3,6.7c19.4,21.5,31.1,48.7,46.1,73.5c2.9,4.9,6,9.6,9.2,14.3c4.4,6.4,9.1,12.6,13.7,18.8 c0.1,0.2,0.2,0.3,0.4,0.4C380.7,288.3,380.8,287.2,381,286.8z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st2\",\n d: \"M362.3,141.8c-0.5,0.1-3.9-5.5-4.2-6c-4.8-6.9-10.1-13.5-14.9-20.4c-0.5-0.7-0.9-1.6-1-2.5 c-0.1-2.4,2.2-4,3.2-5.9\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st3\",\n d: \"M562.3,179.9c0.3,6.9,0.2,14.1-2.6,20.5c-2.8,6.2-8.3,9.2-13.4,13.2c-8,6.3-12.3,15.6-17.4,24.1 c-5.5,9.2-13,16.4-21.2,23.2c-7.4,6.1-16,15.6-26.3,16.2c-5,0.3-9.5-2.8-13.2-5.9c-3.2-2.7-6.3-5.6-9.4-8.5 c-2.7-2.5-5.6-4.9-8.2-7.6c-1.8-1.9-3.4-4.1-5.1-6c-0.9-1.1-2-2.1-3.1-2.9c-1.2-0.8-2.5-1.1-3.7-1.8c0.5-1.5,4.7-3.3,5.7-4.5 c0.8-1,6.1,2.9,6.7,3.3c4.1,2.7,8.1,5.6,12.1,8.4c2.3,1.7,4.7,3.4,7,5c0.9,0.6,1.7,1.3,2.7,1.8c5.7,3.1,13.6-0.4,18.6-3.4 c5.7-3.5,10.7-8.2,15.2-13.2c2-2.2,4-4.5,5.9-6.8c7.8-9.7,12.2-21.2,19.4-31.2c3.8-5.2,8.3-10,13.7-13.5c2.9-1.9,6.1-3.1,9.2-4.8 c1.5-0.8,3-1.7,4.3-2.8c1.1-0.9,1.8-2.1,2.7-3.2C562,179.7,562.1,179.8,562.3,179.9z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st3\",\n d: \"M345.4,107c-1.1,1.9-3.4,3.5-3.2,5.9c0,0.9,0.5,1.7,1,2.5c4.8,6.9,10,13.5,14.9,20.4c0.3,0.5,3.8,6.1,4.2,6 c2.3-0.6,4.5-2,6-3.9c-2.3-4-5-7.8-8.2-11.2c-2.3-2.5-4.6-4.8-6.6-7.7c-1.8-2.7-3.5-5.5-5.7-7.9C346.8,109.9,346.5,108.1,345.4,107 z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st4\",\n d: \"M340,272.3c1.7,3.6,1.9,8.2,2.2,11.7c0.5,7.6-0.9,15.2-3.5,22.3c-2.1,5.7-8.5,21.1-16.8,18.6 c-2.8-0.9-4.3-4.4-3-8.7c0.1-0.3,0.2-0.5,0.3-0.8c0.3-0.7,0.1-1.6-0.5-2.1c-1.2-1-2.4-1.7-3.8-2.1c-14.1-4.3-25.4,13.2-22.4,25.4 c1.9,7.8,7.9,14.2,14.9,18c16,8.7,33.8,5.3,47-6.8c10.5-9.6,16.8-22.5,20.4-36.1c1-3.8,1.8-7.7,2.5-11.6c1-6.1,1.7-13.3,7.6-16.8 c4-2.3,9.3-2.2,13.6-4c4.6-1.9,8.7-4.7,12.1-8.2c5.6-5.8,9.4-12.9,16-19.7c7.4-7.6,16.1-11.8,26.1-14.9c10.5-3.2,18-7.6,23.1-17.4 c5-9.5,9.7-19.1,14.9-28.5c6.1-11,12.2-22,16.4-33.9c3.9-11.2,6.2-23.8,1.4-35.1c-0.3-0.7-0.7-1.4-1.2-2.1 c-10.4-15.2-41.4-13.2-57.2-13c-11.1,0.1-22.2,0.8-33.3,1.8c-5.4,0.5-11.4,0.3-16.6,1.8c-7.7,2.1-13.3,8.3-18,14.4 c-6.5,8.2-13.5,14.8-23.3,19c-8.7,3.7-16.7,4.7-24.1,7.8c-4.6,1.9-8.6,4.7-12,8.3c-3.2,3.4-5,8.4-8.6,11.2c-7.5,6-18,1.1-25-3.3 c-10.9-6.8-20.4-13.8-33.5-15.6c-11.7-1.7-23.9,0.1-35,3.2c-26.7,7.4-69.6,33.7-52.5,68.8c2,4.1,9.1,13.1,15.8,15.3 c10.6,3.5,25.5-6.7,21.5-20.5c-0.4-1.3-1-2.6-1.9-3.8c-0.4-0.6-1.1-0.8-1.7-0.6c-0.2,0.1-0.5,0.2-0.7,0.2c-3.7,1.1-6.8-0.6-7.6-3.3 c-1.5-5.4,3.8-9.4,7.8-11.7c5.5-3.2,11.9-5.4,18.2-6c3.3-0.3,6.7-0.3,9.9,0.3c1.8,0.3,3.6,0.6,5.4,1.2c0.8,0.3,1.9,0.6,2.6,1.1 c1,0.8,0.5,1.2,0.1,2.3c-0.4,1.2-0.2,2.6,0.2,3.8c0.8,2.3,2.5,4.4,4.7,5.5c4,2,8.8,1.7,13.3,1.5c4.5-0.1,9.4,0.2,12.8,3.1 c3.2,2.7,4.4,7.2,7.3,10.1c6.2,6.2,18.7,4.4,22.8,12.2c2,3.8,1.1,8.7,3.3,12.4c3.4,6,12.4,5.8,17.3,10.5c2.7,2.6,4.2,6.6,7.6,8.2 c3.3,1.6,7.8,0.5,10.7,2.8C338.6,269.8,339.4,270.9,340,272.3z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st5\",\n d: \"M382.2,207.7c-2.5,1.9-10-8.7-13.4-13.6c-0.8-1.2-0.5-2.9,0.8-3.7c5.6-3.4,13.6-4.7,17.6,1.6 C389.8,196.2,389,202.6,382.2,207.7z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st6\",\n d: \"M406.7,199.2c3.8,2.8,8.4,4.2,12.7,6.1c4.3,1.8,8.7,4.2,11.2,8.2c4,6.4,2,15.3-3.1,20.8 c-3,3.2-7.1,5.5-11.4,5.4c-7.2-0.1-12.8-6.4-15.5-13c-4.5-11.1-3.4-23.8-0.9-35.4c0.1-0.6,0.9-0.6,1.1,0 C401.7,194.5,404.1,197.3,406.7,199.2z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st7\",\n d: \"M406.7,199.2c3.8,2.8,8.4,4.2,12.7,6.1c4.3,1.8,8.7,4.2,11.2,8.2c4,6.4,2,15.3-3.1,20.8 c-3,3.2-7.1,5.5-11.4,5.4c-7.2-0.1-12.8-6.4-15.5-13c-4.5-11.1-3.4-23.8-0.9-35.4c0.1-0.6,0.9-0.6,1.1,0 C401.7,194.5,404.1,197.3,406.7,199.2z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st0\",\n d: \"M422.1,233.8c0,0,0.1-0.1,0.1-0.1c0.1-0.2,0.1-0.6,0.1-0.9c-0.3-1.3-1.5-1.8-2.6-2c-2.1-0.3-4-0.1-5.8-1.8 c-0.8-0.8-1.5-1.9-2.4-2.6c-1.1-0.8-2.4-0.3-2.8,0.8c-0.6,1.3-0.3,3,0.7,4.3c1.4,1.7,3.6,2.6,5.5,2.9c2.1,0.4,4.2,0.2,6.1-0.2 C421.3,234.2,421.8,234.1,422.1,233.8z\"\n }))), _g2 || (_g2 = /*#__PURE__*/React.createElement(\"g\", {\n id: \"outline\"\n }, /*#__PURE__*/React.createElement(\"path\", {\n className: \"st2\",\n d: \"M562.3,178.8c0.3,7.3,0.4,14.9-2.6,21.7c-2.8,6.2-8.3,9.2-13.4,13.2c-8,6.3-12.3,15.6-17.4,24.1 c-5.5,9.2-13,16.4-21.2,23.2c-7.4,6.1-16,15.6-26.3,16.2c-5,0.3-9.5-2.8-13.2-5.9c-3.2-2.7-6.3-5.6-9.4-8.5 c-2.7-2.5-5.6-4.9-8.2-7.6c-1.8-1.9-3.4-4.1-5.1-6c-0.9-1.1-2-2.1-3.1-2.9c-1.2-0.8-2.5-1.1-3.7-1.8c0.5-1.5,4.7-3.3,5.7-4.5 c0.8-1,6.1,2.9,6.7,3.3c4.1,2.7,8.1,5.6,12.1,8.4c2.3,1.7,4.7,3.4,7,5c0.9,0.6,1.7,1.3,2.7,1.8c5.7,3.1,13.6-0.4,18.6-3.4 c5.7-3.5,10.7-8.2,15.2-13.2c2-2.2,4-4.5,5.9-6.8c7.8-9.7,12.2-21.2,19.4-31.2c3.8-5.2,8.3-10,13.7-13.5c5.4-3.5,12.5-5,16.2-10.8 c0.5-0.8,0.5-1.8,0-2.6c-9.8-14.1-20-28.5-31-41.8c-11.4-13.7-24.7-25.6-36.3-39.1c-0.2-0.3-0.5-0.6-0.7-0.9 c-4.6-5.4-8.6-11.1-12.5-17c-3.1-4.7-5.7-10.6-9.9-14.4c-3.6-3.2-12.6-1.1-17.2-0.9c-13,0.7-26.2,2.4-38.5,7.2 c-11.9,4.7-22.6,12.2-34.7,16.3c-8.8,3-18.3,4.2-26.1,9.2c-4.3,2.7-6.9,6.7-9.3,11c-0.1,0.3-0.3,0.5-0.3,0.8c-0.1,1.1,1.3,2.3,2,3 c2.5,2.6,4.3,5.6,6.3,8.5c1.9,2.8,4.3,5.2,6.6,7.7c3.1,3.4,5.9,7.2,8.2,11.2c-1.5,1.9-3.6,3.3-6,3.9c-0.5,0.1-3.9-5.5-4.2-6 c-4.8-6.9-10.1-13.5-14.9-20.4c-0.5-0.7-0.9-1.6-1-2.5c-0.1-2.4,2.2-4,3.2-5.9\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st8\",\n d: \"M330.1,256.9c5.2,4.2,8.4,11.5,10.3,17.4c3.4,10.4,2,21.8-1.7,31.9c-2.1,5.7-8.5,21.1-16.8,18.6 c-2.8-0.9-4.3-4.4-3-8.7c0.1-0.3,0.2-0.5,0.3-0.8c0.3-0.7,0.1-1.6-0.5-2.1c-1.2-1-2.4-1.7-3.8-2.1c-14.1-4.3-25.4,13.2-22.4,25.4 c1.9,7.8,7.9,14.2,14.9,18c16,8.7,33.8,5.3,47-6.8c10.5-9.6,16.8-22.5,20.4-36.1c1-3.8,1.8-7.7,2.5-11.6c1-6.1,1.7-13.3,7.6-16.8 c4-2.3,9.3-2.2,13.6-4c4.6-1.9,8.7-4.7,12.1-8.2c5.6-5.8,9.4-12.9,16-19.7c7.4-7.6,16.1-11.8,26.1-14.9c10.5-3.2,18-7.6,23.1-17.4 c5-9.5,9.7-19.1,14.9-28.5c6.1-11,12.2-22,16.4-33.9c3.9-11.2,6.2-23.8,1.4-35.1c-0.3-0.7-0.7-1.4-1.2-2.1 c-10.4-15.2-41.4-13.2-57.2-13c-11.1,0.1-22.2,0.8-33.3,1.8c-5.4,0.5-11.4,0.3-16.6,1.8c-7.7,2.1-13.3,8.3-18,14.4 c-6.5,8.2-13.5,14.8-23.3,19c-8.7,3.7-16.7,4.7-24.1,7.8c-4.6,1.9-8.6,4.7-12,8.3c-3.2,3.4-5,8.4-8.6,11.2c-7.5,6-18,1.1-25-3.3 c-10.9-6.8-20.4-13.8-33.5-15.6c-11.7-1.7-23.9,0.1-35,3.2c-26.7,7.4-69.6,33.7-52.5,68.8c2,4.1,9.1,13.1,15.8,15.3 c10.6,3.5,25.5-6.7,21.5-20.5c-0.4-1.3-1-2.6-1.9-3.8c-0.4-0.6-1.1-0.8-1.7-0.6c-0.2,0.1-0.5,0.2-0.7,0.2c-3.7,1.1-6.8-0.6-7.6-3.3 c-2.3-8.1,11-13.6,15.8-15.4c8.7-3.1,18.5-3.9,27.6-0.1c3.8,1.6,7.2,3.9,10.5,6.3c0,0,0.1,0.1,0.1,0.1\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st5\",\n d: \"M393.9,148.7c5.7,2.7,10.4,7.4,12.3,13.6c1.7-1,3.2-1.9,5.1-2.5c0.6-0.2-1.5-5.7-1.7-6.3 c-1.9-4.2-5.3-7.7-8.8-10.6c-0.8-0.7-4.8-4.6-5.8-4c-3,1.9-6.4,4.3-8.3,7.3c0.2-0.3,3.8,1.1,4.1,1.2 C391.9,147.8,392.9,148.2,393.9,148.7z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st5\",\n d: \"M435.5,204c-0.3-6.4-3-12.5-8.1-16.6c1.6-1.2,3-2.3,4.2-3.9c0.4-0.5,4.8,3.5,5.2,4c3.2,3.4,5.2,7.8,6.7,12.2 c0.3,1,2.5,6.2,1.5,7c-2.9,2.1-6.4,4.4-10,5c0.4-0.1,0.4-3.9,0.4-4.3C435.5,206.2,435.5,205.1,435.5,204z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st9\",\n d: \"M382.2,207.7c-2.5,1.9-10-8.7-13.4-13.6c-0.8-1.2-0.5-2.9,0.8-3.7c5.6-3.4,13.6-4.7,17.6,1.6 C389.8,196.2,389,202.6,382.2,207.7z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st10\",\n d: \"M382.4,206.9c-2.2,1.7-9.2-8.3-12.3-12.8c-0.8-1.1-0.5-2.7,0.6-3.4c2.6-1.6,7.2,1.2,10.4,3.9 c0.3,0.3,0.6,0.6,0.8,1C383,197.5,387.4,203,382.4,206.9z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st11\",\n d: \"M406.7,199.2c3.8,2.8,8.4,4.2,12.7,6.1c4.3,1.8,8.7,4.2,11.2,8.2c4,6.4,2,15.3-3.1,20.8 c-3,3.2-7.1,5.5-11.4,5.4c-7.2-0.1-12.8-6.4-15.5-13c-4.5-11.1-3.4-23.8-0.9-35.4c0.1-0.6,0.9-0.6,1.1,0 C401.7,194.5,404.1,197.3,406.7,199.2z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st12\",\n d: \"M380.9,286.6c9.3-1.7,19.5-8.6,26.6-2.5c5.2,4.6,4,13.4,8.7,18.6c2.7,3.1,7,4.2,10.5,6.2 c12.8,7,15.9,26.8,6,37.5c-20.9,1.8-37.9,11.5-56.6,20c-17.4,7.9-35.2,1.8-53.3,1.9c-15.7,0.1-30.5,6.1-45.9,8.4 c-11.3,1.7-22.9,2.7-34.4,3.9c-7.1,0.7-13.9,1-20.7,1c-13.7,0-27.1-1-40.8-1c-26.7,0-31,9.6-48.4,9.6c-8.6,0-10.6-4.2-17.1-6.9 c-2.2-0.9-4.4-2-6.6-3.2c-17.6-9.5-26.3-26.6-27.4-46.2c-0.5-8.9,2.2-16.1,5.8-23c7.7-14.6,15.4-22.3,15.4-32.8 c0-4.8-1.6-10.1-5.5-16.9c-1.7-3-3.2-6.1-3.6-9.9c-1-9.2,0.9-18.5,3.3-27.3c3.8-13.6,9.2-28.9,18.2-40c5-6.1,11.9-10.3,17.5-15.8 c5.7-5.7,8.7-13.6,14.8-18.8c4.6-4,10.7-5.7,16.7-7.3c20-5.4,39.9-10.9,60.2-16.4c3.2-0.9,6.9-2,10.7-2.8c12.6-2.9,28.2-10,42.5-10 c16,0,25.3,9.6,25.3,20.6c0,4.9-1.7,9.7-1.7,14.5c0,9.6,7.5,13.7,7.6,21.9c0,0,0,0.1,0,0.1c0,1.4-0.4,3.5-1.9,3.9 c0.3-0.1,0.7-0.2,1.1-0.3c-1.2,0.6-1.6,0.8-2.8,0.9\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n className: \"st12\",\n d: \"M238.4,196.7c-2.6,3.7,0.1,9.1,4.1,11.1c4,2,8.8,1.7,13.3,1.5c4.5-0.1,9.4,0.2,12.8,3.1 c3.2,2.7,4.4,7.2,7.3,10.1c6.2,6.2,18.7,4.4,22.8,12.2c2,3.8,1.1,8.7,3.3,12.4c3.4,6,12.4,5.8,17.3,10.5c2.7,2.6,4.2,6.6,7.6,8.2 c3.9,1.9,9.9,0.4,12,4.3\"\n }))));\n}\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(SvgSonarSleep);\nexport default __webpack_public_path__ + \"static/media/SonarSleep.f138ed4f.svg\";\nexport { ForwardRef as ReactComponent };","import { LoadingOutlined } from '@ant-design/icons';\nimport { Spin } from 'antd';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { withRenderDelay } from '../hocs/withRenderDelay';\nimport { compose } from '../util/compose';\nimport { Duration } from '../util/Duration';\n\nconst RENDER_DELAY = Duration.sec(1);\n\nconst Outer = styled.div`\n margin-top: 7em;\n text-align: center;\n`;\n\nconst Icon = styled(LoadingOutlined)`\n font-size: 5em;\n`;\n\nconst enhance = compose(withRenderDelay(RENDER_DELAY));\n\nexport const Fallback: React.FC = enhance(() => {\n return (\n \n } />\n \n );\n});\n","import { useEffectOnce } from './useEffectOnce';\n\ntype Callback = () => void;\n\nexport const useTimeout = (callback: Callback, ms: number) => {\n useEffectOnce(() => {\n const handle = window.setTimeout(callback, ms);\n return () => {\n window.clearTimeout(handle);\n };\n });\n};\n","import { createAction, createReducer } from '@reduxjs/toolkit';\nimport React, { PropsWithChildren, useEffect, useReducer } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { RouteInfoState } from './types';\n\nconst ROUTE_INFO_ACTIONS = {\n setPrevRoute: createAction<{ prevRoute: string }>('setPrevRoute'),\n setReturnToRoute: createAction<{ returnToRoute: string }>('setReturnToRoute'),\n};\n\nconst RETURN_TO_PATHNAMES = ['/library', '/n', '/upload', '/users', '/tags'];\n\nconst getInitialState = (): RouteInfoState => ({ prevRoute: '', returnToRoute: '/library' });\n\nconst routeInfoReducer = createReducer(getInitialState(), (builder) => {\n builder.addCase(ROUTE_INFO_ACTIONS.setPrevRoute, (state, action) => {\n state.prevRoute = action.payload.prevRoute;\n });\n builder.addCase(ROUTE_INFO_ACTIONS.setReturnToRoute, (state, action) => {\n state.returnToRoute = action.payload.returnToRoute;\n });\n});\n\nexport const RouteInfoCtx = React.createContext(getInitialState());\n\nexport const RouteInfoProvider: React.FC> = (props) => {\n const [state, dispatch] = useReducer(routeInfoReducer, getInitialState());\n const location = useLocation();\n\n useEffect(\n // This only fires when cleaning up so it is not run when it initially mounts.\n () => () => {\n dispatch(ROUTE_INFO_ACTIONS.setPrevRoute({ prevRoute: location.pathname }));\n },\n [location]\n );\n\n useEffect(() => {\n if (RETURN_TO_PATHNAMES.some((pathname) => location.pathname.startsWith(pathname))) {\n dispatch(ROUTE_INFO_ACTIONS.setReturnToRoute({ returnToRoute: location.pathname }));\n }\n }, [location]);\n\n return {props.children};\n};\n","import { useContext } from 'react';\nimport { RouteInfoCtx } from './RouteInfoCtx';\n\nexport const useRouteInfo = () => {\n const state = useContext(RouteInfoCtx);\n return state;\n};\n","import { useLocalStorage } from './useLocalStorage';\n\nconst ROUTING_CACHE_KEY = 'stringsync_routing_cache';\n\ntype RoutingCache = {\n lastVisitedLandingAtMsSinceEpoch: number;\n};\n\nconst DEFAULT_ROUTING_CACHE: RoutingCache = Object.freeze({\n lastVisitedLandingAtMsSinceEpoch: 0,\n});\n\nexport const useRoutingLocalCache = () => useLocalStorage(ROUTING_CACHE_KEY, DEFAULT_ROUTING_CACHE);\n","import { useCallback, useEffect, useState } from 'react';\nimport { useRouteInfo } from '../ctx/route-info';\nimport { Duration } from '../util/Duration';\nimport { NumberRange } from '../util/NumberRange';\nimport { useRoutingLocalCache } from './useRoutingLocalCache';\n\n// Redirect the user if they last visited between 1 minute and 14 days.\n// This gives people a 1 minute grace period to try to visit the landing using a link\n// again.\nconst ONE_MINUTE = Duration.min(1);\nconst TWO_WEEKS = Duration.day(14);\nconst REDIRECT_LANDING_TO_LIBRARY_TIME_MS_RANGE = NumberRange.from(ONE_MINUTE.ms).to(TWO_WEEKS.ms);\n\nconst getMsSinceEpoch = () => new Date().getTime();\n\nexport const useRoutingBehavior = () => {\n const { prevRoute } = useRouteInfo();\n const isInitialPage = prevRoute === '';\n const [cache, updateCache] = useRoutingLocalCache();\n const [shouldRedirectFromLandingToLibrary, setShouldRedirectFromLandingToLibrary] = useState(() => {\n const msSinceLandingLastVisited = getMsSinceEpoch() - cache.lastVisitedLandingAtMsSinceEpoch;\n return isInitialPage && REDIRECT_LANDING_TO_LIBRARY_TIME_MS_RANGE.contains(msSinceLandingLastVisited);\n });\n\n const recordLandingVisit = useCallback(() => {\n updateCache({\n ...cache,\n lastVisitedLandingAtMsSinceEpoch: getMsSinceEpoch(),\n });\n }, [cache, updateCache]);\n\n useEffect(() => {\n setShouldRedirectFromLandingToLibrary(false);\n }, [isInitialPage]);\n\n return { shouldRedirectFromLandingToLibrary, recordLandingVisit };\n};\n","import { first } from 'lodash';\nimport { useCallback } from 'react';\nimport { UNKNOWN_ERROR_MSG } from '../lib/errors';\nimport { $gql, QuerySuggestedNotationsArgs, t } from '../lib/graphql';\nimport { GqlStatus, useGql } from './useGql';\nimport { useGqlHandler } from './useGqlHandler';\n\ntype SuccessCallback = (notationId: string) => void;\ntype ErrorsCallback = (errors: string[]) => void;\ntype GetRandomNotationId = () => void;\n\nconst RANDOM_NOTATION_ID_GQL = $gql\n .query('suggestedNotations')\n .setQuery([{ id: t.string }])\n .setVariables({ limit: t.number })\n .build();\n\nexport const useRandomNotationIdGetter = (\n onSuccess: SuccessCallback,\n onErrors: ErrorsCallback\n): [getRandomNotationId: GetRandomNotationId, loading: boolean] => {\n const [execute, res] = useGql(RANDOM_NOTATION_ID_GQL);\n const loading = res.status === GqlStatus.Pending;\n useGqlHandler\n .onSuccess(res, ({ data }) => {\n const id = first(data.suggestedNotations.map((notation) => notation.id));\n if (id) {\n onSuccess(id);\n } else {\n onErrors([UNKNOWN_ERROR_MSG]);\n }\n })\n .onErrors(res, ({ errors }) => {\n onErrors(errors);\n });\n\n const getRandomNotationId = useCallback(() => {\n execute({ limit: 1 });\n }, [execute]);\n\n return [getRandomNotationId, loading];\n};\n","import { Button } from 'antd';\nimport React, { useCallback, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useRandomNotationIdGetter } from '../hooks/useRandomNotationIdGetter';\nimport { notify } from '../lib/notify';\n\nexport const ImFeelingLucky = () => {\n const navigate = useNavigate();\n const [hasError, setHasError] = useState(false);\n const onSuccess = useCallback(\n (notationId: string) => {\n notify.message.success({ content: 'lucky!' });\n navigate(`/n/${notationId}`);\n },\n [navigate]\n );\n const onErrors = useCallback(() => {\n notify.message.error({ content: 'something went wrong' });\n setHasError(true);\n }, []);\n const [getRandomNotationId, loading] = useRandomNotationIdGetter(onSuccess, onErrors);\n\n return (\n \n );\n};\n","import { CustomerServiceOutlined, SearchOutlined, ThunderboltOutlined } from '@ant-design/icons';\nimport { Col, Divider, Row, Space } from 'antd';\nimport React from 'react';\nimport { Link, Navigate } from 'react-router-dom';\nimport styled from 'styled-components';\nimport { Layout, withLayout } from '../hocs/withLayout';\nimport { useEffectOnce } from '../hooks/useEffectOnce';\nimport { useRoutingBehavior } from '../hooks/useRoutingBehavior';\nimport { compose } from '../util/compose';\nimport { Box } from './Box';\nimport { ImFeelingLucky } from './ImFeelingLucky';\nimport { Logo } from './Logo';\nimport { Wordmark } from './Wordmark';\n\nconst LANDING_SRC = 'static/landing.jpg';\n\nconst Outer = styled.div`\n background-color: white;\n`;\n\nconst Jumbotron = styled.div`\n padding-top: 128px;\n padding-bottom: 128px;\n text-align: center;\n background-image: url(\"${LANDING_SRC}\");\n background-repeat: no-repeat;\n background-size: cover;\n position: relative;\n`;\n\nconst Overlay = styled.div`\n background-color: rgba(0, 0, 0, 0.6);\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 0;\n`;\n\nconst WhiteBox = styled(Box)`\n width: 55%;\n min-width: 300px;\n max-width: 500px;\n margin: 0 auto;\n text-align: center;\n position: relative;\n z-index: 1;\n color: ${(props) => props.theme['@primary-color']};\n`;\n\nconst Header = styled.h1`\n font-size: 3em;\n font-weight: lighter;\n margin-bottom: 0;\n background: white;\n`;\n\nconst SubHeader = styled.h2`\n font-size: 1.2em;\n margin-bottom: 1em;\n`;\n\nconst Title = styled.h3`\n color: ${(props) => props.theme['@primary-color']};\n`;\n\nconst Footer = styled.div`\n padding: 24px 48px;\n text-align: center;\n display: flex;\n\n svg {\n width: 2.5em;\n height: 2.5em;\n color: ${(props) => props.theme['@secondary-color']};\n }\n`;\n\ntype Props = {};\n\nconst enhance = compose(withLayout(Layout.DEFAULT));\n\nexport const Landing: React.FC = enhance((props: Props) => {\n const { shouldRedirectFromLandingToLibrary, recordLandingVisit } = useRoutingBehavior();\n\n useEffectOnce(() => {\n recordLandingVisit();\n });\n\n return (\n \n {shouldRedirectFromLandingToLibrary && }\n\n \n \n \n \n \n learn how to play guitar for free\n\n \n \n start learning\n \n \n \n \n \n\n \n \n );\n});\n","import React from 'react';\n\nexport const Nothing: React.FC = () => null;\n","import Button, { ButtonProps } from 'antd/lib/button';\nimport React, { MouseEventHandler, useCallback, useEffect, useState } from 'react';\n\ntype Props = Omit & {\n timeoutMs: number;\n onTimeout?: () => void;\n};\n\nexport const TimeoutButton: React.FC = (props) => {\n const [disabled, setDisabled] = useState(false);\n\n const onClick = useCallback>(\n (event) => {\n setDisabled(true);\n props.onClick && props.onClick(event);\n },\n [props]\n );\n\n useEffect(() => {\n if (!disabled) {\n return;\n }\n const handle = setTimeout(() => {\n setDisabled(false);\n props.onTimeout && props.onTimeout();\n }, props.timeoutMs);\n return () => {\n clearTimeout(handle);\n };\n }, [disabled, props]);\n\n return ;\n};\n","import { once } from 'lodash';\nimport { useEffect } from 'react';\nimport { useMeta } from '../ctx/meta/useMeta';\nimport { useEffectOnce } from '../hooks/useEffectOnce';\nimport { useLocalStorage } from '../hooks/useLocalStorage';\nimport { notify } from '../lib/notify';\nimport { Duration } from '../util/Duration';\nimport { Nothing } from './Nothing';\nimport { TimeoutButton } from './TimeoutButton';\n\nconst LAST_LOADED_VERSION_KEY = 'stringsync_last_loaded_version';\nconst INITIAL_LAST_LOADED_VERSION = { version: '0.0.0' };\n\nexport const NewVersionNotifier = () => {\n const { version } = useMeta();\n const [lastLoaded, setLastLoaded] = useLocalStorage(LAST_LOADED_VERSION_KEY, INITIAL_LAST_LOADED_VERSION);\n\n useEffectOnce(() => {\n const lastLoadedVersion = lastLoaded.version;\n setLastLoaded({ version });\n // Prevent the message element from being baked into the crawled\n if (lastLoadedVersion !== INITIAL_LAST_LOADED_VERSION.version && version !== lastLoadedVersion) {\n notify.message.success({ content: `updated to ${version}`, duration: Duration.sec(3) });\n }\n });\n\n useEffect(() => {\n const serviceWorker = navigator.serviceWorker;\n if (!serviceWorker) {\n return;\n }\n\n // Adapted from https://whatwebcando.today/articles/handling-service-worker-updates/\n serviceWorker.ready.then((registration) => {\n // Sends a popup indicating to the user that a new update is available.\n const notifyUpdateAvailable = once((serviceWorker: ServiceWorker) => {\n const onClick = () => {\n serviceWorker.postMessage({ type: 'SKIP_WAITING' });\n serviceWorker.addEventListener('statechange', () => {\n if (serviceWorker.state === 'activated') {\n window.location.reload();\n }\n });\n };\n notify.popup.info({\n title: 'update available',\n content: (\n \n You are{' '}\n \n not\n {' '}\n on the latest version of stringsync.\n
\n ),\n placement: 'bottomLeft',\n closeIcon: ,\n button: (\n \n refresh\n \n ),\n duration: Duration.zero(),\n });\n });\n\n // ensure the case when the updatefound event was missed is also handled\n // by re-invoking the prompt when there's a waiting Service Worker\n if (registration.waiting) {\n notifyUpdateAvailable(registration.waiting);\n }\n\n // detect Service Worker update available and wait for it to become installed\n registration.addEventListener('updatefound', () => {\n if (registration.installing) {\n // wait until the new Service worker is actually installed (ready to take over)\n registration.installing.addEventListener('statechange', () => {\n if (registration.waiting) {\n // if there's an existing controller (previous Service Worker), show the prompt\n if (navigator.serviceWorker.controller) {\n notifyUpdateAvailable(registration.waiting);\n } else {\n // otherwise it's the first install, nothing to do\n console.log('Service Worker initialized for the first time');\n }\n }\n });\n }\n });\n });\n }, []);\n\n return null;\n};\n","export type DeepPartial = {\n [P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial[] : T[P] extends object ? DeepPartial : T[P];\n};\n\nexport type Nullable = T | null;\n\nexport type Ctor = {\n new (...args: any[]): T;\n};\n\nexport type OnlyKey = {\n [P in K]: V;\n};\n\nexport type Replace = Omit & {\n [P in K]: V;\n};\n\nexport type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;\n\nexport type IsUnion = [T] extends [UnionToIntersection] ? false : true;\n\nexport enum AuthRequirement {\n NONE,\n LOGGED_IN,\n LOGGED_OUT,\n LOGGED_IN_AS_STUDENT,\n LOGGED_IN_AS_TEACHER,\n LOGGED_IN_AS_ADMIN,\n}\n\nexport type Await = T extends PromiseLike ? U : T;\n\nexport type ukeyof = T extends T ? keyof T : never;\n\nexport type ValuesOf = T[keyof T];\n\nexport enum PromiseStatus {\n Idle = 'Idle',\n Pending = 'Pending',\n Resolved = 'Resolved',\n Rejected = 'Rejected',\n}\n\nexport type PromiseState = {\n result: T | undefined;\n error: Error | undefined;\n status: PromiseStatus;\n};\n\nexport type UnwrapPromise = T extends Promise ? R : T;\n\nexport interface PromiseResolver {\n resolve: (result: T) => void | PromiseLike;\n reject: (error: Error) => void;\n cancel: () => void;\n}\n\nexport type Dimensions = {\n width: number;\n height: number;\n};\n\nexport type FlatSerializable = {\n [key: string]: string | number | boolean | null;\n};\n","import React, { useEffect } from 'react';\nimport { Link } from 'react-router-dom';\nimport styled from 'styled-components';\nimport { SonarSleep } from './SonarSleep';\n\ninterface Props {}\n\nconst Outer = styled.div`\n text-align: center;\n margin-top: 24px;\n`;\n\nexport const NotFound: React.FC = () => {\n useEffect(() => {\n const originalTitle = document.title;\n document.title = `404 - ${originalTitle}`;\n return () => {\n document.title = originalTitle;\n };\n }, []);\n\n return (\n \n 404
\n not found
\n library\n\n
\n
\n\n \n \n );\n};\n","import { debounce } from 'lodash';\nimport Scroll from 'react-scroll';\n\nexport const scrollToTop = debounce(\n (opts?: any) => {\n Scroll.animateScroll.scrollToTop({\n duration: 200,\n ignoreCancelEvents: true,\n offset: 5,\n smooth: true,\n ...opts,\n });\n },\n 1000,\n { leading: true, trailing: true }\n);\n","import React, { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { scrollToTop } from '../util/scrollToTop';\n\nexport const ScrollBehavior: React.FC = () => {\n const location = useLocation();\n\n // Scroll to the top of the page whenever the route changes.\n useEffect(() => {\n return () => {\n scrollToTop({ duration: 0 });\n };\n }, [location.pathname]);\n\n return null;\n};\n","import React, { useEffect } from 'react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport { Fallback } from '../components/Fallback';\nimport { isLoggedInSelector, useAuth } from '../ctx/auth';\nimport { useRouteInfo } from '../ctx/route-info';\nimport { gtEqAdmin, gtEqStudent, gtEqTeacher } from '../domain';\nimport { UserRole } from '../lib/graphql';\nimport { notify } from '../lib/notify';\nimport { AuthRequirement } from '../util/types';\n\nconst isMeetingAuthReq = (authReqs: AuthRequirement, isLoggedIn: boolean, userRole: UserRole) => {\n switch (authReqs) {\n case AuthRequirement.NONE:\n return true;\n case AuthRequirement.LOGGED_IN:\n return isLoggedIn;\n case AuthRequirement.LOGGED_OUT:\n return !isLoggedIn;\n case AuthRequirement.LOGGED_IN_AS_STUDENT:\n return isLoggedIn && gtEqStudent(userRole);\n case AuthRequirement.LOGGED_IN_AS_TEACHER:\n return isLoggedIn && gtEqTeacher(userRole);\n case AuthRequirement.LOGGED_IN_AS_ADMIN:\n return isLoggedIn && gtEqAdmin(userRole);\n default:\n // fail open for unhandled authReqs\n return true;\n }\n};\n\nexport const withAuthRequirement = (authReq: AuthRequirement) =>\n function(Component: React.ComponentType
): React.FC
{\n return (props) => {\n const [authState] = useAuth();\n const isLoggedIn = isLoggedInSelector(authState);\n const isAuthPending = authState.isPending;\n const userRole = authState.user.role;\n const navigate = useNavigate();\n const location = useLocation();\n\n let { returnToRoute } = useRouteInfo();\n returnToRoute = location.pathname === returnToRoute ? '/library' : returnToRoute;\n\n const meetsAuthReqs = isMeetingAuthReq(authReq, isLoggedIn, userRole);\n\n useEffect(() => {\n if (isAuthPending || meetsAuthReqs) {\n return;\n }\n // when the current session fails to meet auth\n // reqs, redirect the user to somewhere reasonable\n switch (authReq) {\n case AuthRequirement.NONE:\n break;\n case AuthRequirement.LOGGED_IN:\n notify.message.error({ content: 'must be logged in' });\n navigate('/login');\n break;\n case AuthRequirement.LOGGED_OUT:\n navigate(returnToRoute);\n break;\n case AuthRequirement.LOGGED_IN_AS_STUDENT:\n notify.message.error({ content: 'must be logged in as a student' });\n navigate(isLoggedIn ? returnToRoute : '/login');\n break;\n case AuthRequirement.LOGGED_IN_AS_TEACHER:\n notify.message.error({ content: 'must be logged in as a teacher' });\n navigate(isLoggedIn ? returnToRoute : '/login');\n break;\n case AuthRequirement.LOGGED_IN_AS_ADMIN:\n notify.message.error({ content: 'must be logged in as a admin' });\n navigate(isLoggedIn ? returnToRoute : '/login');\n break;\n }\n }, [isAuthPending, meetsAuthReqs, navigate, isLoggedIn, returnToRoute]);\n\n return meetsAuthReqs ? : ;\n };\n };\n","import { ConfigProvider } from 'antd';\nimport enUS from 'antd/lib/locale-provider/en_US';\nimport * as React from 'react';\nimport { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';\nimport { ThemeProvider } from 'styled-components';\nimport './App.less';\nimport { Fallback } from './components/Fallback';\nimport { Landing } from './components/Landing';\nimport { NewVersionNotifier } from './components/NewVersionNotifier';\nimport { NotFound } from './components/NotFound';\nimport { Nothing } from './components/Nothing';\nimport { ScrollBehavior } from './components/ScrollBehavior';\nimport { AuthProvider } from './ctx/auth';\nimport { DeviceProvider } from './ctx/device';\nimport { MetaProvider } from './ctx/meta';\nimport { RouteInfoProvider } from './ctx/route-info';\nimport { ViewportProvider } from './ctx/viewport';\nimport { withAuthRequirement } from './hocs/withAuthRequirement';\nimport { theme } from './theme';\nimport { compose } from './util/compose';\nimport { AuthRequirement } from './util/types';\n\n// TODO(jared): Remove after types get fixed.\n// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/59765\nconst ThemeProviderProxy: any = ThemeProvider;\n\nconst Library = compose(withAuthRequirement(AuthRequirement.NONE))(React.lazy(() => import('./components/Library')));\nconst N = compose(withAuthRequirement(AuthRequirement.NONE))(React.lazy(() => import('./components/N')));\nconst NEdit = compose(withAuthRequirement(AuthRequirement.LOGGED_IN_AS_TEACHER))(\n React.lazy(() => import('./components/NEdit'))\n);\nconst NExport = compose(withAuthRequirement(AuthRequirement.LOGGED_IN_AS_ADMIN))(\n React.lazy(() => import('./components/NExport'))\n);\nconst NRecord = compose(withAuthRequirement(AuthRequirement.LOGGED_IN_AS_ADMIN))(\n React.lazy(() => import('./components/NRecord'))\n);\nconst Signup = compose(withAuthRequirement(AuthRequirement.LOGGED_OUT))(\n React.lazy(() => import('./components/Signup'))\n);\nconst Login = compose(withAuthRequirement(AuthRequirement.LOGGED_OUT))(React.lazy(() => import('./components/Login')));\nconst ConfirmEmail = compose(withAuthRequirement(AuthRequirement.LOGGED_IN))(\n React.lazy(() => import('./components/ConfirmEmail'))\n);\nconst ForgotPassword = compose(withAuthRequirement(AuthRequirement.LOGGED_OUT))(\n React.lazy(() => import('./components/ForgotPassword'))\n);\nconst ResetPassword = compose(withAuthRequirement(AuthRequirement.LOGGED_OUT))(\n React.lazy(() => import('./components/ResetPassword'))\n);\nconst Upload = compose(withAuthRequirement(AuthRequirement.LOGGED_IN_AS_TEACHER))(\n React.lazy(() => import('./components/Upload'))\n);\nconst UserIndex = compose(withAuthRequirement(AuthRequirement.LOGGED_IN_AS_ADMIN))(\n React.lazy(() => import('./components/UserIndex'))\n);\nconst TagIndex = compose(withAuthRequirement(AuthRequirement.LOGGED_IN_AS_ADMIN))(\n React.lazy(() => import('./components/TagIndex'))\n);\n\nexport const App: React.FC = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n }>\n \n } />\n }>\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n \n \n \n \n \n \n \n \n \n \n );\n};\n","import { ReportHandler } from 'web-vitals';\n\nconst reportWebVitals = (onPerfEntry?: ReportHandler) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://cra.link/PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(/^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)\n);\n\ntype Config = {\n onSuccess?: (registration: ServiceWorkerRegistration) => void;\n onUpdate?: (registration: ServiceWorkerRegistration) => void;\n};\n\nexport function register(config?: Config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://cra.link/PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl: string, config?: Config) {\n navigator.serviceWorker\n .register(swUrl)\n .then((registration) => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://cra.link/PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch((error) => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl: string, config?: Config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' },\n })\n .then((response) => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then((registration) => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log('No internet connection found. App is running in offline mode.');\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready\n .then((registration) => {\n registration.unregister();\n })\n .catch((error) => {\n console.error(error.message);\n });\n }\n}\n","import React from 'react';\nimport { createRoot, hydrateRoot } from 'react-dom/client';\nimport { App } from './App';\nimport { REACT_SNAP_ACTIVE } from './constants';\nimport reportWebVitals from './reportWebVitals';\nimport * as serviceWorker from './serviceWorkerRegistration';\n\nconst rootElement = document.getElementById('root');\nif (rootElement?.hasChildNodes()) {\n hydrateRoot(rootElement, );\n} else {\n const root = createRoot(rootElement!);\n root.render();\n}\n\nconst isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';\nif (isLocalhost) {\n serviceWorker.unregister();\n} else if (!REACT_SNAP_ACTIVE) {\n serviceWorker.register();\n}\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n","export enum HttpStatus {\n OK = 200,\n INTERNAL_SERVER_ERROR = 500,\n}\n\nexport enum ErrorCode {\n UNKNOWN,\n FORBIDDEN,\n CONFLICT,\n BAD_REQUEST,\n NOT_FOUND,\n INTERNAL,\n NOT_IMPLEMENTED,\n}\n","export const UNKNOWN_ERROR_MSG = 'something went wrong';\nexport const NOT_IMPLEMENTED_MSG = 'not implemented';\nexport const MISSING_DATA_MSG = 'missing data';\n","import { MISSING_DATA_MSG } from '.';\nimport { NOT_IMPLEMENTED_MSG, UNKNOWN_ERROR_MSG } from './constants';\nimport { ErrorCode } from './types';\n\nexport class StringSyncError extends Error {\n code: ErrorCode;\n\n constructor(message: string, code: ErrorCode) {\n super(message);\n this.code = code;\n }\n\n get extensions() {\n return { code: this.code };\n }\n}\n\nexport class UnknownError extends StringSyncError {\n constructor(message: string = UNKNOWN_ERROR_MSG) {\n super(message, ErrorCode.UNKNOWN);\n }\n}\n\nexport class ConflictError extends StringSyncError {\n constructor(message: string) {\n super(message, ErrorCode.CONFLICT);\n }\n}\n\nexport class ForbiddenError extends StringSyncError {\n constructor(message: string) {\n super(message, ErrorCode.FORBIDDEN);\n }\n}\n\nexport class BadRequestError extends StringSyncError {\n constructor(message: string) {\n super(message, ErrorCode.BAD_REQUEST);\n }\n}\n\nexport class NotFoundError extends StringSyncError {\n constructor(message: string) {\n super(message, ErrorCode.NOT_FOUND);\n }\n}\n\nexport class InternalError extends StringSyncError {\n constructor(message: string) {\n super(message, ErrorCode.INTERNAL);\n }\n}\n\nexport class NotImplementedError extends StringSyncError {\n constructor(message: string = NOT_IMPLEMENTED_MSG) {\n super(message, ErrorCode.NOT_IMPLEMENTED);\n }\n}\n\nexport class MissingDataError extends StringSyncError {\n constructor(message: string = MISSING_DATA_MSG) {\n super(message, ErrorCode.INTERNAL);\n }\n}\n","import { get, set, toPath } from 'lodash';\n\n/**\n * An object path represents the property keys needed to get to a specific value.\n *\n * It is intended to be exclusively used with the Gql module.\n */\nexport class ObjectPath {\n static STAR = '0';\n\n private static SEPARATOR = '.';\n\n static create(...parts: string[]) {\n return new ObjectPath(...parts);\n }\n\n static fromString(partStr: string) {\n return ObjectPath.create(...toPath(partStr));\n }\n\n readonly parts: string[];\n\n private constructor(...parts: string[]) {\n this.parts = parts;\n }\n\n toString() {\n return this.parts.join(ObjectPath.SEPARATOR);\n }\n\n get(object: any): T | undefined {\n return get(object, this.parts);\n }\n\n set(object: any, value: any) {\n set(object, this.parts, value);\n }\n\n add(part: string): ObjectPath {\n return new ObjectPath(...this.parts, part);\n }\n\n match(path: ObjectPath): boolean {\n return this.toString() === path.toString();\n }\n}\n","import { isBoolean, isNull, isNumber, isObject, isPlainObject, isString } from 'lodash';\nimport { ObjectPath } from './ObjectPath';\n\ntype Meta = {\n isEnum?: boolean;\n isFile?: boolean;\n};\n\nconst META_KEY = Symbol('meta');\n\ntype Leaf = string | number | null | boolean | File;\n\ntype Entry = Leaf | Object | Object[] | Leaf[];\n\ntype ForEachEntryCallback = (entry: Entry, truePath: ObjectPath, schemaPath: ObjectPath) => void;\n\nexport const injectMeta = (object: any, meta: Meta) => {\n if (!isPlainObject(object)) {\n throw new Error(`can only inject metadata into plain objects, got: ${object}`);\n }\n object[META_KEY] = meta;\n return object;\n};\n\nexport const getMeta = (object: any): Meta | undefined => {\n if (!isPlainObject(object)) {\n return undefined;\n }\n return object[META_KEY];\n};\n\nconst isLeaf = (val: any): val is Leaf => {\n return isString(val) || isNumber(val) || isNull(val) || isBoolean(val) || val instanceof File;\n};\n\nexport const forEachEntry = (\n root: any,\n callback: ForEachEntryCallback,\n truePath = ObjectPath.create(),\n schemaPath = ObjectPath.create()\n) => {\n callback(root, truePath, schemaPath);\n\n if (isLeaf(root)) {\n return;\n } else if (Array.isArray(root)) {\n root.forEach((el, ndx) => {\n forEachEntry(el, callback, truePath.add(ndx.toString()), schemaPath.add(ObjectPath.STAR));\n });\n } else if (isObject(root)) {\n Object.entries(root).forEach(([key, val]) => {\n forEachEntry(val, callback, truePath.add(key), schemaPath.add(key));\n });\n } else {\n const msg = `unhandled root: ${root}`;\n console.error(msg);\n throw new Error(msg);\n }\n};\n","import { onUnion, types } from 'typed-graphqlify';\nimport { Nullable } from '../../util/types';\nimport * as helpers from './helpers';\nimport { GraphqlUnionSelection, GType, UnionSelection } from './types';\n\ntype ValueOf = T[keyof T];\n\ntype OptionalT = {\n number: Nullable;\n string: Nullable;\n boolean: Nullable;\n file: Nullable;\n oneOf: (_e: T) => Nullable>;\n custom: () => Nullable;\n};\n\n/**\n * Wrapper around typed-graphqlify's types.\n */\nexport class t {\n static string = types.string;\n static number = types.number;\n static boolean = types.boolean;\n static oneOf = (enumerable: T): ValueOf | keyof T => {\n const type = types.oneOf(enumerable);\n // This only works because t is actually a lie:\n // https://github.com/acro5piano/typed-graphqlify/blob/4b9d6d2bd1466dc3e503b1220a20abf8f554133c/src/types.ts#L50\n // If typed-graphqlify changes this, the metadata will have to be attached some other way.\n helpers.injectMeta(type, { isEnum: true });\n return type;\n };\n static custom = types.custom;\n static constant = types.constant;\n static get file(): File {\n const type = types.custom();\n helpers.injectMeta(type, { isFile: true });\n return type;\n }\n static optional: OptionalT = {\n number: t.number,\n string: t.string,\n boolean: t.boolean,\n oneOf: t.oneOf,\n custom: t.custom,\n file: t.file,\n } as any;\n // The reason why we have a returning function is to allow S to be inferred. Otherwise, we get the more genrealized\n // type and we have to specify all parameters.\n static union = >() => >(types: S): GraphqlUnionSelection => {\n const frags = {};\n for (const [__typename, fields] of Object.entries(types)) {\n Object.assign(frags, onUnion({ [__typename]: { __typename: t.constant(__typename), ...(fields as any) } }));\n }\n return frags as any;\n };\n}\n","import { extractFiles } from 'extract-files';\nimport { GraphQLError } from 'graphql';\nimport { cloneDeep, isObject, isString, isUndefined, toPath } from 'lodash';\nimport { mutation, onUnion, params, query, rawString } from 'typed-graphqlify';\nimport { Params } from 'typed-graphqlify/dist/render';\nimport { GRAPHQL_URI } from '.';\nimport { OnlyKey } from '../../util/types';\nimport { UnknownError } from '../errors';\nimport { Mutation, Query } from './graphqlTypes';\nimport * as helpers from './helpers';\nimport { ObjectPath } from './ObjectPath';\nimport { t } from './t';\nimport { StrictSelection } from './types';\n\nexport type Root = Query | Mutation;\nexport type Fields = keyof T;\nexport type Compiler = typeof query | typeof mutation;\ntype MaybeNullable = T extends any[] ? T : T | null;\n\nexport type Any$gql = $gql;\nexport type RootOf = G extends $gql ? T : never;\nexport type FieldOf = G extends $gql ? F : never;\nexport type DataOf = G extends $gql ? MaybeNullable : never;\nexport type VariablesOf = G extends $gql ? V : never;\n\nexport type SuccessfulResponse = { data: OnlyKey, DataOf>; errors?: never };\nexport type FailedResponse = { data: null; errors: GraphQLError[] };\nexport type GqlResponseOf = SuccessfulResponse | FailedResponse;\n\ntype VariableNameLookup = Record;\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport class $gql, Q, V> {\n static t = t;\n static union = onUnion;\n\n static query>(field: F) {\n return new GqlBuilder(query, field, undefined, undefined);\n }\n\n static mutation>(field: F) {\n return new GqlBuilder(mutation, field, undefined, undefined);\n }\n\n static async toGqlResponse(res: Response): Promise> {\n const contentType = res.headers.get('content-type');\n if (!contentType?.toLowerCase().includes('application/json')) {\n console.warn(`unexpected content-type, got: ${contentType}`);\n throw new UnknownError();\n }\n\n const json = await res.json();\n if (!$gql.isGqlResponse(json)) {\n console.warn('unexpected graphql response from server');\n throw new UnknownError();\n }\n\n return json;\n }\n\n private static isGqlResponse = (value: any): value is GqlResponseOf => {\n return isObject(value) && 'data' in value;\n };\n\n public readonly compiler: Compiler;\n public readonly field: F;\n public readonly query: Q;\n public readonly variables: V;\n\n constructor(compiler: Compiler, field: F, query: Q, variables: V) {\n this.compiler = compiler;\n this.field = field;\n this.query = query;\n this.variables = variables;\n }\n\n async fetch(variables: V, abortSignal?: AbortSignal): Promise> {\n const res = await fetch(GRAPHQL_URI, this.toRequestInit(variables, abortSignal));\n return await $gql.toGqlResponse(res);\n }\n\n toString(variables: V): string {\n const lookup = this.createVariableNameLookup(variables);\n\n const uploadVariableNames = Object.values(lookup);\n const name =\n uploadVariableNames.length > 0\n ? `${this.field.toString()}(${uploadVariableNames\n .map((variableName) => `$${variableName}: Upload!`)\n .join(', ')})`\n : this.field.toString();\n\n if (isObject(variables)) {\n return this.compiler(name, { [this.field]: params(this.graphqlify(variables, lookup), this.query) }).toString();\n } else {\n return this.compiler(name, { [this.field]: this.query }).toString();\n }\n }\n\n toRequestInit(variables: V, abortSignal?: AbortSignal): RequestInit {\n return {\n method: 'POST',\n headers: { Accept: 'application/json' },\n body: this.toFormData(variables),\n credentials: 'include',\n mode: 'cors',\n signal: abortSignal,\n };\n }\n\n toFormData(variables: V): FormData {\n const lookup = this.createVariableNameLookup(variables);\n\n // extract files\n const extraction = extractFiles(\n { query: this.toString(variables), variables },\n undefined,\n (value: any): value is File => value instanceof File\n );\n const clone = extraction.clone;\n const fileMap = extraction.files;\n\n // compute map\n const map: { [key: string]: string | string[] } = {};\n const pathGroups = Array.from(fileMap.values());\n for (let ndx = 0; ndx < pathGroups.length; ndx++) {\n const paths = pathGroups[ndx];\n map[ndx] = paths.map((path) => {\n const [first, ...rest] = toPath(path);\n const key = ObjectPath.create(...rest).toString();\n return key in lookup ? `${first}.${lookup[key]}` : path;\n });\n }\n\n // create form data\n const formData = new FormData();\n formData.append('operations', JSON.stringify(clone));\n formData.append('map', JSON.stringify(map));\n\n // append files to form data\n const files = Array.from(fileMap.keys());\n for (let ndx = 0; ndx < files.length; ndx++) {\n const file = files[ndx];\n formData.append(ndx.toString(), file, `@${file.name}`);\n }\n\n return formData;\n }\n\n private createVariableNameLookup(variables: V) {\n const lookup: Record = {};\n\n if (isUndefined(variables)) {\n return lookup;\n }\n\n let id = 0;\n\n helpers.forEachEntry(variables, (entry, truePath) => {\n if (entry instanceof File) {\n lookup[truePath.toString()] = `upload${id++}`;\n }\n });\n\n return lookup;\n }\n\n private graphqlify(variables: V, lookup: VariableNameLookup) {\n const params: Params = {};\n\n helpers.forEachEntry(variables, (entry, truePath, schemaPath) => {\n if (isString(entry) && !this.isEnum(schemaPath)) {\n truePath.set(params, rawString(entry));\n } else if (entry instanceof File) {\n const variableName = lookup[truePath.toString()];\n truePath.set(params, variableName ? `$${variableName}` : null);\n } else if (Array.isArray(entry)) {\n truePath.set(params, []);\n } else if (isObject(entry)) {\n truePath.set(params, {});\n } else {\n truePath.set(params, entry);\n }\n });\n\n return params;\n }\n\n private isEnum(path: ObjectPath): boolean {\n const t = path.get(this.variables);\n const meta = helpers.getMeta(t);\n return !!meta && !!meta.isEnum;\n }\n}\n\nclass GqlBuilder<\n T extends Root,\n F extends Fields,\n Q extends StrictSelection | void = void,\n V extends Record | void = void\n> {\n private compiler: Compiler;\n private field: F;\n private query: Q;\n private variables: V;\n\n constructor(compiler: Compiler, field: F, query: Q, variables: V) {\n this.compiler = compiler;\n this.field = field;\n this.query = query;\n this.variables = variables;\n }\n\n setQuery<_Q extends StrictSelection>(query: _Q) {\n return new GqlBuilder(this.compiler, this.field, query, this.variables);\n }\n\n setVariables<_V extends Record>(variables: _V) {\n return new GqlBuilder(this.compiler, this.field, this.query, variables);\n }\n\n build() {\n // validate query\n if (!this.query) {\n throw new Error(`must set query before building`);\n }\n this.compiler(this.query);\n\n // validate variables\n if (this.variables) {\n this.compiler(this.variables);\n }\n\n // For some reason, if we don't create new objects, some queries will be inexplicably \"linked\" to\n // each other causing unwanted mutations to the query object.\n return new $gql(this.compiler, this.field, cloneDeep(this.query), cloneDeep(this.variables));\n }\n}\n","export type Maybe = T | null;\nexport type Exact = { [K in keyof T]: T[K] };\nexport type MakeOptional = Omit & { [SubKey in K]?: Maybe };\nexport type MakeMaybe = Omit & { [SubKey in K]: Maybe };\n/** All built-in and custom scalars, mapped to their actual values */\nexport type Scalars = {\n ID: string;\n String: string;\n Boolean: boolean;\n Int: number;\n Float: number;\n /** The javascript `Date` as string. Type represents date and time as the ISO Date string. */\n DateTime: any;\n /** The `Upload` scalar type represents a file upload. */\n Upload: any;\n};\n\nexport type Query = {\n __typename?: 'Query';\n whoami?: Maybe;\n health: Health;\n version: Scalars['String'];\n notations: NotationConnection;\n notation?: Maybe;\n suggestedNotations: Array;\n tags: Array;\n user?: Maybe;\n users: UserConnection;\n userCount: UserCountOutput;\n};\n\nexport type QueryNotationsArgs = {\n before?: Maybe;\n after?: Maybe;\n first?: Maybe;\n last?: Maybe;\n query?: Maybe;\n tagIds?: Maybe>;\n};\n\nexport type QueryNotationArgs = {\n id: Scalars['String'];\n};\n\nexport type QuerySuggestedNotationsArgs = {\n id?: Maybe;\n limit: Scalars['Int'];\n};\n\nexport type QueryUserArgs = {\n id: Scalars['String'];\n};\n\nexport type QueryUsersArgs = {\n before?: Maybe;\n after?: Maybe;\n first?: Maybe;\n last?: Maybe;\n};\n\nexport type User = {\n __typename?: 'User';\n id: Scalars['ID'];\n createdAt: Scalars['DateTime'];\n updatedAt: Scalars['DateTime'];\n email: Scalars['String'];\n username: Scalars['String'];\n avatarUrl?: Maybe;\n role: UserRole;\n confirmedAt?: Maybe;\n resetPasswordTokenSentAt?: Maybe;\n notations: Array;\n};\n\nexport enum UserRole {\n STUDENT = 'STUDENT',\n TEACHER = 'TEACHER',\n ADMIN = 'ADMIN',\n}\n\nexport type Notation = {\n __typename?: 'Notation';\n id: Scalars['ID'];\n createdAt: Scalars['DateTime'];\n updatedAt: Scalars['DateTime'];\n songName: Scalars['String'];\n artistName: Scalars['String'];\n deadTimeMs: Scalars['Float'];\n durationMs: Scalars['Float'];\n private: Scalars['Boolean'];\n transcriberId: Scalars['String'];\n thumbnailUrl?: Maybe;\n videoUrl?: Maybe;\n musicXmlUrl?: Maybe;\n transcriber: User;\n tags: Array;\n};\n\nexport type Tag = {\n __typename?: 'Tag';\n id: Scalars['ID'];\n category: TagCategory;\n name: Scalars['String'];\n notations?: Maybe>;\n};\n\nexport enum TagCategory {\n GENRE = 'GENRE',\n DIFFICULTY = 'DIFFICULTY',\n}\n\nexport type Health = {\n __typename?: 'Health';\n isDbHealthy: Scalars['Boolean'];\n isCacheHealthy: Scalars['Boolean'];\n};\n\nexport type NotationConnection = {\n __typename?: 'NotationConnection';\n pageInfo: PageInfo;\n edges: Array;\n};\n\nexport type PageInfo = {\n __typename?: 'PageInfo';\n hasNextPage?: Maybe;\n hasPreviousPage?: Maybe;\n startCursor?: Maybe;\n endCursor?: Maybe;\n};\n\nexport type NotationEdge = {\n __typename?: 'NotationEdge';\n node: Notation;\n /** Used in `before` and `after` args */\n cursor: Scalars['String'];\n};\n\nexport type UserConnection = {\n __typename?: 'UserConnection';\n pageInfo: PageInfo;\n edges: Array;\n};\n\nexport type UserEdge = {\n __typename?: 'UserEdge';\n node: User;\n cursor: Scalars['String'];\n};\n\nexport type UserCountOutput = NumberValue | ForbiddenError | UnknownError;\n\nexport type NumberValue = {\n __typename?: 'NumberValue';\n value: Scalars['Float'];\n};\n\nexport type ForbiddenError = {\n __typename?: 'ForbiddenError';\n message: Scalars['String'];\n};\n\nexport type UnknownError = {\n __typename?: 'UnknownError';\n message: Scalars['String'];\n};\n\nexport type Mutation = {\n __typename?: 'Mutation';\n login: LoginOutput;\n logout: LogoutOutput;\n signup: SignupOutput;\n confirmEmail: ConfirmEmailOutput;\n resendConfirmationEmail: ResendConfirmationEmailOutput;\n sendResetPasswordEmail: Processed;\n resetPassword: ResetPasswordOutput;\n createNotation: CreateNotationOutput;\n updateNotation: UpdateNotationOutput;\n updateTag: UpdateTagOutput;\n createTag: CreateTagOutput;\n deleteTag: DeleteTagOutput;\n updateUser: UpdateUserOutput;\n};\n\nexport type MutationLoginArgs = {\n input: LoginInput;\n};\n\nexport type MutationSignupArgs = {\n input: SignupInput;\n};\n\nexport type MutationConfirmEmailArgs = {\n input: ConfirmEmailInput;\n};\n\nexport type MutationSendResetPasswordEmailArgs = {\n input: SendResetPasswordEmailInput;\n};\n\nexport type MutationResetPasswordArgs = {\n input: ResetPasswordInput;\n};\n\nexport type MutationCreateNotationArgs = {\n input: CreateNotationInput;\n};\n\nexport type MutationUpdateNotationArgs = {\n input: UpdateNotationInput;\n};\n\nexport type MutationUpdateTagArgs = {\n input: UpdateTagInput;\n};\n\nexport type MutationCreateTagArgs = {\n input: CreateTagInput;\n};\n\nexport type MutationDeleteTagArgs = {\n id: Scalars['String'];\n};\n\nexport type MutationUpdateUserArgs = {\n input: UpdateUserInput;\n};\n\nexport type LoginOutput = User | ForbiddenError;\n\nexport type LoginInput = {\n usernameOrEmail: Scalars['String'];\n password: Scalars['String'];\n};\n\nexport type LogoutOutput = Processed | ForbiddenError;\n\nexport type Processed = {\n __typename?: 'Processed';\n at: Scalars['DateTime'];\n};\n\nexport type SignupOutput = User | ForbiddenError | ValidationError | UnknownError;\n\nexport type ValidationError = {\n __typename?: 'ValidationError';\n details: Array;\n};\n\nexport type SignupInput = {\n username: Scalars['String'];\n email: Scalars['String'];\n password: Scalars['String'];\n};\n\nexport type ConfirmEmailOutput = EmailConfirmation | NotFoundError | BadRequestError | ForbiddenError | UnknownError;\n\nexport type EmailConfirmation = {\n __typename?: 'EmailConfirmation';\n confirmedAt: Scalars['DateTime'];\n};\n\nexport type NotFoundError = {\n __typename?: 'NotFoundError';\n message: Scalars['String'];\n};\n\nexport type BadRequestError = {\n __typename?: 'BadRequestError';\n message: Scalars['String'];\n};\n\nexport type ConfirmEmailInput = {\n confirmationToken: Scalars['String'];\n};\n\nexport type ResendConfirmationEmailOutput = Processed | ForbiddenError;\n\nexport type SendResetPasswordEmailInput = {\n email: Scalars['String'];\n};\n\nexport type ResetPasswordOutput = Processed | BadRequestError | UnknownError;\n\nexport type ResetPasswordInput = {\n email: Scalars['String'];\n resetPasswordToken: Scalars['String'];\n password: Scalars['String'];\n};\n\nexport type CreateNotationOutput = Notation | ForbiddenError | ValidationError | UnknownError;\n\nexport type CreateNotationInput = {\n songName: Scalars['String'];\n artistName: Scalars['String'];\n thumbnail: Scalars['Upload'];\n video: Scalars['Upload'];\n tagIds: Array;\n};\n\nexport type UpdateNotationOutput =\n | Notation\n | ForbiddenError\n | NotFoundError\n | BadRequestError\n | ValidationError\n | UnknownError;\n\nexport type UpdateNotationInput = {\n id: Scalars['String'];\n songName?: Maybe;\n artistName?: Maybe;\n deadTimeMs?: Maybe;\n durationMs?: Maybe;\n private?: Maybe;\n thumbnail?: Maybe;\n musicXml?: Maybe;\n};\n\nexport type UpdateTagOutput = Tag | ForbiddenError | NotFoundError | BadRequestError | ValidationError | UnknownError;\n\nexport type UpdateTagInput = {\n id: Scalars['String'];\n name?: Maybe;\n category?: Maybe;\n};\n\nexport type CreateTagOutput = Tag | ForbiddenError | ValidationError | BadRequestError | UnknownError;\n\nexport type CreateTagInput = {\n name: Scalars['String'];\n category: TagCategory;\n};\n\nexport type DeleteTagOutput = Processed | ForbiddenError | UnknownError;\n\nexport type UpdateUserOutput = User | ForbiddenError | NotFoundError | BadRequestError | ValidationError | UnknownError;\n\nexport type UpdateUserInput = {\n id: Scalars['String'];\n username?: Maybe;\n email?: Maybe;\n role?: Maybe;\n};\n","export const GRAPHQL_URI = `${window.location.origin}/graphql`;\n","import { EffectCallback, useEffect } from 'react';\n\nexport const useEffectOnce = (callback: EffectCallback) => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => callback(), []);\n};\n","// Conversions to milliseconds\nconst ms = (v: number) => v;\nconst sec = (v: number) => ms(v * 1000);\nconst min = (v: number) => sec(v * 60);\nconst hr = (v: number) => min(v * 60);\nconst day = (v: number) => hr(v * 24);\n\nexport class Duration {\n static zero() {\n return new Duration(0);\n }\n\n static ms(v: number) {\n return new Duration(ms(v));\n }\n\n static sec(v: number) {\n return new Duration(sec(v));\n }\n\n static min(v: number) {\n return new Duration(min(v));\n }\n\n static hr(v: number) {\n return new Duration(hr(v));\n }\n\n static day(v: number) {\n return new Duration(day(v));\n }\n\n private readonly _ms: number;\n\n private constructor(ms: number) {\n this._ms = ms;\n }\n\n eq(duration: Duration) {\n return this.ms === duration.ms;\n }\n\n plus(duration: Duration) {\n return Duration.ms(duration.ms + this.ms);\n }\n\n get ms() {\n return this._ms;\n }\n\n get sec() {\n return this.ms / 1000;\n }\n\n get min() {\n return this.sec / 60;\n }\n\n get hr() {\n return this.min / 60;\n }\n\n get day() {\n return this.hr / 24;\n }\n}\n","import { UserRole } from '../../lib/graphql';\nimport { AuthUser } from './types';\n\nexport const getNullAuthUser = (): AuthUser => ({\n id: '',\n username: '',\n email: '',\n role: UserRole.STUDENT,\n confirmedAt: null,\n});\n","import { GraphQLError } from 'graphql';\nimport { UnknownError } from '../../lib/errors';\nimport { UserRole } from '../../lib/graphql';\nimport { getNullAuthUser } from './getNullAuthUser';\nimport { AuthUser } from './types';\n\ntype AuthUserLike = Omit & {\n role: UserRole;\n};\n\nexport const toAuthUser = (authUserLike: AuthUserLike | null): AuthUser => {\n return authUserLike || getNullAuthUser();\n};\n\nexport const toErrorStrings = (errors: GraphQLError[] | void): string[] => {\n return (errors || [new UnknownError()]).map((error) => error.message);\n};\n","import { $gql, LoginInput, LoginOutput, LogoutOutput, SignupInput, SignupOutput, t, UserRole } from '../../lib/graphql';\n\nexport const whoami = $gql\n .query('whoami')\n .setQuery({\n id: t.string,\n email: t.string,\n username: t.string,\n role: t.optional.oneOf(UserRole)!,\n confirmedAt: t.string,\n })\n .build();\n\nexport const login = $gql\n .mutation('login')\n .setQuery({\n ...t.union()({\n User: {\n __typename: t.constant('User'),\n id: t.string,\n email: t.string,\n username: t.string,\n role: t.optional.oneOf(UserRole)!,\n confirmedAt: t.string,\n },\n ForbiddenError: {\n __typename: t.constant('ForbiddenError'),\n message: t.string,\n },\n }),\n })\n .setVariables<{ input: LoginInput }>({\n input: {\n usernameOrEmail: t.string,\n password: t.string,\n },\n })\n .build();\n\nexport const logout = $gql\n .mutation('logout')\n .setQuery({\n ...t.union()({\n Processed: {\n __typename: t.constant('Processed'),\n at: t.string,\n },\n ForbiddenError: {\n __typename: t.constant('ForbiddenError'),\n message: t.string,\n },\n }),\n })\n .build();\n\nexport const signup = $gql\n .mutation('signup')\n .setQuery({\n ...t.union()({\n User: {\n __typename: t.constant('User'),\n id: t.string,\n email: t.string,\n username: t.string,\n role: t.optional.oneOf(UserRole)!,\n confirmedAt: t.string,\n },\n ForbiddenError: {\n __typename: t.constant('ForbiddenError'),\n message: t.string,\n },\n ValidationError: {\n __typename: t.constant('ValidationError'),\n details: [t.string],\n },\n UnknownError: {\n __typename: t.constant('UnknownError'),\n message: t.string,\n },\n }),\n })\n .setVariables<{ input: SignupInput }>({\n input: {\n email: t.string,\n password: t.string,\n username: t.string,\n },\n })\n .build();\n","import { createAction, createReducer } from '@reduxjs/toolkit';\nimport { noop } from 'lodash';\nimport React, { PropsWithChildren, useCallback, useMemo, useReducer } from 'react';\nimport { useEffectOnce } from '../../hooks/useEffectOnce';\nimport { useGql } from '../../hooks/useGql';\nimport { useGqlHandler } from '../../hooks/useGqlHandler';\nimport { UNKNOWN_ERROR_MSG } from '../../lib/errors';\nimport { LoginInput, SignupInput } from '../../lib/graphql';\nimport { notify } from '../../lib/notify';\nimport { getNullAuthUser } from './getNullAuthUser';\nimport * as helpers from './helpers';\nimport * as queries from './queries';\nimport { AuthState, AuthUser } from './types';\n\nexport type AuthApi = {\n authenticate(): void;\n login(variables: { input: LoginInput }): void;\n logout(): void;\n signup(variables: { input: SignupInput }): void;\n clearErrors(): void;\n reset(): void;\n};\n\nconst getInitialState = (): AuthState => ({\n isPending: true,\n errors: [],\n user: getNullAuthUser(),\n});\n\nexport const AUTH_ACTIONS = {\n pending: createAction('pending'),\n setUser: createAction<{ user: AuthUser }>('setUser'),\n setErrors: createAction<{ errors: string[] }>('setErrors'),\n reset: createAction('reset'),\n clearErrors: createAction('clearErrors'),\n};\n\nconst authReducer = createReducer(getInitialState(), (builder) => {\n builder.addCase(AUTH_ACTIONS.pending, (state) => {\n state.isPending = true;\n state.errors = [];\n });\n builder.addCase(AUTH_ACTIONS.setUser, (state, action) => {\n state.isPending = false;\n state.errors = [];\n state.user = action.payload.user;\n });\n builder.addCase(AUTH_ACTIONS.setErrors, (state, action) => {\n state.isPending = false;\n state.errors = action.payload.errors;\n state.user = getNullAuthUser();\n });\n builder.addCase(AUTH_ACTIONS.clearErrors, (state) => {\n state.errors = [];\n });\n builder.addCase(AUTH_ACTIONS.reset, (state) => {\n state.user = getNullAuthUser();\n state.isPending = false;\n state.errors = [];\n });\n});\n\nexport const AuthStateCtx = React.createContext(getInitialState());\nexport const AuthApiCtx = React.createContext({\n authenticate: noop,\n login: noop,\n logout: noop,\n signup: noop,\n clearErrors: noop,\n reset: noop,\n});\n\nexport const AuthProvider: React.FC> = (props) => {\n const [state, dispatch] = useReducer(authReducer, getInitialState());\n\n const [authenticate, authenticateRes] = useGql(queries.whoami);\n useGqlHandler\n .onInit(authenticateRes, () => {\n dispatch(AUTH_ACTIONS.pending());\n })\n .onSuccess(authenticateRes, ({ data }) => {\n dispatch(AUTH_ACTIONS.setUser({ user: helpers.toAuthUser(data.whoami) }));\n })\n .onErrors(authenticateRes, () => {\n dispatch(AUTH_ACTIONS.setUser({ user: helpers.toAuthUser(null) }));\n })\n .onCancelled(authenticateRes, () => {\n dispatch(AUTH_ACTIONS.setUser({ user: helpers.toAuthUser(null) }));\n });\n\n const [login, loginRes] = useGql(queries.login);\n useGqlHandler\n .onPending(loginRes, () => {\n dispatch(AUTH_ACTIONS.pending());\n })\n .onSuccess(loginRes, ({ data }) => {\n switch (data.login?.__typename) {\n case 'User':\n dispatch(AUTH_ACTIONS.setUser({ user: data.login }));\n notify.message.success({ content: `logged in as ${data.login.username}` });\n break;\n default:\n dispatch(AUTH_ACTIONS.setErrors({ errors: [data.login?.message || UNKNOWN_ERROR_MSG] }));\n }\n });\n\n const [logout, logoutRes] = useGql(queries.logout);\n useGqlHandler\n .onPending(logoutRes, () => {\n dispatch(AUTH_ACTIONS.pending());\n })\n .onSuccess(logoutRes, ({ data }) => {\n switch (data.logout?.__typename) {\n case 'Processed':\n dispatch(AUTH_ACTIONS.setUser({ user: getNullAuthUser() }));\n notify.message.success({ content: 'logged out' });\n break;\n default:\n dispatch(AUTH_ACTIONS.setErrors({ errors: [data.logout?.message || UNKNOWN_ERROR_MSG] }));\n }\n });\n\n const [signup, signupRes] = useGql(queries.signup);\n useGqlHandler\n .onPending(signupRes, () => {\n dispatch(AUTH_ACTIONS.pending());\n })\n .onSuccess(signupRes, ({ data }) => {\n switch (data.signup?.__typename) {\n case 'User':\n dispatch(AUTH_ACTIONS.setUser({ user: data.signup }));\n notify.message.success({ content: `logged in as ${data.signup.username}` });\n break;\n case 'ValidationError':\n dispatch(AUTH_ACTIONS.setErrors({ errors: data.signup.details }));\n break;\n default:\n dispatch(AUTH_ACTIONS.setErrors({ errors: [data.signup?.message || UNKNOWN_ERROR_MSG] }));\n }\n });\n\n const clearErrors = useCallback(() => {\n dispatch(AUTH_ACTIONS.clearErrors());\n }, []);\n\n const reset = useCallback(() => {\n dispatch(AUTH_ACTIONS.reset());\n }, []);\n\n useEffectOnce(() => {\n authenticate();\n });\n\n const api = useMemo(\n () => ({\n authenticate,\n login,\n logout,\n signup,\n clearErrors,\n reset,\n }),\n [authenticate, login, logout, signup, clearErrors, reset]\n );\n\n return (\n \n {props.children}\n \n );\n};\n","import { AuthState } from './types';\n\nexport const isLoggedInSelector = (state: AuthState) => state.user.id !== '';\n","import { useContext } from 'react';\nimport { AuthApi, AuthApiCtx, AuthStateCtx } from './AuthCtx';\nimport { AuthState } from './types';\n\nexport const useAuth = (): [AuthState, AuthApi] => {\n const state = useContext(AuthStateCtx);\n const api = useContext(AuthApiCtx);\n return [state, api];\n};\n","/* eslint-disable react-hooks/rules-of-hooks */\nimport React, { useCallback, useEffect } from 'react';\nimport { Any$gql } from '../lib/graphql';\nimport { GqlRes, GqlStatus } from './useGql';\n\ntype ResHandler = (res: Extract, { status: S }>) => void;\n\ntype DynamicGqlHandler = (\n status: S,\n res: GqlRes,\n handler: ResHandler,\n deps?: React.DependencyList\n) => StaticEventHandlers;\n\ntype StaticGqlHandler = (\n res: GqlRes,\n handler: ResHandler,\n deps?: React.DependencyList\n) => StaticEventHandlers;\n\ntype StaticEventHandlers = {\n onInit: StaticGqlHandler;\n onPending: StaticGqlHandler;\n onSuccess: StaticGqlHandler;\n onErrors: StaticGqlHandler;\n onCancelled: StaticGqlHandler;\n};\n\nexport type UseGqlHandler = DynamicGqlHandler & StaticEventHandlers;\n\nexport const useGqlHandler: UseGqlHandler = (status, res, handler, deps = []): StaticEventHandlers => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const callback = useCallback(handler, deps);\n useEffect(() => {\n if (res.status === status) {\n callback(res as Extract);\n }\n }, [status, res, callback]);\n\n // allow for chaining\n return staticEventHandlers;\n};\n\nuseGqlHandler.onInit = (res, handler, deps = []) => {\n return useGqlHandler(GqlStatus.Init, res, handler, deps);\n};\n\nuseGqlHandler.onPending = (res, handler, deps = []) => {\n return useGqlHandler(GqlStatus.Pending, res, handler, deps);\n};\n\nuseGqlHandler.onSuccess = (res, handler, deps = []) => {\n return useGqlHandler(GqlStatus.Success, res, handler, deps);\n};\n\nuseGqlHandler.onErrors = (res, handler, deps = []) => {\n return useGqlHandler(GqlStatus.Errors, res, handler, deps);\n};\n\nuseGqlHandler.onCancelled = (res, handler, deps = []) => {\n return useGqlHandler(GqlStatus.Cancelled, res, handler, deps);\n};\n\nconst staticEventHandlers: StaticEventHandlers = {\n onInit: useGqlHandler.onInit,\n onPending: useGqlHandler.onPending,\n onSuccess: useGqlHandler.onSuccess,\n onErrors: useGqlHandler.onErrors,\n onCancelled: useGqlHandler.onCancelled,\n};\n","import { UserRole } from '../lib/graphql';\n\nexport const USER_ROLES: UserRole[] = [UserRole.STUDENT, UserRole.TEACHER, UserRole.ADMIN];\n","import { UserRole } from '../lib/graphql';\nimport { USER_ROLES } from './constants';\n\nexport const compareUserRoles = (role1: UserRole, role2: UserRole) => {\n const ndx1 = USER_ROLES.indexOf(role1);\n const ndx2 = USER_ROLES.indexOf(role2);\n\n if (ndx1 < ndx2) {\n return -1;\n }\n if (ndx1 > ndx2) {\n return 1;\n }\n return 0;\n};\n\nexport const ltStudent = (role: UserRole) => compareUserRoles(role, UserRole.STUDENT) < 0;\nexport const ltEqStudent = (role: UserRole) => compareUserRoles(role, UserRole.STUDENT) <= 0;\nexport const eqStudent = (role: UserRole) => compareUserRoles(role, UserRole.STUDENT) === 0;\nexport const gtEqStudent = (role: UserRole) => compareUserRoles(role, UserRole.STUDENT) >= 0;\nexport const gtStudent = (role: UserRole) => compareUserRoles(role, UserRole.STUDENT) > 0;\n\nexport const ltTeacher = (role: UserRole) => compareUserRoles(role, UserRole.TEACHER) < 0;\nexport const ltEqTeacher = (role: UserRole) => compareUserRoles(role, UserRole.TEACHER) <= 0;\nexport const eqTeacher = (role: UserRole) => compareUserRoles(role, UserRole.TEACHER) === 0;\nexport const gtEqTeacher = (role: UserRole) => compareUserRoles(role, UserRole.TEACHER) >= 0;\nexport const gtTeacher = (role: UserRole) => compareUserRoles(role, UserRole.TEACHER) > 0;\n\nexport const ltAdmin = (role: UserRole) => compareUserRoles(role, UserRole.ADMIN) < 0;\nexport const ltEqAdmin = (role: UserRole) => compareUserRoles(role, UserRole.ADMIN) <= 0;\nexport const eqAdmin = (role: UserRole) => compareUserRoles(role, UserRole.ADMIN) === 0;\nexport const gtEqAdmin = (role: UserRole) => compareUserRoles(role, UserRole.ADMIN) >= 0;\nexport const gtAdmin = (role: UserRole) => compareUserRoles(role, UserRole.ADMIN) > 0;\n","import { useContext } from 'react';\nimport { ViewportCtx } from './ViewportCtx';\n\nexport const useViewport = () => useContext(ViewportCtx);\n"],"sourceRoot":""}