@oxygen-cms/ui 1.6.0 → 1.6.3

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 (56) hide show
  1. package/.gitattributes +3 -0
  2. package/assets/bg/autumn-min.jpg +0 -0
  3. package/assets/bg/city-min.jpg +0 -0
  4. package/assets/bg/clouds-min.jpg +0 -0
  5. package/assets/bg/coast-min.jpg +0 -0
  6. package/assets/bg/speckles-min.jpg +0 -0
  7. package/assets/bg/trees-min.jpg +0 -0
  8. package/assets/bg/waves.jpg +0 -0
  9. package/assets/bg/yosemite-falls-min.jpg +0 -0
  10. package/assets/icon/apple-touch-icon-114x114.png +0 -0
  11. package/assets/icon/apple-touch-icon-120x120.png +0 -0
  12. package/assets/icon/apple-touch-icon-144x144.png +0 -0
  13. package/assets/icon/apple-touch-icon-152x152.png +0 -0
  14. package/assets/icon/apple-touch-icon-180x180.png +0 -0
  15. package/assets/icon/apple-touch-icon-57x57.png +0 -0
  16. package/assets/icon/apple-touch-icon-60x60.png +0 -0
  17. package/assets/icon/apple-touch-icon-72x72.png +0 -0
  18. package/assets/icon/apple-touch-icon-76x76.png +0 -0
  19. package/assets/icon/apple-touch-icon-precomposed.png +0 -0
  20. package/assets/icon/apple-touch-icon.png +0 -0
  21. package/assets/icon/browserconfig.xml +12 -0
  22. package/assets/icon/favicon-160x160.png +0 -0
  23. package/assets/icon/favicon-16x16.png +0 -0
  24. package/assets/icon/favicon-192x192.png +0 -0
  25. package/assets/icon/favicon-32x32.png +0 -0
  26. package/assets/icon/favicon-96x96.png +0 -0
  27. package/assets/icon/favicon.ico +0 -0
  28. package/assets/icon/mstile-144x144.png +0 -0
  29. package/assets/icon/mstile-150x150.png +0 -0
  30. package/assets/icon/mstile-310x150.png +0 -0
  31. package/assets/icon/mstile-310x310.png +0 -0
  32. package/assets/icon/mstile-70x70.png +0 -0
  33. package/package.json +2 -10
  34. package/src/AuthApi.js +12 -12
  35. package/src/CrudApi.js +9 -7
  36. package/src/PreferencesApi.js +4 -4
  37. package/src/UsersApi.js +4 -4
  38. package/src/api.js +17 -3
  39. package/src/components/App.vue +4 -3
  40. package/src/components/AuthenticationLog.vue +4 -3
  41. package/src/components/CodeEditor.vue +41 -6
  42. package/src/components/ImportExport.vue +2 -2
  43. package/src/components/LegacyPage.vue +5 -5
  44. package/src/components/MainMenu.vue +4 -1
  45. package/src/components/auth/WelcomeFloat.vue +8 -8
  46. package/src/components/media/MediaList.vue +1 -1
  47. package/src/components/media/MediaResponsiveImages.vue +2 -2
  48. package/src/components/media/media.scss +1 -1
  49. package/src/components/preferences/Preferences.vue +1 -1
  50. package/src/components/preferences/PreferencesList.vue +1 -1
  51. package/src/components/preferences/PreferencesThemeChooser.vue +2 -0
  52. package/src/main.js +33 -12
  53. package/src/modules/Events.js +35 -0
  54. package/src/modules/{LegacyPages.js → PagesPartials.js} +1 -12
  55. package/src/styles/_variables.scss +1 -15
  56. package/src/styles/app.scss +1 -1
package/.gitattributes ADDED
@@ -0,0 +1,3 @@
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.jpg filter=lfs diff=lfs merge=lfs -text
3
+ *.ico filter=lfs diff=lfs merge=lfs -text
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <browserconfig>
3
+ <msapplication>
4
+ <tile>
5
+ <square70x70logo src="/packages/oxygen/ui/img/icon/mstile-70x70.png"/>
6
+ <square150x150logo src="/packages/oxygen/ui/img/icon/mstile-150x150.png"/>
7
+ <square310x310logo src="/packages/oxygen/ui/img/icon/mstile-310x310.png"/>
8
+ <wide310x150logo src="/packages/oxygen/ui/img/icon/mstile-310x150.png"/>
9
+ <TileColor>#2b5797</TileColor>
10
+ </tile>
11
+ </msapplication>
12
+ </browserconfig>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-cms/ui",
3
- "version": "1.6.0",
3
+ "version": "1.6.3",
4
4
  "description": "Various utilities for UI-building in Vue.js",
