Skip to content

Commit

Permalink
feat: compress avatar to webp
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed May 6, 2023
1 parent 5ec2796 commit 7806dde
Show file tree
Hide file tree
Showing 20 changed files with 162 additions and 58 deletions.
1 change: 1 addition & 0 deletions src/GZCTF/CTFServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/GZCTF/ClientApp/src/components/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const AppNavbar: FC = () => {
const logout = () => {
api.account.accountLogOut().then(() => {
navigate('/')
mutate((key) => typeof key === 'string' && key.includes('game'), undefined, {
mutate((key) => typeof key === 'string' && key.includes('game/'), undefined, {
revalidate: false,
})
showNotification({
Expand Down
4 changes: 2 additions & 2 deletions src/GZCTF/ClientApp/src/components/ChallengeDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
Tooltip,
} from '@mantine/core'
import { useClipboard, useDisclosure, useInputState } from '@mantine/hooks'
import { showNotification, updateNotification } from '@mantine/notifications'
import { notifications, showNotification, updateNotification } from '@mantine/notifications'
import { mdiCheck, mdiClose, mdiDownload, mdiLightbulbOnOutline, mdiLoading } from '@mdi/js'
import { Icon } from '@mdi/react'
import { showErrorNotification } from '@Utils/ApiErrorHandler'
Expand Down Expand Up @@ -221,7 +221,7 @@ const ChallengeDetailModal: FC<ChallengeDetailModalProps> = (props) => {
})
.then((res) => {
setSubmitId(res.data)

notifications.clean()
showNotification({
id: 'flag-submitted',
color: 'orange',
Expand Down
45 changes: 35 additions & 10 deletions src/GZCTF/ClientApp/src/components/TeamEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { Dropzone } from '@mantine/dropzone'
import { useClipboard } from '@mantine/hooks'
import { useModals } from '@mantine/modals'
import { showNotification } from '@mantine/notifications'
import { showNotification, updateNotification, notifications } from '@mantine/notifications'
import { mdiCheck, mdiClose, mdiRefresh, mdiStar } from '@mdi/js'
import { Icon } from '@mdi/react'
import { showErrorNotification } from '@Utils/ApiErrorHandler'
Expand Down Expand Up @@ -83,6 +83,8 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
const [dropzoneOpened, setDropzoneOpened] = useState(false)
const [avatarFile, setAvatarFile] = useState<File | null>(null)
const [inviteCode, setInviteCode] = useState('')
const [disabled, setDisabled] = useState(false)
const { mutate: mutateTeams } = api.team.useTeamGetTeamsInfo()

const theme = useMantineTheme()
const clipboard = useClipboard()
Expand Down Expand Up @@ -114,7 +116,7 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
message: '队伍信息已更新',
icon: <Icon path={mdiCheck} size={1} />,
})
api.team.mutateTeamGetTeamsInfo()
mutateTeams((teams) => teams?.filter((x) => x.id !== teamInfo?.id))
props.onClose()
})
.catch(showErrorNotification)
Expand All @@ -134,7 +136,7 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
})
setInviteCode('')
setTeamInfo(null)
api.team.mutateTeamGetTeamsInfo()
mutateTeams((teams) => teams?.filter((x) => x.id !== teamInfo.id), { revalidate: false })
props.onClose()
})
.catch(showErrorNotification)
Expand All @@ -154,8 +156,10 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
message: '队伍信息已更新',
icon: <Icon path={mdiCheck} size={1} />,
})
api.team.mutateTeamGetTeamsInfo()
setTeamInfo(team.data)
mutateTeams((teams) => teams?.map((x) => (x.id === teamInfo.id ? team.data : x)), {
revalidate: false,
})
})
.catch(showErrorNotification)
}
Expand All @@ -171,8 +175,10 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
message: '队伍信息已更新',
icon: <Icon path={mdiCheck} size={1} />,
})
api.team.mutateTeamGetTeamsInfo()
setTeamInfo(data.data)
mutateTeams((teams) => teams?.map((x) => (x.id === teamInfo?.id ? data.data : x)), {
revalidate: false,
})
})
.catch(showErrorNotification)
}
Expand All @@ -195,19 +201,35 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {

const onChangeAvatar = () => {
if (avatarFile && teamInfo?.id) {
setDisabled(true)
notifications.clean()
showNotification({
id: 'upload-avatar',
color: 'orange',
message: '正在上传头像',
loading: true,
autoClose: false,
})

api.team
.teamAvatar(teamInfo?.id, {
file: avatarFile,
})
.then((data) => {
showNotification({
updateNotification({
id: 'upload-avatar',
color: 'teal',
message: '头像已更新',
icon: <Icon path={mdiCheck} size={1} />,
autoClose: true,
})
setTeamInfo({ ...teamInfo, avatar: data.data })
api.team.mutateTeamGetTeamsInfo()
setAvatarFile(null)
const newTeamInfo = { ...teamInfo, avatar: data.data }
setTeamInfo(newTeamInfo)
mutateTeams((teams) => teams?.map((x) => (x.id === teamInfo.id ? newTeamInfo : x)), {
revalidate: false,
})
setDisabled(false)
setDropzoneOpened(false)
})
.catch((err) => {
Expand All @@ -228,7 +250,9 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
message: '队伍信息已更新',
icon: <Icon path={mdiCheck} size={1} />,
})
api.team.mutateTeamGetTeamsInfo()
mutateTeams((teams) => teams?.map((x) => (x.id === teamInfo.id ? teamInfo : x)), {
revalidate: false,
})
})
.catch(showErrorNotification)
}
Expand Down Expand Up @@ -415,6 +439,7 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
m="0 auto 20px auto"
miw={220}
mih={220}
disabled={disabled}
maxSize={3 * 1024 * 1024}
accept={ACCEPT_IMAGE_MIME_TYPE}
>
Expand All @@ -433,7 +458,7 @@ const TeamEditModal: FC<TeamEditModalProps> = (props) => {
)}
</Group>
</Dropzone>
<Button fullWidth variant="outline" onClick={onChangeAvatar}>
<Button fullWidth variant="outline" disabled={disabled} onClick={onChangeAvatar}>
更新头像
</Button>
</Modal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const ChallengeCreateModal: FC<ChallengeCreateModalProps> = (props) => {
.then((data) => {
showNotification({
color: 'teal',
message: '添加比赛题目成功',
message: '比赛题目已添加',
icon: <Icon path={mdiCheck} size={1} />,
})
onAddChallenge(data.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const FlagCreateModal: FC<ModalProps> = (props) => {
.then(() => {
showNotification({
color: 'teal',
message: 'flag 创建成功',
message: 'flag 已创建',
icon: <Icon path={mdiCheck} size={1} />,
})
challenge &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const GameCreateModal: FC<GameCreateModalProps> = (props) => {
.then((data) => {
showNotification({
color: 'teal',
message: '比赛创建成功',
message: '比赛已创建',
icon: <Icon path={mdiCheck} size={1} />,
})
onAddGame(data.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const GameNoticeEditModal: FC<GameNoticeEditModalProps> = (props) => {
.then((data) => {
showNotification({
color: 'teal',
message: '通知修改成功',
message: '通知已修改',
icon: <Icon path={mdiCheck} size={1} />,
})
mutateGameNotice(data.data)
Expand Down
8 changes: 2 additions & 6 deletions src/GZCTF/ClientApp/src/pages/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,11 @@ const useStyles = createStyles((theme) => ({

const Home: FC = () => {
const { data: posts, mutate } = api.info.useInfoGetLatestPosts({
refreshInterval: 0,
revalidateIfStale: false,
revalidateOnFocus: false,
refreshInterval: 5 * 60 * 1000,
})

const { data: allGames } = api.game.useGameGamesAll({
refreshInterval: 0,
revalidateIfStale: false,
revalidateOnFocus: false,
refreshInterval: 5 * 60 * 1000,
})

allGames?.sort((a, b) => new Date(a.end!).getTime() - new Date(b.end!).getTime())
Expand Down
21 changes: 17 additions & 4 deletions src/GZCTF/ClientApp/src/pages/account/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
SimpleGrid,
} from '@mantine/core'
import { Dropzone } from '@mantine/dropzone'
import { showNotification } from '@mantine/notifications'
import { notifications, showNotification, updateNotification } from '@mantine/notifications'
import { mdiCheck, mdiClose } from '@mdi/js'
import { Icon } from '@mdi/react'
import PasswordChangeModal from '@Components/PasswordChangeModal'
Expand All @@ -41,7 +41,7 @@ const Profile: FC = () => {
})
const [avatarFile, setAvatarFile] = useState<File | null>(null)

const [disabled] = useState(false)
const [disabled, setDisabled] = useState(false)

const [mailEditOpened, setMailEditOpened] = useState(false)
const [pwdChangeOpened, setPwdChangeOpened] = useState(false)
Expand All @@ -64,17 +64,30 @@ const Profile: FC = () => {

const onChangeAvatar = () => {
if (avatarFile) {
setDisabled(true)
notifications.clean()
showNotification({
id: 'upload-avatar',
color: 'orange',
message: '正在上传头像',
loading: true,
autoClose: false,
})

api.account
.accountAvatar({
file: avatarFile,
})
.then(() => {
showNotification({
updateNotification({
id: 'upload-avatar',
color: 'teal',
message: '头像已更新',
icon: <Icon path={mdiCheck} size={1} />,
autoClose: true,
})
mutate({ ...user })
setDisabled(false)
mutate({ ...user }, { revalidate: false })
setAvatarFile(null)
setDropzoneOpened(false)
})
Expand Down
19 changes: 16 additions & 3 deletions src/GZCTF/ClientApp/src/pages/admin/games/[id]/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { DatePickerInput, TimeInput } from '@mantine/dates'
import { Dropzone } from '@mantine/dropzone'
import { useClipboard, useInputState } from '@mantine/hooks'
import { useModals } from '@mantine/modals'
import { showNotification } from '@mantine/notifications'
import { notifications, showNotification, updateNotification } from '@mantine/notifications'
import {
mdiKeyboardBackspace,
mdiCheck,
Expand Down Expand Up @@ -90,14 +90,27 @@ const GameInfoEdit: FC = () => {

const onUpdatePoster = (file: File | undefined) => {
if (game && file) {
setDisabled(true)
notifications.clean()
showNotification({
id: 'upload-poster',
color: 'orange',
message: '正在上传海报',
loading: true,
autoClose: false,
})

api.edit
.editUpdateGamePoster(game.id!, { file })
.then((res) => {
showNotification({
updateNotification({
id: 'upload-poster',
color: 'teal',
message: '成功修改比赛海报',
message: '比赛海报已更新',
icon: <Icon path={mdiCheck} size={1} />,
autoClose: true,
})
setDisabled(false)
mutate({ ...game, poster: res.data })
})
.catch(showErrorNotification)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const GameChallengeEdit: FC = () => {
.then(() => {
showNotification({
color: 'teal',
message: '题目状态更新成功',
message: '题目状态已更新',
icon: <Icon path={mdiCheck} size={1} />,
})
mutate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const GameChallengeEdit: FC = () => {
revalidateOnFocus: false,
})

const { mutate: mutateChals } = api.edit.useEditGetGameChallenges(numId)

const [challengeInfo, setChallengeInfo] = useState<ChallengeUpdateModel>({ ...challenge })
const [disabled, setDisabled] = useState(false)

Expand Down Expand Up @@ -92,7 +94,9 @@ const GameChallengeEdit: FC = () => {
})
}
mutate(data.data)
api.edit.mutateEditGetGameChallenges(numId)
mutateChals((chals) => chals?.map((chal) => (chal.id === numCId ? data.data : chal)), {
revalidate: false,
})
})
.catch(showErrorNotification)
.finally(() => {
Expand All @@ -112,7 +116,7 @@ const GameChallengeEdit: FC = () => {
message: '题目已删除',
icon: <Icon path={mdiCheck} size={1} />,
})
api.edit.mutateEditGetGameChallenges(numId)
mutateChals((chals) => chals?.filter((chal) => chal.id !== numCId), { revalidate: false })
navigate(`/admin/games/${id}/challenges`)
})
.catch(showErrorNotification)
Expand Down
10 changes: 6 additions & 4 deletions src/GZCTF/ClientApp/src/utils/useConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ export const localStorageProvider = () => {
const cacheKey = 'gzctf-cache'
const map = new Map(JSON.parse(LZString.decompress(localStorage.getItem(cacheKey) || '') || '[]'))

window.addEventListener('beforeunload', () => {
const appCache = LZString.compress(JSON.stringify(Array.from(map.entries())))
localStorage.setItem(cacheKey, appCache)
})
if (!import.meta.env.DEV) {
window.addEventListener('beforeunload', () => {
const appCache = LZString.compress(JSON.stringify(Array.from(map.entries())))
localStorage.setItem(cacheKey, appCache)
})
}

return map as Cache
}
4 changes: 2 additions & 2 deletions src/GZCTF/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,10 @@ public async Task<IActionResult> Avatar(IFormFile file, CancellationToken token)
if (user!.AvatarHash is not null)
await fileService.DeleteFileByHash(user.AvatarHash, token);

var avatar = await fileService.CreateOrUpdateFile(file, "avatar", token);
var avatar = await fileService.CreateOrUpdateImage(file, "avatar", token);

if (avatar is null)
return BadRequest(new RequestResponse("文件创建失败"));
return BadRequest(new RequestResponse("用户头像更新失败"));

user.AvatarHash = avatar.Hash;
var result = await userManager.UpdateAsync(user);
Expand Down
2 changes: 1 addition & 1 deletion src/GZCTF/Controllers/EditController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public async Task<IActionResult> UpdateGamePoster([FromRoute] int id, IFormFile
if (game is null)
return NotFound(new RequestResponse("比赛未找到", 404));

var poster = await fileService.CreateOrUpdateFile(file, "poster", token);
var poster = await fileService.CreateOrUpdateImage(file, "poster", token, 0);

if (poster is null)
return BadRequest(new RequestResponse("文件创建失败"));
Expand Down
4 changes: 2 additions & 2 deletions src/GZCTF/Controllers/TeamController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,10 +486,10 @@ public async Task<IActionResult> Avatar([FromRoute] int id, IFormFile file, Canc
if (team.AvatarHash is not null)
_ = await FileService.DeleteFileByHash(team.AvatarHash, token);

var avatar = await FileService.CreateOrUpdateFile(file, "avatar", token);
var avatar = await FileService.CreateOrUpdateImage(file, "avatar", token);

if (avatar is null)
return BadRequest(new RequestResponse("未知错误"));
return BadRequest(new RequestResponse("队伍头像更新失败"));

team.AvatarHash = avatar.Hash;
await teamRepository.SaveAsync(token);
Expand Down
Loading

0 comments on commit 7806dde

Please sign in to comment.