@oxygen-cms/ui 1.4.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +23 -0
- package/.github/workflows/node.js.yml +4 -4
- package/.idea/modules.xml +8 -0
- package/.idea/ui.iml +10 -0
- package/package.json +13 -5
- package/src/AuthApi.js +77 -42
- package/src/CrudApi.js +3 -3
- package/src/GroupsApi.js +9 -0
- package/src/MediaDirectoryApi.js +1 -1
- package/src/PreferencesApi.js +2 -0
- package/src/UserPermissions.js +2 -9
- package/src/UserPreferences.js +0 -4
- package/src/UserPreferences.test.js +0 -2
- package/src/UsersApi.js +41 -0
- package/src/api.js +96 -38
- package/src/components/App.vue +19 -240
- package/src/components/AuthenticatedLayout.vue +254 -0
- package/src/components/AuthenticationLog.vue +86 -30
- package/src/components/CodeEditor.vue +16 -32
- package/src/components/EditButtonOnRowHover.vue +21 -0
- package/src/components/Error404.vue +15 -5
- package/src/components/EventsChooser.vue +11 -11
- package/src/components/EventsTable.vue +14 -8
- package/src/components/GenericEditableField.vue +74 -0
- package/src/components/GroupsChooser.vue +58 -0
- package/src/components/GroupsList.vue +129 -0
- package/src/components/ImportExport.vue +32 -1
- package/src/components/LegacyPage.vue +22 -23
- package/src/components/UserJoined.vue +35 -0
- package/src/components/UserManagement.vue +168 -0
- package/src/components/UserProfileForm.vue +214 -0
- package/src/components/ViewProfile.vue +7 -219
- package/src/components/auth/Auth404.vue +16 -0
- package/src/components/auth/Login.vue +135 -0
- package/src/components/auth/LoginLogo.vue +30 -0
- package/src/components/auth/Logout.vue +26 -0
- package/src/components/auth/PasswordRemind.vue +71 -0
- package/src/components/auth/PasswordReset.vue +97 -0
- package/src/components/auth/TwoFactorSetup.vue +115 -0
- package/src/components/auth/VerifyEmail.vue +71 -0
- package/src/components/auth/WelcomeFloat.vue +87 -0
- package/src/components/auth/login.scss +17 -0
- package/src/components/{MediaChooseDirectory.vue → media/MediaChooseDirectory.vue} +12 -12
- package/src/components/{MediaDirectory.vue → media/MediaDirectory.vue} +8 -8
- package/src/components/{MediaInsertModal.vue → media/MediaInsertModal.vue} +2 -2
- package/src/components/{MediaItem.vue → media/MediaItem.vue} +24 -23
- package/src/components/{MediaItemPreview.vue → media/MediaItemPreview.vue} +5 -5
- package/src/components/{MediaList.vue → media/MediaList.vue} +42 -38
- package/src/components/{MediaPage.vue → media/MediaPage.vue} +1 -1
- package/src/components/{MediaResponsiveImages.vue → media/MediaResponsiveImages.vue} +5 -5
- package/src/components/{MediaUpload.vue → media/MediaUpload.vue} +10 -10
- package/src/components/{media.scss → media/media.scss} +1 -1
- package/src/components/preferences/PreferencesField.vue +10 -10
- package/src/components/preferences/PreferencesList.vue +13 -20
- package/src/components/preferences/PreferencesThemeChooser.vue +9 -9
- package/src/components/preferences/ShowIfPermitted.vue +9 -14
- package/src/components/users/CreateUserModal.vue +73 -0
- package/src/icons.js +90 -0
- package/src/main.js +111 -0
- package/src/modules/LegacyPages.js +18 -0
- package/src/modules/Media.js +45 -0
- package/src/modules/UserManagement.js +24 -0
- package/src/routes/index.js +92 -0
- package/src/store/index.js +70 -0
- package/src/styles/_variables.scss +1 -0
- package/src/styles/app.scss +15 -2
- package/src/login.js +0 -17
- package/src/routes.js +0 -61
- package/src/styles/login.scss +0 -86
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</div>
|
|
6
6
|
<div class="card-content">
|
|
7
7
|
<p class="title is-4 cursor-pointer has-text-centered" @click.exact="select(true)" @click.shift.exact="select(false)">{{ item.name }}</p>
|
|
8
|
-
<p class="subtitle is-6 cursor-pointer has-text-centered"
|
|
8
|
+
<p v-if="displayFullPath" class="subtitle is-6 cursor-pointer has-text-centered" @click.exact="select(true)" @click.shift.exact="select(false)">
|
|
9
9
|
inside '{{ directoryPath }}'
|
|
10
10
|
<!-- <b-icon icon="file-image" v-if="item.type === TYPE_IMAGE"></b-icon>-->
|
|
11
11
|
<!-- <b-icon icon="file" v-else-if="item.type === TYPE_DOCUMENT"></b-icon>-->
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
</b-modal>
|
|
48
48
|
|
|
49
|
-
<b-modal :active.sync="isEditModalActive" trap-focus has-modal-card width="80%"
|
|
49
|
+
<b-modal v-hotkey="keymap" :active.sync="isEditModalActive" trap-focus has-modal-card width="80%">
|
|
50
50
|
<div class="modal-card" style="width: auto">
|
|
51
51
|
<header class="modal-card-head">
|
|
52
52
|
<p class="modal-card-title">Edit Media Item - {{ name }}</p>
|
|
@@ -102,10 +102,16 @@
|
|
|
102
102
|
<b-input v-model="description" type="textarea"></b-input>
|
|
103
103
|
</b-field>
|
|
104
104
|
|
|
105
|
-
<label class="label">
|
|
106
|
-
|
|
107
|
-
<b-
|
|
108
|
-
|
|
105
|
+
<label class="label">
|
|
106
|
+
Variants
|
|
107
|
+
<b-tooltip multilined position="is-right" type="is-dark" label="Each uploaded media item will be automatically converted into different sizes/formats to serve optimized images to users.">
|
|
108
|
+
<b-icon size="is-small" icon="question-circle"></b-icon>
|
|
109
|
+
</b-tooltip>
|
|
110
|
+
</label>
|
|
111
|
+
<b-table striped :data="variants" :default-sort="['width']">
|
|
112
|
+
<b-table-column v-slot="props" label="Filename" field="filename"><a :href="'/content/media/' + props.row.filename">{{ props.row.filename }}</a></b-table-column>
|
|
113
|
+
<b-table-column v-slot="props" label="Width (px)" field="width" sortable>{{ props.row.width ? props.row.width : 'Full size' }}</b-table-column>
|
|
114
|
+
<b-table-column v-slot="props" label="Format" field="mime" sortable>{{ props.row.mime }}</b-table-column>
|
|
109
115
|
</b-table>
|
|
110
116
|
|
|
111
117
|
<label class="label">Versions</label>
|
|
@@ -115,13 +121,13 @@
|
|
|
115
121
|
custom-row-key="id"
|
|
116
122
|
detailed
|
|
117
123
|
detail-key="id">
|
|
118
|
-
<b-table-column label="Name" field="name"
|
|
119
|
-
<b-table-column label="Path" field="fullPath"
|
|
120
|
-
<b-table-column label="Last Updated" field="updatedAt"
|
|
124
|
+
<b-table-column v-slot="props" label="Name" field="name">{{ props.row.name }}</b-table-column>
|
|
125
|
+
<b-table-column v-slot="props" label="Path" field="fullPath">{{ props.row.fullPath }}</b-table-column>
|
|
126
|
+
<b-table-column v-slot="props" label="Last Updated" field="updatedAt">
|
|
121
127
|
<div class="is-size-7">{{ Internationalize.formatLastUpdated(props.row.updatedAt) }}</div>
|
|
122
128
|
</b-table-column>
|
|
123
129
|
|
|
124
|
-
<b-table-column
|
|
130
|
+
<b-table-column v-slot="slotProps" label="">
|
|
125
131
|
<b-button rounded :disabled="slotProps.row.headVersion === null" @click="restoreVersion(slotProps.row.id)">
|
|
126
132
|
<span v-if="slotProps.row.headVersion === null">Already current</span>
|
|
127
133
|
<span v-else>Restore version</span>
|
|
@@ -146,7 +152,7 @@
|
|
|
146
152
|
<footer class="modal-card-foot is-flex">
|
|
147
153
|
<div class="is-flex-grow-1"></div>
|
|
148
154
|
<b-button @click="isEditModalActive = false">Close</b-button>
|
|
149
|
-
<b-button
|
|
155
|
+
<b-button type="is-primary" @click="saveEdits">Save</b-button>
|
|
150
156
|
</footer>
|
|
151
157
|
</div>
|
|
152
158
|
</b-modal>
|
|
@@ -155,18 +161,18 @@
|
|
|
155
161
|
|
|
156
162
|
<script>
|
|
157
163
|
|
|
158
|
-
import {morphToNotification} from "
|
|
159
|
-
import MediaApi from "
|
|
164
|
+
import {morphToNotification} from "../../api";
|
|
165
|
+
import MediaApi from "../../MediaApi";
|
|
160
166
|
import MediaChooseDirectory from "./MediaChooseDirectory.vue";
|
|
161
|
-
import Internationalize from "
|
|
162
|
-
import {getDirectoryFullSlug, getDirectoryPathString} from "
|
|
167
|
+
import Internationalize from "../../Internationalize";
|
|
168
|
+
import {getDirectoryFullSlug, getDirectoryPathString} from "../../MediaDirectoryApi";
|
|
163
169
|
import MediaItemPreview from "./MediaItemPreview.vue";
|
|
164
170
|
|
|
165
171
|
export default {
|
|
166
|
-
name: "MediaItem
|
|
172
|
+
name: "MediaItem",
|
|
167
173
|
components: {MediaChooseDirectory, MediaItemPreview},
|
|
168
174
|
props: {
|
|
169
|
-
item: Object,
|
|
175
|
+
item: { type: Object, required: true },
|
|
170
176
|
displayFullPath: Boolean
|
|
171
177
|
},
|
|
172
178
|
data() {
|
|
@@ -193,12 +199,7 @@ export default {
|
|
|
193
199
|
}
|
|
194
200
|
},
|
|
195
201
|
variants() {
|
|
196
|
-
return this.item.variants
|
|
197
|
-
{
|
|
198
|
-
filename: this.item.filename,
|
|
199
|
-
width: 'Full size'
|
|
200
|
-
}
|
|
201
|
-
]);
|
|
202
|
+
return this.item.variants;
|
|
202
203
|
},
|
|
203
204
|
externalLink() {
|
|
204
205
|
return window.location.origin + '/media/' + this.item.fullPath;
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="media-icon-container">
|
|
3
3
|
<img v-if="item.type === TYPE_IMAGE && !loadingError" :src="'/content/media/' + item.filename" :alt="item.caption ? item.caption : item.name" @error="loadingFailed">
|
|
4
|
-
<b-tooltip :label="missingMessage" type="is-dark" position="is-bottom" multilined
|
|
4
|
+
<b-tooltip v-else-if="item.type === TYPE_IMAGE" :label="missingMessage" type="is-dark" position="is-bottom" multilined>
|
|
5
5
|
<b-icon type="is-danger" icon="file-image" size="is-large" class="media-icon"></b-icon>
|
|
6
6
|
</b-tooltip>
|
|
7
7
|
<img v-if="item.type === TYPE_DOCUMENT && !loadingError" :src="'/oxygen/api/media/' + item.id + '/preview'" alt="PDF preview" @error="loadingFailed">
|
|
8
|
-
<b-tooltip :label="missingMessage" type="is-dark" position="is-bottom" multilined
|
|
8
|
+
<b-tooltip v-else-if="item.type === TYPE_DOCUMENT" :label="missingMessage" type="is-dark" position="is-bottom" multilined>
|
|
9
9
|
<b-icon type="is-danger" icon="file-pdf" size="is-large" class="media-icon"></b-icon>
|
|
10
10
|
</b-tooltip>
|
|
11
|
-
<b-icon icon="music" size="is-large" class="media-icon"
|
|
11
|
+
<b-icon v-else-if="item.type === TYPE_AUDIO" icon="music" size="is-large" class="media-icon"></b-icon>
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script>
|
|
16
|
-
import MediaApi from "
|
|
16
|
+
import MediaApi from "../../MediaApi";
|
|
17
17
|
|
|
18
18
|
export default {
|
|
19
19
|
name: "MediaItemPreview",
|
|
20
20
|
props: {
|
|
21
|
-
item: Object,
|
|
21
|
+
item: { type: Object, required: true },
|
|
22
22
|
missingMessage: {
|
|
23
23
|
type: String,
|
|
24
24
|
default: 'There was an error trying to load this media item. The file may be corrupt or missing'
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="full-height full-height-container media-container">
|
|
4
4
|
<div class="top-bar">
|
|
5
|
-
<div
|
|
5
|
+
<div v-if="!paginatedItems.loading && !inTrash && !searchQuery" class="breadcrumb">
|
|
6
6
|
<ul>
|
|
7
|
-
<li v-for="item in getDirectoryBreadcrumbItems(paginatedItems.currentDirectory)" :class="item.separator ? 'separator' : ''">
|
|
7
|
+
<li v-for="item in getDirectoryBreadcrumbItems(paginatedItems.currentDirectory)" :key="JSON.stringify(item)" :class="item.separator ? 'separator' : ''">
|
|
8
8
|
<b-icon v-if="item.home && paginatedItems.currentDirectory === null" class="subtitle" icon="home"></b-icon>
|
|
9
|
-
<b-button v-else-if="item.home" type="is-text" @click="navigateTo({ currentPath: '' })"
|
|
10
|
-
<b-button v-else-if="item.link !== null" type="is-text" @click="navigateTo({ currentPath: item.link })"
|
|
9
|
+
<b-button v-else-if="item.home" type="is-text" class="subtitle" icon-left="home" @click="navigateTo({ currentPath: '' })"></b-button>
|
|
10
|
+
<b-button v-else-if="item.link !== null" type="is-text" class="subtitle" @click="navigateTo({ currentPath: item.link })">{{ item.text }}</b-button>
|
|
11
11
|
<span v-else class="subtitle is-active">{{ item.text }}</span>
|
|
12
12
|
</li>
|
|
13
13
|
</ul>
|
|
14
14
|
</div>
|
|
15
15
|
<div v-else-if="inTrash" class="is-flex">
|
|
16
|
-
<b-button
|
|
16
|
+
<b-button outlined rounded icon-left="arrow-left" class="action-bar-pad" @click="navigateTo({ currentPath: '' })">All Items</b-button>
|
|
17
17
|
<div class="title action-bar-pad">Deleted Photos & Files</div>
|
|
18
18
|
</div>
|
|
19
19
|
<div v-else-if="searchQuery" class="is-flex">
|
|
20
|
-
<b-button
|
|
20
|
+
<b-button outlined rounded icon-left="arrow-left" class="action-bar-pad" @click="navigateTo({ currentPath: '' })">All Items</b-button>
|
|
21
21
|
<div class="title action-bar-pad">Search results for "{{ searchQuery }}"</div>
|
|
22
22
|
</div>
|
|
23
23
|
<ul v-else>
|
|
@@ -28,26 +28,26 @@
|
|
|
28
28
|
|
|
29
29
|
<b-field class="action-bar-pad">
|
|
30
30
|
<p class="control">
|
|
31
|
-
<b-button
|
|
31
|
+
<b-button v-if="numberOfItemsSelected > 0" disabled type="is-primary">{{ numberOfItemsSelected }} item(s) selected</b-button>
|
|
32
32
|
</p>
|
|
33
33
|
<p class="control">
|
|
34
|
-
<b-button v-if="numberOfItemsSelected > 0"
|
|
34
|
+
<b-button v-if="numberOfItemsSelected > 0" icon-left="times" type="is-primary" @click="resetSelection"></b-button>
|
|
35
35
|
</p>
|
|
36
36
|
</b-field>
|
|
37
37
|
|
|
38
|
-
<b-button v-if="!inTrash && !searchQuery" icon-left="folder-plus" @click="isCreateDirectoryModalActive = true"
|
|
39
|
-
<b-button v-if="!inTrash && !searchQuery" icon-left="file-upload"
|
|
40
|
-
<b-input rounded placeholder="Search photos and files..." icon="search"
|
|
41
|
-
:value="searchQuery" @input="value => navigateTo({searchQuery: value})"
|
|
42
|
-
<b-button v-if="!inTrash" icon-left="trash"
|
|
38
|
+
<b-button v-if="!inTrash && !searchQuery" icon-left="folder-plus" class="action-bar-pad" @click="isCreateDirectoryModalActive = true">New Directory</b-button>
|
|
39
|
+
<b-button v-if="!inTrash && !searchQuery" icon-left="file-upload" type="is-success" class="action-bar-pad" @click="$router.push({ query: { upload: true }})">Upload Files</b-button>
|
|
40
|
+
<b-input v-if="!inTrash" rounded placeholder="Search photos and files..." icon="search"
|
|
41
|
+
icon-pack="fas" :value="searchQuery" class="action-bar-pad" @input="value => navigateTo({searchQuery: value})"></b-input>
|
|
42
|
+
<b-button v-if="!inTrash" icon-left="trash" type="is-danger" outlined class="action-bar-pad" @click="navigateTo({inTrash: true})">Deleted Items</b-button>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
<div class="media-items full-height-flex scroll-container">
|
|
47
47
|
|
|
48
|
-
<b-loading :is-full-page="false"
|
|
48
|
+
<b-loading v-model="paginatedItems.loading" :is-full-page="false" :can-cancel="false"></b-loading>
|
|
49
49
|
|
|
50
|
-
<h2
|
|
50
|
+
<h2 v-if="!paginatedItems.loading && paginatedItems.directories.length === 0 && paginatedItems.files.length === 0" class="subtitle media-items-empty">
|
|
51
51
|
No items found.
|
|
52
52
|
</h2>
|
|
53
53
|
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
|
-
<div
|
|
75
|
+
<div v-if="paginatedItems.totalFiles > paginatedItems.filesPerPage" class="pagination-container">
|
|
76
76
|
<b-pagination
|
|
77
|
-
:total="paginatedItems.totalFiles"
|
|
78
77
|
v-model="paginatedItems.currentPage"
|
|
78
|
+
:total="paginatedItems.totalFiles"
|
|
79
79
|
:per-page="paginatedItems.filesPerPage"
|
|
80
80
|
aria-next-label="Next page"
|
|
81
81
|
aria-previous-label="Previous page"
|
|
@@ -91,19 +91,19 @@
|
|
|
91
91
|
</header>
|
|
92
92
|
<section class="modal-card-body">
|
|
93
93
|
<b-field label-position="inside" label="Name">
|
|
94
|
-
<b-input v-model="newDirectoryName"></b-input>
|
|
94
|
+
<b-input v-model="newDirectoryName" @keyup.native.enter="doCreateDirectory"></b-input>
|
|
95
95
|
</b-field>
|
|
96
96
|
</section>
|
|
97
97
|
<footer class="modal-card-foot is-flex">
|
|
98
98
|
<div class="is-flex-grow-1"></div>
|
|
99
99
|
<b-button @click="isCreateDirectoryModalActive = false">Close</b-button>
|
|
100
|
-
<b-button
|
|
100
|
+
<b-button type="is-primary" @click="doCreateDirectory">Create</b-button>
|
|
101
101
|
</footer>
|
|
102
102
|
</div>
|
|
103
103
|
</b-modal>
|
|
104
104
|
|
|
105
105
|
<b-modal :active.sync="isUploadModalActive" trap-focus has-modal-card aria-role="dialog" aria-modal auto-focus>
|
|
106
|
-
<MediaUpload :current-directory="paginatedItems.currentDirectory" @close="
|
|
106
|
+
<MediaUpload :current-directory="paginatedItems.currentDirectory" @close="$router.push({ query: { }})" @uploaded="fetchData"></MediaUpload>
|
|
107
107
|
</b-modal>
|
|
108
108
|
|
|
109
109
|
</div>
|
|
@@ -111,36 +111,32 @@
|
|
|
111
111
|
</template>
|
|
112
112
|
|
|
113
113
|
<script>
|
|
114
|
-
import MediaApi from "
|
|
114
|
+
import MediaApi from "../../MediaApi";
|
|
115
115
|
import MediaDirectory from "./MediaDirectory.vue";
|
|
116
116
|
import MediaItem from "./MediaItem.vue";
|
|
117
|
-
import MediaDirectoryApi, {getDirectoryBreadcrumbItems, getDirectoryPathString} from "
|
|
118
|
-
import {morphToNotification} from "
|
|
117
|
+
import MediaDirectoryApi, {getDirectoryBreadcrumbItems, getDirectoryPathString} from "../../MediaDirectoryApi";
|
|
118
|
+
import {morphToNotification} from "../../api";
|
|
119
119
|
import MediaUpload from "./MediaUpload.vue";
|
|
120
120
|
|
|
121
121
|
export default {
|
|
122
122
|
name: "MediaList",
|
|
123
|
+
components: { MediaDirectory, MediaItem, MediaUpload },
|
|
123
124
|
props: {
|
|
124
125
|
currentPath: {
|
|
125
|
-
type: String
|
|
126
|
+
type: String,
|
|
127
|
+
required: true
|
|
126
128
|
},
|
|
127
129
|
inTrash: {
|
|
128
130
|
type: Boolean
|
|
129
131
|
},
|
|
130
132
|
searchQuery: {
|
|
131
|
-
type: String
|
|
133
|
+
type: String,
|
|
134
|
+
default: null
|
|
132
135
|
}
|
|
133
136
|
},
|
|
134
|
-
watch: {
|
|
135
|
-
'searchQuery': 'debounceFetchData',
|
|
136
|
-
'inTrash': 'fetchData',
|
|
137
|
-
'paginatedItems.currentPage': 'fetchData',
|
|
138
|
-
'currentPath': 'fetchData'
|
|
139
|
-
},
|
|
140
137
|
data() {
|
|
141
138
|
return {
|
|
142
139
|
paginatedItems: {files: [], directories: [], currentDirectory: null, totalFiles: null, filesPerPage: null, loading: false, currentPage: 1},
|
|
143
|
-
isUploadModalActive: false,
|
|
144
140
|
isCreateDirectoryModalActive: false,
|
|
145
141
|
newDirectoryName: '',
|
|
146
142
|
searchDebounce: null,
|
|
@@ -149,11 +145,10 @@ export default {
|
|
|
149
145
|
getDirectoryBreadcrumbItems: getDirectoryBreadcrumbItems
|
|
150
146
|
}
|
|
151
147
|
},
|
|
152
|
-
components: { MediaDirectory, MediaItem, MediaUpload },
|
|
153
|
-
created() {
|
|
154
|
-
this.fetchData()
|
|
155
|
-
},
|
|
156
148
|
computed: {
|
|
149
|
+
isUploadModalActive() {
|
|
150
|
+
return this.$route.query.upload === 'true';
|
|
151
|
+
},
|
|
157
152
|
displayPath() {
|
|
158
153
|
return getDirectoryPathString(this.paginatedItems.currentDirectory);
|
|
159
154
|
},
|
|
@@ -161,6 +156,15 @@ export default {
|
|
|
161
156
|
return this.paginatedItems.files.concat(this.paginatedItems.directories).filter(item => item.selected).length;
|
|
162
157
|
}
|
|
163
158
|
},
|
|
159
|
+
watch: {
|
|
160
|
+
'searchQuery': 'debounceFetchData',
|
|
161
|
+
'inTrash': 'fetchData',
|
|
162
|
+
'paginatedItems.currentPage': 'fetchData',
|
|
163
|
+
'currentPath': 'fetchData'
|
|
164
|
+
},
|
|
165
|
+
created() {
|
|
166
|
+
this.fetchData()
|
|
167
|
+
},
|
|
164
168
|
methods: {
|
|
165
169
|
async fetchData() {
|
|
166
170
|
if(this.paginatedItems.loading) {
|
|
@@ -226,8 +230,8 @@ export default {
|
|
|
226
230
|
</script>
|
|
227
231
|
|
|
228
232
|
<style scoped lang="scss">
|
|
229
|
-
@import '
|
|
230
|
-
@import '
|
|
233
|
+
@import '../../styles/variables';
|
|
234
|
+
@import '../util.css';
|
|
231
235
|
|
|
232
236
|
.box {
|
|
233
237
|
display: flex;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<MediaList
|
|
2
|
+
<MediaList :current-path="currentPath" :in-trash="inTrash" :search-query="searchQuery" @navigate="onNavigate" @double-click-action="viewItem" />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script>
|
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
<div class="content" style="max-width: 80rem;">
|
|
7
7
|
<p><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">Responsive images</a> are images which work well on devices of differing screen sizes, resolutions etc...
|
|
8
8
|
Depending on the size/pixel density of the users' screen, we serve a different image to them. This optimization helps speed up load times dramatically.</p>
|
|
9
|
-
<b-button type="is-primary"
|
|
9
|
+
<b-button type="is-primary" :disabled="requestInFlight" @click="generate">Generate responsive versions for each image</b-button>
|
|
10
10
|
</div>
|
|
11
11
|
<b-progress v-if="requestInFlight" size="is-medium" show-value>Generating...</b-progress>
|
|
12
12
|
<div v-if="hasServerLog" class="full-height-flex full-height-container" style="min-height: 0;">
|
|
13
13
|
<h4 class="subtitle">Server output log:</h4>
|
|
14
|
-
<pre
|
|
14
|
+
<pre class="scroll-container full-height-flex" style="background-color: #000;" v-html="serverLog"></pre>
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
18
|
</template>
|
|
19
19
|
|
|
20
20
|
<script>
|
|
21
|
-
import {API_ROOT} from "
|
|
22
|
-
import {FetchBuilder, morphToNotification} from "
|
|
21
|
+
import {API_ROOT} from "../../CrudApi";
|
|
22
|
+
import {FetchBuilder, morphToNotification} from "../../api";
|
|
23
23
|
|
|
24
24
|
export default {
|
|
25
25
|
name: "MediaResponsiveImages",
|
|
@@ -47,5 +47,5 @@ export default {
|
|
|
47
47
|
</script>
|
|
48
48
|
|
|
49
49
|
<style scoped>
|
|
50
|
-
@import '
|
|
50
|
+
@import '../util.css';
|
|
51
51
|
</style>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="modal-card">
|
|
3
3
|
<header class="modal-card-head">
|
|
4
|
-
<p class="modal-card-title"
|
|
5
|
-
<p class="modal-card-title"
|
|
4
|
+
<p v-if="!isUploading" class="modal-card-title">Upload files to '{{ currentPath }}'</p>
|
|
5
|
+
<p v-else class="modal-card-title">Uploading {{ filesToUpload.length }} item(s)</p>
|
|
6
6
|
</header>
|
|
7
|
-
<section class="modal-card-body"
|
|
7
|
+
<section v-if="!isUploading" class="modal-card-body">
|
|
8
8
|
<b-upload v-model="filesToUpload"
|
|
9
9
|
multiple
|
|
10
10
|
drag-drop expanded>
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
<footer class="modal-card-foot is-flex">
|
|
38
38
|
<div class="is-flex-grow-1"></div>
|
|
39
39
|
<b-button @click="closeModal">Close</b-button>
|
|
40
|
-
<b-button
|
|
40
|
+
<b-button type="is-primary" :disabled="isUploading" @click="doUpload">Upload</b-button>
|
|
41
41
|
</footer>
|
|
42
42
|
</div>
|
|
43
43
|
</template>
|
|
44
44
|
|
|
45
45
|
<script>
|
|
46
|
-
import {morphToNotification} from "
|
|
47
|
-
import MediaApi from "
|
|
48
|
-
import {getDirectoryPathString} from "
|
|
46
|
+
import {morphToNotification} from "../../api";
|
|
47
|
+
import MediaApi from "../../MediaApi";
|
|
48
|
+
import {getDirectoryPathString} from "../../MediaDirectoryApi";
|
|
49
49
|
|
|
50
50
|
export default {
|
|
51
|
-
name: "MediaUpload
|
|
51
|
+
name: "MediaUpload",
|
|
52
52
|
props: {
|
|
53
|
-
currentDirectory: Object
|
|
53
|
+
currentDirectory: { type: Object, default: null }
|
|
54
54
|
},
|
|
55
55
|
data() {
|
|
56
56
|
return {
|
|
@@ -67,7 +67,7 @@ export default {
|
|
|
67
67
|
asyncComputed: {
|
|
68
68
|
async imagePreviews() {
|
|
69
69
|
let promises = this.filesToUpload.map(file => {
|
|
70
|
-
return new Promise((resolve
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
71
|
let reader = new FileReader();
|
|
72
72
|
reader.onload = function (e) {
|
|
73
73
|
resolve(e.target.result);
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
<div v-if="loaded">
|
|
4
4
|
<slot :value="value" :options="options" :update-value="updateValue">
|
|
5
5
|
<div class="horizontal-row">
|
|
6
|
-
<b-switch
|
|
7
|
-
<b-field
|
|
6
|
+
<b-switch v-if="type === 'switch'" :value="value" :passive-type="isFallback ? 'is-dark' : ''" @input="updateValue($event)">{{ label }}</b-switch>
|
|
7
|
+
<b-field v-else-if="type === 'select'" :label="label" label-position="inside" class="pref-field">
|
|
8
8
|
<b-select v-if="grouped" :placeholder="selectPlaceholder" :value="value" @input="updateValue($event)">
|
|
9
|
-
<optgroup v-for="(suboptions,
|
|
9
|
+
<optgroup v-for="(suboptions, groupLabel) in options" :key="groupLabel" :label="groupLabel">
|
|
10
10
|
<option
|
|
11
|
-
v-for="(display,
|
|
12
|
-
:
|
|
13
|
-
:
|
|
11
|
+
v-for="(display, optionValue) in suboptions"
|
|
12
|
+
:key="optionValue"
|
|
13
|
+
:value="optionValue">
|
|
14
14
|
{{ display }}
|
|
15
15
|
</option>
|
|
16
16
|
</optgroup>
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<b-select v-else :placeholder="selectPlaceholder" :value="value" @input="updateValue($event)">
|
|
19
19
|
<option
|
|
20
20
|
v-for="(optionDisplay, optionValue) in options"
|
|
21
|
-
:
|
|
22
|
-
:
|
|
21
|
+
:key="optionValue"
|
|
22
|
+
:value="optionValue">
|
|
23
23
|
{{ optionDisplay }}
|
|
24
24
|
</option>
|
|
25
25
|
</b-select>
|
|
@@ -51,8 +51,8 @@ import {morphToNotification} from "../../api";
|
|
|
51
51
|
export default {
|
|
52
52
|
name: "PreferencesField",
|
|
53
53
|
props: {
|
|
54
|
-
dataKey: String,
|
|
55
|
-
label: String,
|
|
54
|
+
dataKey: { type: String, required: true },
|
|
55
|
+
label: { type: String, required: true },
|
|
56
56
|
type: {
|
|
57
57
|
type: String,
|
|
58
58
|
default: null
|
|
@@ -6,18 +6,13 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div class="box full-height-flex scroll-container">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<em>No preferences found</em>
|
|
17
|
-
</b-tab-item>
|
|
18
|
-
</b-tabs>
|
|
19
|
-
</div>
|
|
20
|
-
</transition>
|
|
9
|
+
<b-tabs class="block">
|
|
10
|
+
<slot :can-access-prefs="canAccessPrefs"></slot>
|
|
11
|
+
<!-- If there were no tabs which are accessible, then display this tab as a fallback -->
|
|
12
|
+
<b-tab-item v-if="!hasAtLeastOneAccess" label="General">
|
|
13
|
+
<em>No preferences found</em>
|
|
14
|
+
</b-tab-item>
|
|
15
|
+
</b-tabs>
|
|
21
16
|
</div>
|
|
22
17
|
</div>
|
|
23
18
|
</template>
|
|
@@ -25,24 +20,22 @@
|
|
|
25
20
|
<script>
|
|
26
21
|
|
|
27
22
|
import {canAccessPrefs} from "../../PreferencesApi";
|
|
28
|
-
import UserPermissions from "../../UserPermissions";
|
|
29
|
-
import AuthApi from "../../AuthApi";
|
|
30
23
|
|
|
31
24
|
export default {
|
|
32
|
-
name: "
|
|
25
|
+
name: "PreferencesList",
|
|
33
26
|
data() {
|
|
34
27
|
return {
|
|
35
|
-
user: null,
|
|
36
28
|
hasAtLeastOneAccess: false
|
|
37
29
|
}
|
|
38
30
|
},
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
computed: {
|
|
32
|
+
userPermissions() {
|
|
33
|
+
return this.$store.getters.userPermissions;
|
|
34
|
+
}
|
|
41
35
|
},
|
|
42
36
|
methods: {
|
|
43
37
|
canAccessPrefs(keys) {
|
|
44
|
-
let
|
|
45
|
-
let result = canAccessPrefs(this.$buefy, userPermissions, keys);
|
|
38
|
+
let result = canAccessPrefs(this.$buefy, this.userPermissions, keys);
|
|
46
39
|
if(result) {
|
|
47
40
|
this.hasAtLeastOneAccess = true;
|
|
48
41
|
}
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
<ShowIfPermitted data-key="appearance.themes">
|
|
3
3
|
<h3 class="subtitle">Website Themes</h3>
|
|
4
4
|
<PreferencesField data-key="appearance.themes::theme" label="">
|
|
5
|
-
<template
|
|
5
|
+
<template #default="slotProps">
|
|
6
6
|
<b-table
|
|
7
7
|
:data="Object.values(slotProps.options)"
|
|
8
8
|
:striped="false">
|
|
9
|
-
<b-table-column
|
|
9
|
+
<b-table-column v-slot="props" label="Key">
|
|
10
10
|
<img :src="props.row.image" class="theme-logo" />
|
|
11
11
|
</b-table-column>
|
|
12
12
|
|
|
13
|
-
<b-table-column field="name" label="Name"
|
|
13
|
+
<b-table-column v-slot="props" field="name" label="Name">
|
|
14
14
|
{{ props.row.name }}
|
|
15
15
|
<strong v-if="getSelectedOption(slotProps.options, slotProps.value) === props.row">(current theme)</strong>
|
|
16
16
|
</b-table-column>
|
|
17
17
|
|
|
18
|
-
<b-table-column field="provides" label="Provides preferences"
|
|
19
|
-
<div v-for="(provideGroup, keyGroup) in props.row.provides" class="is-size-7">
|
|
20
|
-
<span v-for="(value, key) in provideGroup"><code>{{ keyGroup }}::{{ key}}</code><br></span>
|
|
18
|
+
<b-table-column v-slot="props" field="provides" label="Provides preferences">
|
|
19
|
+
<div v-for="(provideGroup, keyGroup) in props.row.provides" :key="keyGroup" class="is-size-7">
|
|
20
|
+
<span v-for="(value, key) in provideGroup" :key="key"><code>{{ keyGroup }}::{{ key}}</code><br></span>
|
|
21
21
|
</div>
|
|
22
22
|
</b-table-column>
|
|
23
23
|
|
|
24
|
-
<b-table-column
|
|
25
|
-
<b-button v-if="getSelectedOption(slotProps.options, slotProps.value) !== props.row" @click="switchToTheme(props.row.key, slotProps.updateValue)"
|
|
26
|
-
<b-button type="is-success" disabled
|
|
24
|
+
<b-table-column v-slot="props" label="">
|
|
25
|
+
<b-button v-if="getSelectedOption(slotProps.options, slotProps.value) !== props.row" :loading="updating" @click="switchToTheme(props.row.key, slotProps.updateValue)">Switch to this theme</b-button>
|
|
26
|
+
<b-button v-else type="is-success" disabled>Theme is already active</b-button>
|
|
27
27
|
</b-table-column>
|
|
28
28
|
</b-table>
|
|
29
29
|
</template>
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
<script>
|
|
8
8
|
import {canAccessPrefs} from "../../PreferencesApi";
|
|
9
|
-
import UserPermissions from "../../UserPermissions";
|
|
10
|
-
import AuthApi from "../../AuthApi";
|
|
11
9
|
|
|
12
10
|
export default {
|
|
13
11
|
name: "ShowIfPermitted",
|
|
@@ -21,18 +19,15 @@ export default {
|
|
|
21
19
|
default: null
|
|
22
20
|
}
|
|
23
21
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
let userPermissions = new UserPermissions((await new AuthApi(this.$buefy).userDetails()).user.permissions);
|
|
35
|
-
this.permitted = await canAccessPrefs(this.$buefy, userPermissions, keys);
|
|
22
|
+
computed: {
|
|
23
|
+
keysArray() {
|
|
24
|
+
if(this.keys === null) {
|
|
25
|
+
return [this.dataKey];
|
|
26
|
+
} else {
|
|
27
|
+
return this.keys;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
permitted() { return canAccessPrefs(this.$buefy, this.$store.getters.userPermissions, this.keysArray); }
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
</script>
|