This commit is contained in:
@@ -7,7 +7,6 @@ import type { Extension } from "@codemirror/state"
|
||||
import { LANGUAGE } from "utils/types"
|
||||
import { oneDark } from "../themes/oneDark"
|
||||
import { smoothy } from "../themes/smoothy"
|
||||
|
||||
interface Props {
|
||||
language?: LANGUAGE
|
||||
fontSize?: number
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { RouterLink } from "vue-router"
|
||||
import { isDesktop, isMobile } from "shared/composables/breakpoints"
|
||||
import { toggleLogin, toggleSignup } from "shared/composables/modal"
|
||||
import { screenMode, switchScreenMode } from "shared/composables/switchScreen"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useAuthModalStore } from "shared/store/authModal"
|
||||
import { useScreenModeStore } from "shared/store/screenMode"
|
||||
import { logout } from "../api"
|
||||
import { useConfigStore } from "../store/config"
|
||||
import { useUserStore } from "../store/user"
|
||||
|
||||
const isDark = useDark()
|
||||
const toggleDark = useToggle(isDark)
|
||||
const userStore = useUserStore()
|
||||
const configStore = useConfigStore()
|
||||
const authStore = useAuthModalStore()
|
||||
const screenModeStore = useScreenModeStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const { isMobile, isDesktop } = useBreakpoints()
|
||||
|
||||
const isDark = useDark()
|
||||
|
||||
// 从 store 中获取屏幕模式状态
|
||||
const { screenMode } = storeToRefs(screenModeStore)
|
||||
|
||||
const names = [
|
||||
"man-with-chinese-cap-1",
|
||||
"cat-face",
|
||||
@@ -213,7 +220,7 @@ function goHome() {
|
||||
isDesktop &&
|
||||
(route.name === 'problem' || route.name === 'contest problem')
|
||||
"
|
||||
@click="() => switchScreenMode()"
|
||||
@click="() => screenModeStore.switchScreenMode()"
|
||||
>
|
||||
{{ screenMode }}
|
||||
</n-button>
|
||||
@@ -227,19 +234,23 @@ function goHome() {
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-flex align="center" v-else>
|
||||
<n-button secondary type="primary" @click="toggleLogin(true)">
|
||||
<n-button
|
||||
secondary
|
||||
type="primary"
|
||||
@click="authStore.openLoginModal()"
|
||||
>
|
||||
登录
|
||||
</n-button>
|
||||
<n-button
|
||||
tertiary
|
||||
v-if="configStore.config?.allow_register"
|
||||
@click="toggleSignup(true)"
|
||||
@click="authStore.openSignupModal()"
|
||||
>
|
||||
注册
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</div>
|
||||
<n-button :bordered="false" circle @click="toggleDark()">
|
||||
<n-button :bordered="false" circle @click="isDark = !isDark">
|
||||
<template #icon>
|
||||
<Icon v-if="isDark" icon="twemoji:sun-behind-small-cloud"></Icon>
|
||||
<Icon v-else icon="twemoji:cloud-with-lightning-and-rain"></Icon>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { login } from "../api"
|
||||
import { loginModal, toggleLogin, toggleSignup } from "../composables/modal"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useAuthModalStore } from "../store/authModal"
|
||||
import { useConfigStore } from "../store/config"
|
||||
import { useUserStore } from "../store/user"
|
||||
|
||||
const userStore = useUserStore()
|
||||
const configStore = useConfigStore()
|
||||
const authStore = useAuthModalStore()
|
||||
|
||||
const {
|
||||
loginModalOpen,
|
||||
loginForm: form,
|
||||
loginLoading: isLoading,
|
||||
loginError: msg,
|
||||
} = storeToRefs(authStore)
|
||||
const loginRef = ref()
|
||||
const [isLoading, toggleLoading] = useToggle()
|
||||
const msg = ref("")
|
||||
const form = reactive({
|
||||
class: "",
|
||||
username: "",
|
||||
password: "",
|
||||
})
|
||||
const classList = computed<SelectOption[]>(() => {
|
||||
const defaults = [{ label: "没有我所在的班级", value: "" }]
|
||||
const configs =
|
||||
@@ -35,29 +37,29 @@ async function submit() {
|
||||
loginRef.value!.validate(async (errors: FormRules | undefined) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
msg.value = ""
|
||||
toggleLoading(true)
|
||||
authStore.clearLoginError()
|
||||
authStore.setLoginLoading(true)
|
||||
const merged = {
|
||||
username: form.username,
|
||||
password: form.password,
|
||||
username: form.value.username,
|
||||
password: form.value.password,
|
||||
}
|
||||
if (form.class) {
|
||||
merged.username = form.class + form.username
|
||||
if (form.value.class) {
|
||||
merged.username = form.value.class + form.value.username
|
||||
}
|
||||
await login(merged)
|
||||
} catch (err: any) {
|
||||
if (err.data === "Your account has been disabled") {
|
||||
msg.value = "此账号已被封禁"
|
||||
authStore.setLoginError("此账号已被封禁")
|
||||
} else if (err.data === "Invalid username or password") {
|
||||
msg.value = "用户名或密码不正确"
|
||||
authStore.setLoginError("用户名或密码不正确")
|
||||
} else {
|
||||
msg.value = "无法登录"
|
||||
authStore.setLoginError("无法登录")
|
||||
}
|
||||
} finally {
|
||||
toggleLoading(false)
|
||||
authStore.setLoginLoading(false)
|
||||
}
|
||||
if (!msg.value) {
|
||||
toggleLogin(false)
|
||||
authStore.closeLoginModal()
|
||||
userStore.getMyProfile()
|
||||
}
|
||||
}
|
||||
@@ -65,19 +67,18 @@ async function submit() {
|
||||
}
|
||||
|
||||
function goSignup() {
|
||||
toggleLogin(false)
|
||||
toggleSignup(true)
|
||||
authStore.switchToSignup()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
msg.value = ""
|
||||
authStore.clearLoginError()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
:mask-closable="false"
|
||||
v-model:show="loginModal"
|
||||
v-model:show="loginModalOpen"
|
||||
preset="card"
|
||||
title="登录"
|
||||
style="width: 400px"
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import { MdEditor } from "md-editor-v3"
|
||||
import "md-editor-v3/lib/style.css"
|
||||
import { uploadImage } from "../../admin/api"
|
||||
|
||||
const isDark = useDark()
|
||||
|
||||
const modelValue = defineModel<string>("value")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { isDesktop } from "shared/composables/breakpoints"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
|
||||
interface Props {
|
||||
total: number
|
||||
@@ -16,6 +16,8 @@ const emit = defineEmits(["update:limit", "update:page"])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const { isDesktop } = useBreakpoints()
|
||||
|
||||
const limit = ref(props.limit)
|
||||
const page = ref(props.page)
|
||||
const sizes = computed(() => {
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { getCaptcha, signup } from "../api"
|
||||
import { signupModal, toggleLogin, toggleSignup } from "../composables/modal"
|
||||
import { useUserStore } from "../store/user"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useAuthModalStore } from "../store/authModal"
|
||||
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthModalStore()
|
||||
|
||||
const {
|
||||
signupModalOpen,
|
||||
signupForm: form,
|
||||
signupLoading: isLoading,
|
||||
signupError: msg,
|
||||
captchaSrc,
|
||||
} = storeToRefs(authStore)
|
||||
const signupRef = ref()
|
||||
const captchaSrc = ref("")
|
||||
|
||||
const form = reactive({
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
passwordAgain: "",
|
||||
captcha: "",
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [{ required: true, message: "用户名必填", trigger: "blur" }],
|
||||
@@ -26,7 +25,8 @@ const rules: FormRules = {
|
||||
{ required: true, message: "密码必填", trigger: "blur" },
|
||||
{ min: 6, max: 20, message: "长度在 6 到 20 位之间", trigger: "input" },
|
||||
{
|
||||
validator: (_: FormItemRule, value: string) => value === form.password,
|
||||
validator: (_: FormItemRule, value: string) =>
|
||||
value === form.value.password,
|
||||
message: "两次密码输入不一致",
|
||||
trigger: "blur",
|
||||
},
|
||||
@@ -36,43 +36,39 @@ const rules: FormRules = {
|
||||
],
|
||||
}
|
||||
|
||||
const [isLoading, toggleLoading] = useToggle()
|
||||
const msg = ref("")
|
||||
|
||||
function goLogin() {
|
||||
toggleLogin(true)
|
||||
toggleSignup(false)
|
||||
authStore.switchToLogin()
|
||||
}
|
||||
|
||||
function submit() {
|
||||
signupRef.value!.validate(async (errors: FormRules | undefined) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
msg.value = ""
|
||||
toggleLoading(true)
|
||||
authStore.clearSignupError()
|
||||
authStore.setSignupLoading(true)
|
||||
await signup({
|
||||
username: form.username,
|
||||
email: form.email,
|
||||
password: form.password,
|
||||
captcha: form.captcha,
|
||||
username: form.value.username,
|
||||
email: form.value.email,
|
||||
password: form.value.password,
|
||||
captcha: form.value.captcha,
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.data === "Invalid captcha") {
|
||||
msg.value = "验证码不正确"
|
||||
authStore.setSignupError("验证码不正确")
|
||||
} else if (err.data === "Username already exists") {
|
||||
msg.value = "用户名已存在"
|
||||
authStore.setSignupError("用户名已存在")
|
||||
} else if (err.data === "Email already exists") {
|
||||
msg.value = "邮箱已存在"
|
||||
authStore.setSignupError("邮箱已存在")
|
||||
} else {
|
||||
msg.value = "无法注册"
|
||||
authStore.setSignupError("无法注册")
|
||||
}
|
||||
getCaptchaSrc()
|
||||
form.captcha = ""
|
||||
form.value.captcha = ""
|
||||
} finally {
|
||||
toggleLoading(false)
|
||||
authStore.setSignupLoading(false)
|
||||
}
|
||||
if (!msg.value) {
|
||||
toggleSignup(false)
|
||||
authStore.closeSignupModal()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -80,10 +76,10 @@ function submit() {
|
||||
|
||||
async function getCaptchaSrc() {
|
||||
const res = await getCaptcha()
|
||||
captchaSrc.value = res.data
|
||||
authStore.setCaptchaSrc(res.data)
|
||||
}
|
||||
|
||||
watch(signupModal, (v) => {
|
||||
watch(signupModalOpen, (v) => {
|
||||
if (v) getCaptchaSrc()
|
||||
})
|
||||
</script>
|
||||
@@ -91,7 +87,7 @@ watch(signupModal, (v) => {
|
||||
<template>
|
||||
<n-modal
|
||||
:mask-closable="false"
|
||||
v-model:show="signupModal"
|
||||
v-model:show="signupModalOpen"
|
||||
preset="card"
|
||||
title="注册"
|
||||
style="width: 400px"
|
||||
|
||||
@@ -8,7 +8,8 @@ import { LANGUAGE } from "utils/types"
|
||||
import { oneDark } from "../themes/oneDark"
|
||||
import { smoothy } from "../themes/smoothy"
|
||||
import { useCodeSync, SYNC_ERROR_CODES } from "../composables/sync"
|
||||
import { isDesktop } from "../composables/breakpoints"
|
||||
import { useBreakpoints } from "../composables/breakpoints"
|
||||
const isDark = useDark()
|
||||
|
||||
interface EditorReadyPayload {
|
||||
view: EditorView
|
||||
@@ -44,7 +45,7 @@ const emit = defineEmits<{
|
||||
]
|
||||
}>()
|
||||
|
||||
const isDark = useDark()
|
||||
const { isDesktop } = useBreakpoints()
|
||||
|
||||
const styleTheme = EditorView.baseTheme({
|
||||
"& .cm-scroller": { "font-family": "Monaco" },
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { breakpointsTailwind } from "@vueuse/core"
|
||||
import {
|
||||
breakpointsTailwind,
|
||||
useBreakpoints as useVueUseBreakpoints,
|
||||
} from "@vueuse/core"
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
/**
|
||||
* 响应式断点检测 composable
|
||||
* 每次调用创建新的断点检测实例
|
||||
*/
|
||||
export function useBreakpoints() {
|
||||
const breakpoints = useVueUseBreakpoints(breakpointsTailwind)
|
||||
|
||||
export const isMobile = breakpoints.smallerOrEqual("md")
|
||||
export const isDesktop = breakpoints.greater("md")
|
||||
const isMobile = breakpoints.smallerOrEqual("md")
|
||||
const isDesktop = breakpoints.greater("md")
|
||||
|
||||
return {
|
||||
isMobile,
|
||||
isDesktop,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const [loginModal, toggleLogin] = useToggle()
|
||||
export const [signupModal, toggleSignup] = useToggle()
|
||||
@@ -1,3 +1,5 @@
|
||||
import { reactive, watch } from "vue"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { filterEmptyValue } from "utils/functions"
|
||||
|
||||
export interface PaginationQuery {
|
||||
@@ -17,6 +19,7 @@ export interface UsePaginationOptions {
|
||||
|
||||
/**
|
||||
* 分页相关的 composable,处理分页状态和 URL 同步
|
||||
* 每次调用创建新的分页状态实例
|
||||
* @param initialQuery 初始查询参数对象
|
||||
* @param options 配置选项
|
||||
*/
|
||||
@@ -139,6 +142,7 @@ export function usePagination<T extends Record<string, any>>(
|
||||
|
||||
/**
|
||||
* 简化版本的分页 composable,只处理基本的分页逻辑
|
||||
* 每次调用创建新的分页状态实例
|
||||
* @param defaultLimit 默认每页条数
|
||||
* @param defaultPage 默认页码
|
||||
*/
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ScreenMode } from "utils/constants"
|
||||
|
||||
export const { state: screenMode, next: switchScreenMode } = useCycleList(
|
||||
Object.values(ScreenMode),
|
||||
{
|
||||
initialValue: ScreenMode.both,
|
||||
},
|
||||
)
|
||||
|
||||
export function resetScreenMode() {
|
||||
screenMode.value = ScreenMode.both
|
||||
}
|
||||
|
||||
export const bothAndProblem = computed(
|
||||
() =>
|
||||
screenMode.value === ScreenMode.both ||
|
||||
screenMode.value === ScreenMode.problem,
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMessage } from "naive-ui"
|
||||
import { useUserStore } from "../store/user"
|
||||
import type { EditorView } from "@codemirror/view"
|
||||
import { Compartment } from "@codemirror/state"
|
||||
@@ -81,10 +82,15 @@ export interface SyncStatus {
|
||||
otherUser?: UserInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码同步 composable
|
||||
* 每次调用创建新的同步实例
|
||||
*/
|
||||
export function useCodeSync() {
|
||||
const userStore = useUserStore()
|
||||
const message = useMessage()
|
||||
|
||||
// 每次调用创建新的实例变量
|
||||
let ydoc: Doc | null = null
|
||||
let provider: WebrtcProvider | null = null
|
||||
let ytext: Text | null = null
|
||||
|
||||
@@ -324,37 +324,26 @@ class SubmissionWebSocket extends BaseWebSocket<SubmissionUpdate> {
|
||||
}
|
||||
}
|
||||
|
||||
// 全局单例
|
||||
let wsInstance: SubmissionWebSocket | null = null
|
||||
|
||||
/**
|
||||
* 获取 WebSocket 实例
|
||||
*/
|
||||
export function getWebSocketInstance(): SubmissionWebSocket {
|
||||
if (!wsInstance) {
|
||||
wsInstance = new SubmissionWebSocket()
|
||||
}
|
||||
return wsInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于组件中使用 WebSocket 的 Composable
|
||||
* 每次调用创建新的 WebSocket 实例
|
||||
*/
|
||||
export function useSubmissionWebSocket(
|
||||
handler?: MessageHandler<SubmissionUpdate>,
|
||||
) {
|
||||
const ws = getWebSocketInstance()
|
||||
const ws = new SubmissionWebSocket()
|
||||
|
||||
// 如果提供了处理器,添加到实例中
|
||||
if (handler) {
|
||||
ws.addHandler(handler)
|
||||
}
|
||||
|
||||
// 组件卸载时移除处理器
|
||||
// 组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
if (handler) {
|
||||
ws.removeHandler(handler)
|
||||
}
|
||||
ws.disconnect()
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
170
src/shared/store/authModal.ts
Normal file
170
src/shared/store/authModal.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { defineStore } from "pinia"
|
||||
|
||||
/**
|
||||
* 认证状态管理 Store
|
||||
* 统一管理登录、注册相关的模态框状态和表单状态
|
||||
*/
|
||||
export const useAuthModalStore = defineStore("authModal", () => {
|
||||
// ==================== 模态框状态 ====================
|
||||
const loginModalOpen = ref(false)
|
||||
const signupModalOpen = ref(false)
|
||||
|
||||
// ==================== 登录表单状态 ====================
|
||||
const loginForm = reactive({
|
||||
class: "",
|
||||
username: "",
|
||||
password: "",
|
||||
})
|
||||
|
||||
const loginLoading = ref(false)
|
||||
const loginError = ref("")
|
||||
|
||||
// ==================== 注册表单状态 ====================
|
||||
const signupForm = reactive({
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
passwordAgain: "",
|
||||
captcha: "",
|
||||
})
|
||||
|
||||
const signupLoading = ref(false)
|
||||
const signupError = ref("")
|
||||
|
||||
// ==================== 验证码 ====================
|
||||
const captchaSrc = ref("")
|
||||
|
||||
// ==================== 模态框操作 ====================
|
||||
/**
|
||||
* 打开登录模态框
|
||||
*/
|
||||
function openLoginModal() {
|
||||
loginModalOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭登录模态框
|
||||
*/
|
||||
function closeLoginModal() {
|
||||
loginModalOpen.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开注册模态框
|
||||
*/
|
||||
function openSignupModal() {
|
||||
signupModalOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭注册模态框
|
||||
*/
|
||||
function closeSignupModal() {
|
||||
signupModalOpen.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 从登录切换到注册
|
||||
*/
|
||||
function switchToSignup() {
|
||||
closeLoginModal()
|
||||
openSignupModal()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从注册切换到登录
|
||||
*/
|
||||
function switchToLogin() {
|
||||
closeSignupModal()
|
||||
openLoginModal()
|
||||
}
|
||||
|
||||
// ==================== 登录表单操作 ====================
|
||||
/**
|
||||
* 设置登录加载状态
|
||||
*/
|
||||
function setLoginLoading(loading: boolean) {
|
||||
loginLoading.value = loading
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置登录错误信息
|
||||
*/
|
||||
function setLoginError(error: string) {
|
||||
loginError.value = error
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空登录错误
|
||||
*/
|
||||
function clearLoginError() {
|
||||
loginError.value = ""
|
||||
}
|
||||
|
||||
// ==================== 注册表单操作 ====================
|
||||
/**
|
||||
* 设置注册加载状态
|
||||
*/
|
||||
function setSignupLoading(loading: boolean) {
|
||||
signupLoading.value = loading
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册错误信息
|
||||
*/
|
||||
function setSignupError(error: string) {
|
||||
signupError.value = error
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空注册错误
|
||||
*/
|
||||
function clearSignupError() {
|
||||
signupError.value = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证码图片地址
|
||||
*/
|
||||
function setCaptchaSrc(src: string) {
|
||||
captchaSrc.value = src
|
||||
}
|
||||
|
||||
return {
|
||||
// 模态框状态
|
||||
loginModalOpen,
|
||||
signupModalOpen,
|
||||
|
||||
// 登录表单状态
|
||||
loginForm,
|
||||
loginLoading,
|
||||
loginError,
|
||||
|
||||
// 注册表单状态
|
||||
signupForm,
|
||||
signupLoading,
|
||||
signupError,
|
||||
|
||||
// 验证码
|
||||
captchaSrc,
|
||||
|
||||
// 模态框操作
|
||||
openLoginModal,
|
||||
closeLoginModal,
|
||||
openSignupModal,
|
||||
closeSignupModal,
|
||||
switchToSignup,
|
||||
switchToLogin,
|
||||
|
||||
// 登录表单操作
|
||||
setLoginLoading,
|
||||
setLoginError,
|
||||
clearLoginError,
|
||||
|
||||
// 注册表单操作
|
||||
setSignupLoading,
|
||||
setSignupError,
|
||||
clearSignupError,
|
||||
setCaptchaSrc,
|
||||
}
|
||||
})
|
||||
34
src/shared/store/screenMode.ts
Normal file
34
src/shared/store/screenMode.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { defineStore } from "pinia"
|
||||
import { ScreenMode } from "utils/constants"
|
||||
|
||||
export const useScreenModeStore = defineStore("screenMode", () => {
|
||||
const { state: screenMode, next: switchScreenMode } = useCycleList(
|
||||
Object.values(ScreenMode),
|
||||
{
|
||||
initialValue: ScreenMode.both,
|
||||
},
|
||||
)
|
||||
|
||||
// 计算属性
|
||||
const isBothMode = computed(() => screenMode.value === ScreenMode.both)
|
||||
const isCodeOnlyMode = computed(() => screenMode.value === ScreenMode.code)
|
||||
|
||||
const shouldShowProblem = computed(
|
||||
() =>
|
||||
screenMode.value === ScreenMode.both ||
|
||||
screenMode.value === ScreenMode.problem,
|
||||
)
|
||||
|
||||
function resetScreenMode() {
|
||||
screenMode.value = ScreenMode.both
|
||||
}
|
||||
|
||||
return {
|
||||
screenMode,
|
||||
isBothMode,
|
||||
isCodeOnlyMode,
|
||||
shouldShowProblem,
|
||||
switchScreenMode,
|
||||
resetScreenMode,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user