diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..cbcf329a Binary files /dev/null and b/.DS_Store differ diff --git a/.angular-cli.json b/.angular-cli.json deleted file mode 100644 index efb433bb..00000000 --- a/.angular-cli.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "project": { - "name": "portfolio", - "ejected": false - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": [ - "assets" - ], - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "testTsconfig": "tsconfig.spec.json", - "prefix": "app", - "styles": [ - "../node_modules/normalize.css/normalize.css", - "../node_modules/primeng/resources/primeng.min.css", - "../node_modules/primeng/resources/themes/omega/theme.css", - "../node_modules/font-awesome/css/font-awesome.min.css", - "assets/style/styles.less" - ], - "scripts": [], - "environmentSource": "environments/environment.ts", - "environments": { - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts" - } - } - ], - "e2e": { - "protractor": { - "config": "./protractor.conf.js" - } - }, - "lint": [ - { - "project": "src/tsconfig.app.json" - }, - { - "project": "src/tsconfig.spec.json" - }, - { - "project": "e2e/tsconfig.e2e.json" - } - ], - "test": { - "karma": { - "config": "./karma.conf.js" - } - }, - "defaults": { - "styleExt": "less", - "serve": { - "port": 4200 - }, - "component": { - } - } -} diff --git a/.editorconfig b/.editorconfig index 6e87a003..8ffc628f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,7 @@ root = true charset = utf-8 indent_style = space indent_size = 2 +end_of_line = crlf insert_final_newline = true trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index 54bfd200..05300181 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,38 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp -/out-tsc - -# dependencies -/node_modules - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -testem.log -/typings - -# e2e -/e2e/*.js -/e2e/*.map - -# System Files +# Server files +/server/node_modules + +# Client files +/client/.DS_Store +/client/node_modules/ +/legacy-client/node_modules/ +/client/dist/ +/client/test/unit/reports/ +/client/test/e2e/reports/ +/client/test/e2e/features/undefined-steps.js +/client/selenium-debug.log + +/client/src/components/projects/.DS_Store +/client/src/components/root/.DS_Store +/client/src/shared/.DS_Store +/client/src/store/.DS_Store +/client/src/store/modules/.DS_Store +/src/app/.DS_Store +/src/.DS_Store +/client/src/.DS_Store +/client/src/components/root/.DS_Store +/.DS_Store + +# Editor directories and files +.idea +.vscode + +#misc +npm-debug.log* +yarn-debug.log* +yarn-error.log* + + +# Ignore Mac DS_Store files .DS_Store -Thumbs.db + + diff --git a/.sequelizerc b/.sequelizerc deleted file mode 100644 index 7e32c6ba..00000000 --- a/.sequelizerc +++ /dev/null @@ -1,8 +0,0 @@ -var path = require('path'); - -module.exports = { - 'config': path.resolve('./server', 'config/config.json'), - 'migrations-path': path.resolve('./server', 'migrations'), - 'models-path': path.resolve('./server', 'models'), - 'seeders-path': path.resolve('./server', 'seeders') -} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..421aa737 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,53 @@ +dist: trusty + +services: + - postgresql + +env: + # Use test credentials for sequelize + - NODE_ENV=test + +sudo: required +before_install: + # Repo for Yarn + - sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg + - echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + - sudo apt-get update -qq + - sudo apt-get install -y -qq yarn=1.5.1-1 +addons: + chrome: stable + +language: node_js +node_js: + - "9" + +cache: + yarn: true + directories: + - ./client/node_modules + - ./server/node_modules + +install: + - yarn run client-server:install + +script: + # Use Chromium instead of Chrome. + # - export CHROME_BIN=chromium-browser + # Initialize database: create, migrate, seed + - xvfb-run -a yarn run db + # Run backend + - xvfb-run -a yarn run server & + # Run linter + - xvfb-run -a yarn run client:lint + # Run unit tests + - xvfb-run -a yarn run client:unit + # Run e2e tests + - xvfb-run -a yarn run client:e2e +# - sleep 3 + +after_script: + # Stop backend + - pkill node + +notifications: + email: false diff --git a/.vs/Portfolio/v15/.suo b/.vs/Portfolio/v15/.suo deleted file mode 100644 index 90733dcb..00000000 Binary files a/.vs/Portfolio/v15/.suo and /dev/null differ diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json deleted file mode 100644 index 6b611411..00000000 --- a/.vs/VSWorkspaceState.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ExpandedNodes": [ - "" - ], - "PreviewInSolutionExplorer": false -} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index 2104a4ed..00000000 Binary files a/.vs/slnx.sqlite and /dev/null differ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 00b48a4f..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}\\proxy.conf.json", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": [ - "${workspaceFolder}/dist/out-tsc/**/*.js" - ] - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index e2559ef7..96b39115 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,31 @@ -# Portfolio +# T-Portfolio -## How to run - -1. Get MySQL server locally or via docker: -`docker run -p 3306:3306 --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:latest` -(where `my-secret-pw` is your `root` user password) - -2. Run `npm install` to install all dependencies +[![Build Status](https://travis-ci.org/T-Systems-RUS/Portfolio.svg?branch=develop)](https://travis-ci.org/T-Systems-RUS/Portfolio) -3. Run `npm run db:init` to initialize database (tweak DB connection settings via`server/config/config.json`) - -4. Run `npm run client-server` to run both client and server in development watch mode. +## How to run +**PREREQUISITE**: Get PostgreSQL server locally or via docker: +`docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=123 -d postgres` (where `123` is your `postgres` user password) -## Development server +There are to possible scenarios to start the app: -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. +### One process for client and server (easy to start with) -## Code scaffolding +1. Run `yarn run client-server:install` to install both server and client dependencies -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`. +3. Run `yarn run db` to initialize database (tweak DB connection settings via`server/config/config.json`) -## Build +4. Run `yarn run client-server` to run both client and server in development watch mode. -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. -## Running unit tests +### Separate processes for client and server (easier to manage) -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +1. Go to `server` folder and run `yarn` to install server dependencies -## Running end-to-end tests +3. Run `yarn run db` to initialize database (tweak DB connection settings via`server/config/config.json`) -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -Before running the tests make sure you are serving the app via `ng serve`. +4. Run `yarn run server:watch` to run server in development watch mode -## Further help +5. Go to `client` folder and run `yarn` to install client dependencies -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). +6. Run `yarn run dev` to run client in development watch mode. diff --git a/client/.babelrc b/client/.babelrc new file mode 100644 index 00000000..9390d164 --- /dev/null +++ b/client/.babelrc @@ -0,0 +1,18 @@ +{ + "presets": [ + ["env", { + "modules": false, + "targets": { + "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] + } + }], + "stage-2" + ], + "plugins": ["transform-vue-jsx", "transform-runtime"], + "env": { + "test": { + "presets": ["env", "stage-2"], + "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] + } + } +} diff --git a/client/.editorconfig b/client/.editorconfig new file mode 100644 index 00000000..9d08a1a8 --- /dev/null +++ b/client/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/client/.eslintignore b/client/.eslintignore new file mode 100644 index 00000000..51ebc9d7 --- /dev/null +++ b/client/.eslintignore @@ -0,0 +1,5 @@ +/build/ +/config/ +/dist/ +/*.js +/test/unit/reports/ diff --git a/client/.eslintrc.js b/client/.eslintrc.js new file mode 100644 index 00000000..5e0369de --- /dev/null +++ b/client/.eslintrc.js @@ -0,0 +1,94 @@ +// https://eslint.org/docs/user-guide/configuring + +module.exports = { + root: true, + parserOptions: { + parser: 'typescript-eslint-parser' + }, + env: { + browser: true, + }, + // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention + extends: ['plugin:vue/recommended', 'airbnb-base'], + // required to lint *.vue files + plugins: [ + 'vue', + 'typescript' + ], + // check if imports actually resolve + settings: { + 'import/resolver': { + webpack: { + config: 'build/webpack.base.conf.js' + } + } + }, + // add your custom rules here + rules: { + // don't require .vue extension when importing + 'import/extensions': ['error', 'always', { + js: 'never', + ts: 'never', + //vue: 'never' + }], + // disallow reassignment of function parameters + // disallow parameter object manipulation except for specific exclusions + 'no-param-reassign': 'off', + // TODO investigate a.b = !a.b assignments + // 'no-param-reassign': ['error', { + // props: true, + // ignorePropertyModificationsFor: [ + // 'state', // for vuex state + // 'acc', // for reduce accumulators + // 'e' // for e.returnvalue + // ] + // }], + // allow optionalDependencies + 'import/no-extraneous-dependencies': ['error', { + optionalDependencies: ['test/unit/index.js'] + }], + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'indent': ['error', 2], + 'object-curly-spacing': ['error', 'never'], + 'comma-dangle': ['error', 'never'], + 'arrow-parens': ['error', 'as-needed'], + 'max-len': ['error', { "code": 120 }], + // Broken + 'space-infix-ops': 'off', + // TypeScript checks this via compiler options - see tsconfig + 'no-undef': 'off', + 'no-unused-vars': 'off', + // TODO fix after parser fixed https://github.com/eslint/typescript-eslint-parser#known-issues + 'import/prefer-default-export': 'off', + // TODO enable later if really needed + 'vue/require-v-for-key': 'off', + + // Typescript Plugin + 'typescript/adjacent-overload-signatures': ['error'], + 'typescript/class-name-casing': ['error'], + 'typescript/interface-name-prefix': ['error', 'always'], + 'typescript/member-ordering': ['error'], + 'typescript/no-angle-bracket-type-assertion': ['error'], + 'typescript/no-array-constructor': ['error'], + 'typescript/no-empty-interface': ['error'], + 'typescript/no-explicit-any': ['error'], + 'typescript/no-namespace': ['error'], + 'typescript/no-triple-slash-reference': ['error'], + // Broken + // 'typescript/type-annotation-spacing': ['error'], + }, + overrides: [ + { + files: ["*.vue"], + rules: { + 'indent': 'off', + 'vue/script-indent': [ + 'error', + 2, + {'baseIndent': 1} + ], + } + } + ] +} diff --git a/client/.postcssrc.js b/client/.postcssrc.js new file mode 100644 index 00000000..eee3e92d --- /dev/null +++ b/client/.postcssrc.js @@ -0,0 +1,10 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + "postcss-import": {}, + "postcss-url": {}, + // to edit target browsers: use "browserslist" field in package.json + "autoprefixer": {} + } +} diff --git a/client/README.md b/client/README.md new file mode 100644 index 00000000..2b450660 --- /dev/null +++ b/client/README.md @@ -0,0 +1,89 @@ +# t-portfolio client + +> A Vue.js project + +It's highly recommended to use [Yarn](https://yarnpkg.com/en/) for this project + +Unit tests are done with Jest. +e2e tests are based on Nightwatch and Cucumber. + +## How to run +``` bash +# Install dependencies +yarn +OR +npm install + +# Serve with hot reload at localhost:8080 +yarn run dev + +# Build for production with minification +yarn run build + +# Run unit tests +yarn run unit +# Report will be located at: test/unit/reports/test-report.html +# Coverage report will be located at: test/unit/reports/coverage/index.html + +# Run e2e tests with output to console +yarn run e2e + +# Generate HTML report (after running e2e) +yarn run e2e-html-report +# Report will be located at: test/e2e/reports/html/cucumber_report.html + +# Perform ESLint code check +yarn run lint +``` + +## Other commands +``` +# build for production and view the bundle analyzer report +yarn run build --report + +# Run unit tests in watch mode for development +yarn run unit-watch + +# Run e2e and generate HTML report together +# (!) Report will not be generated in case of error in e2e tests +yarn run e2e-html-report +``` + +## WebStorm / Intellij IDEA configuration + +Enable ESlint plugin, Vue plugin and TypeScript integration. +All of these should work by default. + +### Unit testing via Jest + +1. Click Run in the main toolbar +2. Edit Configurations +3. On the top left of the Run/Debug Configurations dialog, click the + sign. +4. Choose Jest +5. Name the new configuration "Jest" +6. Under "Configuration file" enter `{YOUR_PATH}\test\unit\jest.conf.js` (be sure to change {YOUR_PATH}) +7. Click Apply + +You can now both run Unit tests and debug them inside the IDE. + +### e2e testing via Nightwatch + +1. Click Run in the main toolbar +2. Edit Configurations +3. On the top left of the Run/Debug Configurations dialog, click the + sign. +4. Choose Node.js +5. Name the new configuration "Nightwatch" +6. Under "JavaScript file" enter `node_modules\nightwatch\bin\runner.js` +7. Under "Application parameters" enter `--config test/e2e/nightwatch.conf.js --env chrome` +8. Click Apply + +You can now both run e2e tests and debug them inside the IDE. + +**Note that this is different from running via console** `yarn run e2e` +**which also starts the http server and checks if port is in use.** + +**If your app is not served at default port (8080), change devServerURL in** `nightwatch.conf.js` + +**For this to work you will need a running SPA in background, e.g. run** `yarn run dev` **and start e2e in IDE afterwards.** + +For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). diff --git a/client/build/build.js b/client/build/build.js new file mode 100644 index 00000000..8f2ad8ad --- /dev/null +++ b/client/build/build.js @@ -0,0 +1,41 @@ +'use strict' +require('./check-versions')() + +process.env.NODE_ENV = 'production' + +const ora = require('ora') +const rm = require('rimraf') +const path = require('path') +const chalk = require('chalk') +const webpack = require('webpack') +const config = require('../config') +const webpackConfig = require('./webpack.prod.conf') + +const spinner = ora('building for production...') +spinner.start() + +rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { + if (err) throw err + webpack(webpackConfig, (err, stats) => { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. + chunks: false, + chunkModules: false + }) + '\n\n') + + if (stats.hasErrors()) { + console.log(chalk.red(' Build failed with errors.\n')) + process.exit(1) + } + + console.log(chalk.cyan(' Build complete.\n')) + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )) + }) +}) diff --git a/client/build/check-versions.js b/client/build/check-versions.js new file mode 100644 index 00000000..3ef972a0 --- /dev/null +++ b/client/build/check-versions.js @@ -0,0 +1,54 @@ +'use strict' +const chalk = require('chalk') +const semver = require('semver') +const packageConfig = require('../package.json') +const shell = require('shelljs') + +function exec (cmd) { + return require('child_process').execSync(cmd).toString().trim() +} + +const versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + } +] + +if (shell.which('npm')) { + versionRequirements.push({ + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + }) +} + +module.exports = function () { + const warnings = [] + + for (let i = 0; i < versionRequirements.length; i++) { + const mod = versionRequirements[i] + + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + + for (let i = 0; i < warnings.length; i++) { + const warning = warnings[i] + console.log(' ' + warning) + } + + console.log() + process.exit(1) + } +} diff --git a/client/build/logo.png b/client/build/logo.png new file mode 100644 index 00000000..f3d2503f Binary files /dev/null and b/client/build/logo.png differ diff --git a/client/build/utils.js b/client/build/utils.js new file mode 100644 index 00000000..5e3d20a9 --- /dev/null +++ b/client/build/utils.js @@ -0,0 +1,101 @@ +'use strict' +const path = require('path') +const config = require('../config') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const packageConfig = require('../package.json') + +exports.assetsPath = function (_path) { + const assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { + options = options || {} + + const cssLoader = { + loader: 'css-loader', + options: { + sourceMap: options.sourceMap + } + } + + const postcssLoader = { + loader: 'postcss-loader', + options: { + sourceMap: options.sourceMap + } + } + + // generate loader string to be used with extract text plugin + function generateLoaders (loader, loaderOptions) { + const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] + + if (loader) { + loaders.push({ + loader: loader + '-loader', + options: Object.assign({}, loaderOptions, { + sourceMap: options.sourceMap + }) + }) + } + + // Extract CSS when that option is specified + // (which is the case during production build) + if (options.extract) { + return ExtractTextPlugin.extract({ + use: loaders, + fallback: 'vue-style-loader' + }) + } else { + return ['vue-style-loader'].concat(loaders) + } + } + + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + return { + css: generateLoaders(), + postcss: generateLoaders(), + less: generateLoaders('less'), + sass: generateLoaders('sass', { indentedSyntax: true }), + scss: generateLoaders('sass'), + stylus: generateLoaders('stylus'), + styl: generateLoaders('stylus') + } +} + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + const output = [] + const loaders = exports.cssLoaders(options) + + for (const extension in loaders) { + const loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + use: loader + }) + } + + return output +} + +exports.createNotifierCallback = () => { + const notifier = require('node-notifier') + + return (severity, errors) => { + if (severity !== 'error') return + + const error = errors[0] + const filename = error.file && error.file.split('!').pop() + + notifier.notify({ + title: 'Error', + message: error.file, + subtitle: filename || '', + icon: path.join(__dirname, 'logo.png') + }) + } +} diff --git a/client/build/vue-loader.conf.js b/client/build/vue-loader.conf.js new file mode 100644 index 00000000..33ed58bc --- /dev/null +++ b/client/build/vue-loader.conf.js @@ -0,0 +1,22 @@ +'use strict' +const utils = require('./utils') +const config = require('../config') +const isProduction = process.env.NODE_ENV === 'production' +const sourceMapEnabled = isProduction + ? config.build.productionSourceMap + : config.dev.cssSourceMap + +module.exports = { + loaders: utils.cssLoaders({ + sourceMap: sourceMapEnabled, + extract: isProduction + }), + cssSourceMap: sourceMapEnabled, + cacheBusting: config.dev.cacheBusting, + transformToRequire: { + video: ['src', 'poster'], + source: 'src', + img: 'src', + image: 'xlink:href' + } +} diff --git a/client/build/webpack.base.conf.js b/client/build/webpack.base.conf.js new file mode 100644 index 00000000..09db511a --- /dev/null +++ b/client/build/webpack.base.conf.js @@ -0,0 +1,105 @@ +'use strict' +const path = require('path') +const utils = require('./utils') +const config = require('../config') +const vueLoaderConfig = require('./vue-loader.conf') + +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + +// i18n with YAML format support +vueLoaderConfig.loaders.i18n = '@kazupon/vue-i18n-loader'; +vueLoaderConfig.preLoaders = { + i18n: 'yaml-loader' +}; + +const createLintingRule = () => ({ + test: /\.(js|vue)$/, + loader: 'eslint-loader', + enforce: 'pre', + include: [resolve('src'), resolve('test')], + options: { + formatter: require('eslint-friendly-formatter'), + emitWarning: !config.dev.showEslintErrorsInOverlay + } +}) + +module.exports = { + context: path.resolve(__dirname, '../'), + entry: { + app: './src/main.ts' + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: process.env.NODE_ENV === 'production' + ? config.build.assetsPublicPath + : config.dev.assetsPublicPath + }, + resolve: { + extensions: ['.ts', '.js', '.vue', '.json'], + alias: { + 'vue$': 'vue/dist/vue.esm.js', + '@': resolve('src'), + } + }, + module: { + rules: [ + ...(config.dev.useEslint ? [createLintingRule()] : []), + { + test: /\.vue$/, + loader: 'vue-loader', + options: vueLoaderConfig + }, + { + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] + }, + { + test: /\.ts$/, + loader: "ts-loader", + options: { + appendTsSuffixTo: [/\.vue$/] + } + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } + }, + { + test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('media/[name].[hash:7].[ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + } + ] + }, + node: { + // prevent webpack from injecting useless setImmediate polyfill because Vue + // source contains it (although only uses it if it's native). + setImmediate: false, + // prevent webpack from injecting mocks to Node native modules + // that does not make sense for the client + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + } +} diff --git a/client/build/webpack.dev.conf.js b/client/build/webpack.dev.conf.js new file mode 100644 index 00000000..070ae221 --- /dev/null +++ b/client/build/webpack.dev.conf.js @@ -0,0 +1,95 @@ +'use strict' +const utils = require('./utils') +const webpack = require('webpack') +const config = require('../config') +const merge = require('webpack-merge') +const path = require('path') +const baseWebpackConfig = require('./webpack.base.conf') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') +const portfinder = require('portfinder') + +const HOST = process.env.HOST +const PORT = process.env.PORT && Number(process.env.PORT) + +const devWebpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) + }, + // cheap-module-eval-source-map is faster for development + devtool: config.dev.devtool, + + // these devServer options should be customized in /config/index.js + devServer: { + clientLogLevel: 'warning', + historyApiFallback: { + rewrites: [ + { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, + ], + }, + hot: true, + contentBase: false, // since we use CopyWebpackPlugin. + compress: true, + host: HOST || config.dev.host, + port: PORT || config.dev.port, + open: config.dev.autoOpenBrowser, + overlay: config.dev.errorOverlay + ? { warnings: false, errors: true } + : false, + publicPath: config.dev.assetsPublicPath, + proxy: config.dev.proxyTable, + quiet: true, // necessary for FriendlyErrorsPlugin + watchOptions: { + poll: config.dev.poll, + } + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': require('../config/dev.env') + }), + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }), + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.dev.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +module.exports = new Promise((resolve, reject) => { + portfinder.basePort = process.env.PORT || config.dev.port + portfinder.getPort((err, port) => { + if (err) { + reject(err) + } else { + // publish the new Port, necessary for e2e tests + process.env.PORT = port + // add port to devServer config + devWebpackConfig.devServer.port = port + + // Add FriendlyErrorsPlugin + devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ + compilationSuccessInfo: { + messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], + }, + onErrors: config.dev.notifyOnErrors + ? utils.createNotifierCallback() + : undefined + })) + + resolve(devWebpackConfig) + } + }) +}) diff --git a/client/build/webpack.prod.conf.js b/client/build/webpack.prod.conf.js new file mode 100644 index 00000000..2f172596 --- /dev/null +++ b/client/build/webpack.prod.conf.js @@ -0,0 +1,149 @@ +'use strict' +const path = require('path') +const utils = require('./utils') +const webpack = require('webpack') +const config = require('../config') +const merge = require('webpack-merge') +const baseWebpackConfig = require('./webpack.base.conf') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') + +const env = process.env.NODE_ENV === 'testing' + ? require('../config/test.env') + : require('../config/prod.env') + +const webpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true, + usePostCSS: true + }) + }, + devtool: config.build.productionSourceMap ? config.build.devtool : false, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/en/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env + }), + new UglifyJsPlugin({ + uglifyOptions: { + compress: { + warnings: false + } + }, + sourceMap: config.build.productionSourceMap, + parallel: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css'), + // Setting the following option to `false` will not extract CSS from codesplit chunks. + // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. + // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, + // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 + allChunks: true, + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: config.build.productionSourceMap + ? { safe: true, map: { inline: false } } + : { safe: true } + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: process.env.NODE_ENV === 'testing' + ? 'index.html' + : config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // keep module.id stable when vendor modules does not change + new webpack.HashedModuleIdsPlugin(), + // enable scope hoisting + new webpack.optimize.ModuleConcatenationPlugin(), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks (module) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) + } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + minChunks: Infinity + }), + // This instance extracts shared chunks from code splitted chunks and bundles them + // in a separate chunk, similar to the vendor chunk + // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk + new webpack.optimize.CommonsChunkPlugin({ + name: 'app', + async: 'vendor-async', + children: true, + minChunks: 3 + }), + + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.build.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +if (config.build.productionGzip) { + const CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +if (config.build.bundleAnalyzerReport) { + const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + webpackConfig.plugins.push(new BundleAnalyzerPlugin()) +} + +module.exports = webpackConfig diff --git a/client/config/dev.env.js b/client/config/dev.env.js new file mode 100644 index 00000000..1e22973a --- /dev/null +++ b/client/config/dev.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/client/config/index.js b/client/config/index.js new file mode 100644 index 00000000..44ee94ec --- /dev/null +++ b/client/config/index.js @@ -0,0 +1,79 @@ +'use strict' +// Template version: 1.3.1 +// see http://vuejs-templates.github.io/webpack for documentation. + +const path = require('path') + +module.exports = { + dev: { + + // Paths + assetsSubDirectory: 'static', + assetsPublicPath: '/', + proxyTable: { + '/api': 'http://localhost:3000', + '/server/images': 'http://localhost:3000' + }, + + // Various Dev Server settings + host: 'localhost', // can be overwritten by process.env.HOST + port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined + autoOpenBrowser: false, + errorOverlay: true, + notifyOnErrors: true, + poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- + + // Use Eslint Loader? + // If true, your code will be linted during bundling and + // linting errors and warnings will be shown in the console. + useEslint: true, + // If true, eslint errors and warnings will also be shown in the error overlay + // in the browser. + showEslintErrorsInOverlay: false, + + /** + * Source Maps + */ + + // https://webpack.js.org/configuration/devtool/#development + devtool: 'cheap-module-eval-source-map', + + // If you have problems debugging vue-files in devtools, + // set this to false - it *may* help + // https://vue-loader.vuejs.org/en/options.html#cachebusting + cacheBusting: true, + + cssSourceMap: true + }, + + build: { + // Template for index.html + index: path.resolve(__dirname, '../dist/index.html'), + + // Paths + assetsRoot: path.resolve(__dirname, '../dist'), + assetsSubDirectory: 'static', + assetsPublicPath: '/', + + /** + * Source Maps + */ + + productionSourceMap: true, + // https://webpack.js.org/configuration/devtool/#production + devtool: '#source-map', + + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + } +} diff --git a/client/config/prod.env.js b/client/config/prod.env.js new file mode 100644 index 00000000..a6f99761 --- /dev/null +++ b/client/config/prod.env.js @@ -0,0 +1,4 @@ +'use strict' +module.exports = { + NODE_ENV: '"production"' +} diff --git a/client/config/test.env.js b/client/config/test.env.js new file mode 100644 index 00000000..c2824a30 --- /dev/null +++ b/client/config/test.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const devEnv = require('./dev.env') + +module.exports = merge(devEnv, { + NODE_ENV: '"testing"' +}) diff --git a/client/index.html b/client/index.html new file mode 100644 index 00000000..7c79f670 --- /dev/null +++ b/client/index.html @@ -0,0 +1,12 @@ + + + + + + T-Portfolio + + +
+ + + diff --git a/client/jesthtmlreporter.config.json b/client/jesthtmlreporter.config.json new file mode 100644 index 00000000..f54cb443 --- /dev/null +++ b/client/jesthtmlreporter.config.json @@ -0,0 +1,4 @@ +{ + "outputPath": "./test/unit/reports/test-report.html", + "includeFailureMsg": true +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 00000000..9d9937fc --- /dev/null +++ b/client/package.json @@ -0,0 +1,112 @@ +{ + "name": "t-portfolio-client", + "version": "1.0.0", + "description": "A Vue.js project", + "author": "Igogrek ", + "private": true, + "scripts": { + "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", + "start": "npm run dev", + "unit": "jest --config test/unit/jest.conf.js --coverage", + "unit-watch": "jest --config test/unit/jest.conf.js --watch", + "e2e": "node test/e2e/runner.js", + "e2e-html-report": "node test/e2e/html-report.js", + "e2e-report": "npm run e2e && npm run e2e-html-report", + "test": "npm run unit && npm run e2e", + "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", + "build": "node build/build.js" + }, + "dependencies": { + "axios": "^0.18.0", + "buefy": "^0.6.6", + "bulma": "^0.6.2", + "eslint-plugin-typescript": "^0.11.0", + "pptxgenjs": "^2.1.0", + "vue": "^2.5.2", + "vue-i18n": "^7.8.0", + "vue-router": "^3.0.1", + "vuelidate": "^0.7.4", + "vuex": "^3.0.1" + }, + "devDependencies": { + "@kazupon/vue-i18n-loader": "^0.3.0", + "@types/jest": "^22.1.3", + "@vue/test-utils": "^1.0.0-beta.12", + "autoprefixer": "^7.1.2", + "babel-core": "^6.22.1", + "babel-eslint": "^8.2.1", + "babel-helper-vue-jsx-merge-props": "^2.0.3", + "babel-jest": "^21.0.2", + "babel-loader": "^7.1.1", + "babel-plugin-dynamic-import-node": "^1.2.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-plugin-transform-vue-jsx": "^3.5.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "babel-register": "^6.22.0", + "chalk": "^2.0.1", + "chromedriver": "^2.27.2", + "copy-webpack-plugin": "^4.0.1", + "cross-spawn": "^5.0.1", + "css-loader": "^0.28.0", + "cucumber": "^4.0.0", + "cucumber-html-reporter": "^4.0.1", + "cucumber-pretty": "^1.4.0", + "eslint": "^4.15.0", + "eslint-config-airbnb-base": "^11.3.0", + "eslint-friendly-formatter": "^3.0.0", + "eslint-import-resolver-webpack": "^0.8.3", + "eslint-loader": "^1.7.1", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-vue": "^4.0.0", + "extract-text-webpack-plugin": "^3.0.0", + "file-loader": "^1.1.4", + "friendly-errors-webpack-plugin": "^1.6.1", + "html-webpack-plugin": "^2.30.1", + "jest": "^22.0.4", + "jest-html-reporter": "^1.2.0", + "jest-serializer-vue": "^0.3.0", + "nightwatch": "^0.9.19", + "nightwatch-cucumber": "^9.1.0", + "node-notifier": "^5.1.2", + "node-sass": "^4.7.2", + "optimize-css-assets-webpack-plugin": "^3.2.0", + "ora": "^1.2.0", + "portfinder": "^1.0.13", + "postcss-import": "^11.0.0", + "postcss-loader": "^2.0.8", + "postcss-url": "^7.2.1", + "rimraf": "^2.6.0", + "sass-loader": "^6.0.6", + "selenium-server": "^3.0.1", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "style-loader": "^0.20.2", + "ts-jest": "^22.0.4", + "ts-loader": "^3.5.0", + "typescript": "^2.7.2", + "typescript-eslint-parser": "^13.0.0", + "uglifyjs-webpack-plugin": "^1.1.1", + "url-loader": "^0.5.8", + "vue-jest": "^1.0.2", + "vue-loader": "^13.3.0", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.5.2", + "webpack": "^3.6.0", + "webpack-bundle-analyzer": "^2.9.0", + "webpack-dev-server": "^2.9.1", + "webpack-merge": "^4.1.0", + "yaml-loader": "^0.5.0" + }, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/client/src/.DS_Store b/client/src/.DS_Store new file mode 100644 index 00000000..c4261e3a Binary files /dev/null and b/client/src/.DS_Store differ diff --git a/client/src/App.vue b/client/src/App.vue new file mode 100644 index 00000000..0c359055 --- /dev/null +++ b/client/src/App.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/client/src/assets/fonts/TeleGroteskNext-Bold.woff b/client/src/assets/fonts/TeleGroteskNext-Bold.woff new file mode 100644 index 00000000..487172fd Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Bold.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Bold.woff2 b/client/src/assets/fonts/TeleGroteskNext-Bold.woff2 new file mode 100644 index 00000000..fbcc3918 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Bold.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-BoldItalic.woff b/client/src/assets/fonts/TeleGroteskNext-BoldItalic.woff new file mode 100644 index 00000000..7aaab08b Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-BoldItalic.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-BoldItalic.woff2 b/client/src/assets/fonts/TeleGroteskNext-BoldItalic.woff2 new file mode 100644 index 00000000..6e94b5f5 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-BoldItalic.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Medium.woff b/client/src/assets/fonts/TeleGroteskNext-Medium.woff new file mode 100644 index 00000000..ec95ac9a Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Medium.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Medium.woff2 b/client/src/assets/fonts/TeleGroteskNext-Medium.woff2 new file mode 100644 index 00000000..7f5fbf4c Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Medium.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-MediumItalic.woff b/client/src/assets/fonts/TeleGroteskNext-MediumItalic.woff new file mode 100644 index 00000000..8d1ea064 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-MediumItalic.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-MediumItalic.woff2 b/client/src/assets/fonts/TeleGroteskNext-MediumItalic.woff2 new file mode 100644 index 00000000..c9950a0a Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-MediumItalic.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Regular.woff b/client/src/assets/fonts/TeleGroteskNext-Regular.woff new file mode 100644 index 00000000..3a8dd415 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Regular.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Regular.woff2 b/client/src/assets/fonts/TeleGroteskNext-Regular.woff2 new file mode 100644 index 00000000..54e5524b Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Regular.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-RegularItalic.woff b/client/src/assets/fonts/TeleGroteskNext-RegularItalic.woff new file mode 100644 index 00000000..c171f9b4 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-RegularItalic.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-RegularItalic.woff2 b/client/src/assets/fonts/TeleGroteskNext-RegularItalic.woff2 new file mode 100644 index 00000000..ef407dc1 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-RegularItalic.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Thin.woff b/client/src/assets/fonts/TeleGroteskNext-Thin.woff new file mode 100644 index 00000000..08f70429 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Thin.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Thin.woff2 b/client/src/assets/fonts/TeleGroteskNext-Thin.woff2 new file mode 100644 index 00000000..b4b77361 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Thin.woff2 differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Ultra.woff b/client/src/assets/fonts/TeleGroteskNext-Ultra.woff new file mode 100644 index 00000000..3297178c Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Ultra.woff differ diff --git a/client/src/assets/fonts/TeleGroteskNext-Ultra.woff2 b/client/src/assets/fonts/TeleGroteskNext-Ultra.woff2 new file mode 100644 index 00000000..76d889f4 Binary files /dev/null and b/client/src/assets/fonts/TeleGroteskNext-Ultra.woff2 differ diff --git a/client/src/assets/images/close.svg b/client/src/assets/images/close.svg new file mode 100644 index 00000000..cefc2345 --- /dev/null +++ b/client/src/assets/images/close.svg @@ -0,0 +1,12 @@ + + + + + cancel_outline + + + diff --git a/client/src/assets/images/close_fill.svg b/client/src/assets/images/close_fill.svg new file mode 100644 index 00000000..ac5f0d4a --- /dev/null +++ b/client/src/assets/images/close_fill.svg @@ -0,0 +1,12 @@ + + + + + cancel_outline + + + diff --git a/client/src/components/.DS_Store b/client/src/components/.DS_Store new file mode 100644 index 00000000..80a2caab Binary files /dev/null and b/client/src/components/.DS_Store differ diff --git a/client/src/components/common/.DS_Store b/client/src/components/common/.DS_Store new file mode 100644 index 00000000..34404070 Binary files /dev/null and b/client/src/components/common/.DS_Store differ diff --git a/client/src/components/common/Accordion/Accordion.test.ts b/client/src/components/common/Accordion/Accordion.test.ts new file mode 100644 index 00000000..6dad62ca --- /dev/null +++ b/client/src/components/common/Accordion/Accordion.test.ts @@ -0,0 +1,14 @@ +import {mount} from '@vue/test-utils'; +import Accordion from './Accordion.vue'; + +describe('Accordion', () => { + + const wrapper = mount(Accordion); + + it('change value on close button click', () => { + wrapper.vm.toggleOpened(); + + expect(wrapper.emitted()['update:opened']).toEqual([[false]]); + }); + +}); diff --git a/client/src/components/common/Accordion/Accordion.vue b/client/src/components/common/Accordion/Accordion.vue new file mode 100644 index 00000000..e2530525 --- /dev/null +++ b/client/src/components/common/Accordion/Accordion.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/client/src/components/common/Autocomplete/Autocomplete.vue b/client/src/components/common/Autocomplete/Autocomplete.vue new file mode 100644 index 00000000..f5d3a10c --- /dev/null +++ b/client/src/components/common/Autocomplete/Autocomplete.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/client/src/components/common/Checkbox/Checkbox.test.ts b/client/src/components/common/Checkbox/Checkbox.test.ts new file mode 100644 index 00000000..026a12d0 --- /dev/null +++ b/client/src/components/common/Checkbox/Checkbox.test.ts @@ -0,0 +1,18 @@ +import {mount} from '@vue/test-utils' +import Checkbox from './Checkbox.vue' + +describe('Checkbox', () => { + + const wrapper = mount(Checkbox); + + it('unchecked by default', () => { + expect(wrapper.vm.checked).toEqual(false); + }); + + it('change value on check', () => { + wrapper.vm.toggleCheck(); + + expect(wrapper.emitted()['update:checked']).toEqual([[true]]); + }); + +}); diff --git a/client/src/components/common/Checkbox/Checkbox.vue b/client/src/components/common/Checkbox/Checkbox.vue new file mode 100644 index 00000000..8323c174 --- /dev/null +++ b/client/src/components/common/Checkbox/Checkbox.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/client/src/components/common/Checkbox/check.svg b/client/src/components/common/Checkbox/check.svg new file mode 100644 index 00000000..58a76a95 --- /dev/null +++ b/client/src/components/common/Checkbox/check.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/client/src/components/common/Chip/Chip.test.ts b/client/src/components/common/Chip/Chip.test.ts new file mode 100644 index 00000000..3b5edf3f --- /dev/null +++ b/client/src/components/common/Chip/Chip.test.ts @@ -0,0 +1,29 @@ +import {mount} from '@vue/test-utils'; +import Chip from './Chip.vue'; +import store from '../../../store/index'; + +const name = 'Agile'; + +describe('Chips', () => { + + const wrapper = mount(Chip, { + store, + propsData: { + name + }, + filters: { + date(value) { + return "" + } + } + }); + + it('not active by default', () => { + expect(wrapper.vm.isSelected).toEqual(false); + }); + + + it('should render correct name property', () => { + expect(wrapper.props().name).toBe(name); + }); +}); diff --git a/client/src/components/common/Chip/Chip.vue b/client/src/components/common/Chip/Chip.vue new file mode 100644 index 00000000..06e2d8ca --- /dev/null +++ b/client/src/components/common/Chip/Chip.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/client/src/components/common/CommonModal/CommonModal.vue b/client/src/components/common/CommonModal/CommonModal.vue new file mode 100644 index 00000000..fc2cd3a1 --- /dev/null +++ b/client/src/components/common/CommonModal/CommonModal.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/client/src/components/common/ConfirmModal/ConfirmModal.vue b/client/src/components/common/ConfirmModal/ConfirmModal.vue new file mode 100755 index 00000000..a718705b --- /dev/null +++ b/client/src/components/common/ConfirmModal/ConfirmModal.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/client/src/components/common/ConfirmModal/cancel_outline.svg b/client/src/components/common/ConfirmModal/cancel_outline.svg new file mode 100755 index 00000000..6bc01a2e --- /dev/null +++ b/client/src/components/common/ConfirmModal/cancel_outline.svg @@ -0,0 +1,12 @@ + + + + + cancel_outline + + + diff --git a/client/src/components/common/ErrorMessage/ErrorMessage.vue b/client/src/components/common/ErrorMessage/ErrorMessage.vue new file mode 100755 index 00000000..9f01cfb2 --- /dev/null +++ b/client/src/components/common/ErrorMessage/ErrorMessage.vue @@ -0,0 +1,42 @@ + + + + diff --git a/client/src/components/common/ErrorMessage/ErrorMessage.yml b/client/src/components/common/ErrorMessage/ErrorMessage.yml new file mode 100755 index 00000000..e0c32991 --- /dev/null +++ b/client/src/components/common/ErrorMessage/ErrorMessage.yml @@ -0,0 +1,3 @@ +en: + + diff --git a/client/src/components/common/FileUploader/FileList/FileList.vue b/client/src/components/common/FileUploader/FileList/FileList.vue new file mode 100644 index 00000000..d332f67f --- /dev/null +++ b/client/src/components/common/FileUploader/FileList/FileList.vue @@ -0,0 +1,225 @@ + + + + + + + diff --git a/client/src/components/common/FileUploader/FileList/FileList.yml b/client/src/components/common/FileUploader/FileList/FileList.yml new file mode 100644 index 00000000..d7c4efbf --- /dev/null +++ b/client/src/components/common/FileUploader/FileList/FileList.yml @@ -0,0 +1,13 @@ +en: + delete: "Delete" + deleteFileQuestion: "Delete File?" + reallyDelete: "Do you really want to delete the file" + actionUndone: "This action can not be undone." + deleteFileBtn: "Delete File" + +de: + delete: "Löschen" + deleteFileQuestion: "Datei löschen" + reallyDelete: "Wollen Sie die folgende Datei wirklich löschen:" + actionUndone: "Diese Aktion kann nicht rückgängig gemacht werden." + deleteFileBtn: "Datei löschen" diff --git a/client/src/components/common/FileUploader/FileUploader.vue b/client/src/components/common/FileUploader/FileUploader.vue new file mode 100644 index 00000000..51dbb9cb --- /dev/null +++ b/client/src/components/common/FileUploader/FileUploader.vue @@ -0,0 +1,139 @@ + + + + + + + diff --git a/client/src/components/common/FileUploader/FileUploader.yml b/client/src/components/common/FileUploader/FileUploader.yml new file mode 100644 index 00000000..43ffbdb7 --- /dev/null +++ b/client/src/components/common/FileUploader/FileUploader.yml @@ -0,0 +1,5 @@ +en: + addFiles: "Add Files" + +de: + addFiles: "Dateien hinzufügen" diff --git a/client/src/components/common/FileUploader/FileUploaderModal/FileUploaderModal.vue b/client/src/components/common/FileUploader/FileUploaderModal/FileUploaderModal.vue new file mode 100644 index 00000000..bb0e2c4a --- /dev/null +++ b/client/src/components/common/FileUploader/FileUploaderModal/FileUploaderModal.vue @@ -0,0 +1,395 @@ + + + + + + + diff --git a/client/src/components/common/FileUploader/FileUploaderModal/FileUploaderModal.yml b/client/src/components/common/FileUploader/FileUploaderModal/FileUploaderModal.yml new file mode 100644 index 00000000..5a052741 --- /dev/null +++ b/client/src/components/common/FileUploader/FileUploaderModal/FileUploaderModal.yml @@ -0,0 +1,39 @@ +en: + uploadFiles: "Upload Files" + defaultOptional: "Upload an image from your computer or an URL?" + addFiles: "Add Files" + filesInfo: "Optional files may be added to this new dataset (Maximum file size: 10 MB)" + dragFilesHere: "Drag and drop or" + selectFiles: "select files from computer" + addFilesBtn: "Add Files" + skipThisStep: "Skip this step" + replaceImageQuestion: "Replace Image" + reallyReplace: "Do you really want to replace the image" + actionUndone: "This action can not be undone." + replaceImageBtn: "Replace Image" + uploadImage: "Add From Computer" + addUrl: "Add URL" + imageUrl: "Image URL" + exampleUrl: "http://example.com/image.jpg" + fileNotUploadedError: "The following files cannot be uploaded." + fileSizeError: "The size is bigger" + fileExtensionError: "The file with this extension is not allowed" + imageExtensionError: "The file format is not supported. Supported formats: .jpg, .jpeg, .png" + +de: + uploadFiles: "Dateien hochladen" + defaultOptional: "Möchten Sie ein Bild von Ihrem Computer oder von einer URL hochladen?" + addFiles: "Dateien hinzufügen" + filesInfo: "Zusätzliche Dateien werden dem neuen Datensatz hinzugefügt (Maximale Dateigröße: 10 MB)" + dragFilesHere: "Drag & Drop oder" + selectFiles: "Dateien vom Computer auswählen" + addFilesBtn: "Dateien hinzufügen" + skipThisStep: "Schritt überspringen" + replaceImageQuestion: "Bild ersetzen" + reallyReplace: "Wollen Sie das Bild wirklich ersetzen" + actionUndone: "Diese Aktion kann nicht rückgängig gemacht werden." + replaceImageBtn: "Bild ersetzen" + uploadImage: "Vom Computer" + addUrl: "Von URL" + imageUrl: "Bild-URL" + exampleUrl: "http://beispiel.de/bild.jpg" diff --git a/client/src/components/common/FileUploader/FileUploaderService.ts b/client/src/components/common/FileUploader/FileUploaderService.ts new file mode 100644 index 00000000..b9dc5d67 --- /dev/null +++ b/client/src/components/common/FileUploader/FileUploaderService.ts @@ -0,0 +1,66 @@ +import {FileUploadStatus, IFileUpload, IImageUrl} from './IFileUploadList'; +import axios from 'axios'; +import Guid from '../../../shared/classes/Guid'; + +const harmfulFileExtensionRegex = new RegExp('(.|/)(bat|exe|cmd|sh|php([0-9])?|pl|cgi|' + + '386|dll|com|torrent|js|app|jar|pif|vb|vbscript|wsf|asp|cer|csr|jsp|drv|sys|ade|adp|bas|chm' + + '|cpl|crt|csh|fxp|hlp|hta|inf|ins|isp|jse|htaccess|htpasswd|ksh|lnk|mdb|mde|mdt|mdw' + + '|msc|msi|msp|mst|ops|pcd|prg|reg|scr|sct|shb|shs|url|vbe|vbs|wsc|wsf|wsh)$'); +const imageFileExtesionRegex = /\.(jpg|jpeg|png)$/; + +export class FileUploaderService { + public static fileListToFileUploadList(fileList: FileList): IFileUpload[] { + return Array.prototype.map.call(fileList, (item: File) => FileUploaderService.fileToFileUpload(item)); + } + + public static fileToFileUpload(file: File): IFileUpload { + return { + file, + loadingStatus: FileUploadStatus.NULL, + imageDataUrl: '' + }; + } + + public static isFileInUploadList(file: File, fileUploadList: IFileUpload[]): boolean { + return fileUploadList.some((item: IFileUpload) => item.file.name === file.name); + } + + public static getFileNameFromUrl(url: string): string { + return url.split('/').slice(-1)[0]; + } + + public static urlToImageUrl(url: string): IImageUrl { + return { + url, + name: FileUploaderService.getFileNameFromUrl(url), + loadingStatus: FileUploadStatus.NULL + }; + } + + // TODO: should be removed when API is working + public static fakeUploadFiles(): Promise { + return new Promise(resolve => setTimeout(resolve, Math.random() * 2000)); + } + + public static uploadFiles(file: IFileUpload) { + const fileExtension = file.file.type.replace(/^[^/]*[/]/, ''); + + return axios.post('/api/images/add/', { + 'data': file.imageDataUrl, + 'name': `${Guid.newGuid()}.${fileExtension}`, + 'type': fileExtension + }); + } + + public static validateFileSize(file: File, maxSize: number): boolean { + return file.size <= maxSize; + } + + public static validateFileExtension(fileName: string): boolean { + return !harmfulFileExtensionRegex.test(fileName); + } + + public static validateImageExtension(fileName: string): boolean { + return imageFileExtesionRegex.test(fileName); + } +} diff --git a/client/src/components/common/FileUploader/IFileUploadList.ts b/client/src/components/common/FileUploader/IFileUploadList.ts new file mode 100644 index 00000000..54b50234 --- /dev/null +++ b/client/src/components/common/FileUploader/IFileUploadList.ts @@ -0,0 +1,18 @@ +export enum FileUploadStatus { + NULL = 'NULL', + LOADING = 'LOADING', + COMPLETED = 'COMPLETED' +} + +export interface IFileUpload { + file: File, + loadingStatus: FileUploadStatus, + imageDataUrl?: string +} + +export interface IImageUrl { + url: string; + name: string; + loadingStatus: FileUploadStatus; +} + diff --git a/client/src/components/common/FileUploader/assets/check.svg b/client/src/components/common/FileUploader/assets/check.svg new file mode 100644 index 00000000..58a76a95 --- /dev/null +++ b/client/src/components/common/FileUploader/assets/check.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/client/src/components/common/FileUploader/assets/download-outline.svg b/client/src/components/common/FileUploader/assets/download-outline.svg new file mode 100644 index 00000000..827d394d --- /dev/null +++ b/client/src/components/common/FileUploader/assets/download-outline.svg @@ -0,0 +1,12 @@ + + + + + download_outline + + + diff --git a/client/src/components/common/FileUploader/assets/drag-and-drop-outline.svg b/client/src/components/common/FileUploader/assets/drag-and-drop-outline.svg new file mode 100644 index 00000000..033d7da3 --- /dev/null +++ b/client/src/components/common/FileUploader/assets/drag-and-drop-outline.svg @@ -0,0 +1,14 @@ + + + +Icons/drag-and-drop/outline +Created with Sketch. + + + + diff --git a/client/src/components/common/FileUploader/assets/file-solid.svg b/client/src/components/common/FileUploader/assets/file-solid.svg new file mode 100644 index 00000000..ef54992e --- /dev/null +++ b/client/src/components/common/FileUploader/assets/file-solid.svg @@ -0,0 +1,11 @@ + + + +Icons/file/solid +Created with Sketch. + + + + diff --git a/client/src/components/common/FileUploader/assets/loading.svg b/client/src/components/common/FileUploader/assets/loading.svg new file mode 100644 index 00000000..66c6ada8 --- /dev/null +++ b/client/src/components/common/FileUploader/assets/loading.svg @@ -0,0 +1,13 @@ + + + + +loading_outline + + + diff --git a/client/src/components/common/FileUploader/fileUploadStore/action-types.ts b/client/src/components/common/FileUploader/fileUploadStore/action-types.ts new file mode 100644 index 00000000..9d7cca08 --- /dev/null +++ b/client/src/components/common/FileUploader/fileUploadStore/action-types.ts @@ -0,0 +1,13 @@ +export const ADD_TEMP_TO_FILES = 'ADD_TEMP_TO_FILES'; +export const DELETE_FROM_ALL_LISTS = 'DELETE_FROM_ALL_LISTS'; +export const UPLOAD_FILES = 'UPLOAD_FILES'; +export const SET_PREVIEW_IMAGES = 'SET_PREVIEW_IMAGES'; +export const UPDATE_TEMP_FILES = 'UPDATE_TEMP_FILES'; +export const HANDLE_FILES_CHANGE = 'HANDLE_FILES_CHANGE'; +export const VALIDATE_FILES = 'VALIDATE_FILES'; +export const HANDLE_MULTIPLE_FILE_CHANGE = 'HANDLE_MULTIPLE_FILE_CHANGE'; +export const HANDLE_SINGLE_FILE_CHANGE = 'HANDLE_SINGLE_FILE_CHANGE'; +export const HANDLE_REPLACE_FILES = 'HANDLE_REPLACE_FILES'; +export const HANDLE_URL_CHANGE = 'HANDLE_URL_CHANGE'; +export const UPDATE_REPLACEMENT_FILES = 'UPDATE_REPLACEMENT_FILES'; +export const UPDATE_REPLACEMENT_URL = 'UPDATE_REPLACEMENT_URL'; diff --git a/client/src/components/common/FileUploader/fileUploadStore/actions.ts b/client/src/components/common/FileUploader/fileUploadStore/actions.ts new file mode 100644 index 00000000..78fbc76d --- /dev/null +++ b/client/src/components/common/FileUploader/fileUploadStore/actions.ts @@ -0,0 +1,146 @@ +import {ActionTree} from 'vuex'; +import {FILE_MAX_SIZE, FileUploadErrorType, IFileUploadState, IMAGE_URL_INITIAL} from './index'; +import { + ADD_FILES, + DELETE_FILE, + DELETE_TEMP_FILE, + SET_FILE_LOADING, + SET_FILE_COMPLETED, + SET_FILE_DATA_URL, + SET_IMAGE_STATUS, + SET_FILE_UPLOAD_ERROR, + ADD_TEMP_FILES, + SET_TEMP_FILES, + RESET_UPLOAD_ERRORS, + SET_REPLACEMENT_FILES, RESET_TEMP_FILES, SET_IMAGE_URL, RESET_FILES, SET_REPLACEMENT_URL, RESET_UPLOAD_REPLACEMENTS +} from './mutation-types'; +import { + ADD_TEMP_TO_FILES, + DELETE_FROM_ALL_LISTS, + HANDLE_FILES_CHANGE, + HANDLE_MULTIPLE_FILE_CHANGE, + HANDLE_REPLACE_FILES, + HANDLE_SINGLE_FILE_CHANGE, HANDLE_URL_CHANGE, + SET_PREVIEW_IMAGES, UPDATE_REPLACEMENT_FILES, UPDATE_REPLACEMENT_URL, + UPDATE_TEMP_FILES, + UPLOAD_FILES, + VALIDATE_FILES +} from './action-types'; +import {FileUploaderService} from '../FileUploaderService'; +import {FileUploadStatus, IFileUpload} from '../IFileUploadList'; +import {ALL_UPLOAD_FILES} from './getter-types'; +import {SET_PROJECT_IMAGE} from '../../../../store/modules/projects/mutation-types'; + +export const actions: ActionTree = { + [ADD_TEMP_TO_FILES]({state, commit, dispatch}) { + commit(ADD_FILES, state.tempFiles); + commit(RESET_TEMP_FILES); + dispatch(UPLOAD_FILES); + }, + [UPDATE_TEMP_FILES]({state, dispatch, commit}, payload) { + dispatch(VALIDATE_FILES, payload) + .then(fileUploadList => { + if (fileUploadList.length) { + commit(state.isMultiple ? ADD_TEMP_FILES : SET_TEMP_FILES, fileUploadList); + } + }); + }, + [VALIDATE_FILES]({state, commit}, payload) { + return payload.filter((item: IFileUpload) => { + let isValid = true; + if (!FileUploaderService.validateFileSize(item.file, FILE_MAX_SIZE)) { + commit(SET_FILE_UPLOAD_ERROR, {errorType: FileUploadErrorType.FILE_SIZE, fileName: item.file.name}); + isValid = false; + } + if (state.isImageUpload && !FileUploaderService.validateImageExtension(item.file.name)) { + commit(SET_FILE_UPLOAD_ERROR, {errorType: FileUploadErrorType.IMAGE_EXTENSION, fileName: item.file.name}); + isValid = false; + } else if (!FileUploaderService.validateFileExtension(item.file.name)) { + commit(SET_FILE_UPLOAD_ERROR, {errorType: FileUploadErrorType.FILE_EXTENSION, fileName: item.file.name}); + isValid = false; + } + return isValid; + }); + }, + [DELETE_FROM_ALL_LISTS]({commit, dispatch}, payload) { + commit(DELETE_FILE, payload); + commit(DELETE_TEMP_FILE, payload); + }, + [UPLOAD_FILES]({commit, state}) { + return Promise.all(state.files.map((file: IFileUpload) => { + commit(SET_FILE_LOADING, file); + return FileUploaderService.uploadFiles(file) + .then((res) => { + commit(SET_PROJECT_IMAGE, res.data.filename); + return commit(SET_FILE_COMPLETED, file) + }); + })); + }, + [SET_PREVIEW_IMAGES]({state, commit, getters}) { + if (state.isImageUpload) { + getters[ALL_UPLOAD_FILES].forEach((fileUpload: IFileUpload) => { + if (!fileUpload.imageDataUrl) { + const reader = new FileReader(); + reader.addEventListener('load', () => { + commit(SET_FILE_DATA_URL, {file: fileUpload, dataUrl: reader.result}); + }, false); + reader.readAsDataURL(fileUpload.file); + } + }); + } + }, + [HANDLE_FILES_CHANGE]({state, commit, dispatch}, payload) { + commit(RESET_UPLOAD_ERRORS); + dispatch( + state.isMultiple ? HANDLE_MULTIPLE_FILE_CHANGE : HANDLE_SINGLE_FILE_CHANGE, + FileUploaderService.fileListToFileUploadList(payload) + ); + }, + [HANDLE_MULTIPLE_FILE_CHANGE]({dispatch}, payload) { + dispatch(UPDATE_TEMP_FILES, payload) + .then(() => dispatch(SET_PREVIEW_IMAGES)); + }, + [HANDLE_SINGLE_FILE_CHANGE]({state, dispatch, getters}, payload) { + if (getters[ALL_UPLOAD_FILES].length || state.imageUrl.url) { + dispatch(UPDATE_REPLACEMENT_FILES, payload); + } else { + dispatch(HANDLE_MULTIPLE_FILE_CHANGE, payload); + } + }, + [HANDLE_REPLACE_FILES]({state, commit, dispatch}) { + commit(RESET_FILES); + if (state.replacementFiles.length) { + commit(SET_TEMP_FILES, state.replacementFiles); + commit(SET_IMAGE_URL, IMAGE_URL_INITIAL); + dispatch(SET_PREVIEW_IMAGES); + } else if (state.replacementUrl) { + commit(RESET_TEMP_FILES); + commit(SET_IMAGE_URL, FileUploaderService.urlToImageUrl(state.replacementUrl)); + } + commit(RESET_UPLOAD_REPLACEMENTS); + }, + [UPDATE_REPLACEMENT_FILES]({commit, dispatch}, payload) { + dispatch(VALIDATE_FILES, payload) + .then(fileUploadList => { + commit(SET_REPLACEMENT_FILES, fileUploadList); + }); + }, + [UPDATE_REPLACEMENT_URL]({commit}, payload) { + const urlFileName = FileUploaderService.getFileNameFromUrl(payload); + if (!FileUploaderService.validateImageExtension(urlFileName)) { + commit(SET_FILE_UPLOAD_ERROR, {errorType: FileUploadErrorType.IMAGE_EXTENSION, fileName: urlFileName}); + } else { + commit(SET_REPLACEMENT_URL, payload); + } + }, + [HANDLE_URL_CHANGE]({state, commit, getters, dispatch}, payload) { + commit(RESET_UPLOAD_ERRORS); + dispatch(UPDATE_REPLACEMENT_URL, payload) + .then(() => { + if (!getters[ALL_UPLOAD_FILES].length && !state.imageUrl.url) { + commit(SET_IMAGE_URL, FileUploaderService.urlToImageUrl(state.replacementUrl)); + commit(SET_REPLACEMENT_URL, ''); + } + }); + } +}; diff --git a/client/src/components/common/FileUploader/fileUploadStore/getter-types.ts b/client/src/components/common/FileUploader/fileUploadStore/getter-types.ts new file mode 100644 index 00000000..f8190858 --- /dev/null +++ b/client/src/components/common/FileUploader/fileUploadStore/getter-types.ts @@ -0,0 +1,14 @@ +export const ALL_UPLOAD_FILES = 'ALL_UPLOAD_FILES'; +export const UPLOAD_IMAGE_URL = 'UPLOAD_IMAGE_URL'; +export const IS_IMAGE_URL = 'IS_IMAGE_URL'; +export const IS_IMAGE_UPLOAD = 'IS_IMAGE_UPLOAD'; +export const IS_UPLOAD_MULTIPLE = 'IS_UPLOAD_MULTIPLE'; +export const IS_UPLOAD_MODAL_OPEN = 'IS_UPLOAD_MODAL_OPEN'; +export const FILE_UPLOAD_ERRORS = 'FILE_UPLOAD_ERRORS'; +export const FILE_EXTENSION_ERROR = 'FILE_EXTENSION_ERROR'; +export const FILE_SIZE_ERROR = 'FILE_SIZE_ERROR'; +export const IMAGE_EXTENSION_ERROR = 'IMAGE_EXTENSION_ERROR'; +export const HAS_UPLOAD_ERRORS = 'HAS_UPLOAD_ERRORS'; +export const REPLACEMENT_FILES = 'REPLACEMENT_FILES'; +export const REPLACEMENT_URL = 'REPLACEMENT_URL'; +export const HAS_UPLOAD_REPLACEMENTS = 'HAS_UPLOAD_REPLACEMENTS'; diff --git a/client/src/components/common/FileUploader/fileUploadStore/index.ts b/client/src/components/common/FileUploader/fileUploadStore/index.ts new file mode 100644 index 00000000..d831d20c --- /dev/null +++ b/client/src/components/common/FileUploader/fileUploadStore/index.ts @@ -0,0 +1,104 @@ +import {Module} from 'vuex'; +import {FileUploadStatus, IFileUpload, IImageUrl} from '../IFileUploadList'; +import {mutations} from './mutations'; +import {actions} from './actions'; +import { + ALL_UPLOAD_FILES, + UPLOAD_IMAGE_URL, + IS_IMAGE_UPLOAD, + IS_IMAGE_URL, + IS_UPLOAD_MULTIPLE, + FILE_UPLOAD_ERRORS, + HAS_UPLOAD_ERRORS, + FILE_EXTENSION_ERROR, + FILE_SIZE_ERROR, + IMAGE_EXTENSION_ERROR, + REPLACEMENT_URL, + REPLACEMENT_FILES, + HAS_UPLOAD_REPLACEMENTS, IS_UPLOAD_MODAL_OPEN +} from './getter-types'; + +export enum FileUploadErrorType { + FILE_SIZE = 'FILE_SIZE', + FILE_EXTENSION = 'FILE_EXTENSION', + IMAGE_EXTENSION = 'IMAGE_EXTENSION', +} + +export interface IFileUploadError { + hasError: boolean; + fileNames: string[]; +} + +export type IFileUploadErrors = { + [k in FileUploadErrorType]: IFileUploadError +}; + +export interface IFileUploadState { + isImageUpload: boolean, + isMultiple: boolean, + isUploadModalOpen: boolean, + files: IFileUpload[], + tempFiles: IFileUpload[], + isImageUrl: boolean, + imageUrl: IImageUrl, + replacementFiles: IFileUpload[], + replacementUrl: string, + errors: IFileUploadErrors +} + +export const IMAGE_URL_INITIAL = { + url: '', + name: '', + loadingStatus: FileUploadStatus.NULL +}; + +export const FILE_MAX_SIZE = 10485760; // 10Mb + +const fileUploadState: Module = { + state: { + files: [], + tempFiles: [], + isImageUpload: false, + isMultiple: false, + isUploadModalOpen: false, + isImageUrl: false, + imageUrl: IMAGE_URL_INITIAL, + replacementFiles: [], + replacementUrl: '', + errors: { + [FileUploadErrorType.FILE_SIZE]: { + hasError: false, + fileNames: [] + }, + [FileUploadErrorType.FILE_EXTENSION]: { + hasError: false, + fileNames: [] + }, + [FileUploadErrorType.IMAGE_EXTENSION]: { + hasError: false, + fileNames: [] + } + } + }, + mutations, + actions, + getters: { + [ALL_UPLOAD_FILES]: state => [...state.files, ...state.tempFiles], + [UPLOAD_IMAGE_URL]: state => state.imageUrl, + [IS_IMAGE_URL]: state => state.isImageUrl, + [IS_IMAGE_UPLOAD]: state => state.isImageUpload, + [IS_UPLOAD_MULTIPLE]: state => state.isMultiple, + [IS_UPLOAD_MODAL_OPEN]: state => state.isUploadModalOpen, + [FILE_UPLOAD_ERRORS]: state => state.errors, + [FILE_EXTENSION_ERROR]: state => state.errors[FileUploadErrorType.FILE_EXTENSION], + [FILE_SIZE_ERROR]: state => state.errors[FileUploadErrorType.FILE_SIZE], + [IMAGE_EXTENSION_ERROR]: state => state.errors[FileUploadErrorType.IMAGE_EXTENSION], + [HAS_UPLOAD_ERRORS]: state => Object.keys(state.errors) + .some(key => state.errors[key as FileUploadErrorType].hasError), + [REPLACEMENT_FILES]: state => state.replacementFiles, + [REPLACEMENT_URL]: state => state.replacementUrl, + [HAS_UPLOAD_REPLACEMENTS]: state => state.replacementFiles.length || state.replacementUrl + } +}; + +export default fileUploadState; diff --git a/client/src/components/common/FileUploader/fileUploadStore/mutation-types.ts b/client/src/components/common/FileUploader/fileUploadStore/mutation-types.ts new file mode 100644 index 00000000..0c4a7055 --- /dev/null +++ b/client/src/components/common/FileUploader/fileUploadStore/mutation-types.ts @@ -0,0 +1,22 @@ +export const ADD_FILES = 'ADD_FILES'; +export const ADD_TEMP_FILES = 'ADD_TEMP_FILES'; +export const DELETE_FILE = 'DELETE_FILE'; +export const DELETE_TEMP_FILE = 'DELETE_TEMP_FILE'; +export const RESET_TEMP_FILES = 'RESET_TEMP_FILES'; +export const RESET_FILES = 'RESET_FILES'; +export const SET_FILE_LOADING = 'SET_FILE_STATUS'; +export const SET_FILE_COMPLETED = 'SET_FILE_COMPLETED'; +export const SET_FILE_DATA_URL = 'SET_FILE_DATA_URL'; +export const SET_FILES = 'SET_FILES'; +export const SET_TEMP_FILES = 'SET_TEMP_FILES'; +export const SET_IS_IMAGE_URL = 'SET_IS_IMAGE_URL'; +export const SET_IMAGE_URL = 'SET_IMAGE_URL'; +export const SET_IS_IMAGE_UPLOAD = 'SET_IS_IMAGE_UPLOAD'; +export const SET_IS_UPLOAD_MULTIPLE = 'SET_IS_UPLOAD_MULTIPLE'; +export const SET_IS_UPLOAD_MODAL_OPEN = 'SET_IS_UPLOAD_MODAL_OPEN'; +export const SET_IMAGE_STATUS = 'SET_IMAGE_STATUS'; +export const RESET_UPLOAD_ERRORS = 'RESET_UPLOAD_ERRORS'; +export const SET_FILE_UPLOAD_ERROR = 'SET_FILE_UPLOAD_ERROR'; +export const SET_REPLACEMENT_FILES = 'SET_REPLACEMENT_FILES'; +export const SET_REPLACEMENT_URL = 'SET_REPLACEMENT_URL'; +export const RESET_UPLOAD_REPLACEMENTS = 'RESET_UPLOAD_REPLACEMENTS'; diff --git a/client/src/components/common/FileUploader/fileUploadStore/mutations.ts b/client/src/components/common/FileUploader/fileUploadStore/mutations.ts new file mode 100644 index 00000000..7a2d95d4 --- /dev/null +++ b/client/src/components/common/FileUploader/fileUploadStore/mutations.ts @@ -0,0 +1,121 @@ +import {MutationTree} from 'vuex'; +import {FileUploadErrorType, IFileUploadState} from './index'; +import { + ADD_FILES, + SET_FILE_LOADING, + SET_FILE_COMPLETED, + ADD_TEMP_FILES, + DELETE_FILE, + DELETE_TEMP_FILE, + RESET_FILES, + RESET_TEMP_FILES, + SET_FILE_DATA_URL, + SET_FILES, + SET_TEMP_FILES, + SET_IMAGE_URL, + SET_IS_IMAGE_URL, + SET_IMAGE_STATUS, + RESET_UPLOAD_ERRORS, + SET_FILE_UPLOAD_ERROR, + SET_IS_IMAGE_UPLOAD, + SET_IS_UPLOAD_MULTIPLE, + SET_REPLACEMENT_FILES, + SET_REPLACEMENT_URL, RESET_UPLOAD_REPLACEMENTS, SET_IS_UPLOAD_MODAL_OPEN +} from './mutation-types'; +import {FileUploadStatus, IFileUpload} from '../IFileUploadList'; +import {FileUploaderService} from '../FileUploaderService'; + +export const mutations: MutationTree = { + [ADD_FILES](state, filesUploadList: IFileUpload[]) { + filesUploadList.forEach((fileUpload: IFileUpload) => { + if (!FileUploaderService.isFileInUploadList(fileUpload.file, state.files)) { + state.files.push(fileUpload); + } + }); + }, + [ADD_TEMP_FILES](state, filesUploadList: IFileUpload[]) { + filesUploadList.forEach((fileUpload: IFileUpload) => { + if (!FileUploaderService.isFileInUploadList(fileUpload.file, state.tempFiles) && + !FileUploaderService.isFileInUploadList(fileUpload.file, state.files)) { + state.tempFiles.push(fileUpload); + } + }); + }, + [SET_FILES](state, payload) { + state.files = payload; + }, + [SET_TEMP_FILES](state, payload) { + state.tempFiles = payload; + }, + [RESET_FILES](state) { + state.files = []; + }, + [RESET_TEMP_FILES](state) { + state.tempFiles = []; + }, + [DELETE_FILE](state, payload) { + const index = state.files.indexOf(payload); + if (index > -1) { + state.files.splice(index, 1); + } + }, + [DELETE_TEMP_FILE](state, payload) { + const index = state.tempFiles.indexOf(payload); + if (index > -1) { + state.tempFiles.splice(index, 1); + } + }, + [SET_FILE_LOADING](state, payload) { + state.files[state.files.indexOf(payload)].loadingStatus = FileUploadStatus.LOADING; + }, + [SET_FILE_COMPLETED](state, payload) { + state.files[state.files.indexOf(payload)].loadingStatus = FileUploadStatus.COMPLETED; + }, + [SET_FILE_DATA_URL](state, {file, dataUrl}) { + if (state.files.indexOf(file) > -1) { + state.files[state.files.indexOf(file)].imageDataUrl = dataUrl; + } + if (state.tempFiles.indexOf(file) > -1) { + state.tempFiles[state.tempFiles.indexOf(file)].imageDataUrl = dataUrl; + } + }, + [SET_IS_IMAGE_URL](state, payload) { + state.isImageUrl = payload; + }, + [SET_IS_IMAGE_UPLOAD](state, payload) { + state.isImageUpload = payload; + }, + [SET_IS_UPLOAD_MULTIPLE](state, payload) { + state.isMultiple = payload; + }, + [SET_IMAGE_URL](state, payload) { + state.imageUrl = payload; + }, + [SET_IMAGE_STATUS](state, payload) { + state.imageUrl.loadingStatus = payload; + }, + [RESET_UPLOAD_ERRORS](state) { + (Object.keys(state.errors) as FileUploadErrorType[]) + .forEach(key => { + state.errors[key].hasError = false; + state.errors[key].fileNames = []; + }); + }, + [SET_FILE_UPLOAD_ERROR](state, {errorType, fileName}: {errorType: FileUploadErrorType; fileName: string;}) { + state.errors[errorType].hasError = true; + state.errors[errorType].fileNames.push(fileName); + }, + [SET_REPLACEMENT_FILES](state, payload) { + state.replacementFiles = payload; + }, + [SET_REPLACEMENT_URL](state, payload) { + state.replacementUrl = payload; + }, + [RESET_UPLOAD_REPLACEMENTS](state) { + state.replacementFiles = []; + state.replacementUrl = ''; + }, + [SET_IS_UPLOAD_MODAL_OPEN](state, payload: boolean) { + state.isUploadModalOpen = payload; + } +}; diff --git a/client/src/components/common/RadioButton/RadioButton.vue b/client/src/components/common/RadioButton/RadioButton.vue new file mode 100644 index 00000000..6f96bfad --- /dev/null +++ b/client/src/components/common/RadioButton/RadioButton.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/client/src/components/common/Ribbon/Ribbon.vue b/client/src/components/common/Ribbon/Ribbon.vue new file mode 100644 index 00000000..1217c2c6 --- /dev/null +++ b/client/src/components/common/Ribbon/Ribbon.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/client/src/components/common/Stepper/Stepper.vue b/client/src/components/common/Stepper/Stepper.vue new file mode 100644 index 00000000..9c612dc3 --- /dev/null +++ b/client/src/components/common/Stepper/Stepper.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/client/src/components/common/assets/arrowDown.svg b/client/src/components/common/assets/arrowDown.svg new file mode 100644 index 00000000..bb2288f3 --- /dev/null +++ b/client/src/components/common/assets/arrowDown.svg @@ -0,0 +1,20 @@ + + + + + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/common/assets/download.svg b/client/src/components/common/assets/download.svg new file mode 100644 index 00000000..16897e6c --- /dev/null +++ b/client/src/components/common/assets/download.svg @@ -0,0 +1,17 @@ + + + + download_outline + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/common/assets/sort.svg b/client/src/components/common/assets/sort.svg new file mode 100644 index 00000000..0ebf326f --- /dev/null +++ b/client/src/components/common/assets/sort.svg @@ -0,0 +1,17 @@ + + + + sort_outline + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/common/index.ts b/client/src/components/common/index.ts new file mode 100644 index 00000000..6dbfa9da --- /dev/null +++ b/client/src/components/common/index.ts @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import Checkbox from './Checkbox/Checkbox.vue'; +import CommonModal from './CommonModal/CommonModal.vue'; +import ConfirmModal from './ConfirmModal/ConfirmModal.vue'; +import FileUploader from './FileUploader/FileUploader.vue'; +import RadioButton from './RadioButton/RadioButton.vue'; +import Autocomplete from './Autocomplete/Autocomplete.vue'; +import Ribbon from './Ribbon/Ribbon.vue'; +import Accordion from './Accordion/Accordion.vue'; +import ErrorMessage from './ErrorMessage/ErrorMessage.vue'; + +const components: { [key: string]: {} } = { + Checkbox, + ConfirmModal, + CommonModal, + FileUploader, + RadioButton, + Autocomplete, + Ribbon, + Accordion, + ErrorMessage +}; + +Object.keys(components) + .forEach(componentName => Vue.component(componentName, components[componentName])); diff --git a/client/src/components/employees/.DS_Store b/client/src/components/employees/.DS_Store new file mode 100644 index 00000000..f8aaa68e Binary files /dev/null and b/client/src/components/employees/.DS_Store differ diff --git a/client/src/components/employees/EmployeeItem/EmployeeItem.vue b/client/src/components/employees/EmployeeItem/EmployeeItem.vue new file mode 100644 index 00000000..10b18bcf --- /dev/null +++ b/client/src/components/employees/EmployeeItem/EmployeeItem.vue @@ -0,0 +1,242 @@ + + + + + + diff --git a/client/src/components/employees/ScheduleItem/ScheduleItem.vue b/client/src/components/employees/ScheduleItem/ScheduleItem.vue new file mode 100644 index 00000000..8c9f6df6 --- /dev/null +++ b/client/src/components/employees/ScheduleItem/ScheduleItem.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/client/src/components/employees/assets/person.svg b/client/src/components/employees/assets/person.svg new file mode 100644 index 00000000..60b29490 --- /dev/null +++ b/client/src/components/employees/assets/person.svg @@ -0,0 +1,23 @@ + + + + person + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/projects/AppliedFilters/AppliedFilters.vue b/client/src/components/projects/AppliedFilters/AppliedFilters.vue new file mode 100644 index 00000000..b7ae618d --- /dev/null +++ b/client/src/components/projects/AppliedFilters/AppliedFilters.vue @@ -0,0 +1,195 @@ + + + + + + diff --git a/client/src/components/projects/AppliedFilters/assets/export_outline.svg b/client/src/components/projects/AppliedFilters/assets/export_outline.svg new file mode 100644 index 00000000..eec5cb3c --- /dev/null +++ b/client/src/components/projects/AppliedFilters/assets/export_outline.svg @@ -0,0 +1,12 @@ + + + + + export_outline + + + diff --git a/client/src/components/projects/AppliedFilters/assets/plus_outline.svg b/client/src/components/projects/AppliedFilters/assets/plus_outline.svg new file mode 100644 index 00000000..04eebc2b --- /dev/null +++ b/client/src/components/projects/AppliedFilters/assets/plus_outline.svg @@ -0,0 +1,15 @@ + + + + plus_outline + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/projects/AppliedFilters/assets/presentation-file_outline.svg b/client/src/components/projects/AppliedFilters/assets/presentation-file_outline.svg new file mode 100644 index 00000000..c431d81e --- /dev/null +++ b/client/src/components/projects/AppliedFilters/assets/presentation-file_outline.svg @@ -0,0 +1,12 @@ + + + + + presentation-file_outline + + + diff --git a/client/src/components/projects/Project/Project.vue b/client/src/components/projects/Project/Project.vue new file mode 100644 index 00000000..2be2dc13 --- /dev/null +++ b/client/src/components/projects/Project/Project.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/client/src/components/projects/Project/ProjectFooter.vue b/client/src/components/projects/Project/ProjectFooter.vue new file mode 100644 index 00000000..3293d0a8 --- /dev/null +++ b/client/src/components/projects/Project/ProjectFooter.vue @@ -0,0 +1,70 @@ + + + diff --git a/client/src/components/projects/Project/assets/copy.svg b/client/src/components/projects/Project/assets/copy.svg new file mode 100644 index 00000000..d852b486 --- /dev/null +++ b/client/src/components/projects/Project/assets/copy.svg @@ -0,0 +1,20 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/projects/Project/assets/edit.svg b/client/src/components/projects/Project/assets/edit.svg new file mode 100644 index 00000000..eef71454 --- /dev/null +++ b/client/src/components/projects/Project/assets/edit.svg @@ -0,0 +1,18 @@ + + + + + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/projects/Project/assets/history.svg b/client/src/components/projects/Project/assets/history.svg new file mode 100644 index 00000000..4de6ed6c --- /dev/null +++ b/client/src/components/projects/Project/assets/history.svg @@ -0,0 +1,20 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/projects/Project/assets/presentation.svg b/client/src/components/projects/Project/assets/presentation.svg new file mode 100644 index 00000000..c8cec2c8 --- /dev/null +++ b/client/src/components/projects/Project/assets/presentation.svg @@ -0,0 +1,12 @@ + + + + + presentation-file_outline + + + diff --git a/client/src/components/projects/Project/assets/trash.svg b/client/src/components/projects/Project/assets/trash.svg new file mode 100644 index 00000000..c2a10634 --- /dev/null +++ b/client/src/components/projects/Project/assets/trash.svg @@ -0,0 +1,20 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/projects/ProjectCard/ProjectCard.test.ts b/client/src/components/projects/ProjectCard/ProjectCard.test.ts new file mode 100644 index 00000000..b92367fa --- /dev/null +++ b/client/src/components/projects/ProjectCard/ProjectCard.test.ts @@ -0,0 +1,34 @@ +import ProjectCard from './ProjectCard.vue'; +import {IProject} from '../../../shared/interfaces/IProject'; +import {ISchedule} from '../../../shared/interfaces/ISchedule'; +import {mount} from '@vue/test-utils'; +import {IProgram} from '../../../shared/interfaces/IProgram'; +import {IType} from '../../../shared/interfaces/IType'; +import {IDomain} from '../../../shared/interfaces/IDomain'; +import {ICustomer} from '../../../shared/interfaces/ICustomer'; +import {ILine} from '../../../shared/interfaces/ILine'; +import {ITechnology} from '../../../shared/interfaces/ITechnology'; +import {TestMocks} from '../../../shared/classes/TestMocks'; + +describe('ProjectChange Card', () => { + + it('should render correct contents', () => { + + + const project = TestMocks.TestProject(); + + const wrapper = mount(ProjectCard, { + propsData: { + project: project + }, + filters: { + date(value) { + return "" + } + } + }); + + expect(wrapper.props().project).toMatchObject(project); + + }); +}); diff --git a/client/src/components/projects/ProjectCard/ProjectCard.vue b/client/src/components/projects/ProjectCard/ProjectCard.vue new file mode 100644 index 00000000..cd2fccf7 --- /dev/null +++ b/client/src/components/projects/ProjectCard/ProjectCard.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/client/src/components/projects/ProjectChange/ProjectChange.vue b/client/src/components/projects/ProjectChange/ProjectChange.vue new file mode 100644 index 00000000..6cd70c46 --- /dev/null +++ b/client/src/components/projects/ProjectChange/ProjectChange.vue @@ -0,0 +1,605 @@ +