Compare commits
No commits in common. "typescript" and "main" have entirely different histories.
typescript
...
main
|
@ -1,3 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie <= 8
|
||||
|
|
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
32
.eslintrc.js
32
.eslintrc.js
|
@ -3,29 +3,15 @@ module.exports = {
|
|||
env: {
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint"
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
"**/__tests__/*.{j,t}s?(x)",
|
||||
"**/tests/unit/**/*.spec.{j,t}s?(x)"
|
||||
],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
}
|
||||
}
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,7 +2,6 @@
|
|||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
@ -11,7 +10,6 @@ node_modules
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
|
|
29
README.md
29
README.md
|
@ -1,29 +1,2 @@
|
|||
# Rebuilding Platform.sh with Vue.js, TypeScript and Tailwind CSS
|
||||
# Rebuilding Platform.sh with Vue.js and Tailwind CSS
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Run your unit tests
|
||||
```
|
||||
yarn test:unit
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
|
@ -1,3 +1,5 @@
|
|||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"]
|
||||
};
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,3 +1,30 @@
|
|||
module.exports = {
|
||||
preset: "@vue/cli-plugin-unit-jest/presets/typescript-and-babel"
|
||||
};
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'vue'
|
||||
],
|
||||
transform: {
|
||||
'^.+\\.vue$': 'vue-jest',
|
||||
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
|
||||
'^.+\\.jsx?$': 'babel-jest'
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'/node_modules/'
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1'
|
||||
},
|
||||
snapshotSerializers: [
|
||||
'jest-serializer-vue'
|
||||
],
|
||||
testMatch: [
|
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||
],
|
||||
testURL: 'http://localhost/',
|
||||
watchPlugins: [
|
||||
'jest-watch-typeahead/filename',
|
||||
'jest-watch-typeahead/testname'
|
||||
]
|
||||
}
|
||||
|
|
56
package.json
56
package.json
|
@ -5,39 +5,35 @@
|
|||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"lint": "vue-cli-service lint"
|
||||
"lint": "vue-cli-service lint",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss7-compat": "^2.0.2",
|
||||
"autoprefixer": "^9",
|
||||
"core-js": "^3.6.5",
|
||||
"postcss": "^7",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
|
||||
"tailwindcss-interaction-variants": "^5.0.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-router": "^3.2.0"
|
||||
"core-js": "^2.6.5",
|
||||
"postcss": "^7.0.17",
|
||||
"sugarss": "^2.0.0",
|
||||
"tailwindcss-interaction-variants": "^2.0.0-beta.1",
|
||||
"tailwindcss-spaced-items": "^0.1.0",
|
||||
"tailwindcss-transforms": "^2.2.0",
|
||||
"tailwindcss-visuallyhidden": "^1.0.2",
|
||||
"vue": "^2.6.10",
|
||||
"vue-router": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.19",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
"@fullhuman/postcss-purgecss": "^1.2.0",
|
||||
"@vue/cli-plugin-babel": "^3.6.0",
|
||||
"@vue/cli-plugin-eslint": "^3.6.0",
|
||||
"@vue/cli-plugin-unit-jest": "^3.6.3",
|
||||
"@vue/cli-service": "^3.10.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"postcss-import": "^12.0.1",
|
||||
"tailwindcss": "^1.0.6",
|
||||
"vue-template-compiler": "^2.5.21"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
module.exports = {
|
||||
parser: 'sugarss',
|
||||
plugins: [
|
||||
require('tailwindcss')
|
||||
require('postcss-import'),
|
||||
require('tailwindcss'),
|
||||
require('postcss-nested'),
|
||||
require('autoprefixer'),
|
||||
process.env.NODE_ENV === 'production' && require('@fullhuman/postcss-purgecss')({
|
||||
content: [
|
||||
'./src/**/*.vue',
|
||||
'./public/index.html',
|
||||
],
|
||||
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
2
public/_redirects
Normal file
2
public/_redirects
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://rebuilding-platformsh.netlify.com/* https://rebuilding-platformsh.oliverdavies.uk/:splat 301!
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<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>
|
||||
<title>rebuilding-platformsh</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,700" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
<strong>We're sorry but rebuilding-platformsh doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
|
|
27
src/App.vue
27
src/App.vue
|
@ -1,16 +1,29 @@
|
|||
<template>
|
||||
<div id="app" class="min-h-screen antialiased font-sans bg-gray-200">
|
||||
<div class="min-h-screen antialiased font-sans bg-gray-200">
|
||||
<alert-message>
|
||||
<p>
|
||||
<!-- eslint-disable-next-line -->
|
||||
A clone of <a href="https://platform.sh">Platform.sh</a>’s hosting dashboard.<br class="hidden sm:inline" />
|
||||
<!-- eslint-disable-next-line -->
|
||||
Built with <a href="https://vuejs.org">Vue.js</a> and <a href="https://tailwindcss.com">Tailwind CSS</a> by <a href="https://www.oliverdavies.uk">Oliver Davies</a>.
|
||||
A clone of <a href="https://platform.sh">Platform.sh</a>’s new hosting dashboard.<br class="hidden sm:inline">
|
||||
Built with <a href="https://vuejs.org">Vue.js</a>
|
||||
and <a href="https://tailwindcss.com">Tailwind CSS</a>
|
||||
by <a href="https://www.oliverdavies.uk">Oliver Davies</a>.
|
||||
</p>
|
||||
</alert-message>
|
||||
|
||||
<router-view />
|
||||
<router-view :projects="projects" ></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style src="../assets/css/tailwind.pcss" lang="postcss" />
|
||||
<style src="./assets/css/tailwind.css"></style>
|
||||
|
||||
<script>
|
||||
import projects from './data/projects.json'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
title: 'Rebuilding Platform.sh',
|
||||
projects
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
2
src/assets/css/base.css
Normal file
2
src/assets/css/base.css
Normal file
|
@ -0,0 +1,2 @@
|
|||
a:hover
|
||||
@apply underline
|
1
src/assets/css/components.css
Normal file
1
src/assets/css/components.css
Normal file
|
@ -0,0 +1 @@
|
|||
@import './components/search-form.css'
|
3
src/assets/css/components/search-form.css
Normal file
3
src/assets/css/components/search-form.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.search-form
|
||||
> input:focus + svg
|
||||
@apply opacity-100
|
7
src/assets/css/tailwind.css
Normal file
7
src/assets/css/tailwind.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
@import 'tailwindcss/base'
|
||||
@import './base.css'
|
||||
|
||||
@import 'tailwindcss/components'
|
||||
@import './components.css'
|
||||
|
||||
@import 'tailwindcss/utilities'
|
BIN
src/assets/img/screenshots/default.jpg
Normal file
BIN
src/assets/img/screenshots/default.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
src/assets/img/screenshots/download.jpg
Normal file
BIN
src/assets/img/screenshots/download.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 477 KiB |
BIN
src/assets/img/screenshots/inviqa.jpeg
Normal file
BIN
src/assets/img/screenshots/inviqa.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
BIN
src/assets/img/screenshots/oliver-davies.png
Normal file
BIN
src/assets/img/screenshots/oliver-davies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 237 KiB |
BIN
src/assets/img/screenshots/php-south-wales.png
Normal file
BIN
src/assets/img/screenshots/php-south-wales.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
14
src/components/AlertMessage.vue
Normal file
14
src/components/AlertMessage.vue
Normal file
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<section class="bg-gun-powder p-3 text-center text-white text-sm font-semibold">
|
||||
<slot></slot>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style type="postcss" scoped>
|
||||
a
|
||||
@apply underline
|
||||
|
||||
&:hover,
|
||||
&:focus
|
||||
@apply no-underline
|
||||
</style>
|
27
src/components/Banner.vue
Normal file
27
src/components/Banner.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div>
|
||||
<header class="bg-gray-900 text-white pt-4 pb-12">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex-1 flex justify-between items-center -mx-4">
|
||||
<div class="w-1/7 px-4">
|
||||
<router-link :to="{ name: 'projects' }">
|
||||
<svg alt="icon" class="w-6 h-6" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg"><defs><polygon id="path-1" points="0 0 27.6677333 0 27.6677333 11.0021333 0 11.0021333"></polygon><polygon id="path-3" points="0 0.0373333333 27.6677333 0.0373333333 27.6677333 3.7744 0 3.7744"></polygon><polygon id="path-5" points="0 0.365866667 27.6677333 0.365866667 27.6677333 6.01066667 0 6.01066667"></polygon></defs><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-61.000000, -43.000000)"><g transform="translate(61.000000, 43.000000)"><g><mask id="mask-2" fill="white"><use xlink:href="#path-1"></use></mask><g></g><polygon fill="#ffffff" mask="url(#mask-2)" points="-1.86666667 12.8688 29.5344 12.8688 29.5344 -1.86666667 -1.86666667 -1.86666667"></polygon></g><g transform="translate(0.000000, 23.893333)"><mask id="mask-4" fill="white"><use xlink:href="#path-3"></use></mask><polygon fill="#ffffff" mask="url(#mask-4)" points="-1.86666667 5.64106667 29.5344 5.64106667 29.5344 -1.82933333 -1.86666667 -1.82933333"></polygon></g><g transform="translate(0.000000, 14.186667)"><mask id="mask-6" fill="white"><use xlink:href="#path-5"></use></mask><polygon fill="#ffffff" mask="url(#mask-6)" points="-1.86666667 7.87733333 29.5344 7.87733333 29.5344 -1.5008 -1.86666667 -1.5008"></polygon></g></g></g></g></svg>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<slot name="banner-left"></slot>
|
||||
|
||||
<div class="flex justify-end px-4">
|
||||
<button type="button" class="w-full inline-flex py-1 px-2 rounded font-semibold hover:bg-gray-700">
|
||||
Oliver Davies
|
||||
<svg class="ml-2 w-6 h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.90002 9.8999C6.90002 9.5999 7.00002 9.3999 7.20002 9.1999C7.60002 8.7999 8.20002 8.7999 8.60002 9.1999L12.1 12.6999L15.6 9.1999C16 8.7999 16.6 8.7999 17 9.1999C17.4 9.5999 17.4 10.1999 17 10.5999L12.8 14.7999C12.4 15.1999 11.8 15.1999 11.4 14.7999L7.20002 10.5999C7.00002 10.3999 6.90002 10.1999 6.90002 9.8999Z" fill="#ffffff"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container mx-auto px-4 py-3 -mt-8 mb-4 bg-charade shadow-lg">
|
||||
<slot name="sub-banner"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
5
src/components/PageTitle.vue
Normal file
5
src/components/PageTitle.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<h1 class="font-bold text-2xl text-gray-800">
|
||||
<slot></slot>
|
||||
</h1>
|
||||
</template>
|
22
src/components/ProjectBreadcrumb.vue
Normal file
22
src/components/ProjectBreadcrumb.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="flex-1 flex items-center text-xs">
|
||||
<ul class="flex spaced-x-1">
|
||||
<li class="flex items-center spaced-x-1">
|
||||
<router-link :to="{ name: 'projects' }" class="font-semibold text-white opacity-75">Projects</router-link>
|
||||
<svg class="h-6 w-6 fill-current text-white opacity-75 -rotate-90" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.9 9.9c0-.3.1-.5.3-.7.4-.4 1-.4 1.4 0l3.5 3.5 3.5-3.5c.4-.4 1-.4 1.4 0 .4.4.4 1 0 1.4l-4.2 4.2c-.4.4-1 .4-1.4 0l-4.2-4.2c-.2-.2-.3-.4-.3-.7z"/></svg>
|
||||
</li>
|
||||
<li class="flex items-center font-semibold" v-text="project.name"/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
project: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
37
src/components/ProjectCard.vue
Normal file
37
src/components/ProjectCard.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<article>
|
||||
<router-link :to="{ name: 'project', params: { id: project.id }}" class="group block hover:no-underline">
|
||||
<span class="block bg-gray-100 shadow-lg rounded overflow-hidden">
|
||||
<span class="block p-6">
|
||||
<h2 class="font-bold text-gray-800 group-hover:underline" v-text="project.name"/>
|
||||
<p class="text-gray-600" v-text="project.owner"/>
|
||||
<img v-bind="{
|
||||
alt: `Screenshot of ${project.name}`,
|
||||
src: projectImage
|
||||
}" class="w-full mt-4 rounded shadow-2xl" aria-hidden/>
|
||||
</span>
|
||||
|
||||
<span class="-mt-10 py-5 px-8 block relative bg-white">
|
||||
<p v-text="project.region"/>
|
||||
</span>
|
||||
</span>
|
||||
</router-link>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
project: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
projectImage () {
|
||||
return require(`@/assets/img/screenshots/${this.project.image}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
17
src/components/ProjectSearch.vue
Normal file
17
src/components/ProjectSearch.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div class="flex justify-between items-center -mx-4">
|
||||
<div class="px-4 flex-1">
|
||||
<form action="" class="search-form flex flex-row-reverse items-center">
|
||||
<input class="w-full text-sm bg-inherit text-white focus:outline-none" type="text" placeholder="Search projects">
|
||||
<svg class="fill-current text-white h-4 w-4 mr-3 opacity-50" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><mask id="project-search-input-mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.8 10.3L15.7 14.2C16 14.6 16 15.2 15.6 15.6C15.2 16 14.6 16 14.2 15.6L10.3 11.7C9.3 12.5 7.9 13 6.5 13C2.9 13 0 10.1 0 6.50005C0 2.90005 2.9 4.57764e-05 6.5 4.57764e-05C10.1 4.57764e-05 13 2.90005 13.1 6.50005C13.1 7.90005 12.6 9.20005 11.8 10.3ZM2 6.50005C2 9.00005 4 11 6.5 11C9 11 11 9.00005 11 6.50005C11 4.00005 9 2.00005 6.5 2.00005C4 2.00005 2 4.00005 2 6.50005Z" fill="#32324c"></path></mask><g mask="url(#project-search-input-mask0)"><rect x="-4" y="-3.99995" width="24" height="24"></rect></g></svg>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="px-4">
|
||||
<a href="#0" class="inline-flex flex-row-reverse items-center px-4 py-2 rounded text-white text-sm bg-blue-700 hocus:bg-blue-800">
|
||||
Add project
|
||||
<svg class="h-3 w-4" xmlns="http://www.w3.org/2000/svg"><path d="M6 6v4H4V6H0V4h4V0h2v4h4v2z" fill="#fff" fill-rule="evenodd"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
34
src/components/ProjectViewSwitcher.vue
Normal file
34
src/components/ProjectViewSwitcher.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 text-gray-700 hover:bg-white hover:rounded hover:shadow"
|
||||
:class="{ 'bg-white rounded shadow text-blue-400': mode == 'grid'}"
|
||||
@click="$emit('changed', 'grid')"
|
||||
>
|
||||
<span class="visuallyhidden">View projects in a grid</span>
|
||||
<svg class="w-6 h-6 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><defs><clipPath id="a"><path d="M12.2 5v4.8H17V5zm0 12H17v-4.8h-4.8zM5 5h4.8v4.8H5zm0 12v-4.8h4.8V17z" fill="none" clip-rule="evenodd"/></clipPath><clipPath id="b"><path fill="none" d="M0 0h22v22H0z"/></clipPath><clipPath id="c"><path fill="none" d="M5 5h12v12H5z"/></clipPath></defs><g clip-path="url("#a")"><g clip-path="url("#b")" style="clip-path:url("#icon-grid-b")"><g clip-path="url("#c")"><path d="M0 0h22v22H0z"/></g></g></g></svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 p-2 text-gray-700 hover:bg-white hover:rounded hover:shadow"
|
||||
:class="{ 'bg-white rounded shadow text-blue-400': mode == 'list'}"
|
||||
@click="$emit('changed', 'list')"
|
||||
>
|
||||
<span class="visuallyhidden">View projects in a list</span>
|
||||
<svg class="w-6 h-6 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 22"><defs><clipPath id="link-icon-a"><path d="M5,5V7H19V5Zm14,7V10H5v2ZM5,17H19V15H5Z" style="fill: none; clip-rule: evenodd;"></path></clipPath><clipPath id="link-icon-b"><rect width="24" height="22" style="fill: none;"></rect></clipPath><clipPath id="link-icon-c"><rect x="5" y="5" width="14" height="12" style="fill: none;"></rect></clipPath></defs><title>Asset 1</title><g style="clip-path: url("#link-icon-a");"><g style="clip-path: url("#link-icon-b");"><g style="clip-path: url("#link-icon-c");"><rect width="24" height="22"></rect></g></g></g></svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,14 +0,0 @@
|
|||
<template>
|
||||
<section
|
||||
class="bg-gun-powder p-3 text-center text-white text-sm font-semibold"
|
||||
>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class AlertMessage extends Vue {}
|
||||
</script>
|
23
src/data/projects.json
Normal file
23
src/data/projects.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
[
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Inviqa",
|
||||
"owner": "inviqa",
|
||||
"region": "Europe (West 1)",
|
||||
"image": "inviqa.jpeg"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "oliverdavies.uk",
|
||||
"owner": "Oliver Davies",
|
||||
"region": "Europe (West 1)",
|
||||
"image": "oliver-davies.png"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "PHP South Wales",
|
||||
"owner": "PHP South Wales organisers",
|
||||
"region": "Europe (West 1)",
|
||||
"image": "php-south-wales.png"
|
||||
}
|
||||
]
|
14
src/main.js
Normal file
14
src/main.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.component('alert-message', require('@/components/AlertMessage').default)
|
||||
Vue.component('banner', require('@/components/Banner').default)
|
||||
Vue.component('page-title', require('@/components/PageTitle').default)
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
13
src/main.ts
13
src/main.ts
|
@ -1,13 +0,0 @@
|
|||
import Vue from "vue";
|
||||
import AlertMessage from "@/components/alert-message.vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.component("AlertMessage", AlertMessage);
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount("#app");
|
24
src/router.js
Normal file
24
src/router.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Project from '@/views/Project'
|
||||
import Projects from '@/views/Projects'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'projects',
|
||||
component: Projects
|
||||
},
|
||||
{
|
||||
path: '/project/:id',
|
||||
name: 'project',
|
||||
component: Project,
|
||||
props: true
|
||||
}
|
||||
]
|
||||
})
|
|
@ -1,21 +0,0 @@
|
|||
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
src/shims-tsx.d.ts
vendored
13
src/shims-tsx.d.ts
vendored
|
@ -1,13 +0,0 @@
|
|||
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
src/shims-vue.d.ts
vendored
4
src/shims-vue.d.ts
vendored
|
@ -1,4 +0,0 @@
|
|||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<h1>Rebuilding Platform.sh</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue } from "vue-property-decorator";
|
||||
|
||||
export default class Home extends Vue {}
|
||||
</script>
|
51
src/views/Project.vue
Normal file
51
src/views/Project.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div>
|
||||
<banner>
|
||||
<template #banner-left>
|
||||
<project-breadcrumb :project="project"/>
|
||||
</template>
|
||||
|
||||
<template #sub-banner>
|
||||
<span class="text-white">Project and environment dropdowns</span>
|
||||
</template>
|
||||
</banner>
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-1/2">
|
||||
<nav>
|
||||
<a class="py-2 text-gray-900 text-xs uppercase tracking-widest border-b-2 border-gray-900 font-semibold" href="#">Overview</a>
|
||||
<a class="ml-6 py-2 text-gray-900 text-xs uppercase tracking-widest" href="#">Settings</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="w-1/2 flex items-center justify-end">
|
||||
<button type="button" class="py-1 pl-2 pr-4 flex items-center text-sm uppercase tracking-wider border border-periwinkle">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 block fill-current text-gray-700"><path d="M6.90002 9.8999C6.90002 9.5999 7.00002 9.3999 7.20002 9.1999C7.60002 8.7999 8.20002 8.7999 8.60002 9.1999L12.1 12.6999L15.6 9.1999C16 8.7999 16.6 8.7999 17 9.1999C17.4 9.5999 17.4 10.1999 17 10.5999L12.8 14.7999C12.4 15.1999 11.8 15.1999 11.4 14.7999L7.20002 10.5999C7.00002 10.3999 6.90002 10.1999 6.90002 9.8999Z"></path></svg>
|
||||
<span class="ml-1">Git</span>
|
||||
</button>
|
||||
<button type="button" class="ml-3 py-1 pl-2 pr-4 flex items-center text-sm uppercase tracking-wider border border-periwinkle">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 block fill-current text-gray-700"><path d="M6.90002 9.8999C6.90002 9.5999 7.00002 9.3999 7.20002 9.1999C7.60002 8.7999 8.20002 8.7999 8.60002 9.1999L12.1 12.6999L15.6 9.1999C16 8.7999 16.6 8.7999 17 9.1999C17.4 9.5999 17.4 10.1999 17 10.5999L12.8 14.7999C12.4 15.1999 11.8 15.1999 11.4 14.7999L7.20002 10.5999C7.00002 10.3999 6.90002 10.1999 6.90002 9.8999Z"></path></svg>
|
||||
<span class="ml-1">CLI</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProjectBreadcrumb from '@/components/ProjectBreadcrumb'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProjectBreadcrumb
|
||||
},
|
||||
|
||||
computed: {
|
||||
project () {
|
||||
return this.$attrs.projects[this.$attrs.id - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
95
src/views/Projects.vue
Normal file
95
src/views/Projects.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div>
|
||||
<banner>
|
||||
<template #sub-banner>
|
||||
<project-search/>
|
||||
</template>
|
||||
</banner>
|
||||
|
||||
<main class="container mx-auto p-6">
|
||||
<div class="flex justify-between items-baseline">
|
||||
<page-title>All Projects</page-title>
|
||||
|
||||
<project-view-switcher @changed="projectViewSwitched" :mode="displayMode"></project-view-switcher>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<div
|
||||
v-if="displayMode == 'grid'"
|
||||
class="
|
||||
grid gap-8
|
||||
md:grid-cols-2
|
||||
xl:grid-cols-3
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="project in projects"
|
||||
:key="project.id"
|
||||
>
|
||||
<project-card :project="project"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="displayMode == 'list'" class="pt-6 pb-6 mt-4 bg-white shadow-md">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-300">
|
||||
<th class="w-1/2 pb-3 px-6 font-semibold text-left text-sm">Project name</th>
|
||||
<th class="pb-3 px-6 font-semibold text-left text-sm">Owner</th>
|
||||
<th class="w-1/4 pb-3 px-6 font-semibold text-left text-sm">Region</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="project in projects" :key="project.id">
|
||||
<td class="px-6 py-4">
|
||||
<router-link :to="{ name: 'project', params: { id: project.id }}" class="font-semibold text-sm hover:underline">{{ project.name }}</router-link>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="#0" class="font-semibold text-sm hover:underline">{{ project.owner }}</a>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="#0" class="font-semibold text-sm hover:underline">{{ project.region }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProjectCard from '@/components/ProjectCard'
|
||||
import ProjectSearch from '@/components/ProjectSearch'
|
||||
import ProjectViewSwitcher from '@/components/ProjectViewSwitcher'
|
||||
|
||||
export default {
|
||||
name: 'projects',
|
||||
|
||||
props: {
|
||||
projects: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ProjectCard,
|
||||
ProjectSearch,
|
||||
ProjectViewSwitcher
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
displayMode: 'grid'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
projectViewSwitched (mode) {
|
||||
this.displayMode = mode
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,6 +1,5 @@
|
|||
const { variants } = require('tailwindcss/defaultConfig');
|
||||
const { fontFamily } = require('tailwindcss/defaultTheme');
|
||||
const variationPlugin = require('tailwindcss-interaction-variants');
|
||||
const { variants } = require('tailwindcss/defaultConfig')
|
||||
const { fontFamily, spacing } = require('tailwindcss/defaultTheme')
|
||||
|
||||
module.exports = {
|
||||
theme: {
|
||||
|
@ -27,6 +26,9 @@ module.exports = {
|
|||
textDecoration: [...variants.textDecoration, 'group-hover', 'hocus', 'group-hocus']
|
||||
},
|
||||
plugins: [
|
||||
variationPlugin
|
||||
require('tailwindcss-interaction-variants')(),
|
||||
require('tailwindcss-spaced-items')({ values: spacing }),
|
||||
require('tailwindcss-transforms')(),
|
||||
require('tailwindcss-visuallyhidden')()
|
||||
]
|
||||
}
|
||||
|
|
5
tests/unit/.eslintrc.js
Normal file
5
tests/unit/.eslintrc.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
12
tests/unit/example.spec.js
Normal file
12
tests/unit/example.spec.js
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)
|
||||
})
|
||||
})
|
|
@ -1,12 +0,0 @@
|
|||
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);
|
||||
});
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": 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"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue