From a314b5350e269676e8c39eda4cc7842751b1a7fc Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 1 Sep 2020 02:32:06 +0800 Subject: ... --- Timeline/ClientApp/src/app/views/common/AppBar.tsx | 107 ++++++ .../ClientApp/src/app/views/common/BlobImage.tsx | 27 ++ .../ClientApp/src/app/views/common/FileInput.tsx | 36 ++ .../src/app/views/common/ImageCropper.tsx | 306 +++++++++++++++++ .../ClientApp/src/app/views/common/LoadingPage.tsx | 12 + .../src/app/views/common/OperationDialog.tsx | 381 +++++++++++++++++++++ .../ClientApp/src/app/views/common/SearchInput.tsx | 63 ++++ .../src/app/views/common/TimelineLogo.tsx | 26 ++ .../src/app/views/common/UserTimelineLogo.tsx | 26 ++ .../src/app/views/common/alert/AlertHost.tsx | 96 ++++++ .../src/app/views/common/alert/alert.sass | 15 + .../ClientApp/src/app/views/common/common.sass | 33 ++ 12 files changed, 1128 insertions(+) create mode 100644 Timeline/ClientApp/src/app/views/common/AppBar.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/BlobImage.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/FileInput.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/ImageCropper.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/LoadingPage.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/OperationDialog.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/SearchInput.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx create mode 100644 Timeline/ClientApp/src/app/views/common/alert/alert.sass create mode 100644 Timeline/ClientApp/src/app/views/common/common.sass (limited to 'Timeline/ClientApp/src/app/views/common') diff --git a/Timeline/ClientApp/src/app/views/common/AppBar.tsx b/Timeline/ClientApp/src/app/views/common/AppBar.tsx new file mode 100644 index 00000000..aefe0f27 --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/AppBar.tsx @@ -0,0 +1,107 @@ +import React from "react"; +import { useHistory, matchPath } from "react-router"; +import { Link, NavLink } from "react-router-dom"; +import { Navbar, NavbarToggler, Collapse, Nav, NavItem } from "reactstrap"; +import { useMediaQuery } from "react-responsive"; +import { useTranslation } from "react-i18next"; + +import { useUser, useAvatar } from "@/services/user"; + +import TimelineLogo from "./TimelineLogo"; +import BlobImage from "./BlobImage"; + +const AppBar: React.FC = (_) => { + const history = useHistory(); + const user = useUser(); + const avatar = useAvatar(user?.username); + + const { t } = useTranslation(); + + const isUpMd = useMediaQuery({ + minWidth: getComputedStyle(document.documentElement).getPropertyValue( + "--breakpoint-md" + ), + }); + + const [isMenuOpen, setIsMenuOpen] = React.useState(false); + + const toggleMenu = React.useCallback((): void => { + setIsMenuOpen((oldIsMenuOpen) => !oldIsMenuOpen); + }, []); + + const isAdministrator = user && user.administrator; + + const rightArea = ( +
+ {user != null ? ( + + + + ) : ( + + {t("nav.login")} + + )} +
+ ); + + return ( + + + + Timeline + + + {isUpMd ? null : rightArea} + + + + + {isUpMd ? rightArea : null} + + + ); +}; + +export default AppBar; diff --git a/Timeline/ClientApp/src/app/views/common/BlobImage.tsx b/Timeline/ClientApp/src/app/views/common/BlobImage.tsx new file mode 100644 index 00000000..0dd25c52 --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/BlobImage.tsx @@ -0,0 +1,27 @@ +import React from "react"; + +const BlobImage: React.FC< + Omit, "src"> & { + blob?: Blob | unknown; + } +> = (props) => { + const { blob, ...otherProps } = props; + + const [url, setUrl] = React.useState(undefined); + + React.useEffect(() => { + if (blob instanceof Blob) { + const url = URL.createObjectURL(blob); + setUrl(url); + return () => { + URL.revokeObjectURL(url); + }; + } else { + setUrl(undefined); + } + }, [blob]); + + return ; +}; + +export default BlobImage; diff --git a/Timeline/ClientApp/src/app/views/common/FileInput.tsx b/Timeline/ClientApp/src/app/views/common/FileInput.tsx new file mode 100644 index 00000000..7b053d5c --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/FileInput.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import clsx from "clsx"; + +export interface FileInputProps + extends Omit, "type" | "id"> { + inputId?: string; + labelText: string; + color?: string; + className?: string; +} + +const FileInput: React.FC = (props) => { + const { inputId, labelText, color, className, ...otherProps } = props; + + const realInputId = React.useMemo(() => { + if (inputId != null) return inputId; + return ( + "file-input-" + + (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) + ); + }, [inputId]); + + return ( + <> + + + + ); +}; + +export default FileInput; diff --git a/Timeline/ClientApp/src/app/views/common/ImageCropper.tsx b/Timeline/ClientApp/src/app/views/common/ImageCropper.tsx new file mode 100644 index 00000000..b9db8b99 --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/ImageCropper.tsx @@ -0,0 +1,306 @@ +import React from "react"; +import clsx from "clsx"; + +import { UiLogicError } from "@/common"; + +export interface Clip { + left: number; + top: number; + width: number; +} + +interface NormailizedClip extends Clip { + height: number; +} + +interface ImageInfo { + width: number; + height: number; + landscape: boolean; + ratio: number; + maxClipWidth: number; + maxClipHeight: number; +} + +interface ImageCropperSavedState { + clip: NormailizedClip; + x: number; + y: number; + pointerId: number; +} + +export interface ImageCropperProps { + clip: Clip | null; + imageUrl: string; + onChange: (clip: Clip) => void; + imageElementCallback?: (element: HTMLImageElement | null) => void; + className?: string; +} + +const ImageCropper = (props: ImageCropperProps): React.ReactElement => { + const { clip, imageUrl, onChange, imageElementCallback, className } = props; + + const [oldState, setOldState] = React.useState( + null + ); + const [imageInfo, setImageInfo] = React.useState(null); + + const normalizeClip = (c: Clip | null | undefined): NormailizedClip => { + if (c == null) { + return { left: 0, top: 0, width: 0, height: 0 }; + } + + return { + left: c.left || 0, + top: c.top || 0, + width: c.width || 0, + height: imageInfo != null ? (c.width || 0) / imageInfo.ratio : 0, + }; + }; + + const c = normalizeClip(clip); + + const imgElementRef = React.useRef(null); + + const onImageRef = React.useCallback( + (e: HTMLImageElement | null) => { + imgElementRef.current = e; + if (imageElementCallback != null && e == null) { + imageElementCallback(null); + } + }, + [imageElementCallback] + ); + + const onImageLoad = React.useCallback( + (e: React.SyntheticEvent) => { + const img = e.currentTarget; + const landscape = img.naturalWidth >= img.naturalHeight; + + const info = { + width: img.naturalWidth, + height: img.naturalHeight, + landscape, + ratio: img.naturalHeight / img.naturalWidth, + maxClipWidth: landscape ? img.naturalHeight / img.naturalWidth : 1, + maxClipHeight: landscape ? 1 : img.naturalWidth / img.naturalHeight, + }; + setImageInfo(info); + onChange({ left: 0, top: 0, width: info.maxClipWidth }); + if (imageElementCallback != null) { + imageElementCallback(img); + } + }, + [onChange, imageElementCallback] + ); + + const onPointerDown = React.useCallback( + (e: React.PointerEvent) => { + if (oldState != null) return; + e.currentTarget.setPointerCapture(e.pointerId); + setOldState({ + x: e.clientX, + y: e.clientY, + clip: c, + pointerId: e.pointerId, + }); + }, + [oldState, c] + ); + + const onPointerUp = React.useCallback( + (e: React.PointerEvent) => { + if (oldState == null || oldState.pointerId !== e.pointerId) return; + e.currentTarget.releasePointerCapture(e.pointerId); + setOldState(null); + }, + [oldState] + ); + + const onPointerMove = React.useCallback( + (e: React.PointerEvent) => { + if (oldState == null) return; + + const oldClip = oldState.clip; + + const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; + + const { current: imgElement } = imgElementRef; + + if (imgElement == null) throw new UiLogicError("Image element is null."); + + const moveRatio = { + x: movement.x / imgElement.width, + y: movement.y / imgElement.height, + }; + + const newRatio = { + x: oldClip.left + moveRatio.x, + y: oldClip.top + moveRatio.y, + }; + if (newRatio.x < 0) { + newRatio.x = 0; + } else if (newRatio.x > 1 - oldClip.width) { + newRatio.x = 1 - oldClip.width; + } + if (newRatio.y < 0) { + newRatio.y = 0; + } else if (newRatio.y > 1 - oldClip.height) { + newRatio.y = 1 - oldClip.height; + } + + onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width }); + }, + [oldState, onChange] + ); + + const onHandlerPointerMove = React.useCallback( + (e: React.PointerEvent) => { + if (oldState == null) return; + + const oldClip = oldState.clip; + + const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; + + const ratio = imageInfo == null ? 1 : imageInfo.ratio; + + const { current: imgElement } = imgElementRef; + + if (imgElement == null) throw new UiLogicError("Image element is null."); + + const moveRatio = { + x: movement.x / imgElement.width, + y: movement.x / imgElement.width / ratio, + }; + + const newRatio = { + x: oldClip.width + moveRatio.x, + y: oldClip.height + moveRatio.y, + }; + + const maxRatio = { + x: Math.min(1 - oldClip.left, newRatio.x), + y: Math.min(1 - oldClip.top, newRatio.y), + }; + + const maxWidthRatio = Math.min(maxRatio.x, maxRatio.y * ratio); + + let newWidth; + if (newRatio.x < 0) { + newWidth = 0; + } else if (newRatio.x > maxWidthRatio) { + newWidth = maxWidthRatio; + } else { + newWidth = newRatio.x; + } + + onChange({ left: oldClip.left, top: oldClip.top, width: newWidth }); + }, + [imageInfo, oldState, onChange] + ); + + const toPercentage = (n: number): string => `${n}%`; + + // fuck!!! I just can't find a better way to implement this in pure css + const containerStyle: React.CSSProperties = (() => { + if (imageInfo == null) { + return { width: "100%", paddingTop: "100%", height: 0 }; + } else { + if (imageInfo.ratio > 1) { + return { + width: toPercentage(100 / imageInfo.ratio), + paddingTop: "100%", + height: 0, + }; + } else { + return { + width: "100%", + paddingTop: toPercentage(100 * imageInfo.ratio), + height: 0, + }; + } + } + })(); + + return ( +
+ to crop +
+
+
+
+
+ ); +}; + +export default ImageCropper; + +export function applyClipToImage( + image: HTMLImageElement, + clip: Clip, + mimeType: string +): Promise { + return new Promise((resolve, reject) => { + const naturalSize = { + width: image.naturalWidth, + height: image.naturalHeight, + }; + const clipArea = { + x: naturalSize.width * clip.left, + y: naturalSize.height * clip.top, + length: naturalSize.width * clip.width, + }; + + const canvas = document.createElement("canvas"); + canvas.width = clipArea.length; + canvas.height = clipArea.length; + const context = canvas.getContext("2d"); + + if (context == null) throw new Error("Failed to create context."); + + context.drawImage( + image, + clipArea.x, + clipArea.y, + clipArea.length, + clipArea.length, + 0, + 0, + clipArea.length, + clipArea.length + ); + + canvas.toBlob((blob) => { + if (blob == null) { + reject(new Error("canvas.toBlob returns null")); + } else { + resolve(blob); + } + }, mimeType); + }); +} diff --git a/Timeline/ClientApp/src/app/views/common/LoadingPage.tsx b/Timeline/ClientApp/src/app/views/common/LoadingPage.tsx new file mode 100644 index 00000000..a849126d --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/LoadingPage.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { Spinner } from "reactstrap"; + +const LoadingPage: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default LoadingPage; diff --git a/Timeline/ClientApp/src/app/views/common/OperationDialog.tsx b/Timeline/ClientApp/src/app/views/common/OperationDialog.tsx new file mode 100644 index 00000000..402ffbec --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/OperationDialog.tsx @@ -0,0 +1,381 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Spinner, + Container, + ModalBody, + Label, + Input, + FormGroup, + FormFeedback, + ModalFooter, + Button, + Modal, + ModalHeader, + FormText, +} from "reactstrap"; + +import { UiLogicError } from "@/common"; + +const DefaultProcessPrompt: React.FC = (_) => { + return ( + + + + ); +}; + +interface DefaultErrorPromptProps { + error?: string; +} + +const DefaultErrorPrompt: React.FC = (props) => { + const { t } = useTranslation(); + + let result =

{t("operationDialog.error")}

; + + if (props.error != null) { + result = ( + <> + {result} +

{props.error}

+ + ); + } + + return result; +}; + +export type OperationInputOptionalError = undefined | null | string; + +export interface OperationInputErrorInfo { + [index: number]: OperationInputOptionalError; +} + +export type OperationInputValidator = ( + value: TValue, + values: (string | boolean)[] +) => OperationInputOptionalError | OperationInputErrorInfo; + +export interface OperationTextInputInfo { + type: "text"; + password?: boolean; + label?: string; + initValue?: string; + textFieldProps?: Omit< + React.InputHTMLAttributes, + "type" | "value" | "onChange" | "aria-relevant" + >; + helperText?: string; + validator?: OperationInputValidator; +} + +export interface OperationBoolInputInfo { + type: "bool"; + label: string; + initValue?: boolean; +} + +export interface OperationSelectInputInfoOption { + value: string; + label: string; + icon?: React.ReactElement; +} + +export interface OperationSelectInputInfo { + type: "select"; + label: string; + options: OperationSelectInputInfoOption[]; + initValue?: string; +} + +export type OperationInputInfo = + | OperationTextInputInfo + | OperationBoolInputInfo + | OperationSelectInputInfo; + +interface OperationResult { + type: "success" | "failure"; + data: unknown; +} + +interface OperationDialogProps { + open: boolean; + close: () => void; + title: React.ReactNode; + titleColor?: "default" | "dangerous" | "create" | string; + onProcess: (inputs: (string | boolean)[]) => Promise; + inputScheme?: OperationInputInfo[]; + inputPrompt?: string | (() => React.ReactNode); + processPrompt?: () => React.ReactNode; + successPrompt?: (data: unknown) => React.ReactNode; + failurePrompt?: (error: unknown) => React.ReactNode; + onSuccessAndClose?: () => void; +} + +const OperationDialog: React.FC = (props) => { + const inputScheme = props.inputScheme ?? []; + + const { t } = useTranslation(); + + type Step = "input" | "process" | OperationResult; + const [step, setStep] = useState("input"); + const [values, setValues] = useState<(boolean | string)[]>( + inputScheme.map((i) => { + if (i.type === "bool") { + return i.initValue ?? false; + } else if (i.type === "text" || i.type === "select") { + return i.initValue ?? ""; + } else { + throw new UiLogicError("Unknown input scheme."); + } + }) + ); + const [inputError, setInputError] = useState({}); + + const close = (): void => { + if (step !== "process") { + props.close(); + if ( + typeof step === "object" && + step.type === "success" && + props.onSuccessAndClose + ) { + props.onSuccessAndClose(); + } + } else { + console.log("Attempt to close modal when processing."); + } + }; + + const onConfirm = (): void => { + setStep("process"); + props.onProcess(values).then( + (d: unknown) => { + setStep({ + type: "success", + data: d, + }); + }, + (e: unknown) => { + setStep({ + type: "failure", + data: e, + }); + } + ); + }; + + let body: React.ReactNode; + if (step === "input") { + let inputPrompt = + typeof props.inputPrompt === "function" + ? props.inputPrompt() + : props.inputPrompt; + inputPrompt =
{inputPrompt}
; + + const updateValue = ( + index: number, + newValue: string | boolean + ): (string | boolean)[] => { + const oldValues = values; + const newValues = oldValues.slice(); + newValues[index] = newValue; + setValues(newValues); + return newValues; + }; + + const testErrorInfo = (errorInfo: OperationInputErrorInfo): boolean => { + for (let i = 0; i < inputScheme.length; i++) { + if (inputScheme[i].type === "text" && errorInfo[i] != null) { + return true; + } + } + return false; + }; + + const calculateError = ( + oldError: OperationInputErrorInfo, + index: number, + newError: OperationInputOptionalError | OperationInputErrorInfo + ): OperationInputErrorInfo => { + if (newError === undefined) { + return oldError; + } else if (newError === null || typeof newError === "string") { + return { ...oldError, [index]: newError }; + } else { + const newInputError: OperationInputErrorInfo = { ...oldError }; + for (const [index, error] of Object.entries(newError)) { + if (error !== undefined) { + newInputError[+index] = error as OperationInputOptionalError; + } + } + return newInputError; + } + }; + + const validateAll = (): boolean => { + let newInputError = inputError; + for (let i = 0; i < inputScheme.length; i++) { + const item = inputScheme[i]; + if (item.type === "text") { + newInputError = calculateError( + newInputError, + i, + item.validator?.(values[i] as string, values) + ); + } + } + const result = !testErrorInfo(newInputError); + setInputError(newInputError); + return result; + }; + + body = ( + <> + + {inputPrompt} + {inputScheme.map((item, index) => { + const value = values[index]; + const error: string | undefined = ((e) => + typeof e === "string" ? t(e) : undefined)(inputError?.[index]); + + if (item.type === "text") { + return ( + + {item.label && } + { + const v = e.target.value; + const newValues = updateValue(index, v); + setInputError( + calculateError( + inputError, + index, + item.validator?.(v, newValues) + ) + ); + }} + invalid={error != null} + {...item.textFieldProps} + /> + {error != null && {error}} + {item.helperText && {t(item.helperText)}} + + ); + } else if (item.type === "bool") { + return ( + + { + updateValue( + index, + (e.target as HTMLInputElement).checked + ); + }} + /> + + + ); + } else if (item.type === "select") { + return ( + + + { + updateValue(index, event.target.value); + }} + > + {item.options.map((option, i) => { + return ( + + ); + })} + + + ); + } + })} + + + + + + + ); + } else if (step === "process") { + body = ( + + {props.processPrompt?.() ?? } + + ); + } else { + let content: React.ReactNode; + const result = step; + if (result.type === "success") { + content = + props.successPrompt?.(result.data) ?? t("operationDialog.success"); + if (typeof content === "string") + content =

{content}

; + } else { + content = props.failurePrompt?.(result.data) ?? ; + if (typeof content === "string") + content = ; + } + body = ( + <> + {content} + + + + + ); + } + + const title = typeof props.title === "string" ? t(props.title) : props.title; + + return ( + + + {title} + + {body} + + ); +}; + +export default OperationDialog; diff --git a/Timeline/ClientApp/src/app/views/common/SearchInput.tsx b/Timeline/ClientApp/src/app/views/common/SearchInput.tsx new file mode 100644 index 00000000..5a0b0eaa --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/SearchInput.tsx @@ -0,0 +1,63 @@ +import React, { useCallback } from "react"; +import clsx from "clsx"; +import { Spinner, Input, Button } from "reactstrap"; +import { useTranslation } from "react-i18next"; + +export interface SearchInputProps { + value: string; + onChange: (value: string) => void; + onButtonClick: () => void; + className?: string; + loading?: boolean; + buttonText?: string; + placeholder?: string; + additionalButton?: React.ReactNode; +} + +const SearchInput: React.FC = (props) => { + const { onChange, onButtonClick } = props; + + const { t } = useTranslation(); + + const onInputChange = useCallback( + (event: React.ChangeEvent): void => { + onChange(event.currentTarget.value); + }, + [onChange] + ); + + const onInputKeyPress = useCallback( + (event: React.KeyboardEvent): void => { + if (event.key === "Enter") { + onButtonClick(); + } + }, + [onButtonClick] + ); + + return ( +
+ +
+ {props.additionalButton} +
+
+ {props.loading ? ( + + ) : ( + + )} +
+
+ ); +}; + +export default SearchInput; diff --git a/Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx b/Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx new file mode 100644 index 00000000..27d188fc --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx @@ -0,0 +1,26 @@ +import React, { SVGAttributes } from "react"; + +export interface TimelineLogoProps extends SVGAttributes { + color?: string; +} + +const TimelineLogo: React.FC = (props) => { + const { color, ...forwardProps } = props; + const coercedColor = color ?? "currentcolor"; + return ( + + + + + + ); +}; + +export default TimelineLogo; diff --git a/Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx b/Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx new file mode 100644 index 00000000..29f6a69f --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx @@ -0,0 +1,26 @@ +import React, { SVGAttributes } from "react"; + +export interface UserTimelineLogoProps extends SVGAttributes { + color?: string; +} + +const UserTimelineLogo: React.FC = (props) => { + const { color, ...forwardProps } = props; + const coercedColor = color ?? "currentcolor"; + + return ( + + + + + + + + + + + + ); +}; + +export default UserTimelineLogo; diff --git a/Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx b/Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx new file mode 100644 index 00000000..31c0fb86 --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx @@ -0,0 +1,96 @@ +import React, { useCallback } from "react"; +import { Alert } from "reactstrap"; +import without from "lodash/without"; +import concat from "lodash/concat"; +import { useTranslation } from "react-i18next"; + +import { + alertService, + AlertInfoEx, + kAlertHostId, + AlertInfo, +} from "@/services/alert"; + +interface AutoCloseAlertProps { + alert: AlertInfo; + close: () => void; +} + +export const AutoCloseAlert: React.FC = (props) => { + const { alert } = props; + const { dismissTime } = alert; + + const { t } = useTranslation(); + + React.useEffect(() => { + const tag = + dismissTime === "never" + ? null + : typeof dismissTime === "number" + ? window.setTimeout(props.close, dismissTime) + : window.setTimeout(props.close, 5000); + return () => { + if (tag != null) { + window.clearTimeout(tag); + } + }; + }, [dismissTime, props.close]); + + return ( + + {(() => { + const { message } = alert; + if (typeof message === "function") { + const Message = message; + return ; + } else if (typeof message === "object" && message.type === "i18n") { + return t(message.key); + } else return alert.message; + })()} + + ); +}; + +// oh what a bad name! +interface AlertInfoExEx extends AlertInfoEx { + close: () => void; +} + +const AlertHost: React.FC = () => { + const [alerts, setAlerts] = React.useState([]); + + // react guarantee that state setters are stable, so we don't need to add it to dependency list + + const consume = useCallback((alert: AlertInfoEx): void => { + const alertEx: AlertInfoExEx = { + ...alert, + close: () => { + setAlerts((oldAlerts) => { + return without(oldAlerts, alertEx); + }); + }, + }; + setAlerts((oldAlerts) => { + return concat(oldAlerts, alertEx); + }); + }, []); + + React.useEffect(() => { + alertService.registerConsumer(consume); + return () => { + alertService.unregisterConsumer(consume); + }; + }, [consume]); + + return ( +
+ {alerts.map((alert) => { + return ( + + ); + })} +
+ ); +}; + +export default AlertHost; diff --git a/Timeline/ClientApp/src/app/views/common/alert/alert.sass b/Timeline/ClientApp/src/app/views/common/alert/alert.sass new file mode 100644 index 00000000..5b6e65c2 --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/alert/alert.sass @@ -0,0 +1,15 @@ +.alert-container + position: fixed + z-index: $zindex-popover + +@include media-breakpoint-up(sm) + .alert-container + bottom: 0 + right: 0 + +@include media-breakpoint-down(sm) + .alert-container + bottom: 0 + right: 0 + left: 0 + text-align: center diff --git a/Timeline/ClientApp/src/app/views/common/common.sass b/Timeline/ClientApp/src/app/views/common/common.sass new file mode 100644 index 00000000..15d34d7c --- /dev/null +++ b/Timeline/ClientApp/src/app/views/common/common.sass @@ -0,0 +1,33 @@ +.image-cropper-container + position: relative + box-sizing: border-box + user-select: none + +.image-cropper-container img + position: absolute + left: 0 + top: 0 + width: 100% + height: 100% + +.image-cropper-mask-container + position: absolute + left: 0 + top: 0 + right: 0 + bottom: 0 + overflow: hidden + +.image-cropper-mask + position: absolute + box-shadow: 0 0 0 10000px rgba(255, 255, 255, 80%) + touch-action: none + +.image-cropper-handler + position: absolute + width: 26px + height: 26px + border: black solid 2px + border-radius: 50% + background: white + touch-action: none -- cgit v1.2.3 From f476135ddb3031e5e3a93739909f98800f565181 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Sep 2020 22:36:04 +0800 Subject: ... --- Timeline/ClientApp/.pnp.js | 241 +++++++++++++++++++++ Timeline/ClientApp/package.json | 3 + Timeline/ClientApp/src/app/index.sass | 3 - Timeline/ClientApp/src/app/views/common/AppBar.tsx | 115 +++------- .../ClientApp/src/app/views/common/LoadingPage.tsx | 4 +- .../ClientApp/src/app/views/common/SearchInput.tsx | 8 +- Timeline/ClientApp/yarn.lock | 203 ++++++++++++++++- 7 files changed, 483 insertions(+), 94 deletions(-) (limited to 'Timeline/ClientApp/src/app/views/common') diff --git a/Timeline/ClientApp/.pnp.js b/Timeline/ClientApp/.pnp.js index c0be249f..51c7e7ae 100644 --- a/Timeline/ClientApp/.pnp.js +++ b/Timeline/ClientApp/.pnp.js @@ -101,6 +101,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "@types/react-router", "npm:5.1.8::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router%2Fdownload%2F%40types%2Freact-router-5.1.8.tgz" ], + [ + "@types/react-router-bootstrap", + "npm:0.24.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-bootstrap%2Fdownload%2F%40types%2Freact-router-bootstrap-0.24.5.tgz" + ], [ "@types/react-router-dom", "npm:5.1.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-dom%2Fdownload%2F%40types%2Freact-router-dom-5.1.5.tgz" @@ -261,6 +265,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz" ], + [ + "react-bootstrap", + "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:1.3.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-bootstrap%2Fdownload%2Freact-bootstrap-1.3.0.tgz" + ], [ "react-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz" @@ -285,6 +293,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "react-router", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router%2Fdownload%2Freact-router-5.2.0.tgz" ], + [ + "react-router-bootstrap", + "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:0.25.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-bootstrap%2Fdownload%2Freact-router-bootstrap-0.25.0.tgz" + ], [ "react-router-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-dom%2Fdownload%2Freact-router-dom-5.2.0.tgz" @@ -391,6 +403,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/react-dom", "npm:16.9.8::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-dom%2Fdownload%2F%40types%2Freact-dom-16.9.8.tgz"], ["@types/react-responsive", "npm:8.0.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-responsive%2Fdownload%2F%40types%2Freact-responsive-8.0.2.tgz"], ["@types/react-router", "npm:5.1.8::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router%2Fdownload%2F%40types%2Freact-router-5.1.8.tgz"], + ["@types/react-router-bootstrap", "npm:0.24.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-bootstrap%2Fdownload%2F%40types%2Freact-router-bootstrap-0.24.5.tgz"], ["@types/react-router-dom", "npm:5.1.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-dom%2Fdownload%2F%40types%2Freact-router-dom-5.1.5.tgz"], ["@types/reactstrap", "npm:8.5.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freactstrap%2Fdownload%2F%40types%2Freactstrap-8.5.1.tgz"], ["@types/webpack-env", "npm:1.15.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwebpack-env%2Fdownload%2F%40types%2Fwebpack-env-1.15.2.tgz"], @@ -431,12 +444,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["postcss-preset-env", "npm:6.7.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-preset-env%2Fdownload%2Fpostcss-preset-env-6.7.0.tgz"], ["prettier", "npm:2.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprettier%2Fdownload%2Fprettier-2.1.1.tgz"], ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-bootstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:1.3.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-bootstrap%2Fdownload%2Freact-bootstrap-1.3.0.tgz"], ["react-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz"], ["react-hot-loader", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:4.12.21::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-hot-loader%2Fdownload%2Freact-hot-loader-4.12.21.tgz"], ["react-i18next", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:11.7.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-i18next%2Fdownload%2Freact-i18next-11.7.2.tgz"], ["react-inlinesvg", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:2.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-inlinesvg%2Fdownload%2Freact-inlinesvg-2.0.0.tgz"], ["react-responsive", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:8.1.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-responsive%2Fdownload%2Freact-responsive-8.1.0.tgz"], ["react-router", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router%2Fdownload%2Freact-router-5.2.0.tgz"], + ["react-router-bootstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:0.25.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-bootstrap%2Fdownload%2Freact-router-bootstrap-0.25.0.tgz"], ["react-router-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-dom%2Fdownload%2Freact-router-dom-5.2.0.tgz"], ["reactstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:8.5.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freactstrap%2Fdownload%2Freactstrap-8.5.1.tgz"], ["regenerator-runtime", "npm:0.13.7::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.7.tgz"], @@ -2894,6 +2909,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["regenerator-runtime", "npm:0.13.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.5.tgz"] ], "linkType": "HARD", + }], + ["npm:7.11.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz", { + "packageLocation": "./.yarn/cache/@babel-runtime-npm-7.11.2-84d52b99b9-2f127ad60a.zip/node_modules/@babel/runtime/", + "packageDependencies": [ + ["@babel/runtime", "npm:7.11.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz"], + ["regenerator-runtime", "npm:0.13.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.5.tgz"] + ], + "linkType": "HARD", }] ]], ["@babel/runtime-corejs3", [ @@ -3092,6 +3115,43 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@popperjs/core", [ + ["npm:2.4.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40popperjs%2Fcore%2Fdownload%2F%40popperjs%2Fcore-2.4.4.tgz", { + "packageLocation": "./.yarn/cache/@popperjs-core-npm-2.4.4-0690f2896b-49a1e6cfa2.zip/node_modules/@popperjs/core/", + "packageDependencies": [ + ["@popperjs/core", "npm:2.4.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40popperjs%2Fcore%2Fdownload%2F%40popperjs%2Fcore-2.4.4.tgz"] + ], + "linkType": "HARD", + }] + ]], + ["@restart/context", [ + ["virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:2.1.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fcontext%2Fdownload%2F%40restart%2Fcontext-2.1.4.tgz", { + "packageLocation": "./.yarn/$$virtual/@restart-context-virtual-27eb50dd8d/0/cache/@restart-context-npm-2.1.4-ad630f5721-f9dd1416c1.zip/node_modules/@restart/context/", + "packageDependencies": [ + ["@restart/context", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:2.1.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fcontext%2Fdownload%2F%40restart%2Fcontext-2.1.4.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"] + ], + "packagePeers": [ + "react" + ], + "linkType": "HARD", + }] + ]], + ["@restart/hooks", [ + ["virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:0.3.25::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fhooks%2Fdownload%2F%40restart%2Fhooks-0.3.25.tgz", { + "packageLocation": "./.yarn/$$virtual/@restart-hooks-virtual-a2069ac77d/0/cache/@restart-hooks-npm-0.3.25-f85d375986-08aefc359f.zip/node_modules/@restart/hooks/", + "packageDependencies": [ + ["@restart/hooks", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:0.3.25::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fhooks%2Fdownload%2F%40restart%2Fhooks-0.3.25.tgz"], + ["lodash", "npm:4.17.19::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.19.tgz"], + ["lodash-es", "npm:4.17.15::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.15.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"] + ], + "packagePeers": [ + "react" + ], + "linkType": "HARD", + }] + ]], ["@rollup/plugin-node-resolve", [ ["virtual:3e7d201acb70f99b496fd22382494a547adba7ced83157fcf203a35a809a1a86e9fcdadc98baf8c5919858aa7474058982ca7332a042017e29b0e7f90348de11#npm:7.1.3::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40rollup%2Fplugin-node-resolve%2Fdownload%2F%40rollup%2Fplugin-node-resolve-7.1.3.tgz", { "packageLocation": "./.yarn/$$virtual/@rollup-plugin-node-resolve-virtual-1eac73bdde/0/cache/@rollup-plugin-node-resolve-npm-7.1.3-07432edf25-4d751a407f.zip/node_modules/@rollup/plugin-node-resolve/", @@ -3292,6 +3352,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/invariant", [ + ["npm:2.2.34::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Finvariant%2Fdownload%2F%40types%2Finvariant-2.2.34.tgz", { + "packageLocation": "./.yarn/cache/@types-invariant-npm-2.2.34-dc486af7a1-d0ecc665e5.zip/node_modules/@types/invariant/", + "packageDependencies": [ + ["@types/invariant", "npm:2.2.34::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Finvariant%2Fdownload%2F%40types%2Finvariant-2.2.34.tgz"] + ], + "linkType": "HARD", + }] + ]], ["@types/json-schema", [ ["npm:7.0.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fjson-schema%2Fdownload%2F%40types%2Fjson-schema-7.0.5.tgz", { "packageLocation": "./.yarn/cache/@types-json-schema-npm-7.0.5-8c7a0ff5b6-6290f9fe93.zip/node_modules/@types/json-schema/", @@ -3421,6 +3490,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/react-router-bootstrap", [ + ["npm:0.24.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-bootstrap%2Fdownload%2F%40types%2Freact-router-bootstrap-0.24.5.tgz", { + "packageLocation": "./.yarn/cache/@types-react-router-bootstrap-npm-0.24.5-e11aa3d45e-08b8ed99b7.zip/node_modules/@types/react-router-bootstrap/", + "packageDependencies": [ + ["@types/react-router-bootstrap", "npm:0.24.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-bootstrap%2Fdownload%2F%40types%2Freact-router-bootstrap-0.24.5.tgz"], + ["@types/react", "npm:16.9.43::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact%2Fdownload%2F%40types%2Freact-16.9.43.tgz"], + ["@types/react-router-dom", "npm:5.1.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-dom%2Fdownload%2F%40types%2Freact-router-dom-5.1.5.tgz"] + ], + "linkType": "HARD", + }] + ]], ["@types/react-router-dom", [ ["npm:5.1.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-dom%2Fdownload%2F%40types%2Freact-router-dom-5.1.5.tgz", { "packageLocation": "./.yarn/cache/@types-react-router-dom-npm-5.1.5-bb10e455cb-d25c11cb71.zip/node_modules/@types/react-router-dom/", @@ -3433,6 +3513,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/react-transition-group", [ + ["npm:4.4.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-transition-group%2Fdownload%2F%40types%2Freact-transition-group-4.4.0.tgz", { + "packageLocation": "./.yarn/cache/@types-react-transition-group-npm-4.4.0-44ecca32b5-b761f70623.zip/node_modules/@types/react-transition-group/", + "packageDependencies": [ + ["@types/react-transition-group", "npm:4.4.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-transition-group%2Fdownload%2F%40types%2Freact-transition-group-4.4.0.tgz"], + ["@types/react", "npm:16.9.43::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact%2Fdownload%2F%40types%2Freact-16.9.43.tgz"] + ], + "linkType": "HARD", + }] + ]], ["@types/reactstrap", [ ["npm:8.5.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freactstrap%2Fdownload%2F%40types%2Freactstrap-8.5.1.tgz", { "packageLocation": "./.yarn/cache/@types-reactstrap-npm-8.5.1-9008a6014f-87b383af72.zip/node_modules/@types/reactstrap/", @@ -3492,6 +3582,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/warning", [ + ["npm:3.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwarning%2Fdownload%2F%40types%2Fwarning-3.0.0.tgz", { + "packageLocation": "./.yarn/cache/@types-warning-npm-3.0.0-32d6269905-cb7a16aa88.zip/node_modules/@types/warning/", + "packageDependencies": [ + ["@types/warning", "npm:3.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwarning%2Fdownload%2F%40types%2Fwarning-3.0.0.tgz"] + ], + "linkType": "HARD", + }] + ]], ["@types/webpack", [ ["npm:4.41.21::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwebpack%2Fdownload%2F%40types%2Fwebpack-4.41.21.tgz", { "packageLocation": "./.yarn/cache/@types-webpack-npm-4.41.21-bf9d77607b-3a02667221.zip/node_modules/@types/webpack/", @@ -6581,6 +6680,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/runtime", "npm:7.10.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.10.5.tgz"] ], "linkType": "HARD", + }], + ["npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fdom-helpers%2Fdownload%2Fdom-helpers-5.2.0.tgz", { + "packageLocation": "./.yarn/cache/dom-helpers-npm-5.2.0-50f26cbd58-9ef27628f4.zip/node_modules/dom-helpers/", + "packageDependencies": [ + ["dom-helpers", "npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fdom-helpers%2Fdownload%2Fdom-helpers-5.2.0.tgz"], + ["@babel/runtime", "npm:7.11.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz"], + ["csstype", "npm:3.0.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fcsstype%2Fdownload%2Fcsstype-3.0.2.tgz"] + ], + "linkType": "HARD", }] ]], ["dom-serializer", [ @@ -9703,6 +9811,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["lodash-es", [ + ["npm:4.17.15::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.15.tgz", { + "packageLocation": "./.yarn/cache/lodash-es-npm-4.17.15-15cd80072f-ee2871b76d.zip/node_modules/lodash-es/", + "packageDependencies": [ + ["lodash-es", "npm:4.17.15::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.15.tgz"] + ], + "linkType": "HARD", + }] + ]], ["lodash._reinterpolate", [ ["npm:3.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash._reinterpolate%2Fdownload%2Flodash._reinterpolate-3.0.0.tgz", { "packageLocation": "./.yarn/cache/lodash._reinterpolate-npm-3.0.0-7f339ed5b7-27513557d6.zip/node_modules/lodash._reinterpolate/", @@ -11931,6 +12048,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["prop-types-extra", [ + ["virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:1.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types-extra%2Fdownload%2Fprop-types-extra-1.1.1.tgz", { + "packageLocation": "./.yarn/$$virtual/prop-types-extra-virtual-76d118382a/0/cache/prop-types-extra-npm-1.1.1-20a8bf7d72-f1f1cc23b9.zip/node_modules/prop-types-extra/", + "packageDependencies": [ + ["prop-types-extra", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:1.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types-extra%2Fdownload%2Fprop-types-extra-1.1.1.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-is", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-is%2Fdownload%2Freact-is-16.13.1.tgz"], + ["warning", "npm:4.0.3::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fwarning%2Fdownload%2Fwarning-4.0.3.tgz"] + ], + "packagePeers": [ + "react" + ], + "linkType": "HARD", + }] + ]], ["proxy-addr", [ ["npm:2.0.6::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fproxy-addr%2Fdownload%2Fproxy-addr-2.0.6.tgz", { "packageLocation": "./.yarn/cache/proxy-addr-npm-2.0.6-b18b3fba9e-a7dcfd7025.zip/node_modules/proxy-addr/", @@ -12155,6 +12287,39 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["react-bootstrap", [ + ["virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:1.3.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-bootstrap%2Fdownload%2Freact-bootstrap-1.3.0.tgz", { + "packageLocation": "./.yarn/$$virtual/react-bootstrap-virtual-1f601461ce/0/cache/react-bootstrap-npm-1.3.0-f2b404ff68-f9dc21c00d.zip/node_modules/react-bootstrap/", + "packageDependencies": [ + ["react-bootstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:1.3.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-bootstrap%2Fdownload%2Freact-bootstrap-1.3.0.tgz"], + ["@babel/runtime", "npm:7.11.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz"], + ["@restart/context", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:2.1.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fcontext%2Fdownload%2F%40restart%2Fcontext-2.1.4.tgz"], + ["@restart/hooks", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:0.3.25::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fhooks%2Fdownload%2F%40restart%2Fhooks-0.3.25.tgz"], + ["@types/classnames", "npm:2.2.10::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fclassnames%2Fdownload%2F%40types%2Fclassnames-2.2.10.tgz"], + ["@types/invariant", "npm:2.2.34::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Finvariant%2Fdownload%2F%40types%2Finvariant-2.2.34.tgz"], + ["@types/prop-types", "npm:15.7.3::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fprop-types%2Fdownload%2F%40types%2Fprop-types-15.7.3.tgz"], + ["@types/react", "npm:16.9.49::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact%2Fdownload%2F%40types%2Freact-16.9.49.tgz"], + ["@types/react-transition-group", "npm:4.4.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-transition-group%2Fdownload%2F%40types%2Freact-transition-group-4.4.0.tgz"], + ["@types/warning", "npm:3.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwarning%2Fdownload%2F%40types%2Fwarning-3.0.0.tgz"], + ["classnames", "npm:2.2.6::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fclassnames%2Fdownload%2Fclassnames-2.2.6.tgz"], + ["dom-helpers", "npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fdom-helpers%2Fdownload%2Fdom-helpers-5.2.0.tgz"], + ["invariant", "npm:2.2.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Finvariant%2Fdownload%2Finvariant-2.2.4.tgz"], + ["prop-types", "npm:15.7.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types%2Fdownload%2Fprop-types-15.7.2.tgz"], + ["prop-types-extra", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:1.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types-extra%2Fdownload%2Fprop-types-extra-1.1.1.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz"], + ["react-overlays", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:4.1.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-overlays%2Fdownload%2Freact-overlays-4.1.0.tgz"], + ["react-transition-group", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:4.4.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-transition-group%2Fdownload%2Freact-transition-group-4.4.1.tgz"], + ["uncontrollable", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:7.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Funcontrollable%2Fdownload%2Funcontrollable-7.1.1.tgz"], + ["warning", "npm:4.0.3::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fwarning%2Fdownload%2Fwarning-4.0.3.tgz"] + ], + "packagePeers": [ + "react", + "react-dom" + ], + "linkType": "HARD", + }] + ]], ["react-dom", [ ["virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz", { "packageLocation": "./.yarn/$$virtual/react-dom-virtual-cee64bb823/0/cache/react-dom-npm-16.13.1-d5a639d8b8-fb5c3ad413.zip/node_modules/react-dom/", @@ -12260,6 +12425,29 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["react-overlays", [ + ["virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:4.1.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-overlays%2Fdownload%2Freact-overlays-4.1.0.tgz", { + "packageLocation": "./.yarn/$$virtual/react-overlays-virtual-be6c660a55/0/cache/react-overlays-npm-4.1.0-8e31f6545e-64d46694be.zip/node_modules/react-overlays/", + "packageDependencies": [ + ["react-overlays", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:4.1.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-overlays%2Fdownload%2Freact-overlays-4.1.0.tgz"], + ["@babel/runtime", "npm:7.11.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz"], + ["@popperjs/core", "npm:2.4.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40popperjs%2Fcore%2Fdownload%2F%40popperjs%2Fcore-2.4.4.tgz"], + ["@restart/hooks", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:0.3.25::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40restart%2Fhooks%2Fdownload%2F%40restart%2Fhooks-0.3.25.tgz"], + ["@types/warning", "npm:3.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwarning%2Fdownload%2F%40types%2Fwarning-3.0.0.tgz"], + ["dom-helpers", "npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fdom-helpers%2Fdownload%2Fdom-helpers-5.2.0.tgz"], + ["prop-types", "npm:15.7.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types%2Fdownload%2Fprop-types-15.7.2.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz"], + ["uncontrollable", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:7.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Funcontrollable%2Fdownload%2Funcontrollable-7.1.1.tgz"], + ["warning", "npm:4.0.3::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fwarning%2Fdownload%2Fwarning-4.0.3.tgz"] + ], + "packagePeers": [ + "react", + "react-dom" + ], + "linkType": "HARD", + }] + ]], ["react-popper", [ ["virtual:3b2ce7b5c8060bb05f2f5c2935213be0b80cbf02d12c698b50272c06d2940bcce3e52fed2f822d06cfa7b13a6aae64654f797a522ecaeb419458c4c10e644495#npm:1.3.7::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-popper%2Fdownload%2Freact-popper-1.3.7.tgz", { "packageLocation": "./.yarn/$$virtual/react-popper-virtual-3edf6c5f28/0/cache/react-popper-npm-1.3.7-0019173f32-09ef58054b.zip/node_modules/react-popper/", @@ -12320,6 +12508,22 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["react-router-bootstrap", [ + ["virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:0.25.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-bootstrap%2Fdownload%2Freact-router-bootstrap-0.25.0.tgz", { + "packageLocation": "./.yarn/$$virtual/react-router-bootstrap-virtual-2a8441681c/0/cache/react-router-bootstrap-npm-0.25.0-28ad5f848b-c9a66a8cc9.zip/node_modules/react-router-bootstrap/", + "packageDependencies": [ + ["react-router-bootstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:0.25.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-bootstrap%2Fdownload%2Freact-router-bootstrap-0.25.0.tgz"], + ["prop-types", "npm:15.7.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types%2Fdownload%2Fprop-types-15.7.2.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-router-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-dom%2Fdownload%2Freact-router-dom-5.2.0.tgz"] + ], + "packagePeers": [ + "react", + "react-router-dom" + ], + "linkType": "HARD", + }] + ]], ["react-router-dom", [ ["virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-dom%2Fdownload%2Freact-router-dom-5.2.0.tgz", { "packageLocation": "./.yarn/$$virtual/react-router-dom-virtual-86c7a05f52/0/cache/react-router-dom-npm-5.2.0-8e78cd9ea8-9ad2d72630.zip/node_modules/react-router-dom/", @@ -12341,6 +12545,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }] ]], ["react-transition-group", [ + ["virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:4.4.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-transition-group%2Fdownload%2Freact-transition-group-4.4.1.tgz", { + "packageLocation": "./.yarn/$$virtual/react-transition-group-virtual-de7e044c4a/0/cache/react-transition-group-npm-4.4.1-7d60c95184-e14446123f.zip/node_modules/react-transition-group/", + "packageDependencies": [ + ["react-transition-group", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:4.4.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-transition-group%2Fdownload%2Freact-transition-group-4.4.1.tgz"], + ["@babel/runtime", "npm:7.10.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.10.5.tgz"], + ["dom-helpers", "npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fdom-helpers%2Fdownload%2Fdom-helpers-5.2.0.tgz"], + ["loose-envify", "npm:1.4.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Floose-envify%2Fdownload%2Floose-envify-1.4.0.tgz"], + ["prop-types", "npm:15.7.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types%2Fdownload%2Fprop-types-15.7.2.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz"] + ], + "packagePeers": [ + "react", + "react-dom" + ], + "linkType": "HARD", + }], ["virtual:3b2ce7b5c8060bb05f2f5c2935213be0b80cbf02d12c698b50272c06d2940bcce3e52fed2f822d06cfa7b13a6aae64654f797a522ecaeb419458c4c10e644495#npm:2.9.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-transition-group%2Fdownload%2Freact-transition-group-2.9.0.tgz", { "packageLocation": "./.yarn/$$virtual/react-transition-group-virtual-96734f4ac2/0/cache/react-transition-group-npm-2.9.0-67f8b00a44-eefed08c48.zip/node_modules/react-transition-group/", "packageDependencies": [ @@ -14048,6 +14269,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/react-dom", "npm:16.9.8::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-dom%2Fdownload%2F%40types%2Freact-dom-16.9.8.tgz"], ["@types/react-responsive", "npm:8.0.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-responsive%2Fdownload%2F%40types%2Freact-responsive-8.0.2.tgz"], ["@types/react-router", "npm:5.1.8::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router%2Fdownload%2F%40types%2Freact-router-5.1.8.tgz"], + ["@types/react-router-bootstrap", "npm:0.24.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-bootstrap%2Fdownload%2F%40types%2Freact-router-bootstrap-0.24.5.tgz"], ["@types/react-router-dom", "npm:5.1.5::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact-router-dom%2Fdownload%2F%40types%2Freact-router-dom-5.1.5.tgz"], ["@types/reactstrap", "npm:8.5.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freactstrap%2Fdownload%2F%40types%2Freactstrap-8.5.1.tgz"], ["@types/webpack-env", "npm:1.15.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fwebpack-env%2Fdownload%2F%40types%2Fwebpack-env-1.15.2.tgz"], @@ -14088,12 +14310,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["postcss-preset-env", "npm:6.7.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-preset-env%2Fdownload%2Fpostcss-preset-env-6.7.0.tgz"], ["prettier", "npm:2.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fprettier%2Fdownload%2Fprettier-2.1.1.tgz"], ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-bootstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:1.3.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-bootstrap%2Fdownload%2Freact-bootstrap-1.3.0.tgz"], ["react-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dom%2Fdownload%2Freact-dom-16.13.1.tgz"], ["react-hot-loader", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:4.12.21::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-hot-loader%2Fdownload%2Freact-hot-loader-4.12.21.tgz"], ["react-i18next", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:11.7.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-i18next%2Fdownload%2Freact-i18next-11.7.2.tgz"], ["react-inlinesvg", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:2.0.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-inlinesvg%2Fdownload%2Freact-inlinesvg-2.0.0.tgz"], ["react-responsive", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:8.1.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-responsive%2Fdownload%2Freact-responsive-8.1.0.tgz"], ["react-router", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router%2Fdownload%2Freact-router-5.2.0.tgz"], + ["react-router-bootstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:0.25.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-bootstrap%2Fdownload%2Freact-router-bootstrap-0.25.0.tgz"], ["react-router-dom", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:5.2.0::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-router-dom%2Fdownload%2Freact-router-dom-5.2.0.tgz"], ["reactstrap", "virtual:71f98ed0939a4e8e7ea376e302a494701bc5b6aa7a7eb81870139ee3950a7c417a3d13b346b5b526d93952a598dffe628a0fac2148047debade23536cb3d7957#npm:8.5.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freactstrap%2Fdownload%2Freactstrap-8.5.1.tgz"], ["regenerator-runtime", "npm:0.13.7::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.7.tgz"], @@ -14413,6 +14637,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["uncontrollable", [ + ["virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:7.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Funcontrollable%2Fdownload%2Funcontrollable-7.1.1.tgz", { + "packageLocation": "./.yarn/$$virtual/uncontrollable-virtual-8850ccda84/0/cache/uncontrollable-npm-7.1.1-26d270508f-208d397c41.zip/node_modules/uncontrollable/", + "packageDependencies": [ + ["uncontrollable", "virtual:1f601461ce4bcd02e67eb3e52606d30f8a0e9010becfebdf768394bdced194207f1da63f8bbe3d9dd191b372deda04c1e7c5b145234f01cd0a6a8f90072f9fbc#npm:7.1.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Funcontrollable%2Fdownload%2Funcontrollable-7.1.1.tgz"], + ["@babel/runtime", "npm:7.11.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz"], + ["@types/react", "npm:16.9.49::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact%2Fdownload%2F%40types%2Freact-16.9.49.tgz"], + ["invariant", "npm:2.2.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Finvariant%2Fdownload%2Finvariant-2.2.4.tgz"], + ["react", "npm:16.13.1::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.13.1.tgz"], + ["react-lifecycles-compat", "npm:3.0.4::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-lifecycles-compat%2Fdownload%2Freact-lifecycles-compat-3.0.4.tgz"] + ], + "packagePeers": [ + "react" + ], + "linkType": "HARD", + }] + ]], ["underscore", [ ["npm:1.10.2::__archiveUrl=https%3A%2F%2Fregistry.npm.taobao.org%2Funderscore%2Fdownload%2Funderscore-1.10.2.tgz", { "packageLocation": "./.yarn/cache/underscore-npm-1.10.2-058b625a43-d49b87c03d.zip/node_modules/underscore/", diff --git a/Timeline/ClientApp/package.json b/Timeline/ClientApp/package.json index 144560e5..81232c71 100644 --- a/Timeline/ClientApp/package.json +++ b/Timeline/ClientApp/package.json @@ -18,12 +18,14 @@ "lodash": "^4.17.20", "pepjs": "^0.5.2", "react": "^16.13.1", + "react-bootstrap": "^1.3.0", "react-dom": "^16.13.1", "react-hot-loader": "^4.12.21", "react-i18next": "^11.7.2", "react-inlinesvg": "^2.0.0", "react-responsive": "^8.1.0", "react-router": "^5.2.0", + "react-router-bootstrap": "^0.25.0", "react-router-dom": "^5.2.0", "reactstrap": "^8.5.1", "regenerator-runtime": "^0.13.7", @@ -71,6 +73,7 @@ "@types/react-dom": "^16.9.8", "@types/react-responsive": "^8.0.2", "@types/react-router": "^5.1.8", + "@types/react-router-bootstrap": "^0.24.5", "@types/react-router-dom": "^5.1.5", "@types/reactstrap": "^8.5.1", "@types/webpack-env": "^1.15.2", diff --git a/Timeline/ClientApp/src/app/index.sass b/Timeline/ClientApp/src/app/index.sass index efac4df5..92f8efaf 100644 --- a/Timeline/ClientApp/src/app/index.sass +++ b/Timeline/ClientApp/src/app/index.sass @@ -33,9 +33,6 @@ small right: 0 bottom: 0 -.app-bar - z-index: 1035 - .avatar width: 60px diff --git a/Timeline/ClientApp/src/app/views/common/AppBar.tsx b/Timeline/ClientApp/src/app/views/common/AppBar.tsx index aefe0f27..464747c0 100644 --- a/Timeline/ClientApp/src/app/views/common/AppBar.tsx +++ b/Timeline/ClientApp/src/app/views/common/AppBar.tsx @@ -1,9 +1,7 @@ import React from "react"; -import { useHistory, matchPath } from "react-router"; -import { Link, NavLink } from "react-router-dom"; -import { Navbar, NavbarToggler, Collapse, Nav, NavItem } from "reactstrap"; -import { useMediaQuery } from "react-responsive"; import { useTranslation } from "react-i18next"; +import { LinkContainer } from "react-router-bootstrap"; +import { Navbar, Nav } from "react-bootstrap"; import { useUser, useAvatar } from "@/services/user"; @@ -11,95 +9,54 @@ import TimelineLogo from "./TimelineLogo"; import BlobImage from "./BlobImage"; const AppBar: React.FC = (_) => { - const history = useHistory(); const user = useUser(); const avatar = useAvatar(user?.username); const { t } = useTranslation(); - const isUpMd = useMediaQuery({ - minWidth: getComputedStyle(document.documentElement).getPropertyValue( - "--breakpoint-md" - ), - }); - - const [isMenuOpen, setIsMenuOpen] = React.useState(false); - - const toggleMenu = React.useCallback((): void => { - setIsMenuOpen((oldIsMenuOpen) => !oldIsMenuOpen); - }, []); - const isAdministrator = user && user.administrator; - const rightArea = ( -
- {user != null ? ( - - - - ) : ( - - {t("nav.login")} - - )} -
- ); - return ( - - - - Timeline - - - {isUpMd ? null : rightArea} + + + + + Timeline + + - - -