5
5
  "main": "none",
6
6
  "repository": {
@@ -16,13 +16,11 @@
16
16
  "@fortawesome/free-solid-svg-icons": "^5.15.2",
17
17
  "@fortawesome/vue-fontawesome": "^0.1.10",
18
18
  "autoprefixer": "^9.8.5",
19
- "babel-loader": "^8.1.0",
20
19
  "brace": "^0.11.1",
21
20
  "buefy": "^0.9.10",
22
21
  "bulma": "~0.9.3",
23
22
  "copy-webpack-plugin": "^5.1.1",
24
23
  "downloadjs": "^1.4.7",
25
- "file-loader": "^6.1.1",
26
24
  "libphonenumber-js": "^1.9.11",
27
25
  "lodash": "^4.17.21",
28
26
  "title-case": "^3.0.3",
@@ -31,7 +29,6 @@
31
29
  "vex-js": "~4.1.0",
32
30
  "vue": "^2.6.11",
33
31
  "vue-async-computed": "^3.9.0",
34
- "vue-loader": "^15.9.6",
35
32
  "vue-router": "^3.5.1",
36
33
  "vue-template-compiler": "^2.6.11",
37
34
  "vue2-ace-editor": "^0.0.15",
@@ -42,18 +39,13 @@
42
39
  "@babel/preset-env": "^7.13.5",
43
40
  "babel-jest": "^26.6.3",
44
41
  "babel-polyfill": "^6.26.0",
45
- "css-loader": "^3.5.1",
46
42
  "eslint": "^7.32.0",
47
43
  "eslint-config-prettier": "^8.3.0",
48
44
  "eslint-plugin-jest": "^24.4.0",
49
45
  "eslint-plugin-vue": "^7.17.0",
50
46
  "jest": "^26.6.3",
51
47
  "sass-embedded": "^1.49.9",
52
- "postcss-loader": "^3.0.0",
53
- "regenerator-runtime": "^0.13.7",
54
- "sass-loader": "^8.0.2",
55
- "webpack": "^4.46.0",
56
- "webpack-cli": "^3.3.11"
48
+ "regenerator-runtime": "^0.13.7"
57
49
  },
58
50
  "scripts": {
59
51
  "test": "jest",
package/src/AuthApi.js CHANGED
@@ -1,4 +1,4 @@
1
- import {API_ROOT} from "./CrudApi";
1
+ import {getApiRoot} from "./CrudApi";
2
2
  import {FetchBuilder, initCsrfCookie} from "./api";
3
3
  import UserPermissions from "./UserPermissions";
4
4
 
@@ -19,16 +19,16 @@ export default class AuthApi {
19
19
  password,
20
20
  '2fa_code': code
21
21
  })
22
- .fetch(API_ROOT + 'auth/login');
22
+ .fetch(getApiRoot() + 'auth/login');
23
23
  }
24
24
 
25
25
  async getLoginPreferences() {
26
- return await this.request('get').fetch(API_ROOT + 'auth/preferences');
26
+ return await this.request('get').fetch(getApiRoot() + 'auth/preferences');
27
27
  }
28
28
 
29
29
  async setupTwoFactorAuth() {
30
30
  return await this.request('post')
31
- .fetch(API_ROOT + 'auth/two-factor-setup');
31
+ .fetch(getApiRoot() + 'auth/two-factor-setup');
32
32
  }
33
33
 
