Skip to content

Commit

Permalink
wip(web/actionModal): finished info tab + smaller sttuff
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed Apr 1, 2024
1 parent 75162f4 commit 54e56f0
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 24 deletions.
37 changes: 37 additions & 0 deletions panel/src/components/DateTimeCorrected.tsx
@@ -0,0 +1,37 @@
import { tsToLocaleDate, tsToLocaleDateTime } from "@/lib/utils";
import { txToast } from "./TxToaster";

const clockSkewTolerance = 5 * 60; //5 minutes

type Props = {
tsFetch: number;
tsObject: number;
serverTime: number;
className?: string;
isDateOnly?: boolean;
};

export default function DateTimeCorrected({ tsFetch, tsObject, serverTime, className, isDateOnly }: Props) {
const serverClockDrift = serverTime - tsFetch; //be positive if server is ahead
const howFarInThePast = serverTime - tsObject;
const localTime = tsFetch - howFarInThePast;

const clockDriftBtnHandler = () => {
txToast.warning(`This means that the server clock is ${Math.abs(serverClockDrift)} seconds ${serverClockDrift > 0 ? 'ahead' : 'behind'} your computer time. Make sure both your computer and the server have their clocks synchronized.`);
}
const displayTime = isDateOnly
? tsToLocaleDate(localTime, 'short')
: tsToLocaleDateTime(localTime, 'medium', 'short')
return <span
className={className}
title={tsToLocaleDateTime(localTime, 'long', 'long')}
>
{displayTime}
{Math.abs(serverClockDrift) > clockSkewTolerance && (
<button
className="ml-1 text-warning-inline"
onClick={clockDriftBtnHandler}
>(clock drift)</button>
)}
</span>
}
80 changes: 65 additions & 15 deletions panel/src/layout/ActionModal/ActionInfoTab.tsx
@@ -1,10 +1,11 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { tsToLocaleDateTime } from "@/lib/utils";
import { msToDuration, tsToLocaleDateTime } from "@/lib/utils";
import { useRef, useState } from "react";
import { DatabaseActionType } from "../../../../core/components/PlayerDatabase/databaseTypes";
import { useOpenPlayerModal } from "@/hooks/playerModal";
import DateTimeCorrected from "@/components/DateTimeCorrected";



