@appscode/design-system 1.0.43-alpha.98 → 1.1.0

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 (102) hide show
  1. package/base/utilities/_all.scss +7 -0
  2. package/base/utilities/_customize-bulma.scss +191 -0
  3. package/base/utilities/_default.scss +58 -124
  4. package/base/utilities/_derived-variables.scss +6 -0
  5. package/base/utilities/_grid.scss +29 -0
  6. package/base/utilities/_initial-variables.scss +13 -9
  7. package/base/utilities/_typography.scss +6 -12
  8. package/base/utilities/dark-theme.scss +1 -0
  9. package/components/_ac-accordion.scss +14 -5
  10. package/components/_ac-alert-box.scss +32 -6
  11. package/components/_ac-card.scss +17 -5
  12. package/components/_ac-drag.scss +2 -0
  13. package/components/_ac-input.scss +19 -11
  14. package/components/_ac-modal.scss +1 -1
  15. package/components/_ac-multi-select.scss +60 -4
  16. package/components/_ac-options.scss +1 -0
  17. package/components/_ac-report.scss +53 -0
  18. package/components/_ac-table.scss +60 -2
  19. package/components/_ac-tabs.scss +16 -2
  20. package/components/_ac-tags.scss +85 -0
  21. package/components/_ac-terminal.scss +1 -4
  22. package/components/_all.scss +28 -0
  23. package/components/_buttons.scss +14 -33
  24. package/components/_dashboard-header.scss +32 -0
  25. package/components/_left-sidebar-menu.scss +9 -9
  26. package/components/_navbar.scss +89 -4
  27. package/components/_preview-modal.scss +14 -1
  28. package/components/_transitions.scss +261 -0
  29. package/components/_wizard.scss +1 -0
  30. package/components/bbum/_all.scss +9 -0
  31. package/components/bbum/_single-post-preview.scss +1 -1
  32. package/components/ui-builder/_ui-builder.scss +58 -0
  33. package/components/ui-builder/_vue-open-api.scss +6 -0
  34. package/layouts/_all.scss +2 -0
  35. package/layouts/_code-preview.scss +5 -2
  36. package/main.scss +5 -56
  37. package/package.json +4 -2
  38. package/plugins/caching.ts +243 -0
  39. package/plugins/time-convert.js +49 -0
  40. package/plugins/vue-toaster.js +3 -0
  41. package/vue-components/v2/banner/Banner.vue +2 -2
  42. package/vue-components/v2/breadcrumbs/Breadcrumb.vue +97 -0
  43. package/vue-components/v2/button/Button.vue +5 -0
  44. package/vue-components/v2/button/DownloadBtn.vue +45 -0
  45. package/vue-components/v2/card/Card.vue +1 -0
  46. package/vue-components/v2/content/ContentTable.vue +12 -7
  47. package/vue-components/v2/editor/Editor.vue +37 -24
  48. package/vue-components/v2/editor/FilteredFileEditor.vue +189 -0
  49. package/vue-components/v2/editor/MonacoEditor.vue +125 -0
  50. package/vue-components/v2/editor/ResourceKeyValueEditor.vue +209 -0
  51. package/vue-components/v2/form-fields/Input.vue +1 -1
  52. package/vue-components/v2/loaders/ResourceLoader.vue +101 -0
  53. package/vue-components/v2/loaders/SidebarLoader.vue +43 -0
  54. package/vue-components/v2/modal/Modal.vue +30 -5
  55. package/vue-components/v2/modals/DeleteConfirmationModal.vue +79 -0
  56. package/vue-components/v2/modals/JsonShowModal.vue +12 -2
  57. package/vue-components/v2/navbar/User.vue +229 -17
  58. package/vue-components/v2/notification/Notification.vue +101 -0
  59. package/vue-components/v2/notification/NotificationItem.vue +44 -0
  60. package/vue-components/v2/pagination/Pagination.vue +16 -3
  61. package/vue-components/v2/preloader/Preloader.vue +1 -1
  62. package/vue-components/v2/sidebar/SidebarItemWithDropDown.vue +19 -20
  63. package/vue-components/v2/tab/TabItem.vue +1 -1
  64. package/vue-components/v2/table/Table.vue +49 -8
  65. package/vue-components/v2/table/TableRow.vue +12 -2
  66. package/vue-components/v2/table/table-cell/CellValue.vue +29 -9
  67. package/vue-components/v2/table/table-cell/GenericCell.vue +56 -0
  68. package/vue-components/v2/table/table-cell/ObjectCell.vue +4 -1
  69. package/vue-components/v3/button/Button.vue +6 -1
  70. package/vue-components/v3/content/ContentHeader.vue +2 -1
  71. package/vue-components/v3/content/ContentTable.vue +20 -2
  72. package/vue-components/v3/editor/Editor.vue +36 -33
  73. package/vue-components/v3/editor/FilteredFileEditor.vue +186 -0
  74. package/vue-components/v3/editor/MonacoEditor.vue +131 -0
  75. package/vue-components/v3/editor/ResourceKeyValueEditor.vue +125 -0
  76. package/vue-components/v3/form/Form.vue +63 -0
  77. package/vue-components/v3/form-fields/Input.vue +11 -10
  78. package/vue-components/v3/header/HeaderItem.vue +5 -0
  79. package/vue-components/v3/header/HeaderItems.vue +5 -0
  80. package/vue-components/v3/loaders/ResourceLoader.vue +83 -0
  81. package/vue-components/v3/loaders/SidebarLoader.vue +34 -0
  82. package/vue-components/v3/long-running-tasks/LongRunningTaskItem.vue +92 -0
  83. package/vue-components/v3/modal/Modal.vue +30 -7
  84. package/vue-components/v3/modals/DeleteConfirmationModal.vue +85 -0
  85. package/vue-components/v3/modals/JsonShowModal.vue +25 -16
  86. package/vue-components/v3/modals/LongRunningTasksModal.vue +400 -0
  87. package/vue-components/v3/navbar/ThemeMode.vue +41 -49
  88. package/vue-components/v3/navbar/User.vue +242 -18
  89. package/vue-components/v3/notification/AlertBox.vue +61 -0
  90. package/vue-components/v3/notification/Notification.vue +98 -0
  91. package/vue-components/v3/notification/NotificationItem.vue +52 -0
  92. package/vue-components/v3/pagination/Pagination.vue +16 -3
  93. package/vue-components/v3/sidebar/SidebarItemWithDropDown.vue +120 -0
  94. package/vue-components/v3/tab/TabItem.vue +1 -1
  95. package/vue-components/v3/table/MultiInfoTable.vue +143 -0
  96. package/vue-components/v3/table/Table.vue +52 -13
  97. package/vue-components/v3/table/TableContainer.vue +34 -0
  98. package/vue-components/v3/table/TableRow.vue +93 -6
  99. package/vue-components/v3/table/table-cell/CellValue.vue +23 -7
  100. package/vue-components/v3/table/table-cell/GenericCell.vue +75 -0
  101. package/vue-components/v3/table/table-cell/ObjectCell.vue +7 -2
  102. package/vue-components/v3/terminal/LongRunningTaskTerminal.vue +148 -0
@@ -1,27 +1,24 @@
1
1
  <template>
2
- <div>
3
- <button
4
- v-if="themeMode"
5
- class="button ac-nav-button"
6
- @click="toggleTheme"
7
- :title="themeModes[themeMode].displayName"
8
- >
9
- <i :class="`fa ${themeModes[themeMode].iconClass} width-15`" />
10
- </button>
11
- <div class="ac-menu-content theme-choice">
12
- <ul class="is-flex is-flex-direction-row is-justify-content-space-around">
13
- <li
14
- v-for="theme of Object.keys(themeModes)"
15
- :title="themeModes[theme].displayName"
16
- @click="themeMode = theme"
17
- :class="{'is-active': themeMode === theme}"
18
- :key="theme"
19
- >
20
- <i :class="['fa', themeModes[theme].iconClass]" />
21
- </li>
22
- </ul>
23
- </div>
24
- </div>
2
+ <li class="mt-10 b-t-1 pt-10">
3
+ <ul class="ac-vscrollbar">
4
+ <li>
5
+ <div class="ac-menu-contentt theme-choicee">
6
+ <ul class="is-flex is-flex-direction-row is-justify-content-center">
7
+ <li
8
+ v-for="theme of Object.keys(themeModes)"
9
+ :title="themeModes[theme].displayName"
10
+ @click="themeMode = theme"
11
+ class="p-10 pl-30 pr-30 is-flex-grow-1 has-text-centered"
12
+ :class="{ 'is-active': themeMode === theme }"
13
+ :key="theme"
14
+ >
15
+ <i :class="['fa', themeModes[theme].iconClass]" />
16
+ </li>
17
+ </ul>
18
+ </div>
19
+ </li>
20
+ </ul>
21
+ </li>
25
22
  </template>
