mirror of
https://github.com/directus/directus.git
synced 2026-02-19 10:14:33 -05:00
Public (#373)
* Add note to public page * Add project chooser on public view * Optimize loading order So much nicer to use now Closes #298 * Fix the private project switcher too * Add transition to public view * Prevent project switching if youre already on that project * [WIP] Add reset password form * Add request password reset page * Add jwt-payload util * Install base-64 * Fix test typing * Add new errors to translations * Finish reset password flow * Fix foreground color on v-notice * Fix tests * Allow code in translateError + render project error translated on login * Remove wrong reference to error component * Render project key if name is unknown * Fix date-fns version * Fix tests
This commit is contained in:
@@ -73,6 +73,7 @@ export const withCustomLogo = () =>
|
||||
php_api: 'fpm-fcgi',
|
||||
},
|
||||
},
|
||||
authenticated: true,
|
||||
},
|
||||
];
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="project-chooser">
|
||||
<button class="toggle" :disabled="projects.length === 1" @click="active = !active">
|
||||
{{ currentProject.api.project_name }}
|
||||
{{ currentProject.name }}
|
||||
</button>
|
||||
<transition-expand>
|
||||
<div v-if="active" class="options-wrapper">
|
||||
@@ -17,7 +17,7 @@
|
||||
<v-radio
|
||||
:inputValue="currentProjectKey"
|
||||
:value="project.key"
|
||||
:label="(project.api && project.api.project_name) || project.key"
|
||||
:label="project.name || project.key"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import PublicViewLogo from './logo.vue';
|
||||
|
||||
export { PublicViewLogo };
|
||||
export default PublicViewLogo;
|
||||
@@ -1,22 +0,0 @@
|
||||
<template functional>
|
||||
<a href="https://directus.io" rel="noopener noreferrer" target="_blank" class="logo">
|
||||
<img
|
||||
v-tooltip.right.inverted="`Directus v${props.version}`"
|
||||
alt="Directus Logo"
|
||||
src="./logo-dark.svg"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
version: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,36 +0,0 @@
|
||||
# Public View Logo
|
||||
|
||||
Renders the Directus logo and shows the current version of Directus on hover.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<template>
|
||||
<public-view-logo version="9.0.0" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import PublicViewLogo from '@/views/public/components/logo';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PublicViewLogo
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Props
|
||||
| Prop | Description | Default |
|
||||
|-----------|---------------------------------|---------|
|
||||
| `version` | The version to display on hover | -- |
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
n/a
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
4
src/views/public/components/project-chooser/index.ts
Normal file
4
src/views/public/components/project-chooser/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import ProjectChooser from './project-chooser.vue';
|
||||
|
||||
export { ProjectChooser };
|
||||
export default ProjectChooser;
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,6 +1,6 @@
|
||||
import markdown from './readme.md';
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import PublicViewLogo from './logo.vue';
|
||||
import ProjectChooser from './project-chooser.vue';
|
||||
import withPadding from '../../../../../.storybook/decorators/with-padding';
|
||||
|
||||
export default {
|
||||
@@ -13,8 +13,8 @@ export default {
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
components: { PublicViewLogo },
|
||||
components: { ProjectChooser },
|
||||
template: `
|
||||
<public-view-logo version="9.0.0" />
|
||||
`,
|
||||
<project-chooser />
|
||||
`,
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<v-menu show-arrow placement="bottom" close-on-content-click>
|
||||
<template #activator="{ toggle }">
|
||||
<div class="project-chooser" @click="toggle">
|
||||
<div class="public-view-logo" v-if="project && project.logo">
|
||||
<img :src="project.logo" :alt="project.name || project.key" />
|
||||
</div>
|
||||
<img v-else class="default-logo" src="./logo-dark.svg" alt="Directus" />
|
||||
<h1 class="title type-title">{{ project && (project.name || project.key) }}</h1>
|
||||
<v-icon name="expand_more" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<v-list-item
|
||||
v-for="availableProject in projects"
|
||||
:key="availableProject.key"
|
||||
:active="availableProject.key === project.key"
|
||||
:disabled="availableProject.key === project.key"
|
||||
@click="toProject(availableProject.key)"
|
||||
>
|
||||
<v-list-item-icon><v-icon name="launch" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ availableProject.name || availableProject.key }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import router from '@/router';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
return {
|
||||
project: projectsStore.currentProject,
|
||||
projects: projectsStore.formatted,
|
||||
toProject,
|
||||
};
|
||||
|
||||
function toProject(key: string) {
|
||||
router.push(`/${key}/login`);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-chooser {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: max-content;
|
||||
height: 64px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.public-view-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-color: var(--brand);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.default-logo {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.v-icon {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
35
src/views/public/components/project-chooser/readme.md
Normal file
35
src/views/public/components/project-chooser/readme.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Project Chooser
|
||||
|
||||
Renders the project's logo, and allows you to switch to other projects. If there's only one project
|
||||
available, it renders just the logo.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<template>
|
||||
<project-chooser />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import ProjectChooser from '@/views/public/components/project-chooser';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProjectChooser
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Props
|
||||
n/a
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
n/a
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
@@ -4,12 +4,19 @@ import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import { ProjectWithKey } from '@/stores/projects/types';
|
||||
import Tooltip from '@/directives/tooltip/tooltip';
|
||||
import ClickOutside from '@/directives/click-outside';
|
||||
import VMenu from '@/components/v-menu';
|
||||
import VList, { VListItem, VListItemIcon, VListItemContent } from '@/components/v-list';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.directive('tooltip', Tooltip);
|
||||
localVue.component('v-list', VList);
|
||||
localVue.component('v-list-item', VListItem);
|
||||
localVue.component('v-list-item-icon', VListItemIcon);
|
||||
localVue.component('v-list-item-content', VListItemContent);
|
||||
localVue.component('v-menu', VMenu);
|
||||
localVue.directive('click-outside', ClickOutside);
|
||||
|
||||
import PublicView from './public-view.vue';
|
||||
|
||||
@@ -48,6 +55,7 @@ const mockProject: ProjectWithKey = {
|
||||
php_api: 'fpm-fcgi',
|
||||
},
|
||||
},
|
||||
authenticated: true,
|
||||
};
|
||||
|
||||
describe('Views / Public', () => {
|
||||
@@ -98,6 +106,7 @@ describe('Views / Public', () => {
|
||||
code: 250,
|
||||
message: 'Test error',
|
||||
},
|
||||
authenticated: false,
|
||||
},
|
||||
];
|
||||
store.state.currentProjectKey = 'my-project';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="public-view">
|
||||
<div class="container">
|
||||
<public-view-logo :version="version" />
|
||||
<project-chooser />
|
||||
<div class="content" :class="{ wide }">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -9,19 +9,30 @@
|
||||
<slot name="notice" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="art" :style="artStyles"></div>
|
||||
<div class="art" :style="artStyles">
|
||||
<transition name="scale">
|
||||
<img
|
||||
class="foreground"
|
||||
v-if="project && project.foregroundImage"
|
||||
:src="project.foregroundImage"
|
||||
:alt="project.name"
|
||||
/>
|
||||
</transition>
|
||||
<div class="note" v-if="project && project.note" v-html="marked(project.note)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { version } from '../../../package.json';
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import PublicViewLogo from './components/logo/';
|
||||
import ProjectChooser from './components/project-chooser/';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import marked from 'marked';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
PublicViewLogo,
|
||||
ProjectChooser,
|
||||
},
|
||||
props: {
|
||||
wide: {
|
||||
@@ -35,23 +46,19 @@ export default defineComponent({
|
||||
const backgroundStyles = computed<string>(() => {
|
||||
const defaultColor = '#263238';
|
||||
|
||||
let currentProject = projectsStore.currentProject.value;
|
||||
|
||||
if (currentProject === null) {
|
||||
if (projectsStore.currentProject.value === null) {
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
if (currentProject.error !== undefined) {
|
||||
if (projectsStore.currentProject.value.error) {
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
currentProject = currentProject;
|
||||
|
||||
if (currentProject.api?.project_background?.full_url) {
|
||||
return `url(${currentProject.api?.project_background.full_url})`;
|
||||
if (projectsStore.currentProject.value.backgroundImage) {
|
||||
return `url(${projectsStore.currentProject.value.backgroundImage})`;
|
||||
}
|
||||
|
||||
return currentProject.api?.project_color || defaultColor;
|
||||
return projectsStore.currentProject.value.color || defaultColor;
|
||||
});
|
||||
|
||||
const artStyles = computed(() => ({
|
||||
@@ -60,7 +67,7 @@ export default defineComponent({
|
||||
backgroundPosition: 'center center',
|
||||
}));
|
||||
|
||||
return { version, artStyles };
|
||||
return { version, artStyles, marked, project: projectsStore.currentProject };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -97,14 +104,35 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.art {
|
||||
position: relative;
|
||||
display: none;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
|
||||
.foreground {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.note {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 40px;
|
||||
left: 0;
|
||||
max-width: 340px;
|
||||
margin: 0 auto;
|
||||
padding: 8px 12px;
|
||||
color: var(--white);
|
||||
background-color: #2632383f;
|
||||
border-radius: var(--border-radius);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
@include breakpoint(small) {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +148,16 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: all 600ms var(--transition);
|
||||
}
|
||||
|
||||
.scale-enter,
|
||||
.scale-leave-to {
|
||||
position: absolute;
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user