Commit fbdff855 authored by Yoran Hillion's avatar Yoran Hillion
Browse files

style (project page) : New design for the project page

parent 90671b28
Pipeline #48760 passed with stages
in 4 minutes and 14 seconds
......@@ -3368,12 +3368,42 @@
"unique-filename": "^1.1.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
......@@ -3390,6 +3420,16 @@
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"terser-webpack-plugin": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
......@@ -3406,6 +3446,18 @@
"terser": "^4.6.12",
"webpack-sources": "^1.4.3"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.2",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
}
}
},
......@@ -16277,60 +16329,6 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.2",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-router": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz",
......
......@@ -8,6 +8,18 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
<link type="text/css" rel="stylesheet" href="<%= BASE_URL %>static/stylesheet/vendor/3dhop/3dhop.css"/>
<link
rel="stylesheet"
href="https://s3.unistra.fr/master/common/assets/fonts/unistra-signature/3.0.1/css/sun.css?AWSAccessKeyId=M2M78RKXPAP75Y692QZX&Signature=CgIbptGflBu5hCjAQYmysfF3E4c%3D&Expires=1887617036"
/>
<link
rel="stylesheet"
href="https://s3.unistra.fr/master/common/assets/fonts/unistra-symbol/1.0.4/css/unistra-symbol.css?AWSAccessKeyId=M2M78RKXPAP75Y692QZX&Signature=lfTwAVnRXjgZc9ryRpAcWhiMbCA%3D&Expires=1876298241"
/>
<link
rel="stylesheet"
href="https://s3.unistra.fr/master/common/assets/fonts/unistra-font/1.0.0/css/unistra-font.css?AWSAccessKeyId=M2M78RKXPAP75Y692QZX&Signature=Ros22u4Tp0Dy106qr0rRkGBPoJM%3D&Expires=1870971370"
/>
</head>
<body>
<noscript>
......
<template>
<div
class="object__thumbnails"
:class="{th__large: size > 48}">
<img
<v-avatar
tile
:size="size"
>
<v-img
v-if="image"
:src="image"
:alt="name"
/>
<div
<v-img
v-else
v-html="getIdenticon()"
/>
</div>
</v-avatar>
</template>
<script>
......@@ -35,22 +36,3 @@ export default {
}
</script>
<style lang="scss" scoped>
.object__thumbnails {
margin: auto;
width: 48px;
height: 48px;
display: flex;
justify-content: space-around;
&.th__large {
width: 64px;
height: 64px;
}
img {
margin: auto;
}
}
</style>
<template>
<v-dialog :value="dialog" :width="isMobile ? '100%' : '40%'">
<v-dialog
:value="isOpen"
:width="isMobile ? '100%' : '40%'"
>
<v-card>
<v-card-title class="text-h5">
{{ title }}
<v-card-title>
{{ $t('confirm.title') }}
</v-card-title>
<v-card-text>
{{ content }}
</v-card-text>
<v-card-actions>
<i-button
b-small
@activate="$emit('confirm')"
:aria="$t('btn.confirm')"
icon="mdi-check"
color="success"
/>
<v-spacer></v-spacer>
<i-button
b-small
@activate="$emit('update:dialog', false)"
:aria="$t('btn.cancel')"
icon="mdi-close"
color="error"
/>
<v-spacer />
<v-btn
color="secondary"
tile
elevation="0"
@click="$emit('cancel')"
>
{{ $t('btn.cancel') }}
</v-btn>
<v-btn
color="primary"
tile
elevation="0"
@click="$emit('confirm')"
>
{{ $t('btn.confirm') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
......@@ -29,27 +37,25 @@
import MobileMixin from '@/mixins/MobileMixin'
export default {
name: "ConfirmBox",
name: "ConfirmBox",
mixins: [MobileMixin],
props: {
isOpen: {
type: Boolean,
default: false,
},
object: {
type: String,
default: ''
},
dialog: {
type: Boolean,
default: false,
},
action: {
type: String,
default: 'delete',
}
},
computed: {
title() {
const confirm = this.$t('confirm.title')
const action = this.$t(`confirm.${this.action}`,{object: this.object})
return `${confirm} ${action}`
content() {
return this.$t(`confirm.${this.action}`, { object: this.object });
}
}
}
......
<template>
<v-row no-gutters>
<v-col cols="12">
<v-form v-model="isValid" ref="form" lazy-validation>
<v-file-input
show-size
:label="$t('file.upload.label')"
@change="updateFileToLoad"
:rules="requiredRule"
/>
<v-progress-linear
v-show="currentFile"
color="primary"
:indeterminate="loading"
:value="progress"
/>
<v-btn color="secondary" dark small @click="onUploadFile">
<v-form v-model="isValid" ref="form" lazy-validation>
<v-row>
<v-col>
<v-file-input
show-size
:label="$t('file.upload.label')"
@change="updateFileToLoad"
:rules="requiredRule"
/>
<v-progress-linear
v-show="currentFile"
color="primary"
:indeterminate="loading"
:value="progress"
/>
</v-col>
</v-row>
<v-row>
<v-spacer />
<v-col class="flex-grow-0">
<v-btn
tile
elevation="0"
color="primary"
@click="onUploadFile"
>
{{ $t('file.upload.button') }}
<v-icon right dark>mdi-cloud-upload</v-icon>
</v-btn>
</v-form>
</v-col>
</v-row>
</v-col>
</v-row>
</v-form>
</template>
<script>
......
<template>
<div>
<v-toolbar
flat
>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer />
<v-btn
v-if="button.isDisplayed"
tile
elevation="0"
color="secondary"
:to="button.to"
>
{{ button.text }}
</v-btn>
</v-toolbar>
<v-divider />
<v-data-table
hide-default-header
:items="items"
:options.sync="options"
:server-items-length="paginated ? itemsLength : undefined"
>
<template #item="{ item, index }">
<v-divider v-if="index > 0" />
<div class="d-flex pa-2">
<div
class="d-flex flex-grow-1"
:class="item.to && item.to.length ? 'pointer': ''"
@click="item.to && item.to.length ? $router.push(item.to) : undefined"
>
<div class="align-self-center mr-4">
<avatar
:name="item.name"
:image="item.thumbnail"
/>
</div>
<div class="d-flex flex-grow-1 flex-column flex-lg-row">
<div class="d-flex flex-column justify-center mr-auto">
<div class="font-weight-medium">
{{ item.name }}
<v-chip
v-if="item.status"
:color="item.status.color"
disabled
x-small
>
{{ item.status.label }}
</v-chip>
</div>
<template v-if="!isMobile">
<div
v-for="(subtitle, index) in item.subtitles"
:key="`item-subtitle-${index}`"
class="text--secondary"
>
{{ subtitle }}
</div>
</template>
</div>
<div
v-if="item.updatedAt && item.updatedAt.length"
class="align-self-lg-center text--secondary">
{{ $t('updated.label', { time: fromNow(item.updatedAt) }) }}
</div>
</div>
</div>
<div
v-if="item.actions && item.actions.length"
class="align-self-center ml-4"
>
<v-tooltip
v-for="(action, index) in item.actions"
:key="`item-action-${index}`"
top
>
<template #activator="{ on, attrs }">
<v-btn
icon
:disabled="action.disabled"
@click="$emit(action.name, item.id)"
v-on="on"
v-bind="attrs"
>
<v-icon>{{ action.icon }}</v-icon>
</v-btn>
</template>
{{ action.tooltip }}
</v-tooltip>
</div>
</div>
</template>
</v-data-table>
</div>
</template>
<script>
import MobileMixin from '@/mixins/MobileMixin';
import Avatar from '@/components/common/Avatar';
export default {
name: 'CrudList',
mixins: [
MobileMixin,
],
props: {
paginated: {
type: Boolean,
required: false,
default: true,
},
title: {
type: String,
required: true,
},
button: {
type: Object,
required: true,
},
items: {
type: Array,
required: false,
default: () => [],
},
itemsLength: {
type: Number,
required: false,
default: 0,
},
},
components: {
Avatar,
},
data: () => ({
avatarSize: 48,
options: {},
}),
watch: {
options: {
handler() {
this.onPaginate();
},
deep: true,
},
},
methods: {
onPaginate() {
const { page, itemsPerPage } = this.options;
this.$emit('paginate', { page, itemsPerPage });
},
fromNow(date) {
const dateTime = new Date(date).getTime();
const now = new Date().getTime();
const difference = Math.abs((dateTime / 1000) - (now / 1000));
let time, unit;
if (difference / (60 * 60 * 24 * 365) > 1) {
// Years
time = Math.floor(difference / (60 * 60 * 24 * 365));
unit = this.$tc('updated.year', time);
} else if (difference / (60 * 60 * 24 * 45) > 1) {
// Months
time = Math.floor(difference / (60 * 60 * 24 * 45));
unit = this.$tc('updated.month', time);
} else if (difference / (60 * 60 * 24) > 1) {
// Days
time = Math.floor(difference / (60 * 60 * 24));
unit = this.$tc('updated.day', time);
} else if (difference / (60 * 60) > 1) {
// Hours
time = Math.floor(difference / (60 * 60));
unit = this.$tc('updated.hour', time);
} else if (difference / 60 > 1) {
// Minutes
time = Math.floor(difference / 60);
unit = this.$tc('updated.minute', time);
} else {
// Seconds
time = Math.floor(difference);
unit = this.$tc('updated.second', time);
}
return `${time} ${unit}`;
},
},
};
</script>
<style scoped lang="scss">
.pointer {
cursor: pointer;
}
.v-chip--disabled {
opacity: 1;
}
</style>
<template>
<v-row class="d-flex flex-column">
<v-col>
<v-text-field
v-model="group.name"
:label="$t('form.field.name')"
:rules="requiredField"
/>
</v-col>
<v-col>
<rich-text
v-model="group.description"
:label="$t('form.field.description')"
:rules="requiredField"
/>
</v-col>
<v-col>
<v-select
v-model="group.users"
:label="$t('group.members.self')"
:items="availableUsers"
item-text="name"
item-value="id"
return-object
multiple
small-chips
deletable-chips
/>
</v-col>
<v-col>
<v-select
v-model="group.sets"
:label="$t('group.right.actual')"
:items="availableSets"
item-text="title"
item-value="id"
return-object
multiple
small-chips
deletable-chips
/>
</v-col>
</v-row>
</template>
<script>
import RulesMixin from '@/mixins/RulesMixin';
import RichText from '@/components/common/RichTextInput';
export default {
name: 'GroupForm',
props: {
name: {
type: String,
required: false,
default: '',
},
description: {
type: String,
required: false,
default: '',
},
users: {
type: Array,
required: false,
default: () => [],
},
availableUsers: {
type: Array,
required: true,
},
sets: {
type: Array,
required: false,
default: () => [],