34
34
  async confirmTwoFactorAuth(code) {
@@ -36,7 +36,7 @@ export default class AuthApi {
36
36
  .withJson({
37
37
  '2fa_code': code
38
38
  })
39
- .fetch(API_ROOT + 'auth/two-factor-confirm');
39
+ .fetch(getApiRoot() + 'auth/two-factor-confirm');
40
40
  }
41
41
 
42
42
  async sendReminderEmail(email) {
@@ -44,23 +44,23 @@ export default class AuthApi {
44
44
  .withJson({
45
45
  'email': email
46
46
  })
47
- .fetch(API_ROOT + 'auth/send-reminder-email');
47
+ .fetch(getApiRoot() + 'auth/send-reminder-email');
48
48
  }
49
49
 
50
50
  async sendEmailVerification() {
51
51
  return await this.request('post')
52
- .fetch(API_ROOT + 'auth/verify-email');
52
+ .fetch(getApiRoot() + 'auth/verify-email');
53
53
  }
54
54
 
55
55
  async resetPassword(params) {
56
56
  return await this.request('post')
57
57
  .withJson(params)
58
- .fetch(API_ROOT + 'auth/reset-password');
58
+ .fetch(getApiRoot() + 'auth/reset-password');
59
59
  }
60
60
 
61
61
  async logout() {
62
62
  let response = await this.request('post')
63
- .fetch(API_ROOT + 'auth/logout');
63
+ .fetch(getApiRoot() + 'auth/logout');
64
64
  this.$buefy.notification.open({
65
65
  message: 'You have been logged out',
66
66
  type: 'is-info',
@@ -80,17 +80,17 @@ export default class AuthApi {
80
80
  console.log(params);
81
81
  return this.request('post')
82
82
  .withJson(params)
83
- .fetch(API_ROOT + 'auth/change-password');
83
+ .fetch(getApiRoot() + 'auth/change-password');
84
84
  }
85
85
 
86
86
  async listUserSessions() {
87
87
  return this.request('get')
88
- .fetch(API_ROOT + 'auth/sessions')
88
+ .fetch(getApiRoot() + 'auth/sessions')
89
89
  }
90
90
 
91
91
  async deleteUserSession(id) {
92
92
  return this.request('delete')
93
- .fetch(API_ROOT + 'auth/sessions/' + id)
93
+ .fetch(getApiRoot() + 'auth/sessions/' + id)
94
94
  }
95
95
  }
96
96
 
package/src/CrudApi.js CHANGED
@@ -1,9 +1,13 @@
1
- import {FetchBuilder} from './api.js';
1
+ import {FetchBuilder, getApiHost} from './api.js';
2
2
  import {morphToNotification} from "./api";
3
3
 
4
- const API_ROOT = '/oxygen/api/';
4
+ const API_ROOT = 'oxygen/api/';
5
5
 
6
- class CrudApi {
6
+ export const getApiRoot = () => {
7
+ return getApiHost() + API_ROOT;
8
+ }
9
+
10
+ export class CrudApi {
7
11
 
8
12
  constructor($buefy) {
9
13
  this.$buefy = $buefy;
@@ -18,7 +22,7 @@ class CrudApi {
18
22
  }
19
23
 
20
24
  static getResourceRoot() {
21
- return API_ROOT + this.getResourceName();
25
+ return getApiRoot() + this.getResourceName();
22
26
  }
23
27
 
24
28
  static prepareModelForAPI(data) {
@@ -107,6 +111,4 @@ class CrudApi {
107
111
  return this.request('post')
108
112
  .fetch(this.constructor.getResourceRoot() + '/' + id + '/make-head');
109
113
  }
110
- }
111
-
112
- export { CrudApi, API_ROOT };
114
+ }
@@ -1,4 +1,4 @@
1
- import {API_ROOT} from "./CrudApi";
1
+ import {getApiRoot} from "./CrudApi";
2
2
  import {FetchBuilder} from "./api";
3
3
 
4
4
  export const canAccessPrefs = ($buefy, userPermissions, keys) => {
@@ -26,7 +26,7 @@ export default class PreferencesApi {
26
26
 
27
27
  async getValue(key) {
28
28
  return await this.request('get')
29
- .fetch(API_ROOT + 'preferences/' + key);
29
+ .fetch(getApiRoot() + 'preferences/' + key);
30
30
  }
31
31
 
32
32
  async checkValid(key, value) {
@@ -34,7 +34,7 @@ export default class PreferencesApi {
34
34
  .withJson({
35
35
  value: value
36
36
  })
37
- .fetch(API_ROOT + 'preferences/' + key + '/validate');
37
+ .fetch(getApiRoot() + 'preferences/' + key + '/validate');
38
38
  }
39
39
 
40
40
  async setValue(key, value) {
@@ -42,6 +42,6 @@ export default class PreferencesApi {
42
42
  .withJson({
43
43
  value: value
44
44
  })
45
- .fetch(API_ROOT + 'preferences/' + key);
45
+ .fetch(getApiRoot() + 'preferences/' + key);
46
46
  }
47
47
  }
package/src/UsersApi.js CHANGED
@@ -1,4 +1,4 @@
1
- import {API_ROOT, CrudApi} from './CrudApi';
1
+ import {getApiRoot, CrudApi} from './CrudApi';
2
2
 
3
3
  export default class UsersApi extends CrudApi {
4
4
 
@@ -20,12 +20,12 @@ export default class UsersApi extends CrudApi {
20
20
  .withJson({
21
21
  fullName: name
22
22
  })
23
- .fetch(API_ROOT + 'users/' + id + '/fullName');
23
+ .fetch(getApiRoot() + 'users/' + id + '/fullName');
24
24
  }
25
25
 
26
26
  async impersonate(id) {
27
27
  return await this.request('post')
28
- .fetch(API_ROOT + 'users/' + id + '/impersonate');
28
+ .fetch(getApiRoot() + 'users/' + id + '/impersonate');
29
29
  }
30
30
 
31
31
  async forceDelete(id) {
@@ -35,7 +35,7 @@ export default class UsersApi extends CrudApi {
35
35
 
36
36
  async stopImpersonating() {
37
37
  return await this.request('post')
38
- .fetch(API_ROOT + 'users/stop-impersonating');
38
+ .fetch(getApiRoot() + 'users/stop-impersonating');
39
39
  }
40
40
 
41
41
  }
package/src/api.js CHANGED
@@ -1,8 +1,20 @@
1
+ import {getApiRoot} from "./CrudApi";
2
+
3
+ export const getApiHost = () => {
4
+ if (parseInt(window.location.port) >= 3000) {
5
+ // for local development, Laravel should be running on port 80,
6
+ // whereas the frontend (`npm run dev`) is running on a higher port
7
+ return "http://localhost/";
8
+ } else {
9
+ return '/';
10
+ }
11
+ }
12
+
1
13
  var xsrfToken = null;
2
14
 
3
15
  export const initCsrfCookie = async () => {
4
16
  await window.fetch(
5
- '/sanctum/csrf-cookie',
17
+ getApiRoot() + 'csrf-cookie',
6
18
  {
7
19
  credentials: 'same-origin'
8
20
  });
@@ -46,7 +58,7 @@ export class FetchBuilder {
46
58
  }
47
59
 
48
60
  cookies() {
49
- this.credentials = 'same-origin';
61
+ this.credentials = getApiHost() === '/' ? 'same-origin' : 'include';
50
62
  return this;
51
63
  }
52
64
 
@@ -90,7 +102,7 @@ export class FetchBuilder {
90
102
  queue: false
91
103
  });
92
104
  return {};
93
- } else if(response.status === 204) {
105
+ } else if(response.status === 204 || response.status === 404) {
94
106
  // no content, we're okay
95
107
  } else {
96
108
  console.error('Response did not contain valid JSON: ', e);
@@ -158,6 +170,8 @@ const handleAPIError = function(content, $buefy, $router, response) {
158
170
  } else if(response.status === 403 && content.code === 'email_unverified') {
159
171
  $router.push({ path: '/auth/needs-verified-email', query: {redirect: $router.currentRoute.fullPath } });
160
172
  return;
173
+ } else if(response.status === 404) {
174
+ $router.push({ name: 'error404' });
161
175
  } else if(response.status === 429) {
162
176
  $buefy.notification.open({
163
177
  message: 'Too many requests within a short timeframe. Please wait.',
@@ -1,22 +1,23 @@
1
1
  <template>
2
2
  <div style="height: 100%;">
3
- <!-- <transition name="slide-left" mode="out-in">-->
4
3
  <router-view @logout="signOut">
5
4
  <template #main-navigation>
6
- <slot name="app-navigation"></slot>
5
+ <MainMenu :items="mainMenuItems" />
7
6
  </template>
8
7
  </router-view>
9
- <!-- </transition>-->
10
8
  </div>
11
9
  </template>
12
10
 
13
11
  <script>
14
12
  import AuthApi from "../AuthApi";
13
+ import MainMenu from "./MainMenu.vue";
15
14
  export default {
16
15
  name: "App",
16
+ components: {MainMenu},
17
17
  props: {
18
18
  appTitle: { type: String, required: true },
19
19
  defaultRouteTitle: { type: String, required: true },
20
+ mainMenuItems: { type: Object, required: true },
20
21
  impersonating: {
21
22
  type: Boolean,
22
23
  default: false
@@ -84,8 +84,9 @@
84
84
  <script>
85
85
  import {FetchBuilder} from "../api";
86
86
  import Internationalize from "../Internationalize";
87
- import UAParser from 'ua-parser-js';
87
+ import { UAParser } from 'ua-parser-js';
88
88
  import AuthApi from "../AuthApi";
89
+ import {getApiRoot} from "../CrudApi";
89
90
 
90
91
  const IP_INFO_LOADING = 'loading';
91
92
 
@@ -121,7 +122,7 @@ export default {
121
122
  let data = await FetchBuilder
122
123
  .default(this.$buefy, 'post')
123
124
  .withQueryParams({ page: this.paginatedItems.currentPage })
124
- .fetch('/oxygen/api/auth/login-log-entries');
125
+ .fetch(getApiRoot() + 'auth/login-log-entries');
125
126
 
126
127
  this.paginatedItems.items = data.items.map((item) => { return { geolocationInfo: this.ipInfo.get(item.ipAddress), ...item } });
127
128
  this.paginatedItems.totalItems = data.totalItems;
@@ -143,7 +144,7 @@ export default {
143
144
  }
144
145
  this.ipInfo.set(ip, IP_INFO_LOADING);
145
146
  FetchBuilder.default(this.$buefy, 'post')
146
- .fetch('/oxygen/api/auth/ip-location/' + ip, (data) => data)
147
+ .fetch(getApiRoot() + 'auth/ip-location/' + ip, (data) => data)
147
148
  .then((data) => {
148
149
  this.ipInfo.set(ip, data);
149
150
  this.updateInfoForIp(ip);
@@ -31,9 +31,49 @@
31
31
 
32
32
  <script>
33
33
 
34
+ import AceEditor from 'vue2-ace-editor';
35
+ // language extension pre-requisite...
36
+ import 'brace/ext/language_tools';
37
+ import 'brace/mode/html';
38
+ import 'brace/mode/twig';
39
+ import 'brace/snippets/html';
40
+ import 'brace/snippets/twig';
41
+ import 'brace/theme/tomorrow_night_eighties';
42
+ import 'brace/theme/tomorrow_night';
43
+ import 'brace/theme/ambiance';
44
+ import 'brace/theme/chaos';
45
+ import 'brace/theme/clouds_midnight';
46
+ import 'brace/theme/cobalt';
47
+ import 'brace/theme/idle_fingers';
48
+ import 'brace/theme/merbivore';
49
+ import 'brace/theme/merbivore_soft';
50
+ import 'brace/theme/mono_industrial';
51
+ import 'brace/theme/monokai';
52
+ import 'brace/theme/pastel_on_dark';
53
+ import 'brace/theme/solarized_dark';
54
+ import 'brace/theme/terminal';
55
+ import 'brace/theme/tomorrow_night';
56
+ import 'brace/theme/tomorrow_night_blue';
57
+ import 'brace/theme/tomorrow_night_bright';
58
+ import 'brace/theme/tomorrow_night_eighties';
59
+ import 'brace/theme/twilight';
60
+ import 'brace/theme/vibrant_ink';
61
+ import 'brace/theme/chrome';
62
+ import 'brace/theme/clouds';
63
+ import 'brace/theme/crimson_editor';
64
+ import 'brace/theme/dawn';
65
+ import 'brace/theme/dreamweaver';
66
+ import 'brace/theme/eclipse';
67
+ import 'brace/theme/github';
68
+ import 'brace/theme/kr_theme';
69
+ import 'brace/theme/solarized_light';
70
+ import 'brace/theme/textmate';
71
+ import 'brace/theme/tomorrow';
72
+ import 'brace/theme/xcode';
73
+
34
74
  export default {
35
75
  name: "CodeEditor",
36
- components: { AceEditor: require('vue2-ace-editor') },
76
+ components: { AceEditor },
37
77
  props: {
38
78
  value: { type: String, default: null },
39
79
  height: { type: String, required: true },
@@ -65,11 +105,6 @@ export default {
65
105
  },
66
106
  methods: {
67
107
  editorInit() {
68
- require('brace/ext/language_tools') //language extension prerequsite...
69
- require(`brace/mode/${this.lang}`);
70
- require(`brace/theme/${this.theme}`);
71
- require(`brace/snippets/${this.lang}`);
72
-
73
108
  // ignore first missing DOCTYPE warning
74
109
  let session = this.$refs.ace.editor.getSession();
75
110
  session.on("changeAnnotation", () => {
@@ -14,7 +14,7 @@
14
14
 
15
15
  <script>
16
16
  import {FetchBuilder} from "../api";
17
- import {API_ROOT} from "../CrudApi";
17
+ import {getApiRoot} from "../CrudApi";
18
18
  import download from "downloadjs";
19
19
 
20
20
  export default {
@@ -29,7 +29,7 @@ export default {
29
29
  this.exporting = true;
30
30
  let response = await (new FetchBuilder(this.$buefy, 'post'))
31
31
  .cookies()
32
- .fetchRaw(API_ROOT + "import-export/export");
32
+ .fetchRaw(getApiRoot() + "import-export/export");
33
33
  this.$buefy.notification.open({ message: 'Export successful', type: 'is-success', queue: false });
34
34
  let blob = await response.blob();
35
35
  download(blob, 'database ' + (new Date()).toLocaleString() + '.zip');
@@ -11,7 +11,7 @@
11
11
  <script>
12
12
  import MediaInsertModal from "./media/MediaInsertModal.vue";
13
13
 
14
- import { morphToNotification } from "../api";
14
+ import {getApiHost, morphToNotification} from "../api";
15
15
  import MediaApi from "../MediaApi";
16
16
 
17
17
  // from https://gist.github.com/hdodov/a87c097216718655ead6cf2969b0dcfa
@@ -134,7 +134,7 @@ export default {
134
134
  }
135
135
  },
136
136
  vuePathToURL(path) {
137
- return this.legacyPrefix + path;
137
+ return getApiHost() + this.legacyPrefix.replace(/^\//, '') + path;
138
138
  },
139
139
  // We detect when the iframe url changes, and update our window accordingly...
140
140
  onNavigated(newURL) {
@@ -162,7 +162,7 @@ export default {
162
162
 
163
163
  if(path !== this.currentPath) {
164
164
  let { loadInside, location } = this.fullURLToVuePath(path);
165
- console.log('[LegacyPage] ', loadInside, location);
165
+ console.log('[LegacyPage] ', 'loadInside:', loadInside, 'location:', location);
166
166
  if(loadInside === 'iframe') {
167
167
  window.history.pushState({}, "", location);
168
168
  } else if(loadInside === 'vue') {
@@ -182,9 +182,9 @@ export default {
182
182
  },
183
183
  loadPath(routePath) {
184
184
  let path = this.vuePathToURL(routePath);
185
- if(path === '/oxygen/view/auth/login') {
185
+ if(path.endsWith('/oxygen/view/auth/login')) {
186
186
  console.log('[LegacyPage] Need to login again, redirecting...');
187
- window.location.replace('/oxygen/view/auth/login');
187
+ window.location.replace('/oxygen/auth/login');
188
188
  }
189
189
 
190
190
  console.log('[LegacyPage] Loading', path);
@@ -60,7 +60,10 @@ export default {
60
60
  return Object.fromEntries(Object.entries(items).filter(([, item]) => this.userPermissions.has(item.permission)));
61
61
  },
62
62
  groupsWithPermission(groups) {
63
- return Object.fromEntries(Object.entries(groups).filter(([, group]) => this.userPermissions.hasOneOf(this.getPermissionsForGroup(group))));
63
+ return Object.fromEntries(Object.entries(groups)
64
+ .filter(([, group]) => this.userPermissions.hasOneOf(this.getPermissionsForGroup(group)))
65
+ .sort(([,groupA], [,groupB]) => (groupB.order ?? 0) - (groupA.order ?? 0))
66
+ );
64
67
  }
65
68
  }
66
69
  }
@@ -49,35 +49,35 @@ export default {
49
49
  }
50
50
 
51
51
  .login-theme-autumn {
52
- background-image: url('/vendor/oxygen/ui-theme/img/bg/autumn-min.jpg');
52
+ background-image: url('../../../assets/bg/autumn-min.jpg');
53
53
  }
54
54
 
55
55
  .login-theme-city {
56
- background-image: url('/vendor/oxygen/ui-theme/img/bg/city-min.jpg');
56
+ background-image: url('../../../assets/bg/city-min.jpg');
57
57
  }
58
58
 
59
59
  .login-theme-clouds {
60
- background-image: url('/vendor/oxygen/ui-theme/img/bg/clouds-min.jpg');
60
+ background-image: url('../../../assets/bg/clouds-min.jpg');
61
61
  }
62
62
 
63
63
  .login-theme-coast {
64
- background-image: url('/vendor/oxygen/ui-theme/img/bg/coast-min.jpg');
64
+ background-image: url('../../../assets/bg/coast-min.jpg');
65
65
  }
66
66
 
67
67
  .login-theme-speckles {
68
- background-image: url('/vendor/oxygen/ui-theme/img/bg/speckles-min.jpg');
68
+ background-image: url('../../../assets/bg/speckles-min.jpg');
69
69
  }
70
70
 
71
71
  .login-theme-trees {
72
- background-image: url('/vendor/oxygen/ui-theme/img/bg/trees-min.jpg');
72
+ background-image: url('../../../assets/bg/trees-min.jpg');
73
73
  }
74
74
 
75
75
  .login-theme-waves {
76
- background-image: url('/vendor/oxygen/ui-theme/img/bg/waves.jpg');
76
+ background-image: url('../../../assets/bg/waves.jpg');
77
77
  }
78
78
 
79
79
  .login-theme-yosemite {
80
- background-image: url('/vendor/oxygen/ui-theme/img/bg/yosemite-falls-min.jpg');
80
+ background-image: url('../../../assets/bg/yosemite-falls-min.jpg');
81
81
  }
82
82
 
83
83
  .login-theme-white {
@@ -230,7 +230,7 @@ export default {
230
230
  </script>
231
231
 
232
232
  <style scoped lang="scss">
233
- @import '../../styles/variables';
233
+ @import '../../styles/variables.scss';
234
234
  @import '../util.css';
235
235
 
236
236
  .box {
@@ -18,7 +18,7 @@
18
18
  </template>
19
19
 
20
20
  <script>
21
- import {API_ROOT} from "../../CrudApi";
21
+ import {getApiRoot} from "../../CrudApi";
22
22
  import {FetchBuilder, morphToNotification} from "../../api";
23
23
 
24
24
  export default {
@@ -35,7 +35,7 @@ export default {
35
35
  this.requestInFlight = true;
36
36
  this.hasServerLog = false;
37
37
  this.serverLog = '[generating...]';
38
- let result = await (FetchBuilder.default(this.$buefy, 'post')).fetch(API_ROOT + 'media/make-responsive');
38
+ let result = await (FetchBuilder.default(this.$buefy, 'post')).fetch(getApiRoot() + 'media/make-responsive');
39
39
  this.$buefy.toast.open(morphToNotification(result));
40
40
  this.requestInFlight = false;
41
41
  this.serverLog = result.log;
@@ -1,4 +1,4 @@
1
- @import '../../styles/variables';
1
+ @import '../../styles/variables.scss';
2
2
 
3
3
  .card-image {
4
4
  min-height: 6rem;
@@ -4,7 +4,7 @@
4
4
  <b-tab-item v-if="slotProps.canAccessPrefs(['appearance.themes', 'appearance.pages', 'appearance.events'].concat(getExtraPrefsPermissions('appearance')))" label="Website Theme">
5
5
  <PreferencesThemeChooser @theme-changed="onThemeChanged" />
6
6
  <PreferencesPageTemplates :current-theme="currentTheme" />
7
- <PreferencesEventTemplates :current-theme="currentTheme" />
7
+
8
8
  <PreferencesSiteAppearance :current-theme="currentTheme" />
9
9
  <component :is="pref.component" v-for="pref in getExtraPrefs('appearance')" :key="pref.key" :current-theme="currentTheme" />
10
10
  </b-tab-item>
@@ -46,5 +46,5 @@ export default {
46
46
  </script>
47
47
 
48
48
  <style scoped>
49
-
49
+ @import '../util.css';
50
50
  </style>
@@ -4,6 +4,7 @@
4
4
  <PreferencesField data-key="appearance.themes::theme" label="">
5
5
  <template #default="slotProps">
6
6
  <b-table
7
+ v-if="Object.values(slotProps.options).length > 0"
7
8
  :data="Object.values(slotProps.options)"
8
9
  :striped="false">
9
10
  <b-table-column v-slot="props" label="Key">
@@ -26,6 +27,7 @@
26
27
  <b-button v-else type="is-success" disabled>Theme is already active</b-button>
27
28
  </b-table-column>
28
29
  </b-table>
30
+ <p v-else class="has-text-centered"><em>No themes loaded.</em></p>
29
31
  </template>
30
32
  </PreferencesField>
31
33
  </ShowIfPermitted>
package/src/main.js CHANGED
@@ -12,7 +12,7 @@ import { AuthRoutes, makeAuthenticatedRoute } from "./routes";
12
12
  import createStore from "./store/index";
13
13
  import { checkAuthenticated } from "./AuthApi";
14
14
  import Error404 from "./components/Error404.vue";
15
- import MainMenu from "./components/MainMenu.vue";
15
+ import Preferences from "./components/preferences/Preferences.vue";
16
16
 
17
17
  /**
18
18
  * Creates the Vue.js Oxygen application, allowing for a few points of customization (i.e.: adding modules)
@@ -28,8 +28,11 @@ export default class OxygenUI {
28
28
  this.authenticatedRoutes = []
29
29
  this.unauthenticatedRoutes = []
30
30
  this.mainMenuItems = {}
31
- this.rootComponents = { App, MainMenu }
32
31
  this.beforeMountHooks = []
32
+ this.extraPrefs = {
33
+ 'appearance': [],
34
+ 'external': []
35
+ }
33
36
  }
34
37
 
35
38
  addAuthenticatedRoutes(routes) {
@@ -82,17 +85,32 @@ export default class OxygenUI {
82
85
 
83
86
  const store = createStore(this.Vue);
84
87
 
88
+ this.authenticatedRoutes.push({
89
+ path: '/preferences',
90
+ component: Preferences,
91
+ props: {
92
+ extraPrefs: this.extraPrefs
93
+ },
94
+ meta: { title: 'System Preferences' }
95
+ });
96
+
85
97
  const routes = AuthRoutes
86
98
  .concat([
87
99
  makeAuthenticatedRoute(this.authenticatedRoutes)
88
100
  ])
89
101
  .concat(this.unauthenticatedRoutes)
90
- .concat([{
91
- path: '*',
92
- name: 'error404',
93
- component: Error404,
94
- meta: {title: 'Not found'}
95
- }]);
102
+ .concat([
103
+ {
104
+ path: '/not-found',
105
+ name: 'error404',
106
+ component: Error404,
107
+ meta: {title: 'Not found'}
108
+ },
109
+ {
110
+ path: '/*',
111
+ redirect: { name: 'error404' }
112
+ }
113
+ ]);
96
114
 
97
115
  const router = new Router({
98
116
  routes: routes,
@@ -104,10 +122,13 @@ export default class OxygenUI {
104
122
 
105
123
  this.app = new this.Vue({
106
124
  router: router,
107
- data: {
108
- mainMenuItems: this.mainMenuItems
109
- },
110
- components: this.rootComponents,
125
+ render: (h) => h(App, {
126
+ props: {
127
+ appTitle: document.title,
128
+ defaultRouteTitle: 'Administration Panel',
129
+ mainMenuItems: this.mainMenuItems
130
+ }
131
+ }),
111
132
  store
112
133
  });
113
134
 
@@ -0,0 +1,35 @@
1
+ import LegacyPage from "../components/LegacyPage.vue";
2
+ import { WEB_CONTENT } from "../main.js";
3
+ import PreferencesEventTemplates from "../components/preferences/PreferencesEventTemplates.vue";
4
+
5
+ export default function(ui) {
6
+ ui.addMainMenuGroup(WEB_CONTENT, {
7
+ name: 'Events',
8
+ icon: 'calendar-alt',
9
+ listAction: '/upcoming-events',
10
+ listPermission: 'upcomingEvents.getList',
11
+ addIcon: 'calendar-plus',
12
+ addPermission: 'upcomingEvents.postCreate',
13
+ addAction: '/upcoming-events/create',
14
+ items: {
15
+ }
16
+ });
17
+ ui.extraPrefs['appearance'].push({
18
+ key: 'appearance.events',
19
+ component: PreferencesEventTemplates
20
+ });
21
+ ui.addAuthenticatedRoutes([
22
+ {
23
+ // will match everything, try to render a legacy Oxygen page...
24
+ path: 'upcoming-events/:subpath*',
25
+ component: LegacyPage,
26
+ props: (route) => {
27
+ return {
28
+ fullPath: route.fullPath,
29
+ legacyPrefix: '/oxygen/view',
30
+ adminPrefix: '/oxygen'
31
+ }
32
+ }
33
+ }
34
+ ]);
35
+ }
@@ -24,21 +24,10 @@ export default function(ui) {
24
24
  items: {
25
25
  }
26
26
  });
27
- ui.addMainMenuGroup(WEB_CONTENT, {
28
- name: 'Events',
29
- icon: 'calendar-alt',
30
- listAction: '/upcoming-events',
31
- listPermission: 'upcomingEvents.getList',
32
- addIcon: 'calendar-plus',
33
- addPermission: 'upcomingEvents.postCreate',
34
- addAction: '/upcoming-events/create',
35
- items: {
36
- }
37
- });
38
27
  ui.addAuthenticatedRoutes([
39
28
  {
40
29
  // will match everything, try to render a legacy Oxygen page...
41
- path: '(pages|partials|upcoming-events)/:subpath*',
30
+ path: '(pages|partials)/:subpath*',
42
31
  component: LegacyPage,
43
32
  props: (route) => {
44
33
  return {
@@ -1,22 +1,8 @@
1
1
  // Set your colors
2
2
  $primary: #0053b1;
3
- //$twitter: #4099FF;
4
3
 
5
- @import "~bulma/sass/utilities/_all";
4
+ @import "~bulma/sass/utilities/_all.sass";
6
5
 
7
6
  $menu-item-color: darken($grey, 5%);
8
7
  $menu-item-active-background-color: transparent;
9
8
  $menu-item-active-color: lighten($grey, 4%);
10
-
11
- // $menu-item-hover-background-color: rgba(255, 255, 255, 0.1);
12
- // $menu-item-hover-color: $white-bis;
13
- //$navbar-item-active-color: #fff;
14
- //$navbar-item-hover-background-color: #f3f3f9;
15
- //
16
- //// Links
17
- //$link: $primary;
18
- //$link-focus-border: $primary;
19
-
20
- // Import Bulma's core
21
- // TODO: wtf is going on here??
22
- @import "~bulma/sass/utilities/_all";
@@ -1,7 +1,7 @@
1
1
  @import "_variables.scss";
2
2
 
3
3
  // Import Bulma and Buefy styles
4
- @import "~bulma";
4
+ @import "~bulma/bulma.sass";
5
5
  @import "~buefy/src/scss/buefy";
6
6
 
7
7
  .navbar {