Selaa lähdekoodia

Merge branch 'front-dev' of http://git.choozmo.com:3000/ai-anchor/video-maker into front-dev

tomoya 2 vuotta sitten
vanhempi
commit
03a81144e5

+ 74 - 62
frontend/src/components/NotificationsManager.vue

@@ -1,73 +1,85 @@
-<template>
-  <div>
-    <v-snackbar auto-height :color="currentNotificationColor" v-model="show">
-      <v-progress-circular class="ma-2" indeterminate v-show="showProgress"></v-progress-circular>{{ currentNotificationContent }}
-      <template v-slot:actions>
-        <v-btn flat @click.native="close">Close</v-btn>
-      </template>
-      
-    </v-snackbar>
-  </div>
-</template>
-
 <script setup lang="ts">
-  import { ref, computed, watch } from 'vue';
-  import type { AppNotification } from '@/interfaces';
-  import { useMainStore } from '@/stores/main';
-  import { storeToRefs } from 'pinia';
+import { ref, computed, watch } from "vue";
+import type { AppNotification } from "@/interfaces";
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+import { useI18n } from "vue-i18n";
 
-  const show = ref(false);
-  const showProgress = ref(false);
-  const currentNotification = ref<AppNotification|false>(false);
-  const currentNotificationFlag =  ref(false);
+const { t } = useI18n();
+const show = ref(false);
+const showProgress = ref(false);
+const currentNotification = ref<AppNotification | false>(false);
+const currentNotificationFlag = ref(false);
 
-  const mainStore = useMainStore();
-  const mainStoreRef = storeToRefs(mainStore);
+const mainStore = useMainStore();
+const mainStoreRef = storeToRefs(mainStore);
 
-  const currentNotificationContent = computed(() => { 
-    return currentNotification.value && currentNotification.value.content || ''
-  });
-  const currentNotificationColor = computed(() => {
-    return currentNotification.value && currentNotification.value.color || 'info'
-  });
-  
-  const firstNotification = mainStoreRef.readFirstNotification;
+const currentNotificationContent = computed(() => {
+  return (currentNotification.value && currentNotification.value.content) || "";
+});
+const currentNotificationColor = computed(() => {
+  return (
+    (currentNotification.value && currentNotification.value.color) || "info"
+  );
+});
 
-  async function hide() {
-    show.value = false;
-    await new Promise<void>((resolve, reject) => setTimeout(() => resolve(), 500));
-  }
-  async function close() {
-    await hide();
-    await removeCurrentNotification();
-  }
+const firstNotification = mainStoreRef.readFirstNotification;
 
-  async function removeCurrentNotification() {
-    if (currentNotification.value) {
-      mainStore.removeNotification(currentNotification.value);
-      }
-  }
+async function hide() {
+  show.value = false;
+  await new Promise<void>((resolve, reject) =>
+    setTimeout(() => resolve(), 500)
+  );
+}
+async function close() {
+  await hide();
+  await removeCurrentNotification();
+}
 
+async function removeCurrentNotification() {
+  if (currentNotification.value) {
+    mainStore.removeNotification(currentNotification.value);
+  }
+}
 
