@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.
Files changed (69) hide show
  1. package/.eslintrc.js +23 -0
  2. package/.github/workflows/node.js.yml +4 -4
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/ui.iml +10 -0
  5. package/package.json +13 -5
  6. package/src/AuthApi.js +77 -42
  7. package/src/CrudApi.js +3 -3
  8. package/src/GroupsApi.js +9 -0
  9. package/src/MediaDirectoryApi.js +1 -1
  10. package/src/PreferencesApi.js +2 -0
  11. package/src/UserPermissions.js +2 -9
  12. package/src/UserPreferences.js +0 -4
  13. package/src/UserPreferences.test.js +0 -2
  14. package/src/UsersApi.js +41 -0
  15. package/src/api.js +96 -38
  16. package/src/components/App.vue +19 -240
  17. package/src/components/AuthenticatedLayout.vue +254 -0
  18. package/src/components/AuthenticationLog.vue +86 -30
  19. package/src/components/CodeEditor.vue +16 -32
  20. package/src/components/EditButtonOnRowHover.vue +21 -0
  21. package/src/components/Error404.vue +15 -5
  22. package/src/components/EventsChooser.vue +11 -11
  23. package/src/components/EventsTable.vue +14 -8
  24. package/src/components/GenericEditableField.vue +74 -0
  25. package/src/components/GroupsChooser.vue +58 -0
  26. package/src/components/GroupsList.vue +129 -0
  27. package/src/components/ImportExport.vue +32 -1
  28. package/src/components/LegacyPage.vue +22 -23
  29. package/src/components/UserJoined.vue +35 -0
  30. package/src/components/UserManagement.vue +168 -0
  31. package/src/components/UserProfileForm.vue +214 -0
  32. package/src/components/ViewProfile.vue +7 -219
  33. package/src/components/auth/Auth404.vue +16 -0
  34. package/src/components/auth/Login.vue +135 -0
  35. package/src/components/auth/LoginLogo.vue +30 -0
  36. package/src/components/auth/Logout.vue +26 -0
  37. package/src/components/auth/PasswordRemind.vue +71 -0
  38. package/src/components/auth/PasswordReset.vue +97 -0
  39. package/src/components/auth/TwoFactorSetup.vue +115 -0
  40. package/src/components/auth/VerifyEmail.vue +71 -0
  41. package/src/components/auth/WelcomeFloat.vue +87 -0
  42. package/src/components/auth/login.scss +17 -0
  43. package/src/components/{MediaChooseDirectory.vue → media/MediaChooseDirectory.vue} +12 -12
  44. package/src/components/{MediaDirectory.vue → media/MediaDirectory.vue} +8 -8
  45. package/src/components/{MediaInsertModal.vue → media/MediaInsertModal.vue} +2 -2
  46. package/src/components/{MediaItem.vue → media/MediaItem.vue} +24 -23
  47. package/src/components/{MediaItemPreview.vue → media/MediaItemPreview.vue} +5 -5
  48. package/src/components/{MediaList.vue → media/MediaList.vue} +42 -38
  49. package/src/components/{MediaPage.vue → media/MediaPage.vue} +1 -1
  50. package/src/components/{MediaResponsiveImages.vue → media/MediaResponsiveImages.vue} +5 -5
  51. package/src/components/{MediaUpload.vue → media/MediaUpload.vue} +10 -10
  52. package/src/components/{media.scss → media/media.scss} +1 -1
  53. package/src/components/preferences/PreferencesField.vue +10 -10
  54. package/src/components/preferences/PreferencesList.vue +13 -20
  55. package/src/components/preferences/PreferencesThemeChooser.vue +9 -9
  56. package/src/components/preferences/ShowIfPermitted.vue +9 -14
  57. package/src/components/users/CreateUserModal.vue +73 -0
  58. package/src/icons.js +90 -0
  59. package/src/main.js +111 -0
  60. package/src/modules/LegacyPages.js +18 -0
  61. package/src/modules/Media.js +45 -0
  62. package/src/modules/UserManagement.js +24 -0
  63. package/src/routes/index.js +92 -0
  64. package/src/store/index.js +70 -0
  65. package/src/styles/_variables.scss +1 -0
  66. package/src/styles/app.scss +15 -2
  67. package/src/login.js +0 -17
  68. package/src/routes.js +0 -61
  69. 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" v-if="displayFullPath" @click.exact="select(true)" @click.shift.exact="select(false)">
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%" v-hotkey="keymap">
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">Variants</label>
106
- <b-table striped :data="variants">
107
- <b-table-column label="Filename" field="filename" v-slot="props"><a :href="'/content/media/' + props.row.filename">{{ props.row.filename }}</a></b-table-column>
108
- <b-table-column label="Width (px)" field="width" v-slot="props">{{ props.row.width }}</b-table-column>
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" v-slot="props">{{ props.row.name }}</b-table-column>
119
- <b-table-column label="Path" field="fullPath" v-slot="props">{{ props.row.fullPath }}</b-table-column>
120
- <b-table-column label="Last Updated" field="updatedAt" v-slot="props">
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 label="" v-slot="slotProps">
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 @click="saveEdits" type="is-primary">Save</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 "../api";
159
- import MediaApi from "../MediaApi";
164
+ import {morphToNotification} from "../../api";
165
+ import MediaApi from "../../MediaApi";
160
166
  import MediaChooseDirectory from "./MediaChooseDirectory.vue";
161
- import Internationalize from "../Internationalize";
162
- import {getDirectoryFullSlug, getDirectoryPathString} from "../MediaDirectoryApi";
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.vue",
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.concat([
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 v-else-if="item.type === TYPE_IMAGE">
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 v-else-if="item.type === TYPE_DOCUMENT">
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" v-else-if="item.type === TYPE_AUDIO"></b-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 "../MediaApi";
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 class="breadcrumb" v-if="!paginatedItems.loading && !inTrash && !searchQuery">
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: '' })" class="subtitle" icon-left="home"></b-button>
10
- <b-button v-else-if="item.link !== null" type="is-text" @click="navigateTo({ currentPath: item.link })" class="subtitle">{{ item.text }}</b-button>
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 @click="navigateTo({ currentPath: '' })" outlined rounded icon-left="arrow-left" class="action-bar-pad">All Items</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 @click="navigateTo({ currentPath: '' })" outlined rounded icon-left="arrow-left" class="action-bar-pad">All Items</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 disabled v-if="numberOfItemsSelected > 0" type="is-primary">{{ numberOfItemsSelected }} item(s) selected</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" @click="resetSelection" icon-left="times" type="is-primary"></b-button>
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" class="action-bar-pad">New Directory</b-button>
39
- <b-button v-if="!inTrash && !searchQuery" icon-left="file-upload" @click="isUploadModalActive = true" type="is-success" class="action-bar-pad">Upload Files</b-button>
40
- <b-input rounded placeholder="Search photos and files..." icon="search" icon-pack="fas"
41
- :value="searchQuery" @input="value => navigateTo({searchQuery: value})" class="action-bar-pad" v-if="!inTrash"></b-input>
42
- <b-button v-if="!inTrash" icon-left="trash" @click="navigateTo({inTrash: true})" type="is-danger" outlined class="action-bar-pad">Deleted Items</b-button>
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" v-model="paginatedItems.loading" :can-cancel="false"></b-loading>
48
+ <b-loading v-model="paginatedItems.loading" :is-full-page="false" :can-cancel="false"></b-loading>
49
49
 
50
- <h2 class="subtitle media-items-empty" v-if="!paginatedItems.loading && paginatedItems.directories.length === 0 && paginatedItems.files.length === 0">
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 class="pagination-container" v-if="paginatedItems.totalFiles > paginatedItems.filesPerPage">
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 @click="doCreateDirectory" type="is-primary">Create</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="isUploadModalActive = false" @uploaded="fetchData"></MediaUpload>
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 "../MediaApi";
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 "../MediaDirectoryApi";
118
- import {morphToNotification} from "../api";
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 '../styles/_variables.scss';
230
- @import './util.css';
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 @navigate="onNavigate" :current-path="currentPath" :in-trash="inTrash" :search-query="searchQuery" @double-click-action="viewItem" />
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" @click="generate" :disabled="requestInFlight">Generate responsive versions for each image</b-button>
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 v-html="serverLog" class="scroll-container full-height-flex" style="background-color: #000;"></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 "../CrudApi";
22
- import {FetchBuilder, morphToNotification} from "../api";
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 './util.css';
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" v-if="!isUploading">Upload files to '{{ currentPath }}'</p>
5
- <p class="modal-card-title" v-else>Uploading {{ filesToUpload.length }} item(s)</p>
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" v-if="!isUploading">
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 @click="doUpload" type="is-primary" :disabled="isUploading">Upload</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 "../api";
47
- import MediaApi from "../MediaApi";
48
- import {getDirectoryPathString} from "../MediaDirectoryApi";
46
+ import {morphToNotification} from "../../api";
47
+ import MediaApi from "../../MediaApi";
48
+ import {getDirectoryPathString} from "../../MediaDirectoryApi";
49
49
 
50
50
  export default {
51
- name: "MediaUpload.vue",
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, reject) => {
70
+ return new Promise((resolve) => {
71
71
  let reader = new FileReader();
72
72
  reader.onload = function (e) {
73
73
  resolve(e.target.result);
@@ -1,4 +1,4 @@
1
- @import '../styles/_variables.scss';
1
+ @import '../../styles/variables';
2
2
 
3
3
  .card-image {
4
4
  min-height: 6rem;
@@ -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 :value="value" @input="updateValue($event)" :passive-type="isFallback ? 'is-dark' : ''" v-if="type === 'switch'">{{ label }}</b-switch>
7
- <b-field :label="label" label-position="inside" v-else-if="type === 'select'" class="pref-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, label) in options" :label="label">
9
+ <optgroup v-for="(suboptions, groupLabel) in options" :key="groupLabel" :label="groupLabel">
10
10
  <option
11
- v-for="(display, value) in suboptions"
12
- :value="value"
13
- :key="value">
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
- :value="optionValue"
22
- :key="optionValue">
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
- <transition name="fade">
11
- <div v-if="user">
12
- <b-tabs class="block">
13
- <slot :can-access-prefs="canAccessPrefs"></slot>
14
- <!-- If there were no tabs which are accessible, then display this tab as a fallback -->
15
- <b-tab-item v-if="!hasAtLeastOneAccess" label="General">
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: "PreferencesView",
25
+ name: "PreferencesList",
33
26
  data() {
34
27
  return {
35
- user: null,
36
28
  hasAtLeastOneAccess: false
37
29
  }
38
30
  },
39
- async mounted() {
40
- this.user = (await new AuthApi(this.$buefy).userDetails()).user;
31
+ computed: {
32
+ userPermissions() {
33
+ return this.$store.getters.userPermissions;
34
+ }
41
35
  },
42
36
  methods: {
43
37
  canAccessPrefs(keys) {
44
- let userPermissions = new UserPermissions(this.user.permissions);
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 v-slot:default="slotProps">
5
+ <template #default="slotProps">
6
6
  <b-table
7
7
  :data="Object.values(slotProps.options)"
8
8
  :striped="false">
9
- <b-table-column label="Key" v-slot="props">
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" v-slot="props">
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" v-slot="props">
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 label="" v-slot="props">
25
- <b-button v-if="getSelectedOption(slotProps.options, slotProps.value) !== props.row" @click="switchToTheme(props.row.key, slotProps.updateValue)" :loading="updating">Switch to this theme</b-button>
26
- <b-button type="is-success" disabled v-else>Theme is already active</b-button>
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
- data() {
25
- return {
26
- permitted: false
27
- }
28
- },
29
- async mounted() {
30
- let keys = this.keys;
31
- if(keys === null) {
32
- keys = [this.dataKey];
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>