diff --git a/makefile b/makefile index e29965a..a682419 100644 --- a/makefile +++ b/makefile @@ -14,7 +14,7 @@ build-tsc: ./node_modules/.bin/tsc -p ./src/tsconfig.json build-sass: - ./node_modules/.bin/sass ./src/client/webapp/styles/styles.scss ./dist/client/webapp/styles/styles.css + ./node_modules/.bin/sass ./src/client/webapp/index.scss ./dist/client/webapp/index.css build: lint build-tsc build-sass diff --git a/src/client/webapp/data-types.ts b/src/client/webapp/data-types.ts index 8fb3b00..b680189 100644 --- a/src/client/webapp/data-types.ts +++ b/src/client/webapp/data-types.ts @@ -316,7 +316,7 @@ export class GuildMetadata implements WithEquals { } } -export class GuildMetadataWithIds extends GuildMetadata { +export class GuildMetadataLocal extends GuildMetadata { private constructor( public readonly id: number, name: string, @@ -327,8 +327,8 @@ export class GuildMetadataWithIds extends GuildMetadata { super(name, iconResourceId, source); } - static fromDBData(data: any): GuildMetadataWithIds { - return new GuildMetadataWithIds( + static fromDBData(data: any): GuildMetadataLocal { + return new GuildMetadataLocal( data.id, data.name, data.icon_resource_id, diff --git a/src/client/webapp/elements-styles/contexts/components/context.scss b/src/client/webapp/elements-styles/contexts/components/context.scss index 808e79f..1c05d82 100644 --- a/src/client/webapp/elements-styles/contexts/components/context.scss +++ b/src/client/webapp/elements-styles/contexts/components/context.scss @@ -9,4 +9,33 @@ &.react:not(.aligned) { visibility: hidden; } + + .basic-hover { + display: flex; + align-items: center; + color: theme.$background-floating; + + &.left { + flex-direction: row; + } + + &.right { + flex-direction: row-reverse; + } + + &.bottom { + flex-direction: column; + } + + &.top { + flex-direction: column-reverse; + } + + .hover-text { + padding: 8px; + border-radius: 4px; + color: theme.$interactive-normal; + background-color: theme.$background-floating; + } + } } \ No newline at end of file diff --git a/src/client/webapp/elements/components/button.tsx b/src/client/webapp/elements/components/button.tsx index cd4871f..fbb1564 100644 --- a/src/client/webapp/elements/components/button.tsx +++ b/src/client/webapp/elements/components/button.tsx @@ -1,4 +1,7 @@ -import React, { FC, ReactNode, Ref, useCallback, useMemo } from 'react'; +import React, { FC, ReactNode, Ref, useCallback, useMemo, useRef } from 'react'; +import BasicHover, { BasicHoverSide } from '../contexts/context-hover-basic'; +import BaseElements from '../require/base-elements'; +import { useContextHover } from '../require/react-helper'; export enum ButtonColorType { BRAND = '', @@ -12,17 +15,21 @@ interface ButtonProps { colorType?: ButtonColorType; + hoverText?: string | null; + onClick?: () => void; shaking?: boolean; children?: ReactNode; } const DefaultButtonProps = { - colorType: ButtonColorType.BRAND, -} + colorType: ButtonColorType.BRAND +}; -const Button: FC = React.forwardRef((props: ButtonProps, ref: Ref) => { - const { colorType, onClick, shaking, children } = { ...DefaultButtonProps, ...props }; +const Button: FC = (props: ButtonProps) => { + const { colorType, hoverText, onClick, shaking, children } = { ...DefaultButtonProps, ...props }; + + const buttonRef = useRef(null); const className = useMemo( () => [ @@ -38,7 +45,30 @@ const Button: FC = React.forwardRef((props: ButtonProps, ref: Ref{children} -}); + const [ contextHover, mouseEnterCallable, mouseLeaveCallable ] = useContextHover( + () => { + if (!hoverText) return null; + return ( + + {BaseElements.TAB_ABOVE} +
{hoverText}
+
+ ); + }, + [ hoverText ] + ); + + return ( +
+
{children}
+ {contextHover} +
+ ); +}; export default Button; \ No newline at end of file diff --git a/src/client/webapp/elements/components/token-row.tsx b/src/client/webapp/elements/components/token-row.tsx new file mode 100644 index 0000000..c7954bd --- /dev/null +++ b/src/client/webapp/elements/components/token-row.tsx @@ -0,0 +1,80 @@ +import moment from 'moment'; +import React, { FC } from 'react'; +import { GuildMetadata, Member, Token } from '../../data-types'; +import CombinedGuild from '../../guild-combined'; +import Util from '../../util'; +import { IAddGuildData } from '../overlays/overlay-add-guild'; +import BaseElements from '../require/base-elements'; +import { useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions'; +import { useAsyncVoidCallback, useDownloadButton, useOneTimeAsyncAction } from '../require/react-helper'; +import Button, { ButtonColorType } from './button'; + +export interface TokenRowProps { + url: string; + guild: CombinedGuild; + guildMeta: GuildMetadata; + token: Token; +} + +const TokenRow: FC = (props: TokenRowProps) => { + const { guild, guildMeta, token } = props; + + const [ guildSocketConfigs ] = useOneTimeAsyncAction( + async () => await guild.fetchSocketConfigs(), + null, + [ guild ] + ); + const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta.iconResourceId); + + const [ revoke ] = useAsyncVoidCallback(async () => { + await guild.requestDoRevokeToken(token.token); + }, [ guild, token ]); + + const [ downloadFunc, downloadText, downloadShaking ] = useDownloadButton( + guildMeta.name + '.cordis', + async () => { + if (guildSocketConfigs === null) return null; + const guildSocketConfig = Util.randomChoice(guildSocketConfigs); + const addGuildData: IAddGuildData = { + name: guildMeta.name, + url: guildSocketConfig.url, + cert: guildSocketConfig.cert, + token: token.token, + expires: token.expires?.getTime() ?? null, + iconSrc: iconSrc, + }; + const json = JSON.stringify(addGuildData); + return Buffer.from(json); + }, + [ guildSocketConfigs, guildMeta, token, iconSrc ] + ); + + const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token'; + return ( +
+
+
{userText}
+
{token.token}
+
+
+
{'Created ' + moment(token.created).fromNow()}
+
{token.expires ? 'Expires ' + moment(token.expires).fromNow() : 'Never expires'}
+
+
+ + +
+
+ ); +} + +export default TokenRow; diff --git a/src/client/webapp/elements/contexts/components/context-menu.tsx b/src/client/webapp/elements/contexts/components/context-menu.tsx index 8935bca..05638a3 100644 --- a/src/client/webapp/elements/contexts/components/context-menu.tsx +++ b/src/client/webapp/elements/contexts/components/context-menu.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactNode, RefObject, useRef } from 'react' +import React, { DependencyList, FC, ReactNode, RefObject, useRef } from 'react' import { IAlignment } from '../../require/elements-util'; import { useCloseWhenEscapeOrClickedOrContextOutsideEffect } from '../../require/react-helper'; import Context from './context'; @@ -7,20 +7,24 @@ export interface ContextMenuProps { relativeToRef?: RefObject; relativeToPos?: { x: number, y: number }; alignment: IAlignment; + realignDeps: DependencyList; children: ReactNode; close: () => void; } // Automatically closes when clicked outside of and includes a