-  watch(firstNotification, async (newNotification, oldNotification,) => {
-    if (newNotification !== currentNotification.value) {
-      await setNotification(newNotification);
-      if (newNotification) {
-        mainStore.dispatchRemoveNotification({notification: newNotification, timeout: 6500});
-      }
+watch(firstNotification, async (newNotification, oldNotification) => {
+  if (newNotification !== currentNotification.value) {
+    await setNotification(newNotification);
+    if (newNotification) {
+      mainStore.dispatchRemoveNotification({
+        notification: newNotification,
+        timeout: 1000,
+      });
     }
-  })
+  }
+});
 
-  async function setNotification(notification: AppNotification | false) {
-    if (show.value) {
-      await hide();
-    }
-    if (notification){
-      currentNotification.value = notification;
-      showProgress.value = notification.showProgress || false;
-      show.value = true;
-    } else {
-      currentNotificationFlag.value = false;
-    }
+async function setNotification(notification: AppNotification | false) {
+  if (show.value) {
+    await hide();
   }
-</script>
+  if (notification) {
+    currentNotification.value = notification;
+    showProgress.value = notification.showProgress || false;
+    show.value = true;
+  } else {
+    currentNotificationFlag.value = false;
+  }
+}
+</script>
+
+<template>
+  <div>
+    <v-snackbar auto-height :color="currentNotificationColor" v-model="show">
+      <v-progress-circular
+        class="mx-3"
+        indeterminate
+        v-show="showProgress"
+      ></v-progress-circular
+      >{{ currentNotificationContent }}
+      <template v-slot:actions>
+        <v-btn flat @click.native="close">{{ $t("close") }}</v-btn>
+      </template>
+    </v-snackbar>
+  </div>
+</template>

+ 5 - 2
frontend/src/language/en.json

@@ -4,7 +4,7 @@
     "logout": "Logout",
     "submit": "Submit",
     "cancel": "Cancel",
-    "userName": "User name",
+    "userName": "Username",
     "password": "Password",
     "emailAddress": "Email Address",
     "registerPassword": "Password",
@@ -28,5 +28,8 @@
     "editProfile": "Edit Profile",
     "changePassword": "Change Password",
     "collapse": "Collapse",
-    "language": "Language"
+    "language": "Language",
+    "sginingUp": "Sgining up...",
+    "successfully": "Successfully registered",
+    "close": "Close"
 }

+ 4 - 1
frontend/src/language/zh.json

@@ -28,5 +28,8 @@
     "editProfile": "編輯資料",
     "changePassword": "變更密碼",
     "collapse": "收合",
-    "language": "語言"
+    "language": "語言",
+    "sginingUp": "註冊中...",
+    "successfully": "註冊成功",
+    "close": "關閉"
 }

+ 1 - 14
frontend/src/main.ts

@@ -3,20 +3,7 @@ import router from "./router";
 import App from "./App.vue";
 import { vuetify } from "./plugins/vuetify";
 import { pinia } from "./plugins/pinia";
-import { createI18n } from "vue-i18n";
-import zh from "./language/zh.json";
-import en from "./language/en.json";
-
-const i18n = createI18n({
-  legacy: false,
-  locale: localStorage.getItem("lang") ?? "zh",
-  fallbackLocale: "zh",
-  globalInjection: true,
-  messages: {
-    "zh": zh,
-    "en": en,
-  }
-});
+import i18n from './plugins/i18n'
 
 const app = createApp(App);
 

+ 16 - 0
frontend/src/plugins/i18n.ts

@@ -0,0 +1,16 @@
+import { createI18n } from 'vue-i18n';
+import zh from "../language/zh.json";
+import en from "../language/en.json";
+
+const i18n = createI18n({
+    legacy: false,
+    locale: localStorage.getItem("lang") ?? "zh",
+    fallbackLocale: "zh",
+    globalInjection: true,
+    messages: {
+        "zh": zh,
+        "en": en,
+    }
+})
+
+export default i18n

+ 241 - 237
frontend/src/stores/main.ts

@@ -5,7 +5,9 @@ import router from "@/router"
 import { getLocalToken, removeLocalToken, saveLocalToken } from "@/utils";
 import type { AppNotification } from '@/interfaces';
 import type { IUserProfile, IUserProfileCreate, IUserProfileUpdate, MainState, Video} from '@/interfaces';
+import i18n from '@/plugins/i18n'
 
