├── src ├── assets │ ├── logo.png │ └── logo.svg ├── services │ ├── api.js │ ├── stats.js │ ├── auth.js │ ├── balance.js │ ├── docs.js │ ├── exmas.js │ ├── questions.js │ ├── playground.js │ └── appointments.js ├── App.vue ├── styles │ └── settings.scss ├── utils │ └── fonthelper.js ├── main.js ├── plugins │ ├── webfontloader.js │ ├── index.js │ ├── vuetify.js │ └── auth.js ├── components │ ├── Utils │ │ ├── AppFooter.vue │ │ ├── LoadingSpinner.vue │ │ └── Login.vue │ ├── Dashboard │ │ ├── BalanceCard.vue │ │ ├── ExamsCard.vue │ │ ├── AppointmentCard.vue │ │ └── StatsCard.vue │ ├── Practice │ │ ├── VideoPlayer.vue │ │ └── PracticeQuestion.vue │ └── Exams │ │ ├── ExamResults.vue │ │ ├── QuestionContainer.vue │ │ └── ExamQuestion.vue ├── layouts │ └── default │ │ ├── View.vue │ │ ├── AppBar.vue │ │ └── Default.vue ├── views │ ├── Playground │ │ ├── IDQuestion.vue │ │ ├── SearchResults.vue │ │ ├── PlaygroundQuestion.vue │ │ └── EntranceActivity.vue │ ├── Practice.vue │ ├── Exam.vue │ ├── Documents.vue │ ├── NotFound.vue │ ├── Dashboard.vue │ ├── BalanceHistory.vue │ ├── AppointmentList.vue │ └── About.vue └── router │ └── index.js ├── public ├── apple-icon-180.png ├── apple-splash-1136-640.jpg ├── apple-splash-1334-750.jpg ├── apple-splash-1792-828.jpg ├── apple-splash-640-1136.jpg ├── apple-splash-750-1334.jpg ├── apple-splash-828-1792.jpg ├── apple-splash-1125-2436.jpg ├── apple-splash-1170-2532.jpg ├── apple-splash-1179-2556.jpg ├── apple-splash-1242-2208.jpg ├── apple-splash-1242-2688.jpg ├── apple-splash-1284-2778.jpg ├── apple-splash-1290-2796.jpg ├── apple-splash-1536-2048.jpg ├── apple-splash-1620-2160.jpg ├── apple-splash-1668-2224.jpg ├── apple-splash-1668-2388.jpg ├── apple-splash-2048-1536.jpg ├── apple-splash-2048-2732.jpg ├── apple-splash-2160-1620.jpg ├── apple-splash-2208-1242.jpg ├── apple-splash-2224-1668.jpg ├── apple-splash-2388-1668.jpg ├── apple-splash-2436-1125.jpg ├── apple-splash-2532-1170.jpg ├── apple-splash-2556-1179.jpg ├── apple-splash-2688-1242.jpg ├── apple-splash-2732-2048.jpg ├── apple-splash-2778-1284.jpg ├── apple-splash-2796-1290.jpg ├── manifest-icon-192.maskable.png ├── manifest-icon-512.maskable.png └── logo.svg ├── .gitignore ├── jsconfig.json ├── README.md ├── package.json ├── LICENSE ├── vite.config.js └── index.html /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/src/assets/logo.png -------------------------------------------------------------------------------- /public/apple-icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-icon-180.png -------------------------------------------------------------------------------- /public/apple-splash-1136-640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1136-640.jpg -------------------------------------------------------------------------------- /public/apple-splash-1334-750.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1334-750.jpg -------------------------------------------------------------------------------- /public/apple-splash-1792-828.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1792-828.jpg -------------------------------------------------------------------------------- /public/apple-splash-640-1136.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-640-1136.jpg -------------------------------------------------------------------------------- /public/apple-splash-750-1334.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-750-1334.jpg -------------------------------------------------------------------------------- /public/apple-splash-828-1792.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-828-1792.jpg -------------------------------------------------------------------------------- /public/apple-splash-1125-2436.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1125-2436.jpg -------------------------------------------------------------------------------- /public/apple-splash-1170-2532.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1170-2532.jpg -------------------------------------------------------------------------------- /public/apple-splash-1179-2556.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1179-2556.jpg -------------------------------------------------------------------------------- /public/apple-splash-1242-2208.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1242-2208.jpg -------------------------------------------------------------------------------- /public/apple-splash-1242-2688.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1242-2688.jpg -------------------------------------------------------------------------------- /public/apple-splash-1284-2778.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1284-2778.jpg -------------------------------------------------------------------------------- /public/apple-splash-1290-2796.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1290-2796.jpg -------------------------------------------------------------------------------- /public/apple-splash-1536-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1536-2048.jpg -------------------------------------------------------------------------------- /public/apple-splash-1620-2160.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1620-2160.jpg -------------------------------------------------------------------------------- /public/apple-splash-1668-2224.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1668-2224.jpg -------------------------------------------------------------------------------- /public/apple-splash-1668-2388.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-1668-2388.jpg -------------------------------------------------------------------------------- /public/apple-splash-2048-1536.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2048-1536.jpg -------------------------------------------------------------------------------- /public/apple-splash-2048-2732.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2048-2732.jpg -------------------------------------------------------------------------------- /public/apple-splash-2160-1620.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2160-1620.jpg -------------------------------------------------------------------------------- /public/apple-splash-2208-1242.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2208-1242.jpg -------------------------------------------------------------------------------- /public/apple-splash-2224-1668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2224-1668.jpg -------------------------------------------------------------------------------- /public/apple-splash-2388-1668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2388-1668.jpg -------------------------------------------------------------------------------- /public/apple-splash-2436-1125.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2436-1125.jpg -------------------------------------------------------------------------------- /public/apple-splash-2532-1170.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2532-1170.jpg -------------------------------------------------------------------------------- /public/apple-splash-2556-1179.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2556-1179.jpg -------------------------------------------------------------------------------- /public/apple-splash-2688-1242.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2688-1242.jpg -------------------------------------------------------------------------------- /public/apple-splash-2732-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2732-2048.jpg -------------------------------------------------------------------------------- /public/apple-splash-2778-1284.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2778-1284.jpg -------------------------------------------------------------------------------- /public/apple-splash-2796-1290.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/apple-splash-2796-1290.jpg -------------------------------------------------------------------------------- /public/manifest-icon-192.maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/manifest-icon-192.maskable.png -------------------------------------------------------------------------------- /public/manifest-icon-512.maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaratekHD/openbuzz/main/public/manifest-icon-512.maskable.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /dev-dist 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import axios from "axios"; 9 | 10 | export default (url = import.meta.env.VITE_API_BASE_URL) => { 11 | return axios.create({ 12 | baseURL: url 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /src/styles/settings.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | /** 9 | * src/styles/settings.scss 10 | * 11 | * Configures SASS variables and Vuetify overwrites 12 | */ 13 | 14 | // https://next.vuetifyjs.com/features/sass-variables/` 15 | // @use 'vuetify' with ( 16 | // $color-pack: false 17 | // ); 18 | -------------------------------------------------------------------------------- /src/utils/fonthelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import {useDisplay} from "vuetify"; 9 | 10 | export default { 11 | get_header_size() { 12 | const {mobile} = useDisplay() 13 | if (mobile._object.xs) { 14 | return "text-h4" 15 | 16 | } else { 17 | return "text-h2" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/services/stats.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | 10 | 11 | export default { 12 | answers(auth) { 13 | const route = '/student-question-answers/stats&learningModeId=1&studentEducationId=' + auth.student.education 14 | const config = { 15 | headers: {Authorization: `Bearer ${auth.token}`} 16 | 17 | } 18 | 19 | return api().get(route, config) 20 | } 21 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | /** 9 | * main.js 10 | * 11 | * Bootstraps Vuetify and other plugins then mounts the App` 12 | */ 13 | 14 | // Components 15 | import App from './App.vue' 16 | 17 | // Composables 18 | import { createApp } from 'vue' 19 | 20 | // Plugins 21 | import { registerPlugins } from '@/plugins' 22 | 23 | const app = createApp(App) 24 | 25 | registerPlugins(app) 26 | 27 | app.mount('#app') 28 | -------------------------------------------------------------------------------- /src/plugins/webfontloader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | /** 9 | * plugins/webfontloader.js 10 | * 11 | * webfontloader documentation: https://github.com/typekit/webfontloader 12 | */ 13 | 14 | export async function loadFonts () { 15 | const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader') 16 | 17 | webFontLoader.load({ 18 | google: { 19 | families: ['Roboto:100,300,400,500,700,900&display=swap'], 20 | }, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # base 2 | 3 | ## Project setup 4 | 5 | ``` 6 | # yarn 7 | yarn 8 | 9 | # npm 10 | npm install 11 | 12 | # pnpm 13 | pnpm install 14 | ``` 15 | 16 | ### Compiles and hot-reloads for development 17 | 18 | ``` 19 | # yarn 20 | yarn dev 21 | 22 | # npm 23 | npm run dev 24 | 25 | # pnpm 26 | pnpm dev 27 | ``` 28 | 29 | ### Compiles and minifies for production 30 | 31 | ``` 32 | # yarn 33 | yarn build 34 | 35 | # npm 36 | npm run build 37 | 38 | # pnpm 39 | pnpm build 40 | ``` 41 | 42 | ### Lints and fixes files 43 | 44 | ``` 45 | # yarn 46 | yarn lint 47 | 48 | # npm 49 | npm run lint 50 | 51 | # pnpm 52 | pnpm lint 53 | ``` 54 | 55 | ### Customize configuration 56 | 57 | See [Configuration Reference](https://vitejs.dev/config/). 58 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | /** 9 | * plugins/index.js 10 | * 11 | * Automatically included in `./src/main.js` 12 | */ 13 | 14 | // Plugins 15 | import {loadFonts} from './webfontloader' 16 | import vuetify from './vuetify' 17 | import router from '../router' 18 | import { plugin } from './auth' 19 | import cookies from 'vue-cookies'; 20 | 21 | export function registerPlugins(app) { 22 | loadFonts() 23 | app 24 | .use(vuetify) 25 | .use(router) 26 | .use(plugin) 27 | .use(cookies) 28 | } 29 | -------------------------------------------------------------------------------- /src/services/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | 10 | export default { 11 | login(username, password) { 12 | const payload = {"username": username, "password": password, "authClient": "DRIVE_BUZZ_WEB"} 13 | return api().post('/generate-token', payload) 14 | }, 15 | getEducation(token) { 16 | const config = { 17 | headers: {Authorization: `Bearer ${token}`} 18 | } 19 | const endpoint = "/student-educations/with-theory/student/current" 20 | return api().get(endpoint, config) 21 | } 22 | } -------------------------------------------------------------------------------- /src/services/balance.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | 10 | export default { 11 | getPayments(token) { 12 | const endpoint = '/education-events/student/current' 13 | const config = { 14 | headers: {Authorization: `Bearer ${token}`} 15 | } 16 | return api().get(endpoint, config) 17 | }, 18 | getSaldo(token, id) { 19 | const config = { 20 | headers: {Authorization: `Bearer ${token}`} 21 | } 22 | let endpoint = "/balance/student-education/" + id 23 | return api().get(endpoint, config) 24 | } 25 | } -------------------------------------------------------------------------------- /src/components/Utils/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | /** 9 | * plugins/vuetify.js 10 | * 11 | * Framework documentation: https://vuetifyjs.com` 12 | */ 13 | 14 | // Styles 15 | import '@mdi/font/css/materialdesignicons.css' 16 | import 'vuetify/styles' 17 | 18 | // Composables 19 | import { createVuetify } from 'vuetify' 20 | 21 | // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides 22 | export default createVuetify({ 23 | theme: { 24 | themes: { 25 | light: { 26 | colors: { 27 | primary: '#1867C0', 28 | secondary: '#5CBBF6', 29 | }, 30 | }, 31 | }, 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openbuzz", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "lint": "eslint . --fix --ignore-path .gitignore" 10 | }, 11 | "dependencies": { 12 | "@mdi/font": "7.0.96", 13 | "@videojs-player/vue": "^1.0.0", 14 | "axios": "^1.2.2", 15 | "core-js": "^3.8.3", 16 | "roboto-fontface": "*", 17 | "video.js": "^7.20.3", 18 | "vue": "^3.2.13", 19 | "vue-cookies": "^1.8.2", 20 | "vue-router": "^4.0.0", 21 | "vuetify": "^3.0.0", 22 | "webfontloader": "^1.0.0" 23 | }, 24 | "devDependencies": { 25 | "@vitejs/plugin-vue": "^3.0.3", 26 | "eslint": "^8.22.0", 27 | "eslint-plugin-vue": "^9.3.0", 28 | "sass": "^1.55.0", 29 | "vite": "^3.1.9", 30 | "vite-plugin-pwa": "^0.14.1", 31 | "vite-plugin-vuetify": "^1.0.0-alpha.12" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/layouts/default/View.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 39 | -------------------------------------------------------------------------------- /src/services/docs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | 10 | export default { 11 | async getDocuments(token) { 12 | if(token === undefined) { 13 | return [] 14 | } else { 15 | const endpoint = '/student-documents/student/current' 16 | const config = { 17 | headers: {Authorization: `Bearer ${token}`} 18 | } 19 | const response = await api().get(endpoint, config) 20 | return response.data 21 | } 22 | }, 23 | async getUnread(token) { 24 | const docs = await this.getDocuments(token) 25 | let unread = 0 26 | for (let i in docs) { 27 | if(!docs[i].seenByStudent) { 28 | unread++ 29 | } 30 | } 31 | return unread 32 | } 33 | } -------------------------------------------------------------------------------- /src/layouts/default/AppBar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /src/services/exmas.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | import {useAuth} from "@/plugins/auth"; 10 | 11 | export default { 12 | getExam() { 13 | const auth = useAuth() 14 | const route = '/theory-pre-exams&studentEducationId=' + auth.student.education 15 | const config = { 16 | headers: {Authorization: `Bearer ${auth.token}`} 17 | } 18 | return api().post(route, undefined, config) 19 | }, 20 | 21 | submitExam(auth, answers, id) { 22 | const config = { 23 | headers: {Authorization: `Bearer ${auth.token}`} 24 | } 25 | const route = '/theory-pre-exams/add-question-answers/' 26 | return api().post(route, {"theoryPreExamId": id, "questionAnswers": answers}, config) 27 | }, 28 | getHistory(auth) { 29 | const route = "/theory-pre-exams/student/current" 30 | const config = { 31 | headers: {Authorization: `Bearer ${auth.token}`} 32 | } 33 | return api().get(route, config) 34 | } 35 | } -------------------------------------------------------------------------------- /src/components/Utils/LoadingSpinner.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /src/services/questions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | import {useAuth} from "@/plugins/auth"; 10 | 11 | export default { 12 | next() { 13 | const auth = useAuth() 14 | const route = '/theory-questions/next&studentEducationId=' + auth.student.education 15 | const config = { 16 | headers: {Authorization: `Bearer ${auth.token}`} 17 | } 18 | return api().get(route, config) 19 | }, 20 | nextWithToken(auth) { 21 | const route = '/theory-questions/next&studentEducationId=' + auth.student.education 22 | const config = { 23 | headers: {Authorization: `Bearer ${auth.token}`} 24 | } 25 | return api().get(route, config) 26 | }, 27 | sendResult(auth, question, correct) { 28 | const route = '/student-question-answers' 29 | let payload = {"correct": correct, "studentEducationId": auth.student.education, "theoryQuestionId": question.id} 30 | if(question.withPicture) { 31 | payload["numberOfTimesPictureWasEnlarged"] = 0 32 | } 33 | const config = { 34 | headers: {Authorization: `Bearer ${auth.token}`} 35 | } 36 | api().post(route, payload, config) 37 | } 38 | } -------------------------------------------------------------------------------- /src/components/Dashboard/BalanceCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, KaratekHD 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /src/views/Playground/IDQuestion.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 48 | 49 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/Practice/VideoPlayer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 46 | 47 | -------------------------------------------------------------------------------- /src/services/playground.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | 10 | export default { 11 | get_categories(token) { 12 | const endpoint = "/question-groups" 13 | const config = { 14 | headers: {Authorization: `Bearer ${token}`} 15 | } 16 | return api().get(endpoint, config) 17 | }, 18 | get_questions_by_category(token, name) { 19 | const endpoint = "/question-groups/" + name + "/questions" 20 | const config = { 21 | headers: {Authorization: `Bearer ${token}`} 22 | } 23 | return api().get(endpoint, config) 24 | }, 25 | search_questions(mode, query, token) { 26 | const endpoint = "/theory-questions/student/filter" 27 | const config = { 28 | headers: {Authorization: `Bearer ${token}`} 29 | } 30 | let payload = {limit: 20, offset: 0} 31 | if(mode === 1) { 32 | // Freetext 33 | payload["freeText"] = query 34 | payload["officialNumber"] = "" 35 | } else { 36 | // Amtl. Nr. 37 | payload["officialNumber"] = query 38 | payload["freeText"] = "" 39 | } 40 | return api().post(endpoint, payload, config) 41 | }, 42 | get_question_by_id(id, student, token) { 43 | const endpoint = '/theory-questions/' + id + '&studentEducationId=' + student 44 | const config = { 45 | headers: {Authorization: `Bearer ${token}`} 46 | } 47 | return api().get(endpoint, config) 48 | } 49 | } -------------------------------------------------------------------------------- /src/views/Practice.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 33 | 35 | 36 | -------------------------------------------------------------------------------- /src/plugins/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import {inject} from 'vue' 9 | import authHelper from '@/services/auth' 10 | 11 | const injectionKey = Symbol('auth') 12 | 13 | export const useAuth = () => inject(injectionKey) 14 | export const plugin = { 15 | install(app) { 16 | let auth = { 17 | token: null, 18 | authorized: false, 19 | student: {"firstName": "", "lastName": "", "email": "", "id": ""}, 20 | raw: null, 21 | login: async function (username, password) { 22 | const res = await authHelper.login(username, password) 23 | const data = res.data 24 | this.token = data.token 25 | const educationResult = await authHelper.getEducation(this.token) 26 | const educationData = educationResult.data 27 | this.authorized = true 28 | this.student = { 29 | "firstName": data.student.firstName, 30 | "lastName": data.student.lastName, 31 | "email": data.student.learnAppEmail, 32 | "id": data.student.id, 33 | "education": educationData[0].id 34 | } 35 | this.raw = data 36 | }, 37 | loginCookie: async function (cookie) { 38 | console.log("Login with cookie!") 39 | const res = await authHelper.getEducation(cookie.token) 40 | if (res.status === 200) { 41 | this.student = cookie.student 42 | this.token = cookie.token 43 | this.authorized = true 44 | this.raw = cookie.raw 45 | return true 46 | } else { 47 | console.log("Login with cookie failed!") 48 | return false 49 | } 50 | 51 | } 52 | 53 | 54 | } 55 | 56 | app.provide(injectionKey, auth) 57 | app.config.globalProperties.$auth = auth 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/views/Exam.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | -------------------------------------------------------------------------------- /src/views/Playground/SearchResults.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 38 | 39 | 57 | 58 | -------------------------------------------------------------------------------- /src/views/Playground/PlaygroundQuestion.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | 29 | 60 | 61 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | // Plugins 2 | import vue from '@vitejs/plugin-vue' 3 | import vuetify, {transformAssetUrls} from 'vite-plugin-vuetify' 4 | 5 | // Utilities 6 | import {defineConfig} from 'vite' 7 | import {fileURLToPath, URL} from 'node:url' 8 | import {VitePWA} from "vite-plugin-pwa"; 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | plugins: [ 13 | vue({ 14 | template: {transformAssetUrls} 15 | }), 16 | VitePWA({ 17 | registerType: 'autoUpdate', 18 | devOptions: { 19 | enabled: true 20 | }, 21 | includeAssets: ['assets/*.svg'], 22 | manifest: { 23 | name: "OpenBuzz", 24 | short_name: "OpenBuzz", 25 | description: "Alternatives Frontend für drive.buzz Artemis", 26 | theme_color: '#ffffff', 27 | icons: [ 28 | { 29 | "src": "manifest-icon-192.maskable.png", 30 | "sizes": "192x192", 31 | "type": "image/png", 32 | "purpose": "any" 33 | }, 34 | { 35 | "src": "manifest-icon-192.maskable.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "purpose": "maskable" 39 | }, 40 | { 41 | "src": "manifest-icon-512.maskable.png", 42 | "sizes": "512x512", 43 | "type": "image/png", 44 | "purpose": "any" 45 | }, 46 | { 47 | "src": "manifest-icon-512.maskable.png", 48 | "sizes": "512x512", 49 | "type": "image/png", 50 | "purpose": "maskable" 51 | } 52 | ] 53 | } 54 | }), 55 | // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin 56 | vuetify({ 57 | autoImport: true, 58 | styles: { 59 | configFile: 'src/styles/settings.scss', 60 | }, 61 | }), 62 | ], 63 | define: {'process.env': {}}, 64 | resolve: { 65 | alias: { 66 | '@': fileURLToPath(new URL('./src', import.meta.url)) 67 | }, 68 | extensions: [ 69 | '.js', 70 | '.json', 71 | '.jsx', 72 | '.mjs', 73 | '.ts', 74 | '.tsx', 75 | '.vue', 76 | ], 77 | }, 78 | server: { 79 | port: 3000, 80 | }, 81 | }) 82 | -------------------------------------------------------------------------------- /src/views/Documents.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 42 | 43 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/Utils/Login.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 55 | 56 | 77 | -------------------------------------------------------------------------------- /src/components/Dashboard/ExamsCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 29 | 30 | 73 | 74 | -------------------------------------------------------------------------------- /src/services/appointments.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | import api from "@/services/api"; 9 | import balanceHelper from "@/services/balance"; 10 | 11 | function now () { const d = new Date(); d.setHours(0, 0, 0, 0); return d } 12 | 13 | export default { 14 | async getEvents(token) { 15 | const endpoint = "/appointments/student/current" 16 | const config = { 17 | headers: {Authorization: `Bearer ${token}`} 18 | } 19 | let temp = [] 20 | const response_appointments = await api().get(endpoint, config) 21 | const data_appointments = response_appointments.data 22 | temp.push.apply(temp, data_appointments) 23 | const response_balance = await balanceHelper.getPayments(token) 24 | const data_balance = response_balance.data 25 | for (const i in data_balance) { 26 | if (data_balance[i].educationEventType === "THEORY_EXAM") { 27 | temp.push({ 28 | appointmentType: "THEORY_EXAM", 29 | name: data_balance[i].description, 30 | date: data_balance[i].date, 31 | appointmentId: null, 32 | time: null, 33 | duration: null, 34 | instructorInitials: null, 35 | note: null, 36 | seen: null, 37 | signed: null, 38 | student: null, 39 | standardProductShortName: "TE", 40 | licenseClass: data_balance[i].licenseClass 41 | }) 42 | } 43 | } 44 | temp.sort(function (a, b) { 45 | const date1 = new Date(a.date) 46 | const date2 = new Date(b.date) 47 | return date1 - date2 48 | }) 49 | temp.reverse() 50 | let up = [] 51 | let done = [] 52 | var now = new Date(); 53 | now.setHours(0); 54 | now.setMinutes(0); 55 | now.setSeconds(0); 56 | now.setMilliseconds(0); 57 | for(const i in temp) { 58 | if(now.getTime() > (new Date(temp[i].date))) { 59 | done.push(temp[i]) 60 | } else { 61 | up.push(temp[i]) 62 | } 63 | 64 | } 65 | return {all: temp, up: up, done: done} 66 | } 67 | } -------------------------------------------------------------------------------- /src/components/Exams/ExamResults.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/Dashboard/AppointmentCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 54 | 55 | -------------------------------------------------------------------------------- /src/components/Dashboard/StatsCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 63 | 64 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/components/Exams/QuestionContainer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 51 | 52 | 108 | -------------------------------------------------------------------------------- /src/views/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 68 | 69 | 105 | 106 | 111 | -------------------------------------------------------------------------------- /src/views/BalanceHistory.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 63 | 64 | -------------------------------------------------------------------------------- /src/views/AppointmentList.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 90 | 91 | -------------------------------------------------------------------------------- /src/views/Playground/EntranceActivity.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 96 | 97 | 126 | 127 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | OpenBuzz 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /src/layouts/default/Default.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 119 | 120 | 189 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 KaratekHD. All rights reserverd. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * This project is not affiliated with drive.buzz or Fahrschulcockpit. 6 | */ 7 | 8 | // Composables 9 | import {createRouter, createWebHistory} from 'vue-router' 10 | 11 | const routes = [ 12 | { 13 | path: '/', 14 | component: () => import('@/layouts/default/Default.vue'), 15 | children: [ 16 | { 17 | path: '', 18 | name: 'Home', 19 | // route level code-splitting 20 | // this generates a separate chunk (about.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. 22 | component: () => import(/* webpackChunkName: "home" */ '@/views/Dashboard.vue'), 23 | } 24 | ], 25 | }, 26 | { 27 | path: '/practice', 28 | component: () => import('@/layouts/default/Default.vue'), 29 | children: [ 30 | { 31 | path: '', 32 | name: 'Übungsbogen', 33 | // route level code-splitting 34 | // this generates a separate chunk (about.[hash].js) for this route 35 | // which is lazy-loaded when the route is visited. 36 | component: () => import(/* webpackChunkName: "home" */ '@/views/Practice.vue'), 37 | }, 38 | ], 39 | }, 40 | { 41 | path: '/playground', 42 | component: () => import('@/layouts/default/Default.vue'), 43 | children: [ 44 | { 45 | path: '', 46 | name: 'Spielwiese', 47 | // route level code-splitting 48 | // this generates a separate chunk (about.[hash].js) for this route 49 | // which is lazy-loaded when the route is visited. 50 | component: () => import(/* webpackChunkName: "home" */ '@/views/Playground/EntranceActivity.vue'), 51 | }, 52 | ], 53 | }, 54 | { 55 | path: '/playground/category/:categoryName', 56 | component: () => import('@/layouts/default/Default.vue'), 57 | children: [ 58 | { 59 | path: '', 60 | name: 'Frage nach Kategorie', 61 | // route level code-splitting 62 | // this generates a separate chunk (about.[hash].js) for this route 63 | // which is lazy-loaded when the route is visited. 64 | component: () => import(/* webpackChunkName: "home" */ '@/views/Playground/PlaygroundQuestion.vue'), 65 | }, 66 | ], 67 | }, 68 | { 69 | path: '/playground/question/:questionID', 70 | component: () => import('@/layouts/default/Default.vue'), 71 | children: [ 72 | { 73 | path: '', 74 | name: 'Frage nach ID', 75 | // route level code-splitting 76 | // this generates a separate chunk (about.[hash].js) for this route 77 | // which is lazy-loaded when the route is visited. 78 | component: () => import(/* webpackChunkName: "home" */ '@/views/Playground/IDQuestion.vue'), 79 | }, 80 | ], 81 | }, 82 | { 83 | path: '/playground/search', 84 | component: () => import('@/layouts/default/Default.vue'), 85 | children: [ 86 | { 87 | path: '', 88 | name: 'Suchergebnisse', 89 | // route level code-splitting 90 | // this generates a separate chunk (about.[hash].js) for this route 91 | // which is lazy-loaded when the route is visited. 92 | component: () => import(/* webpackChunkName: "home" */ '@/views/Playground/SearchResults.vue'), 93 | }, 94 | ], 95 | }, 96 | { 97 | path: '/exam', 98 | component: () => import('@/layouts/default/Default.vue'), 99 | children: [ 100 | { 101 | path: '', 102 | name: 'Vorprüfungen', 103 | // route level code-splitting 104 | // this generates a separate chunk (about.[hash].js) for this route 105 | // which is lazy-loaded when the route is visited. 106 | component: () => import(/* webpackChunkName: "home" */ '@/views/Exam.vue'), 107 | }, 108 | ], 109 | }, 110 | { 111 | path: '/balance', 112 | component: () => import('@/layouts/default/Default.vue'), 113 | children: [ 114 | { 115 | path: '', 116 | name: 'Kontostand', 117 | // route level code-splitting 118 | // this generates a separate chunk (about.[hash].js) for this route 119 | // which is lazy-loaded when the route is visited. 120 | component: () => import(/* webpackChunkName: "home" */ '@/views/BalanceHistory.vue'), 121 | }, 122 | ], 123 | }, 124 | { 125 | path: '/appointments', 126 | component: () => import('@/layouts/default/Default.vue'), 127 | children: [ 128 | { 129 | path: '', 130 | name: 'Termine', 131 | // route level code-splitting 132 | // this generates a separate chunk (about.[hash].js) for this route 133 | // which is lazy-loaded when the route is visited. 134 | component: () => import(/* webpackChunkName: "home" */ '@/views/AppointmentList.vue'), 135 | }, 136 | ], 137 | }, 138 | { 139 | path: '/docs', 140 | component: () => import('@/layouts/default/Default.vue'), 141 | children: [ 142 | { 143 | path: '', 144 | name: 'Dokumente', 145 | // route level code-splitting 146 | // this generates a separate chunk (about.[hash].js) for this route 147 | // which is lazy-loaded when the route is visited. 148 | component: () => import(/* webpackChunkName: "home" */ '@/views/Documents.vue'), 149 | }, 150 | ], 151 | }, 152 | { 153 | path: '/about', 154 | component: () => import('@/layouts/default/Default.vue'), 155 | children: [ 156 | { 157 | path: '', 158 | name: 'Über OpenBuzz', 159 | // route level code-splitting 160 | // this generates a separate chunk (about.[hash].js) for this route 161 | // which is lazy-loaded when the route is visited. 162 | component: () => import(/* webpackChunkName: "home" */ '@/views/About.vue'), 163 | }, 164 | ], 165 | }, 166 | { 167 | path: '/:pathMatch(.*)*', 168 | component: () => import('@/layouts/default/Default.vue'), 169 | children: [ 170 | { 171 | path: '', 172 | name: 'NotFound', 173 | // route level code-splitting 174 | // this generates a separate chunk (about.[hash].js) for this route 175 | // which is lazy-loaded when the route is visited. 176 | component: () => import(/* webpackChunkName: "home" */ '@/views/NotFound.vue'), 177 | }, 178 | ], 179 | }, 180 | 181 | ] 182 | 183 | const router = createRouter({ 184 | history: createWebHistory(process.env.BASE_URL), 185 | routes, 186 | }) 187 | 188 | export default router 189 | -------------------------------------------------------------------------------- /src/components/Practice/PracticeQuestion.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 111 | 228 | 229 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 96 | 97 | 222 | 223 | -------------------------------------------------------------------------------- /src/components/Exams/ExamQuestion.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 102 | 257 | 258 | --------------------------------------------------------------------------------