Expand Down Expand Up @@ -36,16 +37,53 @@ function ActionReasonBox({ actionReason }: { actionReason: string }) {


type ActionInfoTabProps = {
actionId: string;
action: DatabaseActionType;
setSelectedTab: (t: string) => void;
refreshModalData: () => void;
serverTime: number;
tsFetch: number;
}

export default function ActionInfoTab({ actionId, action, setSelectedTab, refreshModalData }: ActionInfoTabProps) {
const actionDateTimeText = tsToLocaleDateTime(action.timestamp);
export default function ActionInfoTab({ action, serverTime, tsFetch }: ActionInfoTabProps) {
const openPlayerModal = useOpenPlayerModal();

let banExpirationText: React.ReactNode;
if (action.type === 'ban') {
if (action.expiration === false) {
banExpirationText = <span className="text-destructive-inline">Never</span>;
} else if (action.expiration > serverTime) {
const distance = msToDuration(
(serverTime - action.expiration) * 1000,
{ units: ['mo', 'w', 'd', 'h', 'm'] }
)
banExpirationText = <span className="text-warning-inline">In {distance}</span>;
} else {
banExpirationText = <DateTimeCorrected
className="opacity-75 cursor-help"
serverTime={serverTime}
tsObject={action.expiration}
tsFetch={tsFetch}
/>;
}
}

let revokedText: React.ReactNode;
if (action.revocation.timestamp) {
revokedText = <span className="text-warning-inline">
By {action.revocation.author} on <DateTimeCorrected
isDateOnly
className="opacity-75 cursor-help"
serverTime={serverTime}
tsObject={action.revocation.timestamp}
tsFetch={tsFetch}
/>
</span>;
} else {
revokedText = <span className="opacity-75">No</span>;
}

//Player stuff
const playerDisplayName = action.playerName !== false
? <span>{action.playerName}</span>
: <span className="italic opacity-75">unknown player</span>;
const targetLicenses = action.ids.filter(id => id.startsWith('license:'));
const linkedPlayer = targetLicenses.length === 1 ? targetLicenses[0].split(':')[1] : false;
const handleViewPlayerClick = () => {
Expand All @@ -57,31 +95,43 @@ export default function ActionInfoTab({ actionId, action, setSelectedTab, refres
<dl className="pb-2">
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Date/Time</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">{actionDateTimeText}</dd>
<dd className="text-sm leading-6 col-span-2 mt-0">
<DateTimeCorrected
className="opacity-75 cursor-help"
serverTime={serverTime}
tsObject={action.timestamp}
tsFetch={tsFetch}
/>
</dd>
</div>
{action.type === 'ban' && (
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Expiration</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">{banExpirationText}</dd>
</div>
)}
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Revoked</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">{revokedText}</dd>
</div>

<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Admin</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">{action.author}</dd>
</div>
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Player</dt>
<dd className="text-sm leading-6 col-span-2x mt-0">{action.playerName}</dd>
<dd className="text-sm leading-6 col-span-2x mt-0">{playerDisplayName}</dd>
<dd className="text-right">
<Button
variant="outline"
size='inline'
style={{ minWidth: '8.25ch' }}
onClick={handleViewPlayerClick}
disabled={!linkedPlayer}
>
View
</Button>
>View</Button>
</dd>
</div>
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Status</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">FIXME:</dd>
</div>
</dl>

<ActionReasonBox actionReason={action.reason} />
Expand Down
25 changes: 16 additions & 9 deletions panel/src/layout/ActionModal/ActionModal.tsx
Expand Up @@ -44,6 +44,7 @@ export default function ActionModal() {
const [currRefreshKey, setCurrRefreshKey] = useState(0);
const [modalData, setModalData] = useState<HistoryActionModalSuccess | undefined>(undefined);
const [modalError, setModalError] = useState('');
const [tsFetch, setTsFetch] = useState(0);
const historyGetActionApi = useBackendApi<HistoryActionModalResp>({
method: 'GET',
path: `/history/action`,
Expand All @@ -67,6 +68,7 @@ export default function ActionModal() {
setModalError(resp.error);
} else {
setModalData(resp);
setTsFetch(Math.round(Date.now() / 1000));
}
},
error: (error) => {
Expand All @@ -92,17 +94,19 @@ export default function ActionModal() {

let pageTitle: JSX.Element;
if (modalData) {
const displayName = modalData.action.playerName !== false
? <span>{modalData.action.playerName}</span>
: <span className="italic opacity-75">unknown player</span>;
if (modalData.action.type === 'ban') {
pageTitle = <>
<span className="text-destructive-inline font-mono mr-2">[BAN]</span>
{modalData.action.id}
<span className="text-destructive-inline font-mono mr-2">[{modalData.action.id}]</span>
Banned {displayName}
</>;
} else if (modalData.action.type === 'warn') {
pageTitle = <>
<span className="text-warning-inline font-mono mr-2">[WARN]</span>
{modalData.action.id}
<span className="text-warning-inline font-mono mr-2">[{modalData.action.id}]</span>
Warned {displayName}
</>;

} else {
throw new Error(`Unknown action type: ${modalData.action.type}`);
}
Expand All @@ -119,13 +123,16 @@ export default function ActionModal() {
onOpenAutoFocus={(e) => e.preventDefault()}
>
<DialogHeader className="p-4 border-b">
<DialogTitle className="tracking-wide line-clamp-1 break-all mr-6">{pageTitle}</DialogTitle>
<DialogTitle className="tracking-wide line-clamp-1 break-all mr-6">
{pageTitle}
</DialogTitle>
</DialogHeader>

<div className="flex flex-col md:flex-row md:px-4 h-full">
<div className="flex flex-row md:flex-col gap-1 bg-muted md:bg-transparent p-1 md:p-0 mx-2 md:mx-0 rounded-md">
{modalTabs.map((tab) => (
<Button
id={`action-modal-tab-${tab.title}`}
key={tab.title}
variant={selectedTab === tab.title ? "secondary" : "ghost"}
className={cn(
Expand All @@ -136,6 +143,7 @@ export default function ActionModal() {
tab.className,
)}
onClick={() => setSelectedTab(tab.title)}
onKeyDown={handleTabButtonKeyDown}
>
{tab.icon} {tab.title}
</Button>
Expand All @@ -154,10 +162,9 @@ export default function ActionModal() {
) : (
<>
{selectedTab === 'Info' && <ActionInfoTab
actionId={actionRef!}
action={modalData.action}
setSelectedTab={setSelectedTab}
refreshModalData={refreshModalData}
serverTime={modalData.serverTime}
tsFetch={tsFetch}
/>}
{selectedTab === 'IDs' && <ActionIdsTab
action={modalData.action}
Expand Down

0 comments on commit 54e56f0

Please sign in to comment.