26
23
  <script>
27
24
  import { defineComponent } from "vue";
@@ -31,10 +28,6 @@ export default defineComponent({
31
28
  return {
32
29
  themeMode: "",
33
30
  themeModes: {
34
- system: {
35
- displayName: "System Theme",
36
- iconClass: "fa-desktop",
37
- },
38
31
  light: {
39
32
  displayName: "Light Theme",
40
33
  iconClass: "fa-sun-o",
@@ -42,19 +35,23 @@ export default defineComponent({
42
35
  dark: {
43
36
  displayName: "Dark Theme",
44
37
  iconClass: "fa-moon-o",
45
- }
46
- }
38
+ },
39
+ system: {
40
+ displayName: "System Theme",
41
+ iconClass: "fa-desktop",
42
+ },
43
+ },
47
44
  };
48
45
  },
49
46
 
50
- emits: ['set:theme'],
47
+ emits: ["set:theme"],
51
48
 
52
49
  mounted() {
53
50
  // get theme mode from localStorage or set default one
54
51
  this.themeMode = localStorage.getItem("themeMode") || "light";
55
52
  },
56
53
 
57
- destroyed() {
54
+ unmounted() {
58
55
  this.removeColorSchemeEventListener();
59
56
  },
60
57
 
@@ -62,19 +59,16 @@ export default defineComponent({
62
59
  themeMode: {
63
60
  handler(n) {
64
61
  this.onThemeModeChange(n);
65
- }
66
- }
62
+ },
63
+ },
67
64
  },
68
65
 
69
66
  methods: {
70
67
  // handle theme mode button click
71
68
  toggleTheme() {
72
- if(this.themeMode === "light")
73
- this.themeMode = "dark";
74
- else if(this.themeMode === "dark")
75
- this.themeMode = "system";
76
- else if(this.themeMode === "system")
77
- this.themeMode = "light";
69
+ if (this.themeMode === "light") this.themeMode = "dark";
70
+ else if (this.themeMode === "dark") this.themeMode = "system";
71
+ else if (this.themeMode === "system") this.themeMode = "light";
78
72
  },
79
73
 
80
74
  // triggered when theme mode is updated
@@ -82,8 +76,10 @@ export default defineComponent({
82
76
  localStorage.setItem("themeMode", n);
83
77
 
84
78
  let theme = n;
85
- if(n === "system") {
86
- const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
79
+ if (n === "system") {
80
+ const isDarkMode =
81
+ window.matchMedia &&
82
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
87
83
  this.addColorSchemeEventListener();
88
84
  theme = isDarkMode ? "dark" : "light";
89
85
  } else {
@@ -95,7 +91,7 @@ export default defineComponent({
95
91
 
96
92
  // add proper css class to update the ui theme
97
93
  handleDarkThemeClass(currentTheme) {
98
- if(currentTheme === "light") {
94
+ if (currentTheme === "light") {
99
95
  document.documentElement.classList.remove("is-dark-theme");
100
96
  } else {
101
97
  document.documentElement.classList.add("is-dark-theme");
@@ -106,23 +102,19 @@ export default defineComponent({
106
102
  addColorSchemeEventListener() {
107
103
  window
108
104
  .matchMedia("(prefers-color-scheme: dark)")
109
- .addEventListener(
110
- "change", this.handleSystemThemeChange
111
- );
105
+ .addEventListener("change", this.handleSystemThemeChange);
112
106
  },
113
107
 
114
108
  // remove system theme listener event
115
109
  removeColorSchemeEventListener() {
116
110
  window
117
111
  .matchMedia("(prefers-color-scheme: dark)")
118
- .removeEventListener(
119
- "change", this.handleSystemThemeChange
120
- );
112
+ .removeEventListener("change", this.handleSystemThemeChange);
121
113
  },
122
114
 
123
115
  handleSystemThemeChange() {
124
116
  this.onThemeModeChange(this.themeMode);
125
117
  },
126
- }
118
+ },
127
119
  });
128
120
  </script>
@@ -7,31 +7,158 @@
7
7
  <img :src="user.avatar_url" alt="User Photo" />
8
8
  </div>
9
9
  </button>
10
- <navbar-item-content>
11
- <div v-if="user.username" class="user-profile-wrapper">
10
+ <navbar-item-content class="navbar-dropdown-wrapper">
11
+ <div
12
+ v-if="user.username"
13
+ class="user-profile-wrapper"
14
+ @mouseleave="onMouseLeave()"
15
+ >
12
16
  <div class="profile-area">
13
17
  <div class="profile-photo">
14
- <img :src="user.avatar_url" alt="User Photo" />
18
+ <img
19
+ :src="user.avatar_url"
20
+ alt="User Photo"
21
+ class="width-50 height-50"
22
+ />
15
23
  <button class="camera-icon"></button>
16
24
  </div>
17
- <div class="profile-info">
18
- <p>{{ user.username.toUpperCase() }}</p>
25
+ <div class="profile-info" style="width: calc(100% - 60px)">
26
+ <a
27
+ :href="`${serverDomain}/${user.username}`"
28
+ :title="user.username.toUpperCase()"
29
+ data-testid="user-profile-link"
30
+ class="line-break-anywhere is-ellipsis-1"
31
+ >{{ user.username.toUpperCase() }}</a
32
+ >
19
33
  <a :href="`mailto:${user.email}`"> {{ user.email }}</a>
20
34
  </div>
21
35
  </div>
22
- <ul>
23
- <li>
24
- <a :href="`${serverDomain}/user/settings/`">Settings</a>
36
+ <transition-group name="list" tag="ul">
37
+ <li key="settings">
38
+ <a
39
+ data-testid="user-settings-link"
40
+ :href="`${serverDomain}/user/settings/`"
41
+ >Settings</a
42
+ >
25
43
  </li>
26
- <template v-if="user.is_admin">
27
- <li>
28
- <a :href="`${serverDomain}/admin`"> Site Administration </a>
29
- </li>
30
- </template>
31
- <li>
32
- <a :href="`${serverDomain}/user/logout`"> Sign out </a>
44
+ <li v-if="user.is_admin" key="site-admin">
45
+ <a data-testid="site-admin-link" :href="`${accountsDomain}/admin`"
46
+ >Site Administration</a
47
+ >
33
48
  </li>
34
- </ul>
49
+ <li
50
+ v-if="showAccountSwitcher"
51
+ :class="`is-${dropDownStatus}`"
52
+ key="switcher"
53
+ >
54
+ <a
55
+ class="
56
+ ac-dropdown-button
57
+ is-fullwidth
58
+ is-flex
59
+ is-justify-content-space-between
60
+ is-align-items-center
61
+ "
62
+ @click="toggleList()"
63
+ >
64
+ <span>Switch Account</span>
65
+ <span
66
+ ><i
67
+ :class="`fa fa-angle-${
68
+ dropDownStatus === 'open' ? 'up' : 'down'
69
+ }`"
70
+ ></i
71
+ ></span>
72
+ </a>
73
+ <transition-group
74
+ name="list"
75
+ tag="ul"
76
+ class="ac-vscrollbar"
77
+ ref="dropdownItems"
78
+ :style="{ maxHeight: dropDownSectionHeight }"
79
+ >
80
+ <li
81
+ v-for="(org, idx) in formattedOrganizations"
82
+ :key="org.username"
83
+ >
84
+ <a
85
+ class="is-flex is-align-items-center"
86
+ @click="onOrganizationClick(org.username)"
87
+ >
88
+ <div class="width-30 height-30 image">
89
+ <img
90
+ :src="org.avatar_url"
91
+ class="ac-user-profile is-rounded"
92
+ alt="icon"
93
+ />
94
+ </div>
95
+ <div
96
+ class="
97
+ is-flex
98
+ is-align-items-center
99
+ is-justify-content-space-between
100
+ is-fullwidth
101
+ ml-10
102
+ "
103
+ >
104
+ <div class="org-info">
105
+ <strong
106
+ :title="org.username"
107
+ class="line-break-anywhere is-ellipsis-1"
108
+ >{{ org.username }}</strong
109
+ >
110
+ <p>
111
+ {{
112
+ org.isPersonalAccount
113
+ ? "Personal Account"
114
+ : "Organization"
115
+ }}
116
+ </p>
117
+ </div>
118
+ <span
119
+ v-if="idx === 0"
120
+ class="
121
+ material-icons-outlined
122
+ font-size-18
123
+ ml-10
124
+ is-pulled-right
125
+ "
126
+ >
127
+ check
128
+ </span>
129
+ </div>
130
+ </a>
131
+ </li>
132
+ </transition-group>
133
+ </li>
134
+ <li key="dashboard">
135
+ <nuxt-link
136
+ v-if="isPlatformDomain"
137
+ to="/dashboard"
138
+ data-testid="user-dashboard-link"
139
+ >
140
+ Dashboard
141
+ </nuxt-link>
142
+ <a
143
+ v-else
144
+ :href="`${serverDomain}/dashboard`"
145
+ data-testid="user-dashboard-link"
146
+ >
147
+ Dashboard
148
+ </a>
149
+ </li>
150
+ <li key="signout" @click="$emit('on-logout')">
151
+ <a
152
+ data-testid="user-logout-link"
153
+ :href="`${accountsDomain}/user/logout`"
154
+ >
155
+ Sign out
156
+ </a>
157
+ </li>
158
+ <li key="theme" v-if="showThemeMode">
159
+ <theme-mode @set:theme="setTheme" />
160
+ </li>
161
+ </transition-group>
35
162
  </div>
36
163
  </navbar-item-content>
37
164
  </navbar-item>
@@ -42,6 +169,7 @@ import { defineComponent, defineAsyncComponent } from "vue";
42
169
 
43
170
  export default defineComponent({
44
171
  props: {
172
+ // active user info
45
173
  user: {
46
174
  type: Object,
47
175
  default: () => ({}),
@@ -50,15 +178,111 @@ export default defineComponent({
50
178
  type: String,
51
179
  default: "",
52
180
  },
181
+ accountsDomain: {
182
+ type: String,
183
+ default: "",
184
+ },
185
+ showAccountSwitcher: {
186
+ type: Boolean,
187
+ default: false,
188
+ },
189
+ // all available organization list including personal account
190
+ organizations: {
191
+ type: Array,
192
+ default: () => [],
193
+ },
194
+ showThemeMode: {
195
+ type: Boolean,
196
+ default: false,
197
+ },
198
+ isPlatformDomain: {
199
+ type: Boolean,
200
+ default: false,
201
+ },
53
202
  },
203
+ emits: ["set:theme", "on-logout"],
54
204
 
55
205
  components: {
56
206
  NavbarItem: defineAsyncComponent(() =>
57
207
  import("../../v2/navbar/NavbarItem.vue").then((module) => module.default)
58
208
  ),
59
209
  NavbarItemContent: defineAsyncComponent(() =>
60
- import("../../v2/navbar/NavbarItemContent.vue").then((module) => module.default)
210
+ import("../../v2/navbar/NavbarItemContent.vue").then(
211
+ (module) => module.default
212
+ )
61
213
  ),
214
+ ThemeMode: defineAsyncComponent(() =>
215
+ import("../../v3/navbar/ThemeMode.vue").then((module) => module.default)
216
+ ),
217
+ },
218
+
219
+ computed: {
220
+ formattedOrganizations() {
221
+ let activeUser;
222
+ const filteredList = this.organizations.filter((item) => {
223
+ if (item.username === this.user.username) {
224
+ activeUser = item;
225
+ } else {
226
+ return true;
227
+ }
228
+ return false;
229
+ });
230
+
231
+ filteredList.unshift(activeUser);
232
+
233
+ return filteredList || [];
234
+ },
235
+ },
236
+
237
+ data() {
238
+ return {
239
+ dropDownStatus: "close",
240
+ dropDownSectionHeight: null,
241
+ };
242
+ },
243
+
244
+ methods: {
245
+ toggleList() {
246
+ this.dropDownStatus = this.dropDownStatus === "open" ? "close" : "open";
247
+ this.$nextTick(() => {
248
+ this.$refs["dropdownItems"].$el.scrollTo(0, 0);
249
+ });
250
+ },
251
+ onOrganizationClick(orgName) {
252
+ this.$refs["dropdownItems"].$el.scrollTo(0, 0);
253
+ this.$emit("activeorg$set", orgName);
254
+ },
255
+ onMouseLeave() {
256
+ this.dropDownStatus = "close";
257
+ },
258
+ setTheme(val) {
259
+ this.$emit("set:theme", val);
260
+ },
261
+ },
262
+
263
+ watch: {
264
+ dropDownStatus: {
265
+ immediate: true,
266
+ handler(n) {
267
+ if (n === "open") {
268
+ this.$nextTick(() => {
269
+ const dropDownUl = this.$refs["dropdownItems"];
270
+ if (dropDownUl)
271
+ this.dropDownSectionHeight = `${dropDownUl.scrollHeight}px`;
272
+ });
273
+ } else {
274
+ this.dropDownSectionHeight = null;
275
+ }
276
+ },
277
+ },
62
278
  },
63
279
  });
64
- </script>
280
+ </script>
281
+ <style lang="scss" scoped>
282
+ .ac-vscrollbar {
283
+ overflow: auto !important;
284
+ }
285
+ .line-break-anywhere {
286
+ line-break: anywhere;
287
+ }
288
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <!-- alert-message area start -->
3
+ <!-- plsease, use this class name ('.is-info' 'is-success', 'is-error', 'is-warning') -->
4
+ <div :class="`ac-notification is-${notificationType} mb-15`">
5
+ <p>
6
+ <i v-if="!hideIcon" :class="`fa ${iconClass} mr-5`"></i
7
+ ><span v-html="getSanitizedHtml(content)"></span>
8
+ <ac-button
9
+ v-if="actionButton?.show"
10
+ :title="actionButton?.title"
11
+ :icon-class="actionButton?.iconClass"
12
+ data-testid="notification-action-button"
13
+ @click.prevent="actionButton?.action()"
14
+ >
15
+ </ac-button>
16
+ </p>
17
+ </div>
18
+ <!-- alert-message area end -->
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { toRefs, computed, defineAsyncComponent } from "vue";
23
+ import DOMPurify from "dompurify";
24
+
25
+ const AcButton = defineAsyncComponent(
26
+ () => import("@appscode/design-system/vue-components/v3/button/Button.vue")
27
+ );
28
+
29
+ const props = withDefaults(
30
+ defineProps<{
31
+ notificationType: string;
32
+ content: string;
33
+ hideIcon: boolean;
34
+ actionButton?: {
35
+ show: boolean;
36
+ title: string;
37
+ iconClass: string;
38
+ action: (...args: any) => void | undefined;
39
+ };
40
+ }>(),
41
+ {
42
+ notificationType: "",
43
+ content: "",
44
+ hideIcon: false,
45
+ actionButton: undefined,
46
+ }
47
+ );
48
+
49
+ const { notificationType, content, hideIcon, actionButton } = toRefs(props);
50
+
51
+ const iconClass = computed(() => {
52
+ if (notificationType.value === "success") return "fa-check-circle";
53
+ else if (notificationType.value === "info") return "fa-info-circle";
54
+ else if (notificationType.value === "error") return "fa-times-circle";
55
+ else return "fa-info-circle";
56
+ });
57
+
58
+ const getSanitizedHtml = (content: string) => {
59
+ return DOMPurify.sanitize(content || "");
60
+ };
61
+ </script>
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <div
3
+ @mouseenter="notificationPanelOpen = true"
4
+ @mouseleave="notificationPanelOpen = false"
5
+ >
6
+ <button class="button ac-nav-button">
7
+ <span v-if="unreadCount">{{ unreadCount }}</span>
8
+ <i
9
+ class="fa fa-bell"
10
+ :class="{ 'ac-shake': unreadCount }"
11
+ aria-hidden="true"
12
+ ></i>
13
+ </button>
14
+ <div class="ac-menu-content is-notification">
15
+ <div class="notification-header">
16
+ <div class="left-content">
17
+ <p>
18
+ Notifications <span>({{ notifications.length }})</span>
19
+ </p>
20
+ </div>
21
+ <div class="right-content"></div>
22
+ </div>
23
+ <div :key="notificationPanelOpen ? 1 : 0" class="notification-body">
24
+ <transition-group v-if="notifications.length" name="slide-right">
25
+ <notification-item
26
+ v-for="notification in notifications"
27
+ :key="`${notification.id}${unreadCount}`"
28
+ :notification="notification"
29
+ />
30
+ </transition-group>
31
+ <span v-else class="single-notification-item"
32
+ >No new notifications</span
33
+ >
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { NatsConnection, StringCodec, Subscription } from "nats.ws";
41
+ import {
42
+ computed,
43
+ defineAsyncComponent,
44
+ getCurrentInstance,
45
+ ref,
46
+ Ref,
47
+ watch,
48
+ } from "vue";
49
+ import { TaskLog } from "../../../typings/long-running-tasks";
50
+ import { Notification } from "../../../typings/notification";
51
+
52
+ const NotificationItem = defineAsyncComponent(
53
+ () => import("./NotificationItem.vue")
54
+ );
55
+
56
+ const notifications: Ref<Notification[]> = ref([]);
57
+ const notificationsRead = ref(0);
58
+ const unreadCount = computed(
59
+ () => notifications.value.length - notificationsRead.value
60
+ );
61
+
62
+ const notificationPanelOpen = ref(false);
63
+ watch(notificationPanelOpen, (n) => {
64
+ if (n) notificationsRead.value = notifications.value.length;
65
+ });
66
+
67
+ function addNewNotification(notification: Notification) {
68
+ notifications.value.unshift(notification);
69
+ if (notificationPanelOpen.value) {
70
+ notificationsRead.value = notifications.value.length;
71
+ }
72
+ }
73
+
74
+ const app = getCurrentInstance();
75
+ const $nats: NatsConnection = app?.appContext.config.globalProperties.$nc;
76
+ let subscription: Subscription;
77
+
78
+ async function subscribeToNotifcations() {
79
+ subscription = $nats?.subscribe("notifications");
80
+ console.log("Started listening to Notifications");
81
+
82
+ if (subscription) {
83
+ // listen to channel events
84
+ for await (const msg of subscription) {
85
+ console.log("notifications ===>");
86
+ console.log({ data: StringCodec().decode(msg.data) });
87
+ const log: TaskLog = JSON.parse(StringCodec().decode(msg.data));
88
+ console.log({ log });
89
+ const currentTime = new Date().getTime();
90
+ addNewNotification({ ...log, id: currentTime, time: currentTime });
91
+ msg.respond();
92
+ }
93
+ console.log("Stopped listening to Notifications");
94
+ console.log("Closed Channel Notifications");
95
+ }
96
+ }
97
+ subscribeToNotifcations();
98
+ </script>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <a class="single-notification-item is-complete">
3
+ <p>
4
+ {{ notification.msg }}
5
+ </p>
6
+ <div class="notification-status">
7
+ <p
8
+ :class="{
9
+ 'is-success': notification.status === 'Success',
10
+ 'has-text-danger': notification.status === 'Failed',
11
+ 'is-info':
12
+ notification.status === 'Started' ||
13
+ notification.status === 'Running',
14
+ 'is-warning': notification.status === 'Pending',
15
+ }"
16
+ >
17
+ <i
18
+ class="fa mr-5"
19
+ :class="{
20
+ 'fa-check': notification.status === 'Success',
21
+ 'fa-exclamation-triangle': notification.status === 'Failed',
22
+ 'fa-info-circle':
23
+ notification.status === 'Started' ||
24
+ notification.status === 'Pending' ||
25
+ notification.status === 'Running',
26
+ }"
27
+ />
28
+ {{ notification.status }}
29
+ </p>
30
+ <p>{{ notificationTime }} ago</p>
31
+ </div>
32
+ </a>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { computed, toRefs } from 'vue'
37
+ import { Notification } from '../../../typings/notification'
38
+ import TimeConvert from '../../../plugins/time-convert'
39
+
40
+ const props = withDefaults(defineProps<{ notification: Notification }>(), {
41
+ notification: () => ({
42
+ id: 0,
43
+ status: 'Pending',
44
+ time: 0,
45
+ }),
46
+ })
47
+
48
+ const { notification } = toRefs(props)
49
+ const notificationTime = computed(() =>
50
+ TimeConvert.getDayDifferences({ past: notification.value.time })
51
+ )
52
+ </script>