+// const { t } = useI18n();
 const defaultState: MainState = {
   isLoggedIn: null,
   token: '',
@@ -18,248 +20,250 @@ const defaultState: MainState = {
 };
 
 export const useMainStore = defineStore("MainStoreId", {
-    state: () => defaultState,
-    
-    getters: {
-        readhasAdminAccess: (state) => 
-            state.userProfile && state.userProfile.is_superuser && state.userProfile.is_active,
-        readLoginError: (state) => state.logInError,
-        readDashboardShowDrawer: (state) => state.dashboardShowDrawer,
-        readDashboardMiniDrawer: (state) => state.dashboardMiniDrawer,
-        readUserProfile: (state) => state.userProfile,
-        readToken: (state) => state.token,
-        readIsLoggedIn: (state) => state.isLoggedIn,
-        readFirstNotification: (state) => state.notifications.length > 0 && state.notifications[0], 
-        readVideos: (state) => state.videos,
+  state: () => defaultState,
+
+  getters: {
+    readhasAdminAccess: (state) =>
+      state.userProfile && state.userProfile.is_superuser && state.userProfile.is_active,
+    readLoginError: (state) => state.logInError,
+    readDashboardShowDrawer: (state) => state.dashboardShowDrawer,
+    readDashboardMiniDrawer: (state) => state.dashboardMiniDrawer,
+    readUserProfile: (state) => state.userProfile,
+    readToken: (state) => state.token,
+    readIsLoggedIn: (state) => state.isLoggedIn,
+    readFirstNotification: (state) => state.notifications.length > 0 && state.notifications[0],
+  },
+
+  actions: {
+    // setters
+    setToken(payload: string) { this.token = payload; },
+    setLoggedIn(payload: boolean) { this.isLoggedIn = payload; },
+    setLogInError(payload: boolean) { this.logInError = payload; },
+    setUserProfile(payload: IUserProfile) { this.userProfile = payload },
+    setDashboardMiniDrawer(payload: boolean) { this.dashboardMiniDrawer = payload; },
+    setDashboardShowDrawer(payload: boolean) { this.dashboardShowDrawer = payload; },
+    setVideos(payload: Video[]) { this.videos = payload },
+    addNotification(payload: AppNotification) { this.notifications.push(payload); },
+    removeNotification(payload: AppNotification) {
+      if (payload) {
+        this.notifications = this.notifications.filter(
+          (notification) => notification !== payload,
+        );
+      }
     },
-    
-    actions:{
-        // setters
-        setToken(payload: string) { this.token = payload; },
-        setLoggedIn(payload: boolean) { this.isLoggedIn = payload; },
-        setLogInError(payload: boolean) { this.logInError = payload; },
-        setUserProfile(payload: IUserProfile) { this.userProfile = payload },
-        setDashboardMiniDrawer(payload: boolean) { this.dashboardMiniDrawer = payload; },
-        setDashboardShowDrawer(payload: boolean) { this.dashboardShowDrawer = payload; },
-        setVideos(payload: Video[]) {this.videos = payload},
-        addNotification(payload: AppNotification) { this.notifications.push(payload); },
-        removeNotification(payload: AppNotification) {
-            if (payload) { 
-                this.notifications = this.notifications.filter(
-                    (notification) => notification !== payload,
-                );
-            }
-        },
-        // actions
-        async logIn(username:string, password:string) {
-            try {
-                const response = await api.logInGetToken(username, password);
-                const token: string = response.data.access_token;
-                if (token) {
-                    saveLocalToken(token);
-                    this.setToken(token);
-                    this.setLoggedIn(true);
-                    this.setLogInError(false);
-                    await this.getUserProfile();
-                    await this.routeLoggedIn();
-                    this.addNotification({content: "Logged in", color: "success" });
-                } else {
-                    await this.logOut();
-                }
-            } catch (err) {
-                this.setLogInError(true);
-                await this.logOut();
-            }
-        },
-        async getUserProfile() {
-            try {
-                const response = await api.getMe(this.token);
-                if (response.data) {
-                    this.setUserProfile(response.data)
-                }
-            } catch (error) {
-                await this.checkApiError(error);
-            }
-        },  
-        async updateUserProfile(payload: IUserProfileUpdate) {
-            try {
-                const loadingNotification = { content: "saving", showProgress: true };
-                await this.addNotification(loadingNotification);
-                const response = (
-                  await Promise.all([
-                    api.updateMe(this.token, payload),
-                    await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
-                  ])
-                )[0];
-                this.setUserProfile(response.data);
-                this.removeNotification(loadingNotification);
-                this.addNotification({
-                  content: "Profile successfully updated",
-                  color: "success",
-                });
-            } catch (error) {
-                await this.checkApiError(error);
-            }
-        },
-        async checkLoggedIn() {
-            if (!this.isLoggedIn) {
-                let token = this.token;
-                if (!token) {
-                  const localToken = getLocalToken();
-                  if (localToken) {
-                    this.setToken(localToken);
-                    token = localToken;
-                  }
-                }
-                if (token) {
-                  try {
-                    const response = await api.getMe(token);
-                    this.setLoggedIn(true);
-                    this.setUserProfile(response.data);
-                  } catch (error) {
-                    await this.removeLogIn();
-                  }
-                } else {
-                  await this.removeLogIn();
-                }
-            }
-        },
-        async removeLogIn() {
-            removeLocalToken();
-            this.setToken("");
-            this.setLoggedIn(false);
-        },
-        async logOut() {
-            await this.removeLogIn();
-            this.routeLogOut();
-        },
-        async userLogOut() {
-            await this.logOut();
-            this.addNotification({ content: "Logged out", color: "success" });
-        },
-        routeLogOut() {
-            if (router.currentRoute.value.path !== "/login") {
-                router.push("/login");
-            }
-        },
-        async checkApiError(payload: unknown) {
-            if (axios.isAxiosError(payload)) {
-                if (payload.response?.status === 401) {
-                  await this.logOut();
-                }
-            }
-        },
-        routeLoggedIn() {
-            if (router.currentRoute.value.path === "/login" || router.currentRoute.value.path === "/") {
-                router.push("/main/dashboard");
-            }
-        },
-        async dispatchRemoveNotification(payload: {notification: AppNotification; timeout: number },) {
-            return new Promise((resolve, _) => {
-                setTimeout(() => {
-                  this.removeNotification(payload.notification);
-                  resolve(true);
-                }, payload.timeout);
-            });
-        },
-        async register(payload: IUserProfileCreate) {
-            const loadingNotification = {
-                content: "Sgining up...",
-                showProgress: true,
-            };
-            try {
-                this.addNotification(loadingNotification);
-                const response = (
-                  await Promise.all([
-                    api.registerUser(payload),
-                    await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
-                  ])
-                )[0];
-                this.removeNotification(loadingNotification);
-                this.addNotification({
-                  content: "successfully registered",
-                  color: "success",
-                });
-            } catch (error) {
-              await this.checkApiError(error);
-          }
-        },
-        async passwordRecovery(username: string ) {
-            const loadingNotification = {
-                content: "Sending password recovery email",
-                showProgress: true,
-            };
-            try {
-                this.addNotification(loadingNotification);
-                await Promise.all([
-                  api.passwordRecovery(username),
-                  await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
-                ]);
-                this.removeNotification(loadingNotification);
-                this.addNotification({
-                  content: "Password recovery email sent",
-                  color: "success",
-                });
-                await this.logOut();
-            } catch (error) {
-                this.removeNotification(loadingNotification);
-                this.addNotification({ color: "error", content: "Incorrect username" });
-            }
-        },
-        async resetPassword(password: string, token: string) {
-            const loadingNotification = { content: "Resetting password", showProgress: true };
-            try {
-                this.addNotification(loadingNotification);
-                await Promise.all([
-                    api.resetPassword(token, password),
-                    await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
-                ]);
-                this.removeNotification(loadingNotification);
-                this.addNotification( {
-                    content: "Password successfully reset",
-                    color: "success",
-                });
-                await this.logOut();
-            } catch (error) {
-                this.removeNotification(loadingNotification);
-                this.addNotification({
-                    color: "error",
-                    content: "Error resetting password",
-                });
-            }
-        },
-        async uploadPlot(title: string, file: File) {
-          const mainStore = useMainStore();
-          try {
-            const loadingNotification = { content: "sending", showProgress: true };
-            mainStore.addNotification(loadingNotification);
-            const response = (
-              await Promise.all([
-                api.uploadPlot(mainStore.token, title, file),
-                await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
-              ])
-            );
-            mainStore.removeNotification(loadingNotification);
-            mainStore.addNotification({
-              content: "File received",
-                color: "success",
-            })
-          } catch (error) {
-            await mainStore.checkApiError(error);
+    // actions
+    async logIn(username: string, password: string) {
+      try {
+        const response = await api.logInGetToken(username, password);
+        const token: string = response.data.access_token;
+        if (token) {
+          saveLocalToken(token);
+          this.setToken(token);
+          this.setLoggedIn(true);
+          this.setLogInError(false);
+          await this.getUserProfile();
+          await this.routeLoggedIn();
+          this.addNotification({ content: "Logged in", color: "success" });
+        } else {
+          await this.logOut();
+        }
+      } catch (err) {
+        this.setLogInError(true);
+        await this.logOut();
+      }
+    },
+    async getUserProfile() {
+      try {
+        const response = await api.getMe(this.token);
+        if (response.data) {
+          this.setUserProfile(response.data)
+        }
+      } catch (error) {
+        await this.checkApiError(error);
+      }
+    },
+    async updateUserProfile(payload: IUserProfileUpdate) {
+      try {
+        const loadingNotification = { content: "saving", showProgress: true };
+        await this.addNotification(loadingNotification);
+        const response = (
+          await Promise.all([
+            api.updateMe(this.token, payload),
+            await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
+          ])
+        )[0];
+        this.setUserProfile(response.data);
+        this.removeNotification(loadingNotification);
+        this.addNotification({
+          content: "Profile successfully updated",
+          color: "success",
+        });
+      } catch (error) {
+        await this.checkApiError(error);
+      }
+    },
+    async checkLoggedIn() {
+      if (!this.isLoggedIn) {
+        let token = this.token;
+        if (!token) {
+          const localToken = getLocalToken();
+          if (localToken) {
+            this.setToken(localToken);
+            token = localToken;
           }
-        },
-        async actionGetVideos() {
-          const mainStore = useMainStore();
+        }
+        if (token) {
           try {
-            const response = await api.getVideos(mainStore.token)
-            if (response) {
-              this.setVideos(response.data);
-            }
+            const response = await api.getMe(token);
+            this.setLoggedIn(true);
+            this.setUserProfile(response.data);
           } catch (error) {
-            await mainStore.checkApiError(error);
+            await this.removeLogIn();
           }
-        },
+        } else {
+          await this.removeLogIn();
+        }
+      }
+    },
+    async removeLogIn() {
+      removeLocalToken();
+      this.setToken("");
+      this.setLoggedIn(false);
+    },
+    async logOut() {
+      await this.removeLogIn();
+      this.routeLogOut();
+    },
+    async userLogOut() {
+      await this.logOut();
+      this.addNotification({ content: "Logged out", color: "success" });
+    },
+    routeLogOut() {
+      if (router.currentRoute.value.path !== "/login") {
+        router.push("/login");
+      }
+    },
+    async checkApiError(payload: unknown) {
+      if (axios.isAxiosError(payload)) {
+        if (payload.response?.status === 401) {
+          await this.logOut();
+        }
+      }
+    },
+    routeLoggedIn() {
+      if (router.currentRoute.value.path === "/login" || router.currentRoute.value.path === "/") {
+        router.push("/main/dashboard");
+      }
+    },
+    async dispatchRemoveNotification(payload: { notification: AppNotification; timeout: number },) {
+      return new Promise((resolve, _) => {
+        setTimeout(() => {
+          this.removeNotification(payload.notification);
+          resolve(true);
+        }, payload.timeout);
+      });
+    },
+    async register(payload: IUserProfileCreate) {
+      const loadingNotification = {
+        content: i18n.global.t("sginingUp"),
+        showProgress: true,
+      };
+      try {
+        this.addNotification(loadingNotification);
+        const response = (
+          await Promise.all([
+            api.registerUser(payload),
+            await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
+          ])
+        )[0];
+        this.removeNotification(loadingNotification);
+        this.addNotification({
+          content: i18n.global.t("successfully"),
+          color: "success",
+        });
+        setTimeout(() => {
+          router.push("/login")
+        }, 2000)
+      } catch (error) {
+        await this.checkApiError(error);
+      }
+    },
+    async passwordRecovery(username: string ) {
+        const loadingNotification = {
+            content: "Sending password recovery email",
+            showProgress: true,
+        };
+        try {
+            this.addNotification(loadingNotification);
+            await Promise.all([
+              api.passwordRecovery(username),
+              await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
+            ]);
+            this.removeNotification(loadingNotification);
+            this.addNotification({
+              content: "Password recovery email sent",
+              color: "success",
+            });
+            await this.logOut();
+        } catch (error) {
+            this.removeNotification(loadingNotification);
+            this.addNotification({ color: "error", content: "Incorrect username" });
+        }
+    },
+    async resetPassword(password: string, token: string) {
+        const loadingNotification = { content: "Resetting password", showProgress: true };
+        try {
+            this.addNotification(loadingNotification);
+            await Promise.all([
+                api.resetPassword(token, password),
+                await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
+            ]);
+            this.removeNotification(loadingNotification);
+            this.addNotification( {
+                content: "Password successfully reset",
+                color: "success",
+            });
+            await this.logOut();
+        } catch (error) {
+            this.removeNotification(loadingNotification);
+            this.addNotification({
+                color: "error",
+                content: "Error resetting password",
+            });
+        }
+    },
+    async uploadPlot(title: string, file: File) {
+      const mainStore = useMainStore();
+      try {
+        const loadingNotification = { content: "sending", showProgress: true };
+        mainStore.addNotification(loadingNotification);
+        const response = (
+          await Promise.all([
+            api.uploadPlot(mainStore.token, title, file),
+            await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 500)),
+          ])
+        );
+        mainStore.removeNotification(loadingNotification);
+        mainStore.addNotification({
+          content: "File received",
+            color: "success",
+        })
+      } catch (error) {
+        await mainStore.checkApiError(error);
+      }
+    },
+    async actionGetVideos() {
+      const mainStore = useMainStore();
+      try {
+        const response = await api.getVideos(mainStore.token)
+        if (response) {
+          this.setVideos(response.data);
+        }
+      } catch (error) {
+        await mainStore.checkApiError(error);
+      }
+    },
     }
-  });
+});
 
 

