mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-27 18:29:41 -05:00
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:
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -15,4 +15,4 @@ fragment Foo on App {
|
||||
const props = defineProps<{
|
||||
gql: FooFragment
|
||||
}>()
|
||||
</script>>
|
||||
</script>>
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
<template>
|
||||
<Specs/>
|
||||
<Specs />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user