chore(launchpad): lint vue files (#18212)

* work on linting vue

* update linting

* update linting

* linting npm/vue

* lint all vue files

* fix type check

* update lint

* lint
This commit is contained in:
Lachlan Miller
2021-09-28 11:37:50 +10:00
committed by GitHub
parent 1d5b185c5b
commit 377ef0aae9
116 changed files with 1735 additions and 1150 deletions
+4 -1
View File
@@ -8,7 +8,10 @@
"plugin:@cypress/dev/tests",
"plugin:@cypress/dev/react"
],
"parser": "@typescript-eslint/parser",
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"env": {
"cypress/globals": true
},
@@ -15,10 +15,10 @@
// For the sake of demoing scopedSlots, this Card component
// passes a simple string down to its default slot.
export default {
data() {
data () {
return {
content: "Scoped content!",
};
content: 'Scoped content!',
}
},
};
}
</script>
@@ -8,26 +8,27 @@
</template>
<script>
import Card from "../Card/Card.vue"
import Card from '../Card/Card.vue'
export default {
name: "Hello",
name: 'Hello',
components: { Card },
data() {
data () {
return {
username: "",
};
username: '',
}
},
computed: {
error() {
console.log(this.username);
error () {
console.log(this.username)
return this.username.trim().length < 7
? "Please enter a longer username"
: "";
? 'Please enter a longer username'
: ''
},
},
};
}
</script>
<style scoped>
+3 -1
View File
@@ -23,11 +23,13 @@
"@vitejs/plugin-vue": "1.2.0",
"@vue/compiler-sfc": "3.2.6",
"cypress": "0.0.0-development",
"eslint-plugin-vue": "7.18.0",
"mocha-junit-reporter": "^2.0.0",
"mocha-multi-reporters": "^1.5.1",
"react": "17.0.2",
"vite": "2.4.4",
"vue": "3.2.6"
"vue": "3.2.6",
"vue-eslint-parser": "7.11.0"
},
"peerDependencies": {
"vite": ">= 2.1.3"
+4
View File
@@ -5,6 +5,10 @@
"extends": [
"plugin:@cypress/dev/tests"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"env": {
"cypress/globals": true
},
@@ -8,25 +8,25 @@
</template>
<script>
export default {
name: 'Message',
props: {
message: {
type: String,
required: true,
validator: value => value.length > 1
},
message2: String,
author: {
type: String,
default: 'Paco'
}
export default {
name: 'Message',
props: {
message: {
type: String,
required: true,
validator: (value) => value.length > 1,
},
methods: {
handleClick() {
console.log('lalala')
this.$emit('message-clicked', this.message)
}
}
}
message2: String,
author: {
type: String,
default: 'Paco',
},
},
methods: {
handleClick () {
console.log('lalala')
this.$emit('message-clicked', this.message)
},
},
}
</script>
@@ -12,18 +12,18 @@
<script>
export default {
data() {
data () {
return {
users: []
users: [],
}
},
created() {
created () {
fetch('https://jsonplaceholder.cypress.io/users?_limit=3')
.then(response => response.json())
.then(list => {
.then((response) => response.json())
.then((list) => {
this.users = list
})
}
},
}
</script>
@@ -13,4 +13,4 @@
</select>
<p>message: {{ $t('hello') }}</p>
</div>
</template>
</template>
@@ -8,4 +8,4 @@
</select>
<p>message: {{ $t('hello') }}</p>
</div>
</template>
</template>
@@ -39,7 +39,7 @@ export default {
watch: {
locale (val) {
this.$i18n.locale = val
}
}
},
},
}
</script>
@@ -11,22 +11,22 @@
<script>
// import just the "get" from axios
import {get} from 'axios';
import { get } from 'axios'
export default {
data() {
data () {
return {
users: []
users: [],
}
},
// Fetches posts when the component is created.
created() {
created () {
get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then(response => {
.then((response) => {
// JSON responses are automatically parsed.
this.users = response.data
})
}
},
}
</script>
@@ -11,22 +11,22 @@
<script>
// import the default axios
import axios from 'axios';
import axios from 'axios'
export default {
data() {
data () {
return {
users: []
users: [],
}
},
// Fetches posts when the component is created.
created() {
created () {
axios.get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then(response => {
.then((response) => {
// JSON responses are automatically parsed.
this.users = response.data
})
}
},
}
</script>
@@ -10,22 +10,22 @@
</template>
<script>
import {get} from './AxiosApi';
import { get } from './AxiosApi'
export default {
data() {
data () {
return {
users: []
users: [],
}
},
// Fetches posts when the component is created.
created() {
created () {
get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then(response => {
.then((response) => {
// JSON responses are automatically parsed.
this.users = response.data
})
}
},
}
</script>
@@ -6,6 +6,6 @@
export default {
data () {
return {}
}
},
}
</script>
@@ -13,7 +13,7 @@ export default {
return {}
},
components: {
ChildComponent
}
ChildComponent,
},
}
</script>
@@ -12,18 +12,18 @@
<script>
export default {
data() {
data () {
return {
users: []
users: [],
}
},
created() {
created () {
fetch('https://jsonplaceholder.cypress.io/users?_limit=3')
.then(response => response.json())
.then(list => {
.then((response) => response.json())
.then((list) => {
this.users = list
})
}
},
}
</script>
@@ -3,14 +3,14 @@
</template>
<script>
import { greeting } from "./greeting";
import { greeting } from './greeting'
export default {
name: "Hello",
data() {
name: 'Hello',
data () {
return {
greeting: greeting()
};
}
};
greeting: greeting(),
}
},
}
</script>
@@ -3,12 +3,12 @@
</template>
<script>
export default {
name: 'AlertMessage',
methods: {
handleClick() {
alert('Hello Vue')
}
}
}
export default {
name: 'AlertMessage',
methods: {
handleClick () {
alert('Hello Vue')
},
},
}
</script>
@@ -10,6 +10,6 @@
// from https://alexjoverm.github.io/2017/08/21/Write-the-first-Vue-js-Component-Unit-Test-in-Jest/
export default {
name: 'list',
props: ['messages']
props: ['messages'],
}
</script>
@@ -7,21 +7,21 @@
</template>
<script>
import { add } from "./calc";
import { add } from './calc'
export default {
name: "Calculator",
data() {
name: 'Calculator',
data () {
return {
a: 0,
b: 0
};
b: 0,
}
},
computed: {
sum() {
return add(parseFloat(this.a), parseFloat(this.b));
}
}
};
sum () {
return add(parseFloat(this.a), parseFloat(this.b))
},
},
}
</script>
+12 -11
View File
@@ -7,20 +7,21 @@
<script>
export default {
name: "Hello",
data() {
name: 'Hello',
data () {
return {
username: ""
};
username: '',
}
},
computed: {
error() {
console.log(this.username);
error () {
console.log(this.username)
return this.username.trim().length < 7
? "Please enter a longer username"
: "";
}
}
};
? 'Please enter a longer username'
: ''
},
},
}
</script>
@@ -14,10 +14,10 @@
// For the sake of demoing scopedSlots, this Card component
// passes a simple string down to its default slot.
export default {
data() {
data () {
return {
content: 'Scoped content!'
content: 'Scoped content!',
}
}
},
}
</script>
@@ -16,5 +16,5 @@ export default {
type: String,
},
},
};
}
</script>
@@ -11,18 +11,18 @@
<script>
export default {
name: 'Counter',
data() {
data () {
return {
counter: 0,
};
}
},
methods: {
increment() {
this.counter++;
increment () {
this.counter++
},
decrement() {
this.counter--;
decrement () {
this.counter--
},
},
};
}
</script>
@@ -10,17 +10,17 @@
<script>
export default {
name: 'AppForm',
data() {
data () {
return {
name: '',
email: '',
};
}
},
computed: {
hasValidFields() {
return Boolean(this.email && this.name);
hasValidFields () {
return Boolean(this.email && this.name)
},
},
};
}
</script>
<style></style>
@@ -7,11 +7,11 @@
</template>
<script>
export default {
data() {
data () {
return {
item: '',
};
}
},
};
}
</script>
<style></style>
@@ -6,24 +6,24 @@
</template>
<script>
export default {
name: 'Todo',
props: {
title: {
type: String,
required: true,
default: ''
},
done: {
type: Boolean,
required: true,
default: false
}
export default {
name: 'Todo',
props: {
title: {
type: String,
required: true,
default: '',
},
data () {
return {
isDone: this.done
}
done: {
type: Boolean,
required: true,
default: false,
},
},
data () {
return {
isDone: this.done,
}
}
},
}
</script>
@@ -3,20 +3,20 @@
</template>
<script>
export default {
data () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
export default {
data () {
return {
counter: 0,
}
}
},
methods: {
incrementCounter () {
this.counter += 1
this.$emit('increment')
},
},
}
</script>
<style scoped>
@@ -13,10 +13,11 @@
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState(['count']),
...mapGetters(['evenOrOdd'])
...mapGetters(['evenOrOdd']),
},
methods: {
...mapMutations(['set']),
@@ -24,8 +25,8 @@ export default {
'increment',
'decrement',
'incrementIfOdd',
'incrementAsync'
])
}
'incrementAsync',
]),
},
}
</script>
+2 -2
View File
@@ -6,9 +6,9 @@
export default {
data () {
return {
greeting: 'Hello'
greeting: 'Hello',
}
}
},
}
</script>
@@ -1,8 +1,8 @@
<template>
<div class="shop-home">
<router-link
:to="{ name: 'order' }"
custom v-slot="{ navigate }">
<router-link
:to="{ name: 'order' }"
custom v-slot="{ navigate }">
<button class="order" @click="navigate">Place Your Order</button>
</router-link>
<br>
@@ -34,8 +34,9 @@
<script>
import { ALL_TOPPINGS, PRESETS } from './toppings'
const filterByName = (list, name) =>
list.filter(i => i.name === name)
const filterByName = (list, name) => {
return list.filter((i) => i.name === name)
}
export default {
data () {
@@ -51,22 +52,23 @@ export default {
},
getTopping (name) {
return filterByName(ALL_TOPPINGS, name)[0]
}
},
},
created () {
const presetName = this.$route.params.preset
this.selected = presetName ? PRESETS[presetName]: []
this.selected = presetName ? PRESETS[presetName] : []
const selectedToppings = this.$route.query
for (const toppingName of Object.keys(selectedToppings)) {
if(selectedToppings[toppingName] && !this.hasTopping(toppingName)) {
if (selectedToppings[toppingName] && !this.hasTopping(toppingName)) {
const topping = this.getTopping(toppingName)
this.selected.push(topping)
}
}
}
},
}
</script>
@@ -5,8 +5,10 @@
<script setup lang="ts">
import { defineProps, ref } from 'vue'
defineProps<{
msg: String,
}>()
const count = ref(0)
</script>
</script>
@@ -7,17 +7,17 @@
<script>
export default {
props: ["status"],
props: ['status'],
computed: {
greeting: function() {
greeting () {
if (this.status) {
return "Hello";
} else {
return "Goodbye";
return 'Hello'
}
}
}
};
return 'Goodbye'
},
},
}
</script>
<style scoped>
+6 -6
View File
@@ -10,22 +10,22 @@
<script>
// example from https://alligator.io/vuejs/rest-api-axios/
import axios from 'axios';
import axios from 'axios'
export default {
data() {
data () {
return {
users: []
users: [],
}
},
// Fetches posts when the component is created.
created() {
created () {
axios.get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then(response => {
.then((response) => {
// JSON responses are automatically parsed.
this.users = response.data
})
}
},
}
</script>
+4 -4
View File
@@ -6,13 +6,13 @@
</template>
<script>
import TodoList from "./components/TodoList.vue";
import TodoList from './components/TodoList.vue'
export default {
components: {
TodoList
}
};
TodoList,
},
}
</script>
<style lang="scss">
@@ -7,20 +7,20 @@ export default {
props: {
value: {
type: String,
default: "",
default: '',
},
},
computed: {
listeners() {
listeners () {
return {
// Pass all component listeners directly to input
...this.$listeners,
// Override input listener to work with v-model
input: (event) => this.$emit("input", event.target.value),
};
input: (event) => this.$emit('input', event.target.value),
}
},
},
};
}
</script>
<style lang="scss" scoped>
@@ -33,4 +33,3 @@ export default {
border: 1px solid $vue-blue;
}
</style>
@@ -1,37 +1,40 @@
<script>
import BaseInputText from "./BaseInputText.vue";
import TodoListItem from "./TodoListItem.vue";
let nextTodoId = 1;
import BaseInputText from './BaseInputText.vue'
import TodoListItem from './TodoListItem.vue'
let nextTodoId = 1
export default {
components: {
BaseInputText,
TodoListItem
TodoListItem,
},
data() {
data () {
return {
newTodoText: "",
newTodoText: '',
// empty list at first, each item like with "id" and "text"
todos: []
};
todos: [],
}
},
methods: {
addTodo() {
const trimmedText = this.newTodoText.trim();
addTodo () {
const trimmedText = this.newTodoText.trim()
if (trimmedText) {
this.todos.push({
id: nextTodoId++,
text: trimmedText
});
this.newTodoText = "";
text: trimmedText,
})
this.newTodoText = ''
}
},
removeTodo(idToRemove) {
this.todos = this.todos.filter(todo => {
return todo.id !== idToRemove;
});
}
}
};
removeTodo (idToRemove) {
this.todos = this.todos.filter((todo) => {
return todo.id !== idToRemove
})
},
},
}
</script>
<template>
@@ -10,8 +10,8 @@ export default {
props: {
todo: {
type: Object,
required: true
}
}
};
required: true,
},
},
}
</script>
+2 -2
View File
@@ -11,8 +11,8 @@ import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
HelloWorld,
},
}
</script>
@@ -33,8 +33,8 @@
export default {
name: 'HelloWorld',
props: {
msg: String
}
msg: String,
},
}
</script>
+2 -1
View File
@@ -38,7 +38,7 @@
"css-loader": "3.4.2",
"cypress": "0.0.0-development",
"debug": "4.3.2",
"eslint-plugin-vue": "^6.2.2",
"eslint-plugin-vue": "7.18.0",
"find-webpack": "2.1.0",
"mocha": "7.1.1",
"mocha-junit-reporter": "^2.0.0",
@@ -49,6 +49,7 @@
"tailwindcss": "1.1.4",
"typescript": "^4.2.3",
"vue": "3.2.6",
"vue-eslint-parser": "7.11.0",
"vue-i18n": "9.0.0-rc.6",
"vue-loader": "16.1.2",
"vue-router": "^4.0.0",
+4 -3
View File
@@ -40,7 +40,7 @@
"get-next-version": "node scripts/get-next-version.js",
"postinstall": "yarn-deduplicate --strategy=highest && patch-package && gulp postinstall && ./scripts/run-if-not-ci.sh yarn build",
"jscodeshift": "jscodeshift -t ./node_modules/js-codemod/transforms/arrow-function-arguments.js",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json .",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json,.vue .",
"lint-changed": "lint-changed",
"move-binaries": "node ./scripts/binary.js move-binaries",
"npm-release": "node scripts/npm-release.js",
@@ -89,7 +89,7 @@
"@graphql-codegen/typed-document-node": "2.1.4",
"@graphql-codegen/typescript": "2.2.2",
"@graphql-codegen/typescript-operations": "2.1.4",
"@graphql-eslint/eslint-plugin": "^2.2.0",
"@graphql-eslint/eslint-plugin": "2.2.0",
"@graphql-tools/batch-delegate": "8.1.0",
"@graphql-tools/delegate": "8.2.1",
"@graphql-tools/utils": "8.2.3",
@@ -153,6 +153,7 @@
"eslint-plugin-mocha": "8.1.0",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-vue": "7.18.0",
"execa": "4.0.0",
"execa-wrap": "1.4.0",
"filesize": "4.1.2",
@@ -265,7 +266,7 @@
},
"lint-staged": {
"*.coffee": "yarn stop-only --folder",
"*.{js,jsx,ts,tsx,json,eslintrc}": "eslint --fix"
"*.{js,jsx,ts,tsx,json,eslintrc,vue}": "eslint --fix"
},
"resolutions": {
"**/@types/cheerio": "0.22.21",
+7 -4
View File
@@ -1,14 +1,17 @@
{
"globals": {
"defineProps": "readonly",
"defineEmits": "readonly",
"defineExpose": "readonly",
"withDefaults": "readonly"
},
"plugins": [
"cypress",
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"../reporter/src/.eslintrc.json"
"../frontend-shared/.eslintrc.json"
],
"parser": "@typescript-eslint/parser",
"env": {
"cypress/globals": true
},
+1 -1
View File
@@ -15,4 +15,4 @@ fragment Foo on App {
const props = defineProps<{
gql: FooFragment
}>()
</script>>
</script>>
+15 -4
View File
@@ -1,10 +1,21 @@
<template>
<div class="h-screen overflow-hidden flex flex-row bg-red">
<main class="min-w-0 flex-1 border-t bg-green border-gray-200 lg:flex">
<section aria-labelledby="primary-heading" class="min-w-0 flex-1 h-full flex flex-col overflow-hidden lg:order-last">
<section
aria-labelledby="primary-heading"
class="min-w-0 flex-1 h-full flex flex-col overflow-hidden lg:order-last"
>
<router-view v-slot="{ Component, route }">
<h1 id="primary-heading" class="sr-only">{{ route.name }}</h1>
<transition name="fade" mode="out-in">
<h1
id="primary-heading"
class="sr-only"
>
{{ route.name }}
</h1>
<transition
name="fade"
mode="out-in"
>
<keep-alive>
<component
:is="Component"
@@ -15,7 +26,7 @@
</section>
</main>
<nav class="h-screen order-first w-240px">
<SidebarNavigation class="h-full"></SidebarNavigation>
<SidebarNavigation class="h-full" />
</nav>
</div>
</template>
@@ -6,18 +6,28 @@
class="icon-dark-gray-300
icon-light-gray-800
w-24px
h-24px"/>
<i-bi-bookmark-star class="text-white w-18px h-18px"/>
h-24px"
/>
<i-bi-bookmark-star class="text-white w-18px h-18px" />
</div>
<nav class="flex-1 px-2 mt-5 space-y-1 bg-gray-800" aria-label="Sidebar">
<nav
class="flex-1 px-2 mt-5 space-y-1 bg-gray-800"
aria-label="Sidebar"
>
<router-link
custom
v-slot="{ href, isActive }"
v-for="item in navigation"
v-slot="{ href, isActive }"
:key="item.name"
custom
:to="item.href"
>
<SidebarNavigationRow :active="isActive" :icon="item.icon" :href="href">{{ item.name }}</SidebarNavigationRow>
<SidebarNavigationRow
:active="isActive"
:icon="item.icon"
:href="href"
>
{{ item.name }}
</SidebarNavigationRow>
</router-link>
</nav>
</div>
@@ -34,7 +44,7 @@ import SettingsIcon from '~icons/cy/settings_x24'
const navigation = [
{ name: 'Specs', icon: SpecsIcon, href: '/' },
{ name: 'Runs', icon: CodeIcon, href: '/runs' },
{ name: 'Settings', icon: SettingsIcon, href: '/settings' }
{ name: 'Settings', icon: SettingsIcon, href: '/settings' },
]
defineProps<{
@@ -1,7 +1,8 @@
<template>
<a href="#"
:class="[active ? 'before:bg-green-300' : 'before:bg-transparent']"
class="w-full
<a
href="#"
:class="[active ? 'before:bg-green-300' : 'before:bg-transparent']"
class="w-full
min-w-40px
relative
flex
@@ -16,8 +17,10 @@
before:rounded-r-md
before:text-transparent
before:h-40px
before:w-4px">
<span class="h-full
before:w-4px"
>
<span
class="h-full
flex
items-center
overflow-hidden
@@ -25,31 +28,33 @@
gap-20px
rounded-lg
children:group-focus:text-indigo-300
children:group-hover:text-indigo-300">
<component
v-if="icon"
children:group-hover:text-indigo-300"
>
<component
:is="icon"
v-if="icon"
:class="[active ? 'icon-dark-green-300 icon-light-green-800' : 'icon-dark-gray-500 icon-light-gray-800']"
class="min-w-24px min-h-24px group-hover:icon-dark-indigo-300 group-hover:icon-light-indigo-900 group-focus:icon-dark-indigo-300 group-focus:icon-light-indigo-900"
class="min-w-24px min-h-24px group-hover:icon-dark-indigo-300 group-hover:icon-light-indigo-900 group-focus:icon-dark-indigo-300 group-focus:icon-light-indigo-900"
/>
<span
:class="[active ? 'text-green-300' : 'text-gray-500']"
<span
:class="[active ? 'text-green-300' : 'text-gray-500']"
class="truncate"
>
<slot></slot>
<slot />
</span>
</span>
</a>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import type { FunctionalComponent, SVGAttributes } from 'vue'
withDefaults(defineProps <{
icon?: unknown
icon?: FunctionalComponent<SVGAttributes, {}>
// Currently active row (generally the current route)
active?: boolean
}>(), {
active: false
active: false,
icon: undefined,
})
</script>
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<Specs/>
<Specs />
</template>
<script setup lang="ts">
+16 -6
View File
@@ -1,11 +1,21 @@
<template>
<div class="text-center mt-20 w-400px mx-auto p-20px border-1 rounded-md">
<h1 class="text-2xl">You seem to have gotten lost...</h1>
<p class="text-gray-600">Try one of these links instead</p>
<h1 class="text-2xl">
You seem to have gotten lost...
</h1>
<p class="text-gray-600">
Try one of these links instead
</p>
<nav class="space-y-2 mt-40px">
<li v-for="route in routes" :key="route.path" class="text-left underline underline-2 underline-offset-1 underline-indigo-700 text-indigo-700 hover:text-indigo-500 hover:underline-indigo-500">
<router-link :to="route.path">{{ route.name }}</router-link>
</li>
<li
v-for="route in routes"
:key="route.path"
class="text-left underline underline-2 underline-offset-1 underline-indigo-700 text-indigo-700 hover:text-indigo-500 hover:underline-indigo-500"
>
<router-link :to="route.path">
{{ route.name }}
</router-link>
</li>
</nav>
</div>
</template>
@@ -16,7 +26,7 @@ import { computed } from 'vue'
import { uniqBy } from 'lodash'
const routes = computed(() => {
return uniqBy(useRouter().getRoutes(), 'path').filter(r => r.meta?.layout !== 'error' && !r.meta?.error)
return uniqBy(useRouter().getRoutes(), 'path').filter((r) => r.meta?.layout !== 'error' && !r.meta?.error)
})
</script>
+57 -10
View File
@@ -1,23 +1,70 @@
{
"extends": [
"plugin:@cypress/dev/tests"
"globals": {
"defineProps": "readonly",
"defineEmits": "readonly",
"defineExpose": "readonly",
"withDefaults": "readonly"
},
"plugins": [
"cypress",
"@cypress/dev"
],
"parser": "@typescript-eslint/parser",
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"../reporter/src/.eslintrc.json",
"plugin:vue/vue3-recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"env": {
"cypress/globals": true
},
"plugins": [
"cypress"
],
"overrides": [
{
"files": "**/*.vue",
"rules": {
"vue/no-v-html": "off",
"no-spaced-func": "off",
"no-restricted-imports": [
"error",
{
"patterns": [
"@packages/graphql/*"
]
}
]
}
},
{
"files": [
"./src/entities/**/*.ts"
"lib/*"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": [
"error"
]
"no-console": 1
}
},
{
"files": [
"**/*.json"
],
"rules": {
"quotes": "off",
"comma-dangle": "off"
}
},
{
"files": [
"*.tsx",
"*.jsx"
],
"rules": {
"no-unused-vars": "off",
"react/jsx-no-bind": "off",
"react/react-in-jsx-scope": "off",
"react/no-unknown-property": "off"
}
}
]
@@ -94,7 +94,7 @@ export const registerMountFn = ({ plugins }) => {
})
return mount(defineComponent({
name: `mountFragment`,
name: `MountFragment`,
setup () {
const fieldName = list ? 'testFragmentMemberList' : 'testFragmentMember'
const result = useQuery({
+1
View File
@@ -25,6 +25,7 @@
"rimraf": "3.0.2",
"vite-plugin-icons": "0.6.3",
"vue": "3.2.6",
"vue-eslint-parser": "7.11.0",
"vue-i18n": "9.2.0-beta.1",
"wonka": "^4.0.15"
}
+9 -4
View File
@@ -1,14 +1,17 @@
{
"globals": {
"defineProps": "readonly",
"defineEmits": "readonly",
"defineExpose": "readonly",
"withDefaults": "readonly"
},
"plugins": [
"cypress",
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"../reporter/src/.eslintrc.json"
"../frontend-shared/.eslintrc.json"
],
"parser": "@typescript-eslint/parser",
"env": {
"cypress/globals": true
},
@@ -16,6 +19,8 @@
{
"files": "**/*.vue",
"rules": {
"vue/no-v-html": "off",
"no-spaced-func": "off",
"no-restricted-imports": [
"error",
{
+8 -10
View File
@@ -2,13 +2,16 @@
<div v-if="!backendInitialized">
Loading...
</div>
<div v-else class="h-full mx-auto bg-white">
<div
v-else
class="h-full mx-auto bg-white"
>
<Main />
</div>
</template>
<script lang="ts" setup>
import { computed, watch } from 'vue'
import { computed } from 'vue'
import { gql, useQuery } from '@urql/vue'
import Main from './Main.vue'
import { AppQueryDocument } from './generated/graphql'
@@ -26,14 +29,9 @@ query AppQuery {
* server and current project has been initialized.
* We poll until those conditions are met, then render the app
*/
const query = useQuery({
const query = useQuery({
query: AppQueryDocument,
requestPolicy: 'cache-and-network'
})
watch(query.data, () => {
console.log(query.data.value)
requestPolicy: 'cache-and-network',
})
let interval: number
@@ -58,4 +56,4 @@ const backendInitialized = computed(() => !!query.data?.value?.app)
html, body, #app {
@apply h-full bg-white;
}
</style>
</style>
+8 -3
View File
@@ -11,11 +11,16 @@
<WizardHeader :gql="query.data.value.wizard" />
<TestingTypeCards :gql="query.data.value" />
</template>
<Wizard v-else :gql="query.data.value" />
<Wizard
v-else
:gql="query.data.value"
/>
</template>
</div>
</template>
<div v-else>Loading</div>
<div v-else>
Loading
</div>
</template>
<script lang="ts" setup>
@@ -45,4 +50,4 @@ query MainQuery {
`
const query = useQuery({ query: MainQueryDocument })
</script>
</script>
@@ -3,7 +3,10 @@
class="border-cool-gray-300 grid-flow-col grid items-center justify-between gap-6px rounded-sm border-1 text-sm text-center w-min cursor-default select-none px-8px"
:class="classes"
>
<i-mdi-circle class="h-8px w-8px" :class="classes" />
<i-mdi-circle
class="h-8px w-8px"
:class="classes"
/>
<slot />
</span>
</template>
@@ -4,57 +4,70 @@
class="flex items-center border rounded-sm gap-8px focus:border-indigo-600 focus:outline-transparent"
:class="classes"
>
<span v-if="prefixIcon || $slots.prefix" :class="iconClasses" class="justify-self-start">
<span
v-if="prefixIcon || $slots.prefix"
:class="iconClasses"
class="justify-self-start"
>
<slot name="prefix">
<Icon :icon="prefixIcon" :class="prefixIconClass" />
<Icon
:icon="prefixIcon"
:class="prefixIconClass"
/>
</slot>
</span>
<span class="flex-grow">
<slot />
</span>
<span v-if="suffixIcon || $slots.suffix" :class="iconClasses" class="justify-self-end">
<span
v-if="suffixIcon || $slots.suffix"
:class="iconClasses"
class="justify-self-end"
>
<slot name="suffix">
<Icon :icon="suffixIcon" :class="suffixIconClass" />
<Icon
:icon="suffixIcon"
:class="suffixIconClass"
/>
</slot>
</span>
</button>
</template>
<script lang="ts">
export default defineComponent({
export default {
inheritAttrs: true,
})
}
</script>
<script lang="ts" setup>
import { computed, defineComponent, useAttrs } from "vue"
import type { ButtonHTMLAttributes, FunctionalComponent, SVGAttributes } from "vue"
import { computed, useAttrs, ButtonHTMLAttributes, FunctionalComponent, SVGAttributes } from 'vue'
const VariantClassesTable = {
primary: "border-indigo-600 bg-indigo-600 text-white",
outline: "border-gray-200 text-indigo-600 bg-white",
link: "border-transparent text-indigo-600",
text: 'border-0'
primary: 'border-indigo-600 bg-indigo-600 text-white',
outline: 'border-gray-200 text-indigo-600 bg-white',
link: 'border-transparent text-indigo-600',
text: 'border-0',
}
const SizeClassesTable = {
sm: "px-1 py-1 text-xs",
sm: 'px-1 py-1 text-xs',
md: 'px-2 py-1 text-sm',
lg: "px-4 py-2 text-sm",
xl: "px-6 py-3 text-lg"
lg: 'px-4 py-2 text-sm',
xl: 'px-6 py-3 text-lg',
}
const IconClassesTable = {
md: "min-h-1.25em min-w-1.25em max-h-1.25em max-w-1.25em",
lg: "min-h-2em min-w-2em max-h-2em max-w-2em",
xl: "min-h-2.5em min-w-2.5em max-w-2.5em max-h-2.5em "
md: 'min-h-1.25em min-w-1.25em max-h-1.25em max-w-1.25em',
lg: 'min-h-2em min-w-2em max-h-2em max-w-2em',
xl: 'min-h-2.5em min-w-2.5em max-w-2.5em max-h-2.5em ',
}
const props = defineProps<{
prefixIcon?: FunctionalComponent<SVGAttributes>
suffixIcon?: FunctionalComponent<SVGAttributes>
size?: "xs" | "sm" | "md" | "lg" | "xl"
variant?: "primary" | "outline" | "link" | "text"
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
variant?: 'primary' | 'outline' | 'link' | 'text'
prefixIconClass?: string
suffixIconClass?: string
}>()
@@ -66,10 +79,12 @@ const sizeClasses = SizeClassesTable[props.size || 'md']
const iconClasses = ['flex', 'items-center', IconClassesTable[props.size || 'md']]
const classes = computed(() => [
variantClasses,
sizeClasses,
attrs.class,
attrs.disabled ? 'opacity-50' : ''
])
const classes = computed(() => {
return [
variantClasses,
sizeClasses,
attrs.class,
attrs.disabled ? 'opacity-50' : '',
]
})
</script>
@@ -1,7 +1,10 @@
<template>
<div class="absolute top-2 right-2">
<transition name="fade">
<span class="mx-3" v-show="showCopied">{{ t('clipboard.copied') }}</span>
<span
v-show="showCopied"
class="mx-3"
>{{ t('clipboard.copied') }}</span>
</transition>
<button
class="bg-gray-50 px-3 py-1 rounded text-indigo-600"
@@ -10,12 +13,16 @@
{{ t('clipboard.copy') }}
</button>
</div>
<textarea class="absolute -top-96" ref="textElement">{{ text }}</textarea>
<textarea
ref="textElement"
:value="text"
class="absolute -top-96"
/>
</template>
<script lang="ts">
import { defineComponent, nextTick, ref } from "vue";
import { useI18n } from "../../composables";
import { defineComponent, nextTick, ref } from 'vue'
import { useI18n } from '../../composables'
export default defineComponent({
props: {
@@ -24,20 +31,21 @@ export default defineComponent({
required: true,
},
},
setup() {
setup () {
const showCopied = ref(false)
const textElement = ref<HTMLTextAreaElement | null>(null);
const textElement = ref<HTMLTextAreaElement | null>(null)
const copyToClipboard = async () => {
textElement.value?.select();
document.execCommand("copy");
textElement.value?.select()
document.execCommand('copy')
showCopied.value = true
await nextTick()
showCopied.value = false
};
}
const { t } = useI18n()
return { copyToClipboard, textElement, showCopied, t };
return { copyToClipboard, textElement, showCopied, t }
},
});
})
</script>
<style>
@@ -2,8 +2,8 @@
<div class="relative flex items-center">
<div class="flex items-center h-5">
<input
v-model="modelValue"
:id="id"
:value="modelValue"
:aria-describedby="`${id}-description`"
:name="id"
type="checkbox"
@@ -20,15 +20,19 @@
'checked:border-jade-300 checked:bg-jade-600 checked:text-jade-600': state === 'success',
'checked:border-red-300 checked:bg-red-600 checked:text-red-600': state === 'danger'
}"
/>
@update:modelValue="emit('update:modelValue', !!$event.target.value)"
>
</div>
<div class="ml-2 text-16px leading-normal">
<slot name="label">
<label v-if="label" :for="id" class="disabled:text-gray-500 text-gray-500 font-light select-none">
{{ label }}
</label>
</slot>
<slot name="label">
<label
v-if="label"
:for="id"
class="disabled:text-gray-500 text-gray-500 font-light select-none"
>
{{ label }}
</label>
</slot>
</div>
</div>
</template>
@@ -42,7 +46,11 @@ withDefaults(defineProps<{
state?: InputState
label?: string
}>(), {
state: 'default'
state: 'default',
label: undefined,
})
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
</script>
@@ -1,18 +1,18 @@
<template>
<prism-editor
class="font-mono leading-tight prism-editor"
v-model="localValue"
class="font-mono leading-tight prism-editor"
:readonly="readonly"
:highlight="highlighter"
/>
</template>
<script setup lang="ts">
import prism from "prismjs"
import { PrismEditor } from "vue-prism-editor"
import "vue-prism-editor/dist/prismeditor.min.css" // import the styles somewhere
import "prismjs/themes/prism.css" // import syntax highlighting styles
import { useModelWrapper } from "../../composables"
import prism from 'prismjs'
import { PrismEditor } from 'vue-prism-editor'
import 'vue-prism-editor/dist/prismeditor.min.css' // import the styles somewhere
import 'prismjs/themes/prism.css' // import syntax highlighting styles
import { useModelWrapper } from '../../composables'
const props = defineProps<{
modelValue: string
@@ -1,7 +1,13 @@
<template>
<div class="p-0 m-0 border-0 text-sm" :class="$attrs.class">
<div
class="p-0 m-0 border-0 text-sm"
:class="$attrs.class"
>
<div class="relative rounded-md">
<div v-if="hasPrefix" class="absolute inset-y-0 left-0 pl-4 flex items-center">
<div
v-if="hasPrefix"
class="absolute inset-y-0 left-0 pl-4 flex items-center"
>
<span class="text-gray-500 text-sm flex items-center justify-center">
<slot name="prefix">
<Icon
@@ -9,20 +15,27 @@
class="pointer-events-none"
:icon="prefixIcon"
:class="prefixIconClasses"
></Icon>
/>
</slot>
</span>
</div>
<CodeEditor
v-model="localValue"
:class="_inputClasses"
:readonly="readonly"
class="font-mono w-full h-full rounded border-transparent disabled:bg-gray-100 disabled:text-gray-400 border-gray-300 focus:border-gray-500 focus:bg-white bg-gray-100 focus:ring-0 focus:outline-none focus:bg-white focus:text-gray-900 border-1 py-2 px-4 whitespace-pre overflow-auto"
v-model="localValue"
/>
<div v-if="hasSuffix" class="absolute inset-y-0 right-0 pr-3 flex items-center">
<div
v-if="hasSuffix"
class="absolute inset-y-0 right-0 pr-3 flex items-center"
>
<span class="text-gray-500 text-sm flex items-center justify-center">
<slot name="suffix">
<Icon :icon="suffixIcon" class="pointer-events-none" :class="suffixIconClasses"></Icon>
<Icon
:icon="suffixIcon"
class="pointer-events-none"
:class="suffixIconClasses"
/>
</slot>
</span>
</div>
@@ -32,11 +45,10 @@
<script lang="ts" setup>
import _ from 'lodash'
import "prismjs"
import "@packages/reporter/src/errors/prism.scss"
import 'prismjs'
import '@packages/reporter/src/errors/prism.scss'
import CodeEditor from './CodeEditor.vue'
import type { FunctionalComponent, SVGAttributes } from 'vue'
import { computed, useSlots } from 'vue'
import { FunctionalComponent, SVGAttributes, computed, useSlots } from 'vue'
import { useModelWrapper } from '../../composables'
const slots = useSlots()
@@ -48,28 +60,35 @@ const props = withDefaults(defineProps<{
suffixIcon?: FunctionalComponent<SVGAttributes, {}>
suffixIconClasses?: string | string[] | Record<string, string>
modelValue?: string
readonly?: boolean
readonly: boolean
}>(), {
modelValue: ''
modelValue: '',
inputClasses: undefined,
prefixIcon: undefined,
prefixIconClasses: undefined,
suffixIcon: undefined,
suffixIconClasses: undefined,
readonly: false,
})
const emits = defineEmits(['update:modelValue'])
const localValue = useModelWrapper(props, emits, 'modelValue')
const hasPrefix = computed(() => {
return slots.prefix || props.prefixIcon
return !!(slots.prefix || props.prefixIcon)
})
const hasSuffix = computed(() => {
return slots.suffix || props.suffixIcon
return !!(slots.suffix || props.suffixIcon)
})
const _inputClasses = computed(() => ([
props.inputClasses,
hasPrefix ? 'pl-10' : 'pl-4',
hasSuffix ? 'pr-6' : 'pr-0'
]))
const _inputClasses = computed(() => {
return ([
props.inputClasses,
hasPrefix.value ? 'pl-10' : 'pl-4',
hasSuffix.value ? 'pr-6' : 'pr-0',
])
})
</script>
<style lang="scss">
@@ -1,13 +1,17 @@
<template>
<span class="flex align-center justify-center" v-if="icon">
<span
v-if="icon"
class="flex align-center justify-center"
>
<component :is="icon" />
</span>
</template>
<script lang="ts">
export default {
inheritAttrs: true
inheritAttrs: true,
}
</script>
<script lang="ts" setup>
import type { FunctionalComponent, SVGAttributes } from 'vue'
@@ -1,62 +1,65 @@
<template>
<span class="absolute z-1 inset-y-0 left-0 flex items-center pl-2">
<slot name="prefix" :iconClass="prefixIconClass" :containerClass="buttonClass">
<button type="submit" :class="buttonClass">
<Icon :icon="prefixIcon" :class="prefixIconClass" />
</button>
</slot>
</span>
<span class="w-full h-full"
<span class="absolute z-1 inset-y-0 left-0 flex items-center pl-2">
<slot
name="prefix"
:iconClass="props.prefixIconClass"
:containerClass="buttonClass"
>
<button
type="submit"
:class="buttonClass"
>
<Icon
:icon="props.prefixIcon"
:class="props.prefixIconClass"
/>
</button>
</slot>
</span>
<span
class="w-full h-full"
:class="containerAttrs"
><slot
><slot
:iconOffsetClasses="{
suffix: 'pr-36px',
prefix: 'pl-32px',
}"
></slot></span>
<span class="absolute z-1 inset-y-0 right-0 flex items-center pr-2">
<slot name="suffix" :iconClass="suffixIconClass" :containerClass="buttonClass">
<button type="submit" :class="buttonClass">
<Icon :icon="suffixIcon" :class="suffixIconClass" />
</button>
</slot>
</span>
/></span>
<span class="absolute z-1 inset-y-0 right-0 flex items-center pr-2">
<slot
name="suffix"
:iconClass="props.suffixIconClass"
:containerClass="buttonClass"
>
<button
type="submit"
:class="buttonClass"
>
<Icon
:icon="props.suffixIcon"
:class="props.suffixIconClass"
/>
</button>
</slot>
</span>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { useModelWrapper } from '../../composables'
<script lang="ts" setup>
import { FunctionalComponent, SVGAttributes, useAttrs } from 'vue'
import Icon from './Icon.vue'
import type { FunctionalComponent, SVGAttributes } from 'vue'
type IconType = FunctionalComponent<SVGAttributes>
const buttonClass = "p-1 focus:outline-none focus:shadow-outline flex items-center"
const buttonClass = 'p-1 focus:outline-none focus:shadow-outline flex items-center'
export const iconProps = {
prefixIcon: Object as PropType<IconType>,
prefixIconClass: String,
suffixIcon: Object as PropType<IconType>,
suffixIconClass: String,
}
const props = defineProps<{
prefixIcon: IconType
prefixIconClass: string
suffixIcon: IconType
suffixIconClass: string
modelValue?: string
}>()
export default defineComponent({
inheritAttrs: false,
components: { Icon },
props: {
...iconProps,
modelValue: {
type: String,
default: ''
},
},
setup(props, { emit, attrs, slots }) {
return {
localValue: useModelWrapper(props, emit, 'modelValue'),
buttonClass,
containerAttrs: attrs.class || {}
}
}
})
const containerAttrs = useAttrs().class
</script>
@@ -1,7 +1,13 @@
<template>
<div class="p-0 m-0 border-0" :class="$attrs.class">
<div
class="p-0 m-0 border-0"
:class="$attrs.class"
>
<div class="relative rounded-md">
<div v-if="hasPrefix" class="absolute inset-y-0 left-0 pl-4 flex items-center">
<div
v-if="hasPrefix"
class="absolute inset-y-0 left-0 pl-4 flex items-center"
>
<span class="text-gray-500 flex items-center justify-center">
<slot name="prefix">
<Icon
@@ -9,22 +15,32 @@
class="pointer-events-none"
:icon="prefixIcon"
:class="prefixIconClasses"
></Icon>
<Icon v-else-if="type === 'search'" :icon="IconSearch" />
/>
<Icon
v-else-if="type === 'search'"
:icon="IconSearch"
/>
</slot>
</span>
</div>
<input
:type="type"
v-model="localValue"
:type="type"
:class="_inputClasses"
class="placeholder-gray-400 disabled:bg-gray-100 disabled:text-gray-400 leading-tight focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-10 py-2 border-gray-300 rounded-md"
v-bind="inputAttrs"
/>
<div v-if="hasSuffix" class="absolute inset-y-0 right-0 pr-3 flex items-center">
>
<div
v-if="hasSuffix"
class="absolute inset-y-0 right-0 pr-3 flex items-center"
>
<span class="text-gray-500 flex items-center justify-center">
<slot name="suffix">
<Icon :icon="suffixIcon" class="pointer-events-none" :class="suffixIconClasses"></Icon>
<Icon
:icon="suffixIcon"
class="pointer-events-none"
:class="suffixIconClasses"
/>
</slot>
</span>
</div>
@@ -34,15 +50,14 @@
<script lang="ts">
export default {
inheritAttrs: false
inheritAttrs: false,
}
</script>
<script lang="ts" setup>
import { computed, useSlots, useAttrs, InputHTMLAttributes, FunctionalComponent, SVGAttributes } from 'vue'
import _ from 'lodash'
import IconSearch from 'virtual:vite-icons/mdi/magnify'
import type { InputHTMLAttributes, FunctionalComponent, SVGAttributes } from 'vue'
import { computed, useSlots, useAttrs } from 'vue'
import { useModelWrapper } from '../../composables'
const slots = useSlots()
@@ -60,7 +75,12 @@ const props = withDefaults(defineProps<{
modelValue?: string
}>(), {
type: 'text',
modelValue: ''
modelValue: '',
inputClasses: undefined,
prefixIcon: undefined,
prefixIconClasses: undefined,
suffixIcon: undefined,
suffixIconClasses: undefined,
})
const emits = defineEmits(['update:modelValue'])
@@ -68,17 +88,19 @@ const emits = defineEmits(['update:modelValue'])
const localValue = useModelWrapper(props, emits, 'modelValue')
const hasPrefix = computed(() => {
return slots.prefix || props.prefixIcon || props.type === 'search'
return !!(slots.prefix || props.prefixIcon || props.type === 'search')
})
const hasSuffix = computed(() => {
return slots.suffix || props.suffixIcon
return !!(slots.suffix || props.suffixIcon)
})
const _inputClasses = computed(() => ([
props.inputClasses,
hasPrefix ? 'pl-10' : 'pl-4',
hasSuffix ? 'pr-6' : 'pr-0'
]))
const _inputClasses = computed(() => {
return ([
props.inputClasses,
hasPrefix.value ? 'pl-10' : 'pl-4',
hasSuffix.value ? 'pr-6' : 'pr-0',
])
})
</script>
@@ -1,4 +1,5 @@
import Icon from '../icon/Icon.vue'
import { h } from 'vue'
import IconHeart from 'virtual:vite-icons/mdi/heart'
import { defaultMessages } from '../../locales/i18n'
@@ -127,15 +128,31 @@ describe('<Select />', () => {
})
it('does not render the placeholder after selecting an option', () => {
mountSelect({
placeholder: 'A placeholder',
modelValue: undefined,
}).get(inputSelector)
.should('contain.text', 'A placeholder')
.then(openSelect)
.then(selectFirstOption)
.get(inputSelector)
.should('not.contain.text', 'A placeholder')
// The width and padding need to be here so that
// a click on the body dismisses the options
cy.mount({
components: { Select },
data () {
return {
model: undefined,
}
},
render () {
return h(Select, {
modelValue: this.model,
'onUpdate:modelValue': (value: any) => this.model = value,
options: defaultOptions,
placeholder: 'A placeholder',
})
},
}).then(() => {
cy.get(inputSelector)
.should('contain.text', 'A placeholder')
.then(openSelect)
.then(selectFirstOption)
.get(inputSelector)
.should('not.contain.text', 'A placeholder')
})
})
it('does not render the placeholder when there is a value selected', () => {
@@ -187,50 +204,4 @@ describe('<Select />', () => {
.get(checkIconSelector).should('not.exist')
})
})
describe('playground', () => {
it('renders examples', () => {
const moreOptions = [
{ name: 'Jess', id: '1i24u' },
{ name: 'Bart', id: 'ewopf' },
{ name: 'Lachlan', id: 'ewiofjdew' },
{ name: 'Jill', id: '2r2rj3' },
{ name: 'Bruno', id: '3r2rj3' },
{ name: 'Jade', id: '4r2rj3' },
{ name: 'Bella', id: '5r2rj3' },
]
let firstSelect = undefined
let secondSelect = moreOptions[0]
cy.mount(() => (<div class="w-350px p-12">
<b>With placeholder, null value</b>
<Select modelValue={firstSelect}
data-testid="first-select"
placeholder="Choose a color..."
options={defaultOptions}
></Select>
<b>Pre-selected</b>
<Select modelValue={secondSelect}
data-testid="second-select"
item-value="name"
item-key="id"
options={moreOptions}
></Select>
</div>))
const firstSelector = `[data-testid=first-select]`
const secondSelector = `[data-testid=second-select]`
cy.get(firstSelector).click()
.then(selectFirstOption)
.get(firstSelector).should('have.text', defaultOptions[0].value)
cy.get(secondSelector).click().get(optionsSelector).within(($options) => {
return cy.wrap(Cypress.$($options[1])).click()
}).get(secondSelector)
.should('contain.text', moreOptions[1].name)
})
})
})
@@ -1,78 +1,141 @@
<template>
<Listbox as="div" v-model="modelValue" @update:model-value="$emit('update:modelValue', $event)">
<template #="{ open }">
<ListboxLabel class="block text-sm font-medium text-gray-700">
<template v-if="label"> {{ label }} </template>
<slot v-else name="label" :open="open"></slot>
</ListboxLabel>
<div class="mt-1 relative">
<ListboxButton class="bg-white text-gray-800 relative w-full border border-gray-300 rounded pl-3 pr-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<span class="absolute inset-y-0 flex items-center">
<slot name="input-prefix" :value="modelValue" :open="open"></slot>
</span>
<span class="pr-4" :class="
{
'pl-8': $slots['input-prefix'],
}
">
<span v-if="!modelValue">
{{ placeholder ? placeholder : t('components.select.placeholder') }}
<Listbox
:value="props.modelValue"
as="div"
@update:modelValue="handleUpdate"
>
<template #default="{ open }">
<ListboxLabel class="block text-sm font-medium text-gray-700">
<template v-if="label">
{{ label }}
</template>
<slot
v-else
name="label"
:open="open"
/>
</ListboxLabel>
<div class="mt-1 relative">
<ListboxButton class="bg-white text-gray-800 relative w-full border border-gray-300 rounded pl-3 pr-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<span class="absolute inset-y-0 flex items-center">
<slot
name="input-prefix"
:value="modelValue"
:open="open"
/>
</span>
<slot name="selected" v-else>
{{ get(modelValue, itemValue || '') }}
</slot>
</span>
<span class="absolute inset-y-0 right-0 pr-2 flex items-center">
<slot name="input-suffix" :value="modelValue" :open="open">
<Icon :icon="IconCaret" class="text-lg transform transition-transform"
data-testid="icon-caret"
aria-hidden="true"
:class="{
'rotate-0 text-indigo-600': open,
'rotate-180 text-gray-500': !open
}"/>
</slot>
</span>
</ListboxButton>
<span
class="pr-4"
:class="
{
'pl-8': $slots['input-prefix'],
}
"
>
<span v-if="!modelValue">
{{ placeholder ? placeholder : t('components.select.placeholder') }}
</span>
<slot
v-else
name="selected"
>
{{ get(modelValue, itemValue || '') }}
</slot>
</span>
<span class="absolute inset-y-0 right-0 pr-2 flex items-center">
<slot
name="input-suffix"
:value="modelValue"
:open="open"
>
<Icon
:icon="IconCaret"
class="text-lg transform transition-transform"
data-testid="icon-caret"
aria-hidden="true"
:class="{
'rotate-0 text-indigo-600': open,
'rotate-180 text-gray-500': !open
}"
/>
</slot>
</span>
</ListboxButton>
<transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions class="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
<ListboxOption as="ul" v-for="option in options" :key="get(option, itemKey ?? '')" :value="option" :disabled="option.disabled" v-slot="{ active, selected }">
<li class="cursor-default block truncate select-none relative py-2 pl-3 pr-9" :class="[{
'font-medium': selected,
'bg-gray-50': active,
'text-gray-900': !active,
'text-opacity-40': option.disabled
}]">
<transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions class="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
<ListboxOption
v-for="option in options"
:key="get(option, itemKey ?? '')"
v-slot="{ active, selected }"
as="ul"
:value="option"
:disabled="option.disabled"
>
<li
class="cursor-default block truncate select-none relative py-2 pl-3 pr-9"
:class="[{
'font-medium': selected,
'bg-gray-50': active,
'text-gray-900': !active,
'text-opacity-40': option.disabled
}]"
>
<span class="absolute inset-y-0 flex items-center">
<slot name="item-prefix" :selected="selected" :active="active" :value="option"></slot>
<slot
name="item-prefix"
:selected="selected"
:active="active"
:value="option"
/>
</span>
<span class="inline-block" :class="{
'pl-8': $slots['item-prefix'],
'pr-4': $slots['item-suffix'],
}">
<slot name="item-body" :selected="selected" :active="active" :value="option">
<span
class="inline-block"
:class="{
'pl-8': $slots['item-prefix'],
'pr-4': $slots['item-suffix'],
}"
>
<slot
name="item-body"
:selected="selected"
:active="active"
:value="option"
>
{{ get(option, itemValue || '') }}
</slot>
</span>
<span class="absolute inset-y-0 right-0 pr-8 flex text-sm items-center">
<slot name="item-suffix" :selected="selected" :active="active" :value="option">
<span v-if="selected" class="text-indigo-500 absolute flex items-center">
<Icon :icon="IconCheck"
class="text-sm"
<slot
name="item-suffix"
:selected="selected"
:active="active"
:value="option"
>
<span
v-if="selected"
class="text-indigo-500 absolute flex items-center"
>
<Icon
:icon="IconCheck"
class="text-sm"
data-testid="icon-check"
aria-hidden="true"/>
aria-hidden="true"
/>
</span>
</slot>
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</template>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</template>
</Listbox>
</template>
@@ -101,8 +164,15 @@ const props = withDefaults(defineProps<{
placeholder: '',
label: '',
itemValue: 'value',
itemKey: 'key'
modelValue: undefined,
itemKey: 'key',
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
(event: 'update:modelValue', value: Option)
}>()
const handleUpdate = (value: Option) => {
emit('update:modelValue', value)
}
</script>
@@ -1,32 +1,32 @@
<template>
<svg
:height="radius * 2"
:width="radius * 2"
>
<circle
stroke="#EBEBEB"
fill="transparent"
:stroke-width="stroke"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
<circle
stroke="currentColor"
fill="transparent"
:stroke-dasharray="circumference + ' ' + circumference"
:style="{ strokeDashoffset }"
:stroke-width="stroke"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
stroke-linecap="round"
/>
</svg>
<svg
:height="radius * 2"
:width="radius * 2"
>
<circle
stroke="#EBEBEB"
fill="transparent"
:stroke-width="stroke"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
<circle
stroke="currentColor"
fill="transparent"
:stroke-dasharray="circumference + ' ' + circumference"
:style="{ strokeDashoffset }"
:stroke-width="stroke"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
stroke-linecap="round"
/>
</svg>
</template>
<script lang="ts" setup>
import { computed } from "vue"
import { computed } from 'vue'
const props = defineProps<{
radius: number
@@ -34,11 +34,11 @@ const props = defineProps<{
progress: number
}>()
const normalizedRadius = props.radius - props.stroke * 2;
const circumference = normalizedRadius * 2 * Math.PI;
const normalizedRadius = props.radius - props.stroke * 2
const circumference = normalizedRadius * 2 * Math.PI
const strokeDashoffset = computed(() => {
return circumference - props.progress / 100 * circumference;
return circumference - props.progress / 100 * circumference
})
</script>
@@ -48,4 +48,4 @@ svg{
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
</style>
</style>
@@ -1,9 +1,13 @@
<template>
<div class="text-left relative">
<label class="text-gray-800 text-sm my-3 block" :class="disabledClass">{{
<label
class="text-gray-800 text-sm my-3 block"
:class="disabledClass"
>{{
props.name
}}</label>
<button
v-click-outside="() => (isOpen = false)"
class="
h-10
text-left
@@ -17,30 +21,32 @@
w-full
focus:border-indigo-600 focus:outline-transparent
"
:class="disabledClass
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
:class="disabledClass
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
+ (props.disabled ? ' bg-gray-300 text-gray-600' : '')"
:disabled="props.disabled"
@click="
if (!props.disabled) {
isOpen = !isOpen;
}
"
:disabled="props.disabled"
v-click-outside="() => (isOpen = false)"
>
<template v-if="selectedOptionObject">
<img
:src="FrameworkBundlerLogos[selectedOptionObject.type]"
class="w-5 h-5 mr-3"
/>
>
<span>
{{ selectedOptionObject.name }}
</span>
</template>
<span v-else class="text-gray-400">
<span
v-else
class="text-gray-400"
>
{{ props.placeholder }}
</span>
<span class="flex-grow"></span>
<span class="flex-grow" />
<i-fa-angle-down />
</button>
<ul
@@ -60,14 +66,14 @@
<li
v-for="opt in props.options"
:key="opt.type"
@click="selectOption(opt.type)"
focus="1"
class="cursor-pointer flex items-center py-1 px-2 hover:bg-gray-10"
@click="selectOption(opt.type)"
>
<img
:src="FrameworkBundlerLogos[opt.type]"
class="w-5 h-5 mr-3"
/>
>
<span>
{{ opt.name }}
</span>
@@ -77,9 +83,9 @@
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
import { computed, ref } from 'vue'
import { ClickOutside as vClickOutside } from '../../directives/ClickOutside'
import type { EnvironmentSetupFragment, SupportedBundlers } from "../../generated/graphql";
import type { EnvironmentSetupFragment, SupportedBundlers } from '../../generated/graphql'
import { FrameworkBundlerLogos } from '../../utils/icons'
const emit = defineEmits<{
@@ -93,18 +99,20 @@ const props = withDefaults(defineProps<{
options: EnvironmentSetupFragment['allBundlers']
disabled?: boolean
}>(), {
disabled: false
disabled: false,
value: undefined,
placeholder: undefined,
})
const isOpen = ref(false);
const isOpen = ref(false)
const selectedOptionObject = computed(() => {
return props.options.find((opt) => opt.type === props.value);
});
return props.options.find((opt) => opt.id === props.value)
})
const selectOption = (opt: SupportedBundlers) => {
emit("select", opt);
};
emit('select', opt)
}
const disabledClass = computed(() => props.disabled ? "opacity-50" : undefined)
const disabledClass = computed(() => props.disabled ? 'opacity-50' : undefined)
</script>
@@ -1,9 +1,13 @@
<template>
<div class="text-left relative">
<label class="text-gray-800 text-sm my-3 block" :class="disabledClass">{{
<label
class="text-gray-800 text-sm my-3 block"
:class="disabledClass"
>{{
props.name
}}</label>
<button
v-click-outside="() => (isOpen = false)"
class="
h-10
text-left
@@ -18,30 +22,32 @@
focus:border-indigo-600 focus:outline-transparent
"
data-cy="select-framework"
:class="disabledClass
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
:class="disabledClass
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
+ (props.disabled ? ' bg-gray-300 text-gray-600' : '')"
:disabled="props.disabled"
@click="
if (!props.disabled) {
isOpen = !isOpen;
}
"
:disabled="props.disabled"
v-click-outside="() => (isOpen = false)"
>
<template v-if="selectedOptionObject">
<img
:src="FrameworkBundlerLogos[selectedOptionObject.type]"
class="w-5 h-5 mr-3"
/>
>
<span>
{{ selectedOptionObject.name }}
</span>
</template>
<span v-else class="text-gray-400">
<span
v-else
class="text-gray-400"
>
{{ props.placeholder }}
</span>
<span class="flex-grow"></span>
<span class="flex-grow" />
<i-fa-angle-down />
</button>
<ul
@@ -60,15 +66,15 @@
>
<li
v-for="opt in props.options"
:key="opt.type"
@click="selectOption(opt.type)"
:key="opt.id"
focus="1"
class="cursor-pointer flex items-center py-1 px-2 hover:bg-gray-10"
@click="selectOption(opt.type)"
>
<img
:src="FrameworkBundlerLogos[opt.type]"
class="w-5 h-5 mr-3"
/>
>
<span>
{{ opt.name }}
</span>
@@ -78,11 +84,17 @@
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
import { computed, ref } from 'vue'
import { ClickOutside as vClickOutside } from '../../directives/ClickOutside'
import type { EnvironmentSetupFragment, FrontendFrameworkEnum } from "../../generated/graphql";
import type { EnvironmentSetupFragment, FrontendFrameworkEnum } from '../../generated/graphql'
import { FrameworkBundlerLogos } from '../../utils/icons'
export interface Option {
name: string;
description?: string;
id: string;
}
const emit = defineEmits<{
(event: 'select', type: FrontendFrameworkEnum)
}>()
@@ -94,18 +106,20 @@ const props = withDefaults(defineProps<{
options: EnvironmentSetupFragment['frameworks']
disabled?: boolean
}>(), {
disabled: false
disabled: false,
value: undefined,
placeholder: undefined,
})
const isOpen = ref(false);
const isOpen = ref(false)
const selectedOptionObject = computed(() => {
return props.options.find((opt) => opt.type === props.value);
});
return props.options.find((opt) => opt.id === props.value)
})
const selectOption = (opt: FrontendFrameworkEnum) => {
emit("select", opt);
};
emit('select', opt)
}
const disabledClass = computed(() => props.disabled ? "opacity-50" : undefined)
const disabledClass = computed(() => props.disabled ? 'opacity-50' : undefined)
</script>
+33 -12
View File
@@ -1,9 +1,23 @@
<template>
<main class="text-center" ref="projectUpload">
<h1 class="mb-2 text-2rem">{{ t('globalPage.empty.title') }}</h1>
<p class="mb-6 text-lg font-light text-body-gray">{{ t('globalPage.empty.helper') }}</p>
<FileSelector v-model="files" v-slot="{ openDialog }" allow-multiple>
<Dropzone v-slot="{ hovered }" @click="openDialog">
<main
ref="projectUpload"
class="text-center"
>
<h1 class="mb-2 text-2rem">
{{ t('globalPage.empty.title') }}
</h1>
<p class="mb-6 text-lg font-light text-body-gray">
{{ t('globalPage.empty.helper') }}
</p>
<FileSelector
v-slot="{ openDialog }"
v-model="files"
allow-multiple
>
<Dropzone
v-slot="{ hovered }"
@click="openDialog"
>
<div
class="relative block w-full p-12 text-center border-2 border-gray-300 border-dashed rounded-lg h-240px min-w-220px bg-gray-50 hover:border-gray-400"
:class="{ 'border-blue-200': hovered }"
@@ -12,8 +26,8 @@
<span class="font-light text-body-gray-700 text-18px">
<i18n-t keypath="globalPage.empty.dropText">
<button class="font-medium text-primary hover:underline">
<!--
This button allows keyboard users to fire a click event with the Enter or Space keys,
<!--
This button allows keyboard users to fire a click event with the Enter or Space keys,
which will be handled by the dropzone's existing click handler.
-->
{{ t('globalPage.empty.browseManually') }}
@@ -23,12 +37,17 @@
</div>
</Dropzone>
</FileSelector>
<div data-testid="upload-name" class="hidden">{{ uploadName }}</div>
<div
data-testid="upload-name"
class="hidden"
>
{{ uploadName }}
</div>
</main>
</template>
<script lang="ts" setup>
import { useI18n } from "../composables"
import { useI18n } from '../composables'
import IconPlaceholder from 'virtual:vite-icons/icons8/circle-thin'
import { FileSelector, Dropzone } from 'vue3-file-selector'
import { ref, watch, onMounted } from 'vue'
@@ -38,11 +57,14 @@ const files = ref<File[]>([])
const uploadName = ref('')
const projectUpload = ref<HTMLDivElement>()
const selectProject = (file: File) => { uploadName.value = file.name }
const selectProject = (file: File) => {
uploadName.value = file.name
}
watch(files, (newVal) => {
const uploadLength = newVal.length;
const uploadLength = newVal.length
const latestUpload = newVal[uploadLength - 1]
selectProject(latestUpload)
})
@@ -51,5 +73,4 @@ onMounted(() => {
projectUpload.value?.querySelector('input[type=file]')?.setAttribute('webkitdirectory', 'webkitdirectory')
})
</script>
+18 -15
View File
@@ -9,7 +9,11 @@
<GlobalPageHeader v-model="match" />
</div>
<GlobalProjectCard v-for="project, idx in filteredProjects" :key="idx" :project="project" />
<GlobalProjectCard
v-for="project, idx in filteredProjects"
:key="idx"
:project="project"
/>
</div>
</template>
@@ -18,8 +22,7 @@
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { Ref } from 'vue'
import { computed, ref, Ref } from 'vue'
import WelcomeGuide from './WelcomeGuide.vue'
import GlobalProjectCard from './GlobalProjectCard.vue'
import GlobalPageHeader from './GlobalPageHeader.vue'
@@ -34,7 +37,7 @@ type Project = {
const testProject: Project = {
name: 'Project Name',
lastRunStatus: 'passed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 255 // 255 days ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 255, // 255 days ago
}
// I don't know why this isn't type checking correctly
@@ -45,57 +48,57 @@ const projects: Ref<Project[]> = ref([
{
name: 'Project Name 2',
lastRunStatus: 'failed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 100 // 100 days ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 100, // 100 days ago
},
{
name: 'Fifty Days Ago',
lastRunStatus: 'pending',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 50 // 50 days ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 50, // 50 days ago
},
{
name: 'Ten Days ago',
lastRunStatus: 'passed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 10 // 10 days ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 10, // 10 days ago
},
{
name: 'Five days',
lastRunStatus: 'passed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 5 // 5 days ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 5, // 5 days ago
},
{
name: 'Project Name 6',
lastRunStatus: 'failed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1 // 1 day ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1, // 1 day ago
},
{
name: 'Project Name 6',
lastRunStatus: 'failed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1 // 1 day ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1, // 1 day ago
},
{
name: 'Project Name 6',
lastRunStatus: 'failed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1 // 1 day ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1, // 1 day ago
},
{
name: 'Project Name 6',
lastRunStatus: 'failed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1 // 1 day ago
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 1, // 1 day ago
},
{
name: 'Project Name 7',
lastRunStatus: 'passed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 0 // today
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 0, // today
},
{
name: 'Project Name 8',
lastRunStatus: 'failed',
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 0 // yesterday
lastRun: Date.now() - 1000 * 60 * 60 * 24 * 0, // yesterday
},
])
const filteredProjects = computed(() => {
return projects.value.filter(p => p.name.toLowerCase().indexOf(match.value.toLowerCase()) !== -1)
return projects.value.filter((p) => p.name.toLowerCase().indexOf(match.value.toLowerCase()) !== -1)
})
const match = ref('')
@@ -1,16 +1,18 @@
<template>
<Input
v-model="localValue"
type="search"
class="min-w-200px w-80% flex-grow"
:placeholder="t('globalPage.searchPlaceholder')"
v-model="localValue"
/>
<Button
@click="$emit('new-project')"
:prefixIcon="IconPlus"
prefixIconClass="text-center justify-center text-lg"
:prefix-icon="IconPlus"
prefix-icon-class="text-center justify-center text-lg"
class="w-20% min-w-120px text-size-16px h-full focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
>{{ t('globalPage.newProjectButton') }}</Button>
@click="$emit('new-project')"
>
{{ t('globalPage.newProjectButton') }}
</Button>
</template>
<script lang="ts" setup>
@@ -1,14 +1,23 @@
<template>
<div class="relative min-w-200px rounded-lg border border-gray-300 bg-white px-16px pt-13px pb-15px shadow-sm flex items-center space-x-3 hover:border-gray-400 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<div class="flex-1 min-w-0">
<button class="focus:outline-none underline-transparent grid w-full text-left children:truncate" @click="$emit('click', project)">
<p class="text-16px row-[1] leading-normal font-medium text-indigo-600">{{ project.name }}</p>
<button
class="focus:outline-none underline-transparent grid w-full text-left children:truncate"
@click="emit('projectSelected', project)"
>
<p class="text-16px row-[1] leading-normal font-medium text-indigo-600">
{{ project.name }}
</p>
<p class="text-sm text-gray-500 relative flex flex-wrap self-end items-center gap-1 bullet-points children:flex children:items-center children:gap-1">
<span>{{ getTimeAgo(project.lastRun) }}</span>
<span>project/master</span>
<span>v8.0</span>
</p>
<Icon :icon="iconForStatus.icon" :class="iconForStatus.classes" class="ml-2 justify-self-end self-center row-start-1 row-end-3 col-start-2 text-sm"/>
<Icon
:icon="iconForStatus.icon"
:class="iconForStatus.classes"
class="ml-2 justify-self-end self-center row-start-1 row-end-3 col-start-2 text-sm"
/>
</button>
</div>
</div>
@@ -21,28 +30,39 @@ import IconChecked from 'virtual:vite-icons/mdi/check-circle'
import IconX from 'virtual:vite-icons/mdi/plus-circle'
import IconPending from 'virtual:vite-icons/mdi/refresh-circle'
import { getTimeAgo } from "../utils/time";
import { getTimeAgo } from '../utils/time'
const icons = {
passed: {
icon: IconChecked,
classes: 'text-green-500'
classes: 'text-green-500',
},
failed: {
icon: IconX,
classes: 'text-red-500 rotate-45 translate'
classes: 'text-red-500 rotate-45 translate',
},
pending: {
icon: IconPending,
classes: 'text-blue-500'
}
classes: 'text-blue-500',
},
}
// TOOD: use graphql types here
interface Project {
name: string
lastRun: number
lastRunStatus: string
}
// TODO: I want to use an enum here for 'lastRunStatus'
// but I'm struggling to get the types within the tests
// When GQL exists, I'll be able to pull in the shared types.
const props = defineProps<{
project: { name: string, lastRun: number, lastRunStatus: string }
project: Project
}>()
const emit = defineEmits<{
(event: 'projectSelected', project: Project): void
}>()
const iconForStatus = computed(() => icons[props.project.lastRunStatus])
+29 -16
View File
@@ -1,19 +1,28 @@
<template>
<div class="bg-indigo-50 border-b-1 border-b-indigo-100 w-full p-12 flex relative" v-if="show">
<div
v-if="show"
class="bg-indigo-50 border-b-1 border-b-indigo-100 w-full p-12 flex relative"
>
<Button
variant="link"
class="absolute top-1 right-1 sibling:absolute"
:suffixIcon="IconCircleX"
:suffix-icon="IconCircleX"
@click="show = !show"
>{{ t('components.modal.dismiss') }}</Button>
>
{{ t('components.modal.dismiss') }}
</Button>
<IconPlaceholder
class="min-w-100px mr-[5%] max-w-224px self-start h-full relative justify-center w-full text-indigo-600"
/>
<div class="divide-y divide-gray-300">
<div class="grid">
<div class="children:leading-normal ml-2 mb-4">
<h1 class="text-2rem text-gray-900">{{ t('welcomeGuide.header.title') }}</h1>
<p class="text-16px text-gray-500 font-light">{{ t('welcomeGuide.header.description') }}</p>
<h1 class="text-2rem text-gray-900">
{{ t('welcomeGuide.header.title') }}
</h1>
<p class="text-16px text-gray-500 font-light">
{{ t('welcomeGuide.header.description') }}
</p>
</div>
<div class="grid link-wrappers justify-between">
@@ -21,25 +30,29 @@
class="mb-2"
:header="t('welcomeGuide.projectListHeader')"
:items="projects.slice(0, 3)"
@click="chooseProject"
@itemSelected="chooseProject"
>
<template #="{ item }">{{ item.path }}</template>
<template #default="{ item }">
{{ item.path }}
</template>
</WelcomeGuideLinks>
<WelcomeGuideLinks
:header="t('welcomeGuide.linkHeader')"
:items="links"
@click="openLink"
@itemSelected="openLink"
>
<template #="{ item }">{{ item.description }}</template>
<template #default="{ item }">
{{ item.description }}
</template>
</WelcomeGuideLinks>
</div>
</div>
<div class="py-16px">
<Checkbox
v-model="showWelcomeGuideOnStartup"
id="show-welcome-guide"
v-model="showWelcomeGuideOnStartup"
:label="t('welcomeGuide.confirmWelcomeGuide')"
></Checkbox>
/>
</div>
</div>
</div>
@@ -47,7 +60,7 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { useI18n } from "../composables"
import { useI18n } from '../composables'
import Checkbox from '../components/checkbox/Checkbox.vue'
import Button from '../components/button/Button.vue'
import WelcomeGuideLinks from './WelcomeGuideLinks.vue'
@@ -67,16 +80,16 @@ const projects = [
const links = [
{
description: 'Getting Started',
href: 'https://on.cypress.io/'
href: 'https://on.cypress.io/',
},
{
description: 'Learning Academy',
href: 'https://on.cypress.io/'
href: 'https://on.cypress.io/',
},
{
description: 'Cypress Releases',
href: 'https://on.cypress.io/'
}
href: 'https://on.cypress.io/',
},
]
const { t } = useI18n()
@@ -1,13 +1,19 @@
<template>
<ul>
<h2 class="mb-1 text-gray-900 text-size-16px">{{ header }}</h2>
<li :key="idx" v-for="item, idx in items" class="truncate">
<h2 class="mb-1 text-gray-900 text-size-16px">
{{ header }}
</h2>
<li
v-for="item, idx in items"
:key="idx"
class="truncate"
>
<Button
@click="$emit('click', item)"
class="text-left text-16px truncate w-full children:truncate children:leading-normal"
variant="link"
@click="emit('itemSelected', item)"
>
<slot :item="item"></slot>
<slot :item="item" />
<template #prefix>
<div
class="border-indigo-500 flex-grow bg-indigo-200 w-14px max-w-14px min-w-14px h-14px min-h-14px rounded-full border-width-2px"
@@ -21,6 +27,10 @@
<script lang="ts" setup>
import Button from '../components/button/Button.vue'
const emit = defineEmits<{
(event: 'itemSelected', item: Record<string, string>): void
}>()
defineProps<{
items: Record<string, string>[]
header: string
+5 -3
View File
@@ -2,7 +2,10 @@
<div class="px-6 py-4 border-b mb-7">
<div class="flex items-center justify-between max-content">
<div class="flex items-center">
<img class="mr-2 w-32px h-32px" src="../images/cypress-dark.png" />
<img
class="mr-2 w-32px h-32px"
src="../images/cypress-dark.png"
>
<span class="text-primary">Projects</span>
<i-oi-chevron-right class="text-gray-300 h-8px" />
<span class="text-body-gray-700">{{ props.gql.app.activeProject?.title }}</span>
@@ -35,5 +38,4 @@ const props = defineProps<{
gql: HeaderBarFragment
}>()
</script>
</script>
+26 -19
View File
@@ -35,7 +35,11 @@
>
{{ projectTitle }}
</div>
<div class="bg-gray-900 text-gray-500 flex flex-col items-stretch" :style="`background-image: url('${bottomBackground}');`" style="background-position: bottom center;background-repeat: no-repeat;">
<div
class="bg-gray-900 text-gray-500 flex flex-col items-stretch"
:style="`background-image: url('${bottomBackground}');`"
style="background-position: bottom center;background-repeat: no-repeat;"
>
<SideBarItem
v-for="i in sideMenuDefinition"
:key="i?.id"
@@ -46,7 +50,10 @@
@click="handleSelect(i.type)"
/>
<div class="flex-grow" />
<img src="../images/cypress_s.png" class="m-4 mx-auto w-7" />
<img
src="../images/cypress_s.png"
class="m-4 mx-auto w-7"
>
</div>
<div class="flex items-stretch flex-col">
<slot :item="selected" />
@@ -55,12 +62,12 @@
</template>
<script lang="ts">
import { computed, defineComponent } from "vue";
import SideBarItem from "./SideBarItem.vue";
import { computed, defineComponent } from 'vue'
import SideBarItem from './SideBarItem.vue'
import bottomBackground from '../images/bottom_filler.svg'
import { gql } from "@urql/core";
import { gql } from '@urql/core'
import { useMutation, useQuery } from '@urql/vue'
import { LayoutDocument, NavigationMenuSetItemDocument, NavItem } from "../generated/graphql";
import { LayoutDocument, NavigationMenuSetItemDocument, NavItem } from '../generated/graphql'
import IconDashboardLine from 'virtual:vite-icons/clarity/dashboard-line'
import IconTerminalLine from 'virtual:vite-icons/clarity/terminal-line'
import IconSettingsLine from 'virtual:vite-icons/clarity/settings-line'
@@ -102,42 +109,42 @@ const icons = {
'clarity/bullet-list-line': IconRunsLine,
}
export default defineComponent({
components: {
SideBarItem,
},
setup() {
setup () {
const result = useQuery({
query: LayoutDocument
query: LayoutDocument,
})
const setMenuItem = useMutation(NavigationMenuSetItemDocument)
const projectTitle = computed(() => result.data.value?.app.activeProject?.title);
const projectTitle = computed(() => result.data.value?.app.activeProject?.title)
const handleSelect = (type: NavItem) => {
setMenuItem.executeMutation({ type })
}
const selected = computed(() => {
const item = result.data?.value?.navigationMenu?.items.find(item => item!.selected)
const item = result.data?.value?.navigationMenu?.items.find((item) => item!.selected)
return item?.id ?? null
})
const sideMenuDefinition = computed(() =>
result.data?.value?.navigationMenu?.items
)
const sideMenuDefinition = computed(() => {
return result.data?.value?.navigationMenu?.items
})
return {
return {
icons,
handleSelect,
projectTitle,
projectTitle,
selected,
sideMenuDefinition,
sideMenuDefinition,
bottomBackground,
};
}
},
});
})
</script>
@@ -3,7 +3,10 @@
class="relative mt-3 min-w-25px cursor-pointer flex items-center justify-center overflow-hidden"
>
<!-- Can't do a border-left solution because you need to round the corners. -->
<span class="absolute -left-7px rounded min-w-10px h-full" :class="{ 'bg-green-300': active }" />
<span
class="absolute -left-7px rounded min-w-10px h-full"
:class="{ 'bg-green-300': active }"
/>
<Icon
:icon="icon"
class="pl-0.25rem py-0.25rem min-h-25px min-w-25px w-full h-full"
+41 -24
View File
@@ -1,21 +1,37 @@
<template>
<div class="h-18 border border-gray-200 rounded bg-white flex items-center mb-2 box-border">
<div class="w-18 flex items-center justify-center">
<RunIcon :gql="props.gql" />
</div>
<div class="pl-4 border-l border-gray-200 flex-grow" v-if="run.commitInfo">
<h2 class="font-medium text-indigo-500 leading-4">{{ run.commitInfo.message }}</h2>
<div class="flex">
<span v-for="info in runInfo" class="flex items-center mr-3 mt-1">
<component v-if="info.icon" :is="info.icon" class="mr-1 text-gray-500 text-sm" />
<span class="text-gray-500 text-sm font-light">
{{info.text}}
</span>
</span>
</div>
</div>
<RunResults :gql="props.gql" class="m-6 ml-0" />
</div>
<div class="h-18 border border-gray-200 rounded bg-white flex items-center mb-2 box-border">
<div class="w-18 flex items-center justify-center">
<RunIcon :gql="props.gql" />
</div>
<div
v-if="run.commitInfo"
class="pl-4 border-l border-gray-200 flex-grow"
>
<h2 class="font-medium text-indigo-500 leading-4">
{{ run.commitInfo.message }}
</h2>
<div class="flex">
<span
v-for="info in runInfo"
:key="info.id"
class="flex items-center mr-3 mt-1"
>
<component
:is="info.icon"
v-if="info.icon"
class="mr-1 text-gray-500 text-sm"
/>
<span class="text-gray-500 text-sm font-light">
{{ info.text }}
</span>
</span>
</div>
</div>
<RunResults
:gql="props.gql"
class="m-6 ml-0"
/>
</div>
</template>
<script lang="ts" setup>
@@ -51,15 +67,16 @@ const props = defineProps<{
const run = computed(() => props.gql)
const runInfo = [{
text: run.value.commitInfo?.authorName,
icon: IconUserCircle
id: run.value.id,
text: run.value.commitInfo?.authorName,
icon: IconUserCircle,
},
{
text: run.value.commitInfo?.branch,
icon: IconBranch
text: run.value.commitInfo?.branch,
icon: IconBranch,
},
{
text: run.value.createdAt ? new Date(run.value.createdAt).toLocaleTimeString() : null
}].filter(o => Boolean(o.text))
text: run.value.createdAt ? new Date(run.value.createdAt).toLocaleTimeString() : null,
}].filter((o) => Boolean(o.text))
</script>
</script>
+21 -6
View File
@@ -1,8 +1,23 @@
<template>
<IconPass v-if="props.gql.status === 'PASSED'" class="text-green-500 text-xl" />
<IconFail v-else-if="props.gql.status ==='FAILED'" class="text-red-500 text-xl"/>
<IconWarn v-else-if="props.gql.status === 'CANCELLED'" class="text-orange-400 text-xl"/>
<ProgressCircle v-else :progress="progress" :radius="12" :stroke="2" class="text-indigo-400"/>
<IconPass
v-if="props.gql.status === 'PASSED'"
class="text-green-500 text-xl"
/>
<IconFail
v-else-if="props.gql.status ==='FAILED'"
class="text-red-500 text-xl"
/>
<IconWarn
v-else-if="props.gql.status === 'CANCELLED'"
class="text-orange-400 text-xl"
/>
<ProgressCircle
v-else
:progress="progress"
:radius="12"
:stroke="2"
class="text-indigo-400"
/>
</template>
<script lang="ts" setup>
@@ -10,7 +25,7 @@ import { gql } from '@urql/vue'
import IconPass from '../icons/pass.svg?component'
import IconFail from '../icons/fail.svg?component'
import IconWarn from '../icons/warn.svg?component'
import ProgressCircle from "../components/progress/ProgressCircle.vue"
import ProgressCircle from '../components/progress/ProgressCircle.vue'
import type { RunIconFragment } from '../generated/graphql'
gql`
@@ -26,4 +41,4 @@ const props = defineProps<{
// TODO: figure out how/if we can get number tests passed / num tests to run
const progress = typeof props.gql.status === 'number' ? props.gql.status : 0
</script>
</script>
+21 -21
View File
@@ -1,26 +1,26 @@
<template>
<div class="h-7 border border-gray-200 rounded flex text-gray-500" :class="class">
<div class="flex items-center p-2">
<IconFlake class="text-gray-400 text-sm mr-1" />
0
<!--
<div class="h-7 border border-gray-200 rounded flex text-gray-500">
<div class="flex items-center p-2">
<IconFlake class="text-gray-400 text-sm mr-1" />
0
<!--
TODO: Is flake even exposed via the API?
{{props.flake}}
{{props.flake}}
-->
</div>
<div class="flex items-center p-2">
<IconCancel class="text-gray-400 text-sm mr-1" />
{{props.gql.totalSkipped}}
</div>
<div class="flex items-center p-2">
<IconPass class="text-green-600 text-xs mr-1" />
{{props.gql.totalPassed}}
</div>
<div class="flex items-center p-2">
<IconFail class="text-red-600 mr-1" />
{{props.gql.totalFailed}}
</div>
</div>
</div>
<div class="flex items-center p-2">
<IconCancel class="text-gray-400 text-sm mr-1" />
{{ props.gql.totalSkipped }}
</div>
<div class="flex items-center p-2">
<IconPass class="text-green-600 text-xs mr-1" />
{{ props.gql.totalPassed }}
</div>
<div class="flex items-center p-2">
<IconFail class="text-red-600 mr-1" />
{{ props.gql.totalFailed }}
</div>
</div>
</template>
<script lang="ts" setup>
@@ -49,4 +49,4 @@ fragment RunResults on CloudRun {
const props = defineProps<{
gql: RunResultsFragment
}>()
</script>
</script>
+3 -3
View File
@@ -2,9 +2,9 @@
<main class="min-w-650px max-w-800px">
<div v-if="props.gql.runs?.nodes">
<RunCard
v-for="run of props.gql.runs.nodes"
:gql="run"
v-for="run of props.gql.runs.nodes"
:key="run.id"
:gql="run"
/>
</div>
</main>
@@ -28,4 +28,4 @@ fragment RunsPage on CloudProject {
const props = defineProps<{
gql: RunsPageFragment
}>()
</script>
</script>
@@ -1,47 +1,57 @@
<template>
<div class="p-4 min-w-650px mx-auto my-0">
<Disclosure
as="section"
<Disclosure
as="section"
#="{ open }"
class="border-1 border-gray-300 rounded">
<DisclosureButton
data-testid="settings-card-header"
class="settings-card-header w-full bg-cool-gray-100 py-4 pl-6 pr-3 select-none cursor-pointer grid gap-4">
class="border-1 border-gray-300 rounded"
>
<DisclosureButton
data-testid="settings-card-header"
class="settings-card-header w-full bg-cool-gray-100 py-4 pl-6 pr-3 select-none cursor-pointer grid gap-4"
>
<span class="grid self-center h-full">
<Icon :icon="icon" class="self-center text-indigo-600 text-xl"/>
<Icon
:icon="icon"
class="self-center text-indigo-600 text-xl"
/>
</span>
<div class="w-0.5px bg-gray-300 h-full"/>
<div class="w-0.5px bg-gray-300 h-full" />
<div>
<h2
data-testid="settings-card-title"
class="col-start-3 text-xl text-indigo-600">
<h2
data-testid="settings-card-title"
class="col-start-3 text-xl text-indigo-600"
>
{{ title }}
</h2>
<p data-testid="settings-card-description" class="text-gray-500 text-sm">
<p
data-testid="settings-card-description"
class="text-gray-500 text-sm"
>
{{ description }}
</p>
</div>
<Icon
:icon="IconCaret"
class="transform transition-transform col-start-4 self-center text-xl text-gray-500"
:class="{ 'rotate-90': !open, 'rotate-180': open }"/>
<Icon
:icon="IconCaret"
class="transform transition-transform col-start-4 self-center text-xl text-gray-500"
:class="{ 'rotate-90': !open, 'rotate-180': open }"
/>
</DisclosureButton>
<!-- Content of the Settings Card -->
<DisclosurePanel class="pl-6 pr-10 pt-6 pb-6 border-t-width-1px border-gray-300">
<slot/>
<slot />
</DisclosurePanel>
</Disclosure>
</div>
</template>
<script lang="ts" setup>
import IconCaret from 'virtual:vite-icons/mdi/caret'
import Icon from '../components/icon/Icon.vue'
import type { FunctionalComponent, SVGAttributes } from 'vue'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
defineProps<{
import IconCaret from 'virtual:vite-icons/mdi/caret'
import Icon from '../components/icon/Icon.vue'
import type { FunctionalComponent, SVGAttributes } from 'vue'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
defineProps<{
title: string,
description: string,
icon: FunctionalComponent<SVGAttributes, {}>
@@ -1,6 +1,5 @@
<template>
<main class="children:pb-4 children:border-b-1">
<slot/>
<slot />
</main>
</template>
@@ -1,25 +1,29 @@
<template>
<main class="min-w-650px max-w-800px">
<SettingsCard :title="t('settingsPage.device.title')"
:description="t('settingsPage.device.description')"
:icon="IconLaptop">
<DeviceSettings/>
<SettingsCard
:title="t('settingsPage.device.title')"
:description="t('settingsPage.device.description')"
:icon="IconLaptop"
>
<DeviceSettings />
</SettingsCard>
<SettingsCard :title="t('settingsPage.project.title')"
<SettingsCard
:title="t('settingsPage.project.title')"
:description="t('settingsPage.project.description')"
:icon="IconBook">
<ProjectSettings/>
:icon="IconBook"
>
<ProjectSettings />
</SettingsCard>
</main>
</template>
<script lang="ts" setup>
import { useI18n } from '../composables'
import SettingsCard from './SettingsCard.vue'
import ProjectSettings from './project/ProjectSettings.vue';
import DeviceSettings from './device/DeviceSettings.vue';
import IconLaptop from 'virtual:vite-icons/mdi/laptop'
import IconBook from 'virtual:vite-icons/mdi/book'
import { useI18n } from '../composables'
import SettingsCard from './SettingsCard.vue'
import ProjectSettings from './project/ProjectSettings.vue'
import DeviceSettings from './device/DeviceSettings.vue'
import IconLaptop from 'virtual:vite-icons/mdi/laptop'
import IconBook from 'virtual:vite-icons/mdi/book'
const { t } = useI18n()
const { t } = useI18n()
</script>
@@ -1,10 +1,10 @@
<template>
<section>
<h2 class="text-xl font-medium text-cool-gray-900 leading-normal">
<slot name="title"></slot>
<slot name="title" />
</h2>
<p class="text-sm text-cool-gray-600 leading-snug mb-16px">
<slot name="description"></slot>
<slot name="description" />
</p>
<slot />
</section>
@@ -1,7 +1,7 @@
<template>
<main class="children:pt-7 children:pb-7 children:border-b-1">
<ExternalEditorSettings class="pt-0"/>
<ProxySettings/>
<ExternalEditorSettings class="pt-0" />
<ProxySettings />
</main>
</template>
@@ -1,19 +1,36 @@
<template>
<SettingsSection>
<template #title>{{ t('settingsPage.editor.title') }}</template>
<template #description>{{ t('settingsPage.editor.description') }}</template>
<Select :options="externalEditors"
<template #title>
{{ t('settingsPage.editor.title') }}
</template>
<template #description>
{{ t('settingsPage.editor.description') }}
</template>
<Select
v-model="selectedEditor"
:options="externalEditors"
item-value="name"
:placeholder="t('settingsPage.editor.noEditorSelectedPlaceholder')"
class="w-300px">
class="w-300px"
>
<template #input-prefix="{ value }">
<Icon v-if="value" :icon="value.icon" class="text-md"></Icon>
<Icon v-else :icon="IconTerminal" class="text-gray-600 text-md"></Icon>
<Icon
v-if="value"
:icon="value.icon"
class="text-md"
/>
<Icon
v-else
:icon="IconTerminal"
class="text-gray-600 text-md"
/>
</template>
<template #item-prefix="{value}">
<Icon :icon="value.icon" class="text-md"></Icon>
</template>
<Icon
:icon="value.icon"
class="text-md"
/>
</template>
</Select>
</SettingsSection>
</template>
@@ -23,8 +40,8 @@
import { ref } from 'vue'
import Icon from '../../components/icon/Icon.vue'
import SettingsSection from '../SettingsSection.vue'
import { useI18n } from '../../composables';
import Select from '../../components/input/Select.vue';
import { useI18n } from '../../composables'
import Select from '../../components/input/Select.vue'
import VSCode from 'virtual:vite-icons/logos/visual-studio-code'
import Atom from 'virtual:vite-icons/logos/atom-icon'
import Webstorm from 'virtual:vite-icons/logos/webstorm'
@@ -38,32 +55,32 @@ const externalEditors = [
{
name: 'Visual Studio Code',
key: 'vscode',
icon: VSCode
icon: VSCode,
},
{
name: "Webstorm",
key: "webstorm",
icon: Webstorm
name: 'Webstorm',
key: 'webstorm',
icon: Webstorm,
},
{
name: 'Atom',
key: 'atom',
icon: Atom
icon: Atom,
},
{
name: 'Sublime Text',
key: 'sublime',
icon: Sublime
icon: Sublime,
},
{
name: 'Vim',
key: 'vim',
icon: Vim
icon: Vim,
},
{
name: 'Emacs',
key: 'emacs',
icon: Emacs
icon: Emacs,
},
]
@@ -1,15 +1,25 @@
<template>
<SettingsSection>
<template #title>{{ t('settingsPage.proxy.title') }}</template>
<template #description>{{ t('settingsPage.proxy.description') }}</template>
<template #title>
{{ t('settingsPage.proxy.title') }}
</template>
<template #description>
{{ t('settingsPage.proxy.description') }}
</template>
<div class="bg-gray-50 border-1 rounded py-2 px-4 grid gap-1 w-364px">
<div class="flex justify-between text-sm">
<span class="font-medium text-gray-800">{{ t('settingsPage.proxy.proxyServer') }}</span>
<span class="text-gray-500" data-testid="proxy-server">{{proxyServer}}</span>
<span
class="text-gray-500"
data-testid="proxy-server"
>{{ proxyServer }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="font-medium text-gray-800">{{ t('settingsPage.proxy.bypassList') }}</span>
<span class="text-gray-500" data-testid="bypass-list">{{bypassList}}</span>
<span
class="text-gray-500"
data-testid="bypass-list"
>{{ bypassList }}</span>
</div>
</div>
</SettingsSection>
@@ -17,8 +27,8 @@
<script lang="ts" setup>
import SettingsSection from '../SettingsSection.vue'
import { useI18n } from '../../composables';
import { ref } from 'vue';
import { useI18n } from '../../composables'
import { ref } from 'vue'
const { t } = useI18n()
@@ -1,9 +1,14 @@
<template>
<SettingsSection>
<template #title>{{ t('settingsPage.config.title') }}</template>
<template #title>
{{ t('settingsPage.config.title') }}
</template>
<template #description>
<i18n-t keypath="settingsPage.config.description">
<a href="https://docs.cypress.io" target="_blank">cypress.config.js</a>
<a
href="https://docs.cypress.io"
target="_blank"
>cypress.config.js</a>
</i18n-t>
</template>
<div class="flex w-full select-none">
@@ -1,14 +1,23 @@
<template>
<div class="text-sm">
<p class="font-mono whitespace-nowrap rounded px-2px w-min" data-testid="legend-label" v-bind="$attrs">{{ label }}</p>
<p class="text-gray-600 leading-snug font-light"><slot></slot></p>
<p
class="font-mono whitespace-nowrap rounded px-2px w-min"
data-testid="legend-label"
v-bind="$attrs"
>
{{ label }}
</p>
<p class="text-gray-600 leading-snug font-light">
<slot />
</p>
</div>
</template>
<script lang="ts">
export default {
inheritAttrs: false
inheritAttrs: false,
}
</script>
<script lang="ts" setup>
defineProps<{
@@ -3,10 +3,15 @@
<Button
variant="outline"
class="absolute top-4 right-4"
:prefixIcon="IconCode"
prefixIconClass="text-gray-500"
>{{ t('file.edit') }}</Button>
<pre @dblclick="copy(code)" class="p-2 font-mono text-gray-600 text-sm">{{ code }}</pre>
:prefix-icon="IconCode"
prefix-icon-class="text-gray-500"
>
{{ t('file.edit') }}
</Button>
<pre
class="p-2 font-mono text-gray-600 text-sm"
@dblclick="copy(code)"
>{{ code }}</pre>
<div
aria-hidden="true"
class="pointer-events-none flex items-center text-center transition-opacity absolute top-0 left-0 w-full h-full"
@@ -29,7 +34,7 @@ import { useClipboard } from '@vueuse/core'
defineProps<{
code: string,
mockCopy?: () => any
mockCopy?:() => any
}>()
const { copy, copied } = useClipboard()
@@ -1,62 +1,80 @@
<template>
<div class="children:mb-18px">
<ConfigBadge class="bg-gray-100 text-gray-700"
:label="legendText.default.label">
{{ legendText.default.description}}
<ConfigBadge
class="bg-gray-100 text-gray-700"
:label="legendText.default.label"
>
{{ legendText.default.description }}
</ConfigBadge>
<ConfigBadge class="bg-teal-100 text-teal-700"
:label="legendText.config.label">
<ConfigBadge
class="bg-teal-100 text-teal-700"
:label="legendText.config.label"
>
<i18n-t :keypath="legendText.config.descriptionKey">
<a href="https://docs.cypress.io" target="_blank">cypress.config.js</a>
<a
href="https://docs.cypress.io"
target="_blank"
>cypress.config.js</a>
</i18n-t>
</ConfigBadge>
<ConfigBadge class="bg-yellow-100 text-yellow-700"
:label="legendText.env.label">
<ConfigBadge
class="bg-yellow-100 text-yellow-700"
:label="legendText.env.label"
>
{{ legendText.env.description }}
</ConfigBadge>
<ConfigBadge class="bg-red-50 text-red-700"
:label="legendText.cli.label">
<ConfigBadge
class="bg-red-50 text-red-700"
:label="legendText.cli.label"
>
{{ legendText.cli.description }}
</ConfigBadge>
<ConfigBadge class="bg-purple-50 text-purple-700"
:label="legendText.dynamic.label">
<ConfigBadge
class="bg-purple-50 text-purple-700"
:label="legendText.dynamic.label"
>
<i18n-t :keypath="legendText.dynamic.descriptionKey">
<a href="https://docs.cypress.io" target="_blank">setupNodeEnv</a>
<a
href="https://docs.cypress.io"
target="_blank"
>setupNodeEnv</a>
</i18n-t>
</ConfigBadge>
</div>
</template>
<script lang="ts" setup>
import ConfigBadge from "./ConfigBadge.vue";
import { computed } from "vue";
import { useI18n } from "../../composables";
import ConfigBadge from './ConfigBadge.vue'
import { computed } from 'vue'
import { useI18n } from '../../composables'
const { t } = useI18n()
const legendText = computed(() => ({
default: {
label: t('settingsPage.config.legend.default.label'),
description: t('settingsPage.config.legend.default.description'),
},
config: {
label: t('settingsPage.config.legend.config.label'),
descriptionKey: 'settingsPage.config.legend.config.description'
},
env: {
label: t('settingsPage.config.legend.env.label'),
description: t('settingsPage.config.legend.env.description'),
},
cli: {
label: t('settingsPage.config.legend.cli.label'),
description: t('settingsPage.config.legend.cli.description'),
},
dynamic: {
label: t('settingsPage.config.legend.dynamic.label'),
descriptionKey: 'settingsPage.config.legend.dynamic.description',
},
}))
const legendText = computed(() => {
return {
default: {
label: t('settingsPage.config.legend.default.label'),
description: t('settingsPage.config.legend.default.description'),
},
config: {
label: t('settingsPage.config.legend.config.label'),
descriptionKey: 'settingsPage.config.legend.config.description',
},
env: {
label: t('settingsPage.config.legend.env.label'),
description: t('settingsPage.config.legend.env.description'),
},
cli: {
label: t('settingsPage.config.legend.cli.label'),
description: t('settingsPage.config.legend.cli.description'),
},
dynamic: {
label: t('settingsPage.config.legend.dynamic.label'),
descriptionKey: 'settingsPage.config.legend.dynamic.description',
},
}
})
</script>
@@ -1,12 +1,23 @@
<template>
<div role="row" class="grid grid-flow-row">
<div
role="row"
class="grid grid-flow-row"
>
<div class="inline leading-loose">
<h3 class="text-indigo-600 text-md inline" role="rowheader">{{ experiment.name }}</h3>
<h3
class="text-indigo-600 text-md inline"
role="rowheader"
>
{{ experiment.name }}
</h3>
<span
class="bg-purple-100 text-purple-600 rounded-sm text-sm py-2px px-4px ml-12px font-mono"
>{{ experiment.key }}</span>
</div>
<span role="definition" class="text-cool-gray-500 text-sm">{{ experiment.description }}</span>
<span
role="definition"
class="text-cool-gray-500 text-sm"
>{{ experiment.description }}</span>
<div
class="row-start-1 row-end-3 col-start-2 col-end-auto inline-grid items-center justify-self-end ml-20px"
>
@@ -19,7 +30,7 @@
<script lang="ts" setup>
import StatusIndicator from '../../components/badge/StatusIndicator.vue'
import { useI18n } from '../../composables';
import { useI18n } from '../../composables'
export interface Experiment {
key: string
@@ -1,19 +1,24 @@
<template>
<SettingsSection>
<template #title>{{ t('settingsPage.experiments.title') }}</template>
<template #title>
{{ t('settingsPage.experiments.title') }}
</template>
<template #description>
<i18n-t keypath="settingsPage.experiments.description">
<a href="https://docs.cypress.io" target="_blank">{{ t('links.learnMore') }}</a>
</i18n-t>
<i18n-t keypath="settingsPage.experiments.description">
<a
href="https://docs.cypress.io"
target="_blank"
>{{ t('links.learnMore') }}</a>
</i18n-t>
</template>
<div
class="mx-auto first:border-t-0 border-gray-200 rounded grid gap-0 align-center border px-16px"
>
<ExperimentRow
v-for="experiment in localExperiments"
:key="experiment.key"
data-testid="experiment"
class="border-t-1 first:border-0 py-20px"
:key="experiment.key"
:experiment="experiment"
/>
</div>
@@ -24,8 +29,7 @@
import { computed } from 'vue'
import ExperimentRow from './ExperimentRow.vue'
import SettingsSection from '../SettingsSection.vue'
import { experiments as defaultExperiments } from './projectSettings'
import type { Experiment } from './projectSettings'
import { experiments as defaultExperiments, Experiment } from './projectSettings'
import { useI18n } from '../../composables'
const props = defineProps<{
@@ -1,22 +1,33 @@
<template>
<SettingsSection>
<template #title>{{ t('settingsPage.projectId.title') }}</template>
<template #title>
{{ t('settingsPage.projectId.title') }}
</template>
<template #description>
<i18n-t keypath="settingsPage.projectId.description">
<a href="https://docs.cypress.io" target="_blank">{{ t('links.learnMore') }}</a>
<a
href="https://docs.cypress.io"
target="_blank"
>{{ t('links.learnMore') }}</a>
</i18n-t>
</template>
<div class="inline-grid grid-flow-col justify-start gap-10px">
<InlineCodeEditor
class="max-w-400px"
:prefixIcon="IconCodeBraces"
prefixIconClass="text-cool-gray-400"
readonly
v-model="formattedProjectId"
></InlineCodeEditor>
<Button variant="outline" @click="clipboard.copy(gql?.projectId)">
class="max-w-400px"
:prefix-icon="IconCodeBraces"
prefix-icon-class="text-cool-gray-400"
readonly
/>
<Button
variant="outline"
@click="clipboard.copy(gql?.projectId)"
>
<template #prefix>
<Icon class="text-cool-gray-600" :icon="IconDashedSquare" />
<Icon
class="text-cool-gray-600"
:icon="IconDashedSquare"
/>
</template>
{{ clipboard.copied.value ? t('clipboard.copied') : t('clipboard.copy') }}
</Button>
@@ -27,8 +38,8 @@
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import { gql } from '@urql/core'
import "prismjs"
import "@packages/reporter/src/errors/prism.scss"
import 'prismjs'
import '@packages/reporter/src/errors/prism.scss'
import IconCodeBraces from 'virtual:vite-icons/mdi/code-braces'
import IconDashedSquare from 'virtual:vite-icons/si-glyph/square-dashed-2'
import Button from '../../components/button/Button.vue'
@@ -53,7 +64,7 @@ const clipboard = props.mockClipboard?.() || useClipboard({ source: ref(props.gq
const formattedProjectId = computed(() => `projectId: '${props.gql?.projectId}'`)
onMounted(() => import("prismjs/components/prism-yaml"))
onMounted(() => import('prismjs/components/prism-yaml'))
const { t } = useI18n()
</script>
@@ -1,13 +1,14 @@
<template>
<main class="divide-y divide-gray-200 children:pt-7 children:pb-7">
<template v-if="app?.activeProject">
<ProjectId
<ProjectId
class="pt-0"
:gql="app.activeProject"
/>
<template v-if="app.activeProject.cloudProject">
<RecordKey
<RecordKey
v-for="key of app.activeProject.cloudProject.recordKeys"
:key="key.id"
:gql="key"
/>
</template>
@@ -48,8 +49,8 @@ query ProjectSettings {
}
`
const { data } = useQuery({
query: ProjectSettingsDocument,
const { data } = useQuery({
query: ProjectSettingsDocument,
})
const app = computed(() => data.value?.app)
@@ -1,45 +1,66 @@
<template>
<ProjectSettingsSection>
<template #title>{{ t('settingsPage.recordKey.title') }}</template>
<template #title>
{{ t('settingsPage.recordKey.title') }}
</template>
<template #description>
<i18n-t keypath="settingsPage.recordKey.description">
<a href="https://docs.cypress.io" target="_blank">{{ t('links.learnMore') }}</a>
<a
href="https://docs.cypress.io"
target="_blank"
>{{ t('links.learnMore') }}</a>
</i18n-t>
</template>
<div class="inline-flex justify-start gap-10px" v-if="props.gql.key">
<div
v-if="props.gql.key"
class="inline-flex justify-start gap-10px"
>
<Input
v-model="props.gql.key"
:value="props.gql.key"
class="font-mono"
inputClasses="text-sm"
input-classes="text-sm"
disabled
:type="showRecordKey ? 'text' : 'password'"
>
<template #prefix>
<Icon :icon="IconKey" class="text-cool-gray-400" />
<Icon
:icon="IconKey"
class="text-cool-gray-400"
/>
</template>
<template #suffix>
<button
@click="showRecordKey = !showRecordKey"
aria-label="Record Key Visibility Toggle"
class="text-cool-gray-400 hover:text-cool-gray-500"
@click="showRecordKey = !showRecordKey"
>
<Icon v-if="showRecordKey" :icon="IconEyeOpen"></Icon>
<Icon v-else :icon="IconEyeClosed"></Icon>
<Icon
v-if="showRecordKey"
:icon="IconEyeOpen"
/>
<Icon
v-else
:icon="IconEyeClosed"
/>
</button>
</template>
</Input>
<Button
variant="outline"
:prefix-icon="IconDashedSquare"
prefix-icon-class="text-cool-gray-500"
@click="clipboard.copy()"
:prefixIcon="IconDashedSquare"
prefixIconClass="text-cool-gray-500"
>{{ clipboard.copied.value ? t('clipboard.copied') : t('clipboard.copy') }}</Button>
>
{{ clipboard.copied.value ? t('clipboard.copied') : t('clipboard.copy') }}
</Button>
<Button
variant="outline"
:prefix-icon="IconKey"
prefix-icon-class="text-cool-gray-500 w-1.2rem h-1.2rem"
@click="openManageKeys"
:prefixIcon="IconKey"
prefixIconClass="text-cool-gray-500 w-1.2rem h-1.2rem"
>{{ t('settingsPage.recordKey.manageKeys') }}</Button>
>
{{ t('settingsPage.recordKey.manageKeys') }}
</Button>
</div>
</ProjectSettingsSection>
</template>
+15 -9
View File
@@ -1,26 +1,32 @@
<template>
<div v-if="error">An error occurred while authenticating: {{ error }}</div>
<div v-if="error">
An error occurred while authenticating: {{ error }}
</div>
<div v-else-if="viewer">
<p>
Congrats {{ viewer?.email }}, you authenticated with Cypress Cloud.
</p>
<Button @click="handleLogout">Log out</Button>
<Button @click="handleLogout">
Log out
</Button>
</div>
<div v-else>
<Button @click="handleAuth">Click to Authenticate</Button>
<Button @click="handleAuth">
Click to Authenticate
</Button>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { gql } from "@urql/core"
import { useMutation } from "@urql/vue"
import {
import { gql } from '@urql/core'
import { useMutation } from '@urql/vue'
import {
LoginDocument,
LogoutDocument,
AuthFragment
AuthFragment,
} from '../generated/graphql'
import Button from '../components/button/Button.vue'
@@ -58,9 +64,9 @@ const login = useMutation(LoginDocument)
const logout = useMutation(LogoutDocument)
const error = ref<string>()
const handleAuth = async () => {
const result = await login.executeMutation({})
error.value = result.error?.message ?? undefined
}
@@ -71,4 +77,4 @@ const handleLogout = async () => {
}
const viewer = computed(() => props.gql?.cloudViewer)
</script>
</script>

Some files were not shown because too many files have changed in this diff Show More