Upload project.
This commit is contained in:
41
Aya-Frontend/.gitignore
vendored
Normal file
41
Aya-Frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
*.swp
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
tmp
|
||||
|
||||
# Build
|
||||
public/css/main.css
|
||||
|
||||
# Coverage reports
|
||||
coverage
|
||||
|
||||
# API keys and secrets
|
||||
.env
|
||||
|
||||
# Dependency directory
|
||||
node_modules
|
||||
bower_components
|
||||
|
||||
# Editors
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# OS metadata
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Ignore built ts files
|
||||
dist/**/*
|
||||
|
||||
# ignore yarn.lock
|
||||
yarn.lock
|
||||
2
Aya-Frontend/README.md
Normal file
2
Aya-Frontend/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Aya-Frontend
|
||||
The main frontend to the Aya language learning webapp, using the VueJS framework.
|
||||
3
Aya-Frontend/babel.config.js
Normal file
3
Aya-Frontend/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"]
|
||||
};
|
||||
15245
Aya-Frontend/package-lock.json
generated
Normal file
15245
Aya-Frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
88
Aya-Frontend/package.json
Normal file
88
Aya-Frontend/package.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "aya-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||
"@types/showdown": "^1.9.3",
|
||||
"@types/simplemde": "^1.11.7",
|
||||
"axios": "^0.19.2",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.12.0",
|
||||
"core-js": "^3.6.4",
|
||||
"easymde": "^2.10.1",
|
||||
"showdown": "^1.9.1",
|
||||
"simplemde": "^1.11.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-js-modal": "^2.0.0-rc.3",
|
||||
"vue-property-decorator": "^8.4.1",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-simplemde": "^1.0.4",
|
||||
"vuex": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.19",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
||||
"@vue/cli-plugin-router": "~4.3.0",
|
||||
"@vue/cli-plugin-typescript": "~4.3.0",
|
||||
"@vue/cli-plugin-unit-jest": "~4.3.0",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"@vue/test-utils": "1.0.0-beta.31",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "~3.8.3",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/__tests__/*.{j,t}s?(x)",
|
||||
"**/tests/unit/**/*.spec.{j,t}s?(x)"
|
||||
],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
],
|
||||
"jest": {
|
||||
"preset": "@vue/cli-plugin-unit-jest/presets/typescript-and-babel"
|
||||
}
|
||||
}
|
||||
BIN
Aya-Frontend/public/favicon.ico
Normal file
BIN
Aya-Frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
17
Aya-Frontend/public/index.html
Normal file
17
Aya-Frontend/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
34
Aya-Frontend/src/App.vue
Normal file
34
Aya-Frontend/src/App.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div id="nav">
|
||||
<HeaderPanel />
|
||||
</div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import HeaderPanel from "@/components/HeaderPanel.vue";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
HeaderPanel
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: left;
|
||||
color: #2c3e50;
|
||||
}
|
||||
html, body
|
||||
{
|
||||
height: 100%; overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
BIN
Aya-Frontend/src/assets/Aya_JP_With_Line.png
Normal file
BIN
Aya-Frontend/src/assets/Aya_JP_With_Line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 786 KiB |
1
Aya-Frontend/src/assets/book-open-solid.svg
Normal file
1
Aya-Frontend/src/assets/book-open-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book-open" class="svg-inline--fa fa-book-open fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M542.22 32.05c-54.8 3.11-163.72 14.43-230.96 55.59-4.64 2.84-7.27 7.89-7.27 13.17v363.87c0 11.55 12.63 18.85 23.28 13.49 69.18-34.82 169.23-44.32 218.7-46.92 16.89-.89 30.02-14.43 30.02-30.66V62.75c.01-17.71-15.35-31.74-33.77-30.7zM264.73 87.64C197.5 46.48 88.58 35.17 33.78 32.05 15.36 31.01 0 45.04 0 62.75V400.6c0 16.24 13.13 29.78 30.02 30.66 49.49 2.6 149.59 12.11 218.77 46.95 10.62 5.35 23.21-1.94 23.21-13.46V100.63c0-5.29-2.62-10.14-7.27-12.99z"></path></svg>
|
||||
|
After Width: | Height: | Size: 691 B |
1
Aya-Frontend/src/assets/book-solid.svg
Normal file
1
Aya-Frontend/src/assets/book-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book" class="svg-inline--fa fa-book fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"></path></svg>
|
||||
|
After Width: | Height: | Size: 666 B |
BIN
Aya-Frontend/src/assets/logo.png
Normal file
BIN
Aya-Frontend/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
1
Aya-Frontend/src/assets/plus-square-solid.svg
Normal file
1
Aya-Frontend/src/assets/plus-square-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plus-square" class="svg-inline--fa fa-plus-square fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-32 252c0 6.6-5.4 12-12 12h-92v92c0 6.6-5.4 12-12 12h-56c-6.6 0-12-5.4-12-12v-92H92c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h92v-92c0-6.6 5.4-12 12-12h56c6.6 0 12 5.4 12 12v92h92c6.6 0 12 5.4 12 12v56z"></path></svg>
|
||||
|
After Width: | Height: | Size: 551 B |
97
Aya-Frontend/src/components/AccountDialog.vue
Normal file
97
Aya-Frontend/src/components/AccountDialog.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import Workpage from "../types/Workpage";
|
||||
import API from '../data/API';
|
||||
import UserAPI from "../data/UserAPI"
|
||||
import User from '../types/User';
|
||||
|
||||
@Component
|
||||
export default class WorkpageExplorer extends Vue {
|
||||
|
||||
@Prop() api!: API;
|
||||
|
||||
//Watch for a login request, if a request is made (value is set to true), begin login procedure.
|
||||
@Watch('$store.state.loginRequested')
|
||||
onLoginRequest(newVal: any, oldVal: any)
|
||||
{
|
||||
if (newVal == true)
|
||||
this.loginDialog()
|
||||
}
|
||||
|
||||
//Check whether a token exists in localstorage, if it does then login with that token.
|
||||
created()
|
||||
{
|
||||
document.title = "Aya";
|
||||
if (localStorage.token == null || localStorage.token == "")
|
||||
this.loginDialog();
|
||||
else
|
||||
this.loginWithToken();
|
||||
}
|
||||
|
||||
//Show login dialog.
|
||||
loginDialog()
|
||||
{
|
||||
this.$store.state.loginRequested = false;
|
||||
this.$modal.show("dialog", {
|
||||
title: "Please Login",
|
||||
text: "Username: <input type=\"text\" id=\"username\"><br>" +
|
||||
"Password: <input type=\"password\" id=\"password\">",
|
||||
buttons: [
|
||||
{
|
||||
title: "Create",
|
||||
default: false,
|
||||
handler: () => { this.createAccount((document.getElementById("username") as HTMLInputElement).value, (document.getElementById("password") as HTMLInputElement).value); this.$modal.hide("dialog"); }
|
||||
},
|
||||
{
|
||||
title: "Login",
|
||||
default: true,
|
||||
handler: () => { this.login((document.getElementById("username") as HTMLInputElement).value, (document.getElementById("password") as HTMLInputElement).value); this.$modal.hide("dialog"); }
|
||||
}
|
||||
]
|
||||
},{ clickToClose: false });
|
||||
}
|
||||
|
||||
//Send request to API to login (returning user object with token).
|
||||
async login(username: string, password: string)
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const uAPI = new UserAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await uAPI.login(username, password);
|
||||
this.$store.state.currentUser = new User(response.data.id, response.data.username, response.data.token);
|
||||
localStorage.token = response.data.token;
|
||||
this.$store.state.authed = true;
|
||||
}
|
||||
|
||||
//Send request to API to create a new account (returns the new user with the token).
|
||||
async createAccount(username: string, password: string)
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const uAPI = new UserAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await uAPI.create(username, password);
|
||||
console.log(response);
|
||||
this.$store.state.currentUser = new User(response.data.id, response.data.username, localStorage.token);
|
||||
this.$store.state.authed = true;
|
||||
}
|
||||
|
||||
//Send request to API to login with a given token.
|
||||
async loginWithToken()
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const uAPI = new UserAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await uAPI.getUser(localStorage.token);
|
||||
this.$store.state.currentUser = new User(response.data.id, response.data.username, localStorage.token);
|
||||
this.$store.state.authed = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
92
Aya-Frontend/src/components/HeaderPanel.vue
Normal file
92
Aya-Frontend/src/components/HeaderPanel.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="topnav">
|
||||
<img class="logo" src="../assets/Aya_JP_With_Line.png" width="84" height="49">
|
||||
<a class="username" v-if="logString == 'Logout'" href="#">Logged in as: {{ $store.state.currentUser.Username }}</a>
|
||||
<a class="Log-Link" href="#" @click="handleButton()"> {{ logString }} </a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
//Formatting in this script is odd for some reason, blame VSCode.
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import Workpage from "../types/Workpage";
|
||||
import API from '../data/API';
|
||||
import UserAPI from "../data/UserAPI"
|
||||
import User from '../types/User';
|
||||
|
||||
@Component
|
||||
export default class WorkpageExplorer extends Vue
|
||||
{
|
||||
@Prop() private logString = "Login";
|
||||
|
||||
//Check if authed has changed, and change the text based on whether authed or not.
|
||||
@Watch('$store.state.authed')
|
||||
onAuthChange(newVal: any, oldVal: any)
|
||||
{
|
||||
if (newVal == true)
|
||||
this.logString = "Logout";
|
||||
else
|
||||
this.logString = "Login"
|
||||
}
|
||||
|
||||
//Decides whether to request a login or logout, depending on the current state.
|
||||
handleButton()
|
||||
{
|
||||
if (this.logString == "Login")
|
||||
this.requestLogin();
|
||||
else
|
||||
{
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
|
||||
//Sets the global loginRequested state to be true, saying that the login prompt should open.
|
||||
requestLogin()
|
||||
{
|
||||
this.$store.state.loginRequested = true;
|
||||
}
|
||||
|
||||
//Removes the token from the local storage and sets global authed to false. (Simple logout technique).
|
||||
logout()
|
||||
{
|
||||
this.$store.state.authed = false;
|
||||
localStorage.token = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.topnav {
|
||||
background-color: #333;
|
||||
overflow: hidden;
|
||||
}
|
||||
.topnav a {
|
||||
float: left;
|
||||
color: #f2f2f2;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
font-size: 17px;
|
||||
}
|
||||
.topnav a:hover {
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
}
|
||||
.logo {
|
||||
float: left;
|
||||
color: #f2f2f2;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
font-size: 17px;
|
||||
}
|
||||
.Log-Link
|
||||
{
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
}
|
||||
.username
|
||||
{
|
||||
position: absolute;
|
||||
right: 100px;
|
||||
}
|
||||
</style>
|
||||
138
Aya-Frontend/src/components/WorkbookSidebar.vue
Normal file
138
Aya-Frontend/src/components/WorkbookSidebar.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="workbookList">
|
||||
<li v-for="workbook in workbookArray" :key="workbook.id">
|
||||
<img @click="changeActiveWorkbook(workbook.id)" :src="getBookImage(workbook.id)" width="50" height="50">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="addWorkbook">
|
||||
<img @click="addWorkbookDialog()" src="../assets/plus-square-solid.svg" width="20" height="20"> Add Workbook
|
||||
</div>
|
||||
|
||||
<v-dialog />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import Workbook from "../types/Workbook";
|
||||
import API from '../data/API';
|
||||
import WorkbookAPI from "../data/WorkbookAPI";
|
||||
import { findByKey } from "../utils/collections";
|
||||
|
||||
@Component
|
||||
export default class WorkbookSidebar extends Vue {
|
||||
|
||||
//Local workbook variable, these are from the API.
|
||||
@Prop() private workbookArray!: Workbook[];
|
||||
|
||||
@Prop() private api!: API;
|
||||
|
||||
//When dynamic images are done in this way, they must be required by the compiler.
|
||||
@Prop() private readonly closedBookSolidImage = require('../assets/book-solid.svg');
|
||||
@Prop() private readonly openBookSolidImage = require('../assets/book-open-solid.svg');
|
||||
|
||||
//When the user is authed, get all workbooks from that user from the api and assign them to the local variable.
|
||||
//When the user is not authed, empty the array.
|
||||
@Watch('$store.state.authed')
|
||||
onAuthed(newVal: any, oldVal: any)
|
||||
{
|
||||
if (newVal == true)
|
||||
this.getAllUserWorkbooksFromAPI();
|
||||
else
|
||||
this.emptyWorkbookArray();
|
||||
}
|
||||
|
||||
//Assign the response from the api to the local workbook variable.
|
||||
private async getAllUserWorkbooksFromAPI()
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const wbAPI = new WorkbookAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await wbAPI.getAllUserWorkbooks(localStorage.token);
|
||||
this.workbookArray = response.data;
|
||||
}
|
||||
|
||||
//Get the book image (open/closed) for the specified ID.
|
||||
//(If the current workbook is the workbook ID passed, the open book image will be used, otherwise the closed image will be used).
|
||||
private getBookImage(id: string): any
|
||||
{
|
||||
if (this.$store.state.workbookActive == id)
|
||||
return this.openBookSolidImage;
|
||||
return this.closedBookSolidImage;
|
||||
};
|
||||
|
||||
//Change the store's active workbook to the ID passed.
|
||||
private changeActiveWorkbook(id: string)
|
||||
{
|
||||
this.$store.commit("changeActiveWorkbook", id);
|
||||
}
|
||||
|
||||
//Show the dialog for adding a new workbook.
|
||||
private addWorkbookDialog()
|
||||
{
|
||||
if (this.$store.state.authed == false)
|
||||
return;
|
||||
|
||||
this.$modal.show("dialog", {
|
||||
title: "Add Workbook",
|
||||
text: "Please input the name of the new Workbook: <input id=\"new-workbook-name\" type=\"text\" style=\"width: 370px\">",
|
||||
buttons: [
|
||||
{
|
||||
title: "Cancel",
|
||||
hander: () => { this.$modal.hide("dialog") }
|
||||
},
|
||||
{
|
||||
title: "Create",
|
||||
default: true,
|
||||
handler: () => { this.addWorkbook((document.getElementById("new-workbook-name") as HTMLInputElement).value); this.$modal.hide("dialog"); }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
//Adds a workbook by posting to the API. Then, retrieves all of the workbooks again to update the local version.
|
||||
private async addWorkbook(name: string)
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const wbAPI = new WorkbookAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await wbAPI.addWorkbook(name, localStorage.token);
|
||||
await this.getAllUserWorkbooksFromAPI();
|
||||
}
|
||||
|
||||
//Empties the local workbook array.
|
||||
private emptyWorkbookArray()
|
||||
{
|
||||
this.workbookArray = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
body
|
||||
{
|
||||
background-color: darkslategray;
|
||||
}
|
||||
.addWorkbook
|
||||
{
|
||||
position : fixed;
|
||||
bottom : 1vh;
|
||||
cursor: pointer;
|
||||
}
|
||||
.workbookList
|
||||
{
|
||||
position : fixed;
|
||||
left: 0px;
|
||||
overflow : auto;
|
||||
height : 90vh;
|
||||
list-style-type : none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.workbookList ul, .workbookList li
|
||||
{
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
</style>
|
||||
109
Aya-Frontend/src/components/WorkpageEditor.vue
Normal file
109
Aya-Frontend/src/components/WorkpageEditor.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="editor">
|
||||
<h1>{{ workpageTitle }}</h1>
|
||||
<vue-simplemde ref="markdownEditor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch, Ref } from "vue-property-decorator";
|
||||
import VueSimplemde from 'vue-simplemde';
|
||||
import API from "../data/API";
|
||||
import WorkpageAPI from "../data/WorkpageAPI";
|
||||
import AutoSave from "../utils/autosave";
|
||||
|
||||
@Component
|
||||
export default class WorkpageEditor extends Vue {
|
||||
|
||||
//Local variables for editor.
|
||||
@Prop() private workpageTitle!: string;
|
||||
@Prop() private workpageContent!: string;
|
||||
|
||||
@Prop() private api!: API;
|
||||
|
||||
@Prop() private autoSave!: any;
|
||||
|
||||
//Reference to the markdown editor.
|
||||
@Ref("markdownEditor") readonly mde!: any;
|
||||
|
||||
//Watch for authed changing, if the user gets unauthed, then reset/empty all values.
|
||||
@Watch('$store.state.authed')
|
||||
onAuthChange(newVal: any, oldVal: any)
|
||||
{
|
||||
if (newVal == false)
|
||||
{
|
||||
this.workpageContent = "";
|
||||
this.updateEditorText();
|
||||
this.workpageTitle = "";
|
||||
}
|
||||
}
|
||||
|
||||
//Watch for the active workpage changing, as this is when the text changes need to be applied.
|
||||
@Watch('$store.state.workpageActive')
|
||||
onWorkPageChange(newVal: any, oldVal: any)
|
||||
{
|
||||
this.getWorkpage(newVal);
|
||||
}
|
||||
|
||||
//Create a loop for the autosave function. Creates the object on created (5 seconds per post).
|
||||
created()
|
||||
{
|
||||
this.autoSave = new AutoSave(() => this.updateWorkpage(), 5000);
|
||||
}
|
||||
|
||||
//Creates a hook for when a change is made (for example, text changes), create the timer. If another change is made within the 5 seconds, restart the timer.
|
||||
//This means the save only starts after 5 seconds of inactivity after a change.
|
||||
mounted()
|
||||
{
|
||||
const self = this.startAutosaveTimer;
|
||||
this.mde.simplemde.codemirror.on("change", function(){
|
||||
self();
|
||||
});
|
||||
}
|
||||
|
||||
//Gets a workpage from the api with the specified ID.
|
||||
private async getWorkpage(id: string)
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const wpAPI = new WorkpageAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await wpAPI.getWorkpage(id, localStorage.token);
|
||||
this.workpageTitle = response.data.name;
|
||||
this.workpageContent = response.data.content;
|
||||
this.updateEditorText();
|
||||
}
|
||||
|
||||
//Updates the Editor's text with the workpageContent variable.
|
||||
private updateEditorText()
|
||||
{
|
||||
this.mde.simplemde.value(this.workpageContent);
|
||||
}
|
||||
|
||||
//Updates a workpage to the API based on the content in the editor and the active workpage.
|
||||
private async updateWorkpage()
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const wpAPI = new WorkpageAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await wpAPI.updateWorkpage(this.$store.state.workpageActive, this.mde.simplemde.value(), localStorage.token);
|
||||
}
|
||||
|
||||
//Starts the autosave timer.
|
||||
private startAutosaveTimer()
|
||||
{
|
||||
clearInterval(this.autoSave);
|
||||
this.autoSave = setInterval(this.updateWorkpage, 5000);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '~simplemde/dist/simplemde.min.css';
|
||||
.editor
|
||||
{
|
||||
height: 90vh;
|
||||
}
|
||||
</style>
|
||||
115
Aya-Frontend/src/components/WorkpageExplorer.vue
Normal file
115
Aya-Frontend/src/components/WorkpageExplorer.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="workpageList">
|
||||
<ul style="list-style-type : none;">
|
||||
<li v-for="workpage in workpages" :key="workpage.id">
|
||||
<a href="#" @click="changeSelectedWorkpage(workpage.id)">{{ workpage.name }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="addWorkpage">
|
||||
<img @click="addWorkpageDialog()" src="../assets/plus-square-solid.svg" width="20" height="20"> Add Workpage
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
import Workpage from "../types/Workpage";
|
||||
import API from '../data/API';
|
||||
import WorkpageAPI from "../data/WorkpageAPI"
|
||||
|
||||
@Component
|
||||
export default class WorkpageExplorer extends Vue {
|
||||
|
||||
//The local variable for the workpages displayed.
|
||||
@Prop() private workpages!: Workpage[];
|
||||
|
||||
@Prop() private api!: API;
|
||||
|
||||
//Checks of the workbook changes. When the workbook changes, it gets all pages from that workbook.
|
||||
@Watch('$store.state.workbookActive')
|
||||
onWorkbookChange(newVal: any, oldVal: any)
|
||||
{
|
||||
this.getAllWorkpagesFromWorkbook(newVal);
|
||||
}
|
||||
|
||||
//Checks if the auth gets changed. If the user gets unauthed, all workpages should be removed from the view.
|
||||
@Watch('$store.state.authed')
|
||||
onAuthChange(newVal: any, oldVal: any)
|
||||
{
|
||||
if (newVal == false)
|
||||
{
|
||||
this.workpages = [];
|
||||
}
|
||||
}
|
||||
|
||||
//Gets all of the workpages from the workbook id passed.
|
||||
private async getAllWorkpagesFromWorkbook(id: string)
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const wpAPI = new WorkpageAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await wpAPI.getAllWorkpagesFromWorkbook(id, localStorage.token);
|
||||
this.workpages = response.data;
|
||||
}
|
||||
|
||||
//Commits the new selected workpage to the store.
|
||||
private changeSelectedWorkpage(id: string)
|
||||
{
|
||||
this.$store.commit("changeActiveWorkpage", id);
|
||||
}
|
||||
|
||||
//Shows the dialog to add a workpage.
|
||||
private addWorkpageDialog()
|
||||
{
|
||||
this.$modal.show("dialog", {
|
||||
title: "Add Workpage",
|
||||
text: "Please input the name of the new Workpage: <input id=\"new-workpage-name\" type=\"text\" style=\"width: 370px\">",
|
||||
buttons: [
|
||||
{
|
||||
title: "Cancel",
|
||||
hander: () => { this.$modal.hide("dialog") }
|
||||
},
|
||||
{
|
||||
title: "Create",
|
||||
default: true,
|
||||
handler: () => { this.addWorkpage((document.getElementById("new-workpage-name") as HTMLInputElement).value); this.$modal.hide("dialog"); }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
//Adds a workpage to the API with the given name.
|
||||
private async addWorkpage(name: string)
|
||||
{
|
||||
if (this.api == undefined)
|
||||
return;
|
||||
|
||||
const activeWB = this.$store.state.workbookActive;
|
||||
|
||||
const wpAPI = new WorkpageAPI(this.api.axios, this.api.rootURL);
|
||||
const response = await wpAPI.addWorkpage(name, activeWB, localStorage.token);
|
||||
await this.getAllWorkpagesFromWorkbook(activeWB);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.workpageList
|
||||
{
|
||||
text-align: left;
|
||||
white-space : nowrap;
|
||||
overflow: auto;
|
||||
list-style-type : none;
|
||||
}
|
||||
.addWorkpage
|
||||
{
|
||||
position : fixed;
|
||||
bottom : 1vh;
|
||||
cursor: pointer;
|
||||
}
|
||||
.workpageList ul, .workpageList li
|
||||
{
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
</style>
|
||||
47
Aya-Frontend/src/data/API.ts
Normal file
47
Aya-Frontend/src/data/API.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export default class API
|
||||
{
|
||||
readonly rootURL: string;
|
||||
readonly axios: any;
|
||||
|
||||
constructor(ax: any, root: string)
|
||||
{
|
||||
this.axios = ax;
|
||||
this.rootURL = root;
|
||||
}
|
||||
|
||||
async post(path: string, bodyContent: string)
|
||||
{
|
||||
const res = await this.axios.post(this.rootURL + path, bodyContent, { headers: { "Content-Type": "application/json" }});
|
||||
return res;
|
||||
}
|
||||
|
||||
async postAuthed(path: string, token: string)
|
||||
{
|
||||
const res = await this.axios.post(this.rootURL + path, "", { headers: { "Authorization": "Bearer " + token }});
|
||||
return res;
|
||||
}
|
||||
|
||||
async postAuthedWithBody(path: string, content: string, token: string)
|
||||
{
|
||||
const res = await this.axios.post(this.rootURL + path, content, { headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" }, });
|
||||
return res;
|
||||
}
|
||||
|
||||
async postWithHeaders(path: string, bodyContent: string, options: any)
|
||||
{
|
||||
const res = await this.axios.post(this.rootURL + path, bodyContent, options);
|
||||
return res;
|
||||
}
|
||||
|
||||
async get(path: string)
|
||||
{
|
||||
const res = await this.axios.get(this.rootURL + path);
|
||||
return res;
|
||||
}
|
||||
|
||||
async getAuthed(path: string, token: string)
|
||||
{
|
||||
const res = await this.axios.get(this.rootURL + path, { headers: { "Authorization": "Bearer " + token }});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
26
Aya-Frontend/src/data/UserAPI.ts
Normal file
26
Aya-Frontend/src/data/UserAPI.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import API from "./API";
|
||||
|
||||
export default class UserAPI extends API
|
||||
{
|
||||
private readonly apiPath = "api/user/";
|
||||
|
||||
constructor(ax: any, root: string)
|
||||
{
|
||||
super(ax, root);
|
||||
}
|
||||
|
||||
async login(username: string, password: string)
|
||||
{
|
||||
return await super.post(this.apiPath + "login", JSON.stringify({ Username: username, Password: password }));
|
||||
}
|
||||
|
||||
async getUser(token: string)
|
||||
{
|
||||
return await super.getAuthed(this.apiPath + "get", token);
|
||||
}
|
||||
|
||||
async create(username: string, password: string)
|
||||
{
|
||||
return await super.post(this.apiPath + "create", JSON.stringify({ Username: username, Password: password }));
|
||||
}
|
||||
}
|
||||
22
Aya-Frontend/src/data/WorkbookAPI.ts
Normal file
22
Aya-Frontend/src/data/WorkbookAPI.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import API from './API';
|
||||
|
||||
export default class WorkbookAPI extends API
|
||||
{
|
||||
private readonly apiPath = "api/workbook/";
|
||||
|
||||
constructor(ax: any, root: string)
|
||||
{
|
||||
super(ax, root);
|
||||
}
|
||||
|
||||
async getAllUserWorkbooks(token: string)
|
||||
{
|
||||
return await super.getAuthed(this.apiPath + "userworkbooks", token);
|
||||
}
|
||||
|
||||
async addWorkbook(name: string, token: string)
|
||||
{
|
||||
console.log(name + " " + token);
|
||||
return await super.postAuthed(this.apiPath + "add?name=" + name, token);
|
||||
}
|
||||
}
|
||||
31
Aya-Frontend/src/data/WorkpageAPI.ts
Normal file
31
Aya-Frontend/src/data/WorkpageAPI.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import API from './API';
|
||||
|
||||
export default class WorkpageAPI extends API
|
||||
{
|
||||
private readonly apiPath = "api/workpage/";
|
||||
|
||||
constructor(ax: any, root: string)
|
||||
{
|
||||
super(ax, root);
|
||||
}
|
||||
|
||||
async getAllWorkpagesFromWorkbook(id: string, token: string)
|
||||
{
|
||||
return await super.getAuthed(this.apiPath + "fromworkbook?id=" + id, token);
|
||||
}
|
||||
|
||||
async getWorkpage(id: string, token: string)
|
||||
{
|
||||
return await super.getAuthed(this.apiPath + "get?id=" + id, token)
|
||||
}
|
||||
|
||||
async addWorkpage(name: string, workbookid: number, token)
|
||||
{
|
||||
return await super.postAuthed(this.apiPath + "add?name=" + name + "&workbookid=" + workbookid, token);
|
||||
}
|
||||
|
||||
async updateWorkpage(id: string, content: string, token: string)
|
||||
{
|
||||
return await super.postAuthedWithBody(this.apiPath + "updateworkpagecontent?id=" + id, '"' + content + '"', token);
|
||||
}
|
||||
}
|
||||
30
Aya-Frontend/src/main.ts
Normal file
30
Aya-Frontend/src/main.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store/store";
|
||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue';
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faUserSecret } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import VueSimplemde from 'vue-simplemde';
|
||||
import 'simplemde/dist/simplemde.min.css';
|
||||
import VModal from 'vue-js-modal';
|
||||
|
||||
library.add(faUserSecret);
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
Vue.use(IconsPlugin);
|
||||
Vue.use(VModal, { dialog: true });
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
|
||||
Vue.component('vue-simplemde', VueSimplemde)
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
store,
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount("#app");
|
||||
20
Aya-Frontend/src/router/index.ts
Normal file
20
Aya-Frontend/src/router/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter, { RouteConfig } from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes: Array<RouteConfig> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home
|
||||
}];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
});
|
||||
|
||||
export default router;
|
||||
13
Aya-Frontend/src/shims-tsx.d.ts
vendored
Normal file
13
Aya-Frontend/src/shims-tsx.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import Vue, { VNode } from "vue";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {}
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Aya-Frontend/src/shims-vue.d.ts
vendored
Normal file
4
Aya-Frontend/src/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
}
|
||||
27
Aya-Frontend/src/store/store.ts
Normal file
27
Aya-Frontend/src/store/store.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import User from '@/types/User';
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
workbookActive: 0,
|
||||
workpageActive: 0,
|
||||
authed: false,
|
||||
loginRequested: false,
|
||||
currentUser: User
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
changeActiveWorkbook(state, payload)
|
||||
{
|
||||
state.workbookActive = payload;
|
||||
},
|
||||
changeActiveWorkpage(state, payload)
|
||||
{
|
||||
state.workpageActive = payload;
|
||||
}
|
||||
},
|
||||
actions: {}
|
||||
});
|
||||
13
Aya-Frontend/src/types/User.ts
Normal file
13
Aya-Frontend/src/types/User.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default class User
|
||||
{
|
||||
ID!: string;
|
||||
Username!: string;
|
||||
Token!: string;
|
||||
|
||||
constructor(id: string, username: string, token: string)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Username = username;
|
||||
this.Token = token;
|
||||
}
|
||||
}
|
||||
9
Aya-Frontend/src/types/Workbook.ts
Normal file
9
Aya-Frontend/src/types/Workbook.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import User from "../types/User";
|
||||
import Workpage from "../types/Workpage";
|
||||
|
||||
export default class Workbook
|
||||
{
|
||||
id!: string;
|
||||
name!: string;
|
||||
ownerID!: string;
|
||||
}
|
||||
14
Aya-Frontend/src/types/Workpage.ts
Normal file
14
Aya-Frontend/src/types/Workpage.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import showdown from "showdown";
|
||||
|
||||
export default class Workpage
|
||||
{
|
||||
WorkpageID!: string;
|
||||
WorkpageName!: string;
|
||||
WorkpageContent!: string;
|
||||
|
||||
GetWorkpageContentMarkdown(): string
|
||||
{
|
||||
const converter = new showdown.Converter;
|
||||
return converter.makeHtml(this.WorkpageContent);
|
||||
}
|
||||
}
|
||||
22
Aya-Frontend/src/utils/autosave.ts
Normal file
22
Aya-Frontend/src/utils/autosave.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export default class AutoSave
|
||||
{
|
||||
private currentTimeout!: any;
|
||||
private delayTime!: number;
|
||||
private func!: Function;
|
||||
|
||||
constructor(f: Function, time: number)
|
||||
{
|
||||
this.func = f;
|
||||
this.delayTime = time;
|
||||
}
|
||||
|
||||
public start(): void
|
||||
{
|
||||
this.currentTimeout = setTimeout(this.func, this.delayTime);
|
||||
}
|
||||
|
||||
public stop(): void
|
||||
{
|
||||
clearTimeout(this.currentTimeout);
|
||||
}
|
||||
}
|
||||
8
Aya-Frontend/src/utils/collections.ts
Normal file
8
Aya-Frontend/src/utils/collections.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
//Searches through the array and finds the value with the key, else returns null.
|
||||
export function findByKey(arr: Array<any>, value: any, key = "id"): any
|
||||
{
|
||||
for (const val of arr)
|
||||
if (val[key] == value)
|
||||
return val;
|
||||
return null;
|
||||
}
|
||||
70
Aya-Frontend/src/views/Home.vue
Normal file
70
Aya-Frontend/src/views/Home.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm WorkbookSidebar">
|
||||
<WorkbookSidebar :api="api" />
|
||||
</div>
|
||||
<div class="col-sm WorkpageExplorer">
|
||||
<WorkpageExplorer :api="api" />
|
||||
</div>
|
||||
<div class="col-sm WorkpageEditor">
|
||||
<WorkpageEditor :api="api" />
|
||||
</div>
|
||||
</div>
|
||||
<AccountDialog :api="api" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import WorkbookSidebar from "@/components/WorkbookSidebar.vue";
|
||||
import WorkpageExplorer from "@/components/WorkpageExplorer.vue";
|
||||
import WorkpageEditor from "@/components/WorkpageEditor.vue";
|
||||
import AccountDialog from "@/components/AccountDialog.vue";
|
||||
import API from "@/data/API";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
WorkbookSidebar,
|
||||
WorkpageExplorer,
|
||||
WorkpageEditor,
|
||||
AccountDialog
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
api: new API(require("axios"), "https://localhost:5001/")
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.WorkbookSidebar
|
||||
{
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
width: 150px;
|
||||
height: 90vh;
|
||||
resize : horizontal;
|
||||
background-color: lightslategray;
|
||||
}
|
||||
.WorkpageExplorer
|
||||
{
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
left: 125px;
|
||||
width: 300px;
|
||||
height: 90vh;
|
||||
resize : horizontal;
|
||||
background-color: rgb(183, 188, 200);
|
||||
}
|
||||
.WorkpageEditor
|
||||
{
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
height: 90vh;
|
||||
left: 425px;
|
||||
background-color: rgb(231, 231, 231);
|
||||
}
|
||||
</style>
|
||||
12
Aya-Frontend/tests/unit/example.spec.ts
Normal file
12
Aya-Frontend/tests/unit/example.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import HelloWorld from "@/components/HelloWorld.vue";
|
||||
|
||||
describe("HelloWorld.vue", () => {
|
||||
it("renders props.msg when passed", () => {
|
||||
const msg = "new message";
|
||||
const wrapper = shallowMount(HelloWorld, {
|
||||
propsData: { msg }
|
||||
});
|
||||
expect(wrapper.text()).toMatch(msg);
|
||||
});
|
||||
});
|
||||
40
Aya-Frontend/tsconfig.json
Normal file
40
Aya-Frontend/tsconfig.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": false,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"jest"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user