+ 5 - 5
frontend/src/views/Login.vue

@@ -86,11 +86,11 @@ onMounted(() => {});
           ></v-text-field>
 
           <p class="text-center">
-            還沒有帳號?
-            <router-link to="/signup">註冊</router-link> / <router-link
-              to="/recover-password"
-              >忘記密碼</router-link
-            >
+            {{ t("haventAccount") }}
+            <router-link to="/signup">{{ t("register") }}</router-link> /
+            <router-link to="/recover-password">{{
+              t("forgotPsd")
+            }}</router-link>
           </p>
 
           <v-btn

+ 1 - 1
frontend/src/views/Signup.vue

@@ -78,7 +78,7 @@ async function submit() {
           <v-text-field
             v-model="data.full_name"
             :rules="[(v) => !!v || '請輸入您的帳號']"
-            label="使用者名稱"
+            :label="$t('userName')"
             required
           ></v-text-field>
 

+ 5 - 0
frontend/vite.config.ts

@@ -16,4 +16,9 @@ export default defineConfig({
     host: "0.0.0.0",
     port: 8080,
   },
+  define: {
+    __VUE_I18N_FULL_INSTALL__: true,
+    __VUE_I18N_LEGACY_API__: false,
+    __INTLIFY_PROD_DEVTOOLS__: false,
+  },
 });