adminforth 2.25.0 → 2.26.0-next.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 (53) hide show
  1. package/commands/bundle.js +2 -1
  2. package/commands/createApp/templates/Dockerfile.hbs +12 -0
  3. package/commands/createApp/templates/package.json.hbs +18 -6
  4. package/commands/createApp/templates/pnpm_templates/pnpm-lock.yaml.hbs +8 -0
  5. package/commands/createApp/templates/pnpm_templates/pnpm-workspace.yaml.hbs +2 -0
  6. package/commands/createApp/templates/readme.md.hbs +23 -4
  7. package/commands/createApp/utils.js +107 -26
  8. package/commands/createPlugin/utils.js +5 -6
  9. package/commands/postinstall.js +1 -1
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js.map +1 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +3 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/modules/codeInjector.d.ts +11 -2
  17. package/dist/modules/codeInjector.d.ts.map +1 -1
  18. package/dist/modules/codeInjector.js +197 -49
  19. package/dist/modules/codeInjector.js.map +1 -1
  20. package/dist/modules/styles.d.ts +4 -0
  21. package/dist/modules/styles.d.ts.map +1 -1
  22. package/dist/modules/styles.js +4 -0
  23. package/dist/modules/styles.js.map +1 -1
  24. package/dist/modules/utils.d.ts.map +1 -1
  25. package/dist/servers/express.d.ts +0 -1
  26. package/dist/servers/express.d.ts.map +1 -1
  27. package/dist/spa/README.md +4 -4
  28. package/dist/spa/package-lock.json +1889 -1427
  29. package/dist/spa/package.json +3 -3
  30. package/dist/spa/pnpm-lock.yaml +3546 -0
  31. package/dist/spa/src/afcl/Button.vue +12 -5
  32. package/dist/spa/src/afcl/Dialog.vue +155 -126
  33. package/dist/spa/src/afcl/Modal.vue +25 -6
  34. package/dist/spa/src/afcl/Select.vue +6 -2
  35. package/dist/spa/src/components/MenuLink.vue +18 -4
  36. package/dist/spa/src/components/Sidebar.vue +1 -1
  37. package/dist/spa/src/components/ThreeDotsMenu.vue +1 -1
  38. package/dist/spa/src/components/ValueRenderer.vue +1 -1
  39. package/dist/spa/src/stores/core.ts +1 -2
  40. package/dist/spa/src/types/Back.ts +2 -2
  41. package/dist/spa/src/types/Common.ts +8 -0
  42. package/dist/spa/src/types/FrontendAPI.ts +0 -3
  43. package/dist/spa/src/utils/utils.ts +57 -2
  44. package/dist/spa/src/views/CreateView.vue +12 -11
  45. package/dist/spa/src/views/EditView.vue +9 -13
  46. package/dist/spa/vite.config.ts +29 -40
  47. package/dist/types/Back.d.ts +2 -3
  48. package/dist/types/Back.d.ts.map +1 -1
  49. package/dist/types/Common.d.ts +6 -0
  50. package/dist/types/Common.d.ts.map +1 -1
  51. package/dist/types/Common.js.map +1 -1
  52. package/package.json +15 -7
  53. package/scripts/postinstall.js +25 -0
@@ -6,7 +6,8 @@
6
6
  focus:ring-4 focus:outline-none focus:ring-lightButtonFocusRing focus:ring-opacity-50 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-darkButtonFocusRing dark:text-darkButtonsText dark:border-darkButtonsBorder dark:hover:bg-darkButtonsHover dark:hover:border-darkButtonsBorderHover"
7
7
  :class="{
8
8
  'cursor-default opacity-50 pointer-events-none': props.disabled,
9
- 'active brightness-200 hover:brightness-150' : props.active
9
+ 'active brightness-200 hover:brightness-150' : props.active,
10
+ 'text-lightSecondaryContrast/70 bg-lightSecondary border-lightSecondaryContrast/30 dark:bg-darkSecondary hover:bg-lightSecondary/60 hover:border-lightSecondaryContrast/60 focus:ring-lightSecondary dark:focus:ring-darkSecondary/40 dark:text-darkSecondaryContrast dark:border-darkSecondaryContrast/40 dark:hover:bg-darkSecondary/60 dark:hover:border-darkSecondary/60': props.mode === 'secondary',
10
11
  }"
11
12
  >
12
13
  <svg v-if="props.loader"
@@ -18,10 +19,16 @@
18
19
 
19
20
  <script setup lang="ts">
20
21
 
21
- const props = defineProps({
22
- loader: Boolean,
23
- disabled: Boolean,
24
- active: Boolean,
22
+ const props = withDefaults(defineProps<{
23
+ loader?: boolean;
24
+ disabled?: boolean;
25
+ active?: boolean;
26
+ mode?: 'primary' | 'secondary';
27
+ }>(), {
28
+ loader: false,
29
+ disabled: false,
30
+ active: false,
31
+ mode: 'primary'
25
32
  });
26
33
 
27
34
  </script>
@@ -1,124 +1,188 @@
1
1
  <template>
2
- <div
3
- v-if="$slots.trigger"
4
- @click="modal?.show()" class="inline-flex items-center cursor-pointer"
2
+ <Modal
3
+ ref="modalRef"
4
+ v-bind="$attrs"
5
+ :closeByClickOutside="clickToCloseOutside || closeByClickOutside"
6
+ :closeByEsc="closeByEsc || closable"
7
+ :beforeCloseFunction="beforeCloseFunction"
8
+ :beforeOpenFunction="beforeOpenFunction"
9
+ :askForCloseConfirmation="askForCloseConfirmation"
10
+ :closeConfirmationText="closeConfirmationText"
11
+ :removeFromDomOnClose="removeFromDomOnClose"
5
12
  >
6
- <slot name="trigger"></slot>
7
- </div>
8
- <Teleport to="body">
9
- <div ref="modalEl" tabindex="-1" aria-hidden="true" class="[scrollbar-gutter:stable] hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-full max-h-full">
10
- <div v-bind="$attrs" class="relative p-4 max-w-2xl max-h-full" :class="($attrs.class as string)?.includes('w-') ? '' : 'w-full'">
11
- <!-- Modal content -->
12
- <div class="relative bg-lightDialogBackgorund rounded-lg shadow-sm dark:bg-darkDialogBackgorund">
13
- <!-- Modal header -->
14
- <div
15
- v-if="header"
16
- class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-darkDialogBreakLine border-lightDialogBreakLine"
17
- >
18
- <h3 class="text-xl font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText">
19
- {{ header }}
20
- </h3>
21
- <button
22
- v-if="headerCloseButton"
23
- type="button"
24
- class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
25
- @click="tryToHideModal"
26
- >
27
- <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
28
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
29
- </svg>
30
- <span class="sr-only">Close modal</span>
31
- </button>
32
- </div>
33
- <!-- Modal body -->
34
- <div class="p-4 md:p-5 space-y-4 text-lightDialogBodyText dark:text-darkDialogBodyText">
35
- <slot></slot>
36
- </div>
37
- <!-- Modal footer -->
38
- <div
39
- v-if="buttons.length"
40
- class="flex items-center p-4 md:p-5 border-t border-lightDialogBreakLine rounded-b dark:border-darkDialogBreakLine"
41
- >
42
- <Button
43
- v-for="(button, buttonIndex) in buttons"
44
- :key="buttonIndex"
45
- v-bind="button.options"
46
- :class="{ 'ms-3': buttonIndex > 0 }"
47
- @click="button.onclick(modal)"
48
- >
49
- {{ button.label }}
50
- </Button>
51
- </div>
52
- </div>
53
- </div>
54
- <div>
55
- <!-- Confirmation Modal -->
56
- <div
57
- v-if="showConfirmationOnClose"
58
- class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[60]"
59
- >
60
- <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
61
- <h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
62
- <p class="mb-6 text-lightDialogBodyText dark:text-darkDialogBodyText">{{ props.closeConfirmationText }}</p>
63
- <div class="flex justify-end">
64
- <Button
65
- class="me-3 !bg-gray-50 dark:!bg-gray-700 !text-lightDialogBodyText dark:!text-darkDialogBodyText hover:!bg-gray-100 dark:hover:!bg-gray-600 !border-gray-200 dark:!border-gray-600"
66
- @click="showConfirmationOnClose = false"
67
- >
68
- Cancel
69
- </Button>
70
- <Button
71
- @click="
72
- showConfirmationOnClose = false;
73
- modal?.hide();
74
- "
75
- >
76
- Confirm
77
- </Button>
78
- </div>
79
- </div>
80
- </div>
81
- </div>
13
+ <template v-if="$slots.trigger" #trigger>
14
+ <slot name="trigger"></slot>
15
+ </template>
16
+
17
+ <!-- Modal header -->
18
+ <div
19
+ v-if="header"
20
+ class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-darkDialogBreakLine border-lightDialogBreakLine"
21
+ >
22
+ <h3 class="text-xl font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText">
23
+ {{ header }}
24
+ </h3>
25
+ <button
26
+ v-if="headerCloseButton"
27
+ type="button"
28
+ class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
29
+ @click="tryToHideModal"
30
+ >
31
+ <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
32
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
33
+ </svg>
34
+ <span class="sr-only">{{ t('Close Modal') }}</span>
35
+ </button>
36
+ </div>
37
+ <!-- Modal body -->
38
+ <div class="p-4 md:p-5 text-lightDialogBodyText dark:text-darkDialogBodyText">
39
+ <slot></slot>
40
+ </div>
41
+ <!-- Modal footer -->
42
+ <div
43
+ v-if="buttons.length"
44
+ class="flex items-center p-4 md:p-5 border-t border-lightDialogBreakLine rounded-b dark:border-darkDialogBreakLine"
45
+ >
46
+ <Button
47
+ v-for="(button, buttonIndex) in buttons"
48
+ :key="buttonIndex"
49
+ v-bind="button.options"
50
+ :class="{ 'ms-3': buttonIndex > 0 }"
51
+ @click="button.onclick(dialog)"
52
+ >
53
+ {{ button.label }}
54
+ </Button>
82
55
  </div>
83
- </Teleport>
56
+ </Modal>
84
57
  </template>
85
58
 
86
59
  <script setup lang="ts">
87
60
  import Button from "./Button.vue";
88
- import { ref, onMounted, nextTick, onUnmounted, computed, type Ref } from 'vue';
89
- import { Modal } from 'flowbite';
61
+ import { ref, computed, type Ref } from 'vue';
62
+ import { Modal } from '@/afcl'
63
+ import { useI18n } from "vue-i18n";
64
+
65
+ const { t } = useI18n();
66
+
67
+
68
+ interface IDialogInsideButtonClickHandler {
69
+ hide: () => void
70
+ }
90
71
 
91
- const modalEl = ref(null);
92
- const modal: Ref<Modal|null> = ref(null);
93
72
 
94
73
  interface DialogButton {
95
74
  label: string
96
- onclick: (dialog: any) => void
75
+ onclick: (dialog: IDialogInsideButtonClickHandler) => void
97
76
  options?: Record<string, any>
98
77
  }
99
78
 
79
+
100
80
  interface DialogProps {
81
+ /**
82
+ * The header text to display in the dialog. If not provided, no header will be displayed.
83
+ */
101
84
  header?: string
85
+
86
+ /**
87
+ * If true, a close button will be displayed in the dialog header. Default is true.
88
+ */
102
89
  headerCloseButton?: boolean
90
+
91
+ /**
92
+ * An array of buttons to display in the dialog footer.
93
+ */
103
94
  buttons?: DialogButton[]
95
+
96
+ /**
97
+ * If true, clicking outside the dialog will close it. Default is true.
98
+ *
99
+ * @deprecated Use `closeByClickOutside` instead
100
+ */
104
101
  clickToCloseOutside?: boolean
102
+
103
+ /**
104
+ * If true, pressing the Esc key will close the dialog. Default is true.
105
+ */
106
+ closeByEsc?: boolean
107
+
108
+ /**
109
+ * If true, clicking outside the dialog will close it. Default is true.
110
+ */
111
+ closeByClickOutside?: boolean
112
+
113
+ /**
114
+ * Function that will be called before the dialog is closed.
115
+ */
105
116
  beforeCloseFunction?: (() => void | Promise<void>) | null
117
+
118
+ /**
119
+ * Function that will be called before the dialog is opened.
120
+ */
106
121
  beforeOpenFunction?: (() => void | Promise<void>) | null
122
+
123
+ /**
124
+ * Disables close on Ecs button
125
+ *
126
+ * @deprecated Use `closeByEsc` or instead
127
+ */
107
128
  closable?: boolean
129
+
130
+ /**
131
+ * If true, the dialog will ask for confirmation before closing. Default is false.
132
+ */
108
133
  askForCloseConfirmation?: boolean
134
+
135
+ /**
136
+ * The text to display in the close confirmation dialog. Default is "Are you sure you want to close this dialog?".
137
+ */
109
138
  closeConfirmationText?: string
139
+
140
+ /**
141
+ * If true, the dialog will be removed from the DOM when closed. Default is false.
142
+ */
143
+ removeFromDomOnClose?: boolean
110
144
  }
111
145
 
146
+
147
+
148
+
149
+
150
+
151
+ /********** for the backward compatibility ***************/
152
+ class Dialog implements IDialogInsideButtonClickHandler {
153
+ hide: () => void
154
+ constructor( hide: () => void ) {
155
+ this.hide = hide;
156
+ }
157
+ }
158
+ const dialog: Ref<Dialog> = ref(
159
+ new Dialog(
160
+ () => {
161
+ if (dialog.value) {
162
+ tryToHideModal();
163
+ }
164
+ }
165
+ )
166
+ );
167
+ /*************************************************************/
168
+
169
+
170
+
171
+ const modalRef = ref();
172
+
112
173
  const props = withDefaults(defineProps<DialogProps>(), {
113
174
  header: '',
114
175
  headerCloseButton: true,
115
176
  buttons: () => [],
116
- clickToCloseOutside: true,
177
+ clickToCloseOutside: false,
178
+ closeByEsc: true,
179
+ closeByClickOutside: true,
117
180
  beforeCloseFunction: null,
118
181
  beforeOpenFunction: null,
119
- closable: true,
182
+ closable: false,
120
183
  askForCloseConfirmation: false,
121
184
  closeConfirmationText: 'Are you sure you want to close this dialog?',
185
+ removeFromDomOnClose: false,
122
186
  })
123
187
 
124
188
  const buttons = computed<DialogButton[]>(() => {
@@ -129,51 +193,20 @@ const buttons = computed<DialogButton[]>(() => {
129
193
  {
130
194
  label: 'Close',
131
195
  onclick: (dialog: any) => {
132
- if (!props.askForCloseConfirmation) {
133
- dialog.hide();
134
- } else {
135
- showConfirmationOnClose.value = true;
136
- }
196
+ tryToHideModal();
137
197
  },
138
198
  options: {}
139
199
  }
140
200
  ];
141
201
  });
142
202
 
143
- const showConfirmationOnClose = ref(false);
144
- onMounted(async () => {
145
- //await one tick when all is mounted
146
- await nextTick();
147
- modal.value = new Modal(
148
- modalEl.value,
149
- {
150
- closable: props.closable,
151
- backdrop: props.clickToCloseOutside ? 'dynamic' : 'static',
152
- onHide: async () => {
153
- if (props.beforeCloseFunction) {
154
- await props.beforeCloseFunction();
155
- }
156
- },
157
- onShow: async () => {
158
- if (props.beforeOpenFunction) {
159
- await props.beforeOpenFunction();
160
- }
161
- },
162
- }
163
- );
164
- })
165
-
166
- onUnmounted(() => {
167
- //destroy tooltip
168
- modal.value?.destroy();
169
- })
170
203
 
171
204
  function open() {
172
- modal.value?.show();
205
+ modalRef.value.open();
173
206
  }
174
207
 
175
208
  function close() {
176
- modal.value?.hide();
209
+ modalRef.value.hide();
177
210
  }
178
211
 
179
212
  defineExpose({
@@ -183,11 +216,7 @@ defineExpose({
183
216
  })
184
217
 
185
218
  function tryToHideModal() {
186
- if (!props.askForCloseConfirmation ) {
187
- modal.value?.hide();
188
- } else {
189
- showConfirmationOnClose.value = true;
190
- }
219
+ modalRef.value?.tryToHideModal();
191
220
  }
192
221
 
193
222
 
@@ -6,12 +6,12 @@
6
6
  <slot name="trigger"></slot>
7
7
  </div>
8
8
  <Teleport to="body">
9
- <div v-show="isModalOpen" v-if="!removeFromDom" @click="backdropClick" v-bind="$attrs" class="bg-black/50 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-1rem max-h-full flex" >
9
+ <div v-show="isModalOpen" v-if="!removeFromDom" @click="backdropClick" class="bg-black/50 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full h-full md:inset-0 h-1rem max-h-full flex" >
10
10
  <!-- Modal content -->
11
- <div class="relative bg-lightDialogBackgorund rounded-lg shadow-sm dark:bg-darkDialogBackgorund">
11
+ <div v-bind="$attrs" class="relative bg-lightDialogBackgorund rounded-lg shadow-sm dark:bg-darkDialogBackgorund">
12
12
 
13
13
  <!-- Modal body -->
14
- <div class="p-4 md:p-5 space-y-4 text-lightDialogBodyText dark:text-darkDialogBodyText">
14
+ <div class="text-lightDialogBodyText dark:text-darkDialogBodyText">
15
15
  <slot ></slot>
16
16
  </div>
17
17
 
@@ -57,7 +57,8 @@ const removeFromDom = computed(() => {
57
57
  })
58
58
 
59
59
  interface DialogProps {
60
- clickToCloseOutside?: boolean
60
+ closeByClickOutside?: boolean
61
+ closeByEsc?: boolean
61
62
  beforeCloseFunction?: (() => void | Promise<void>) | null
62
63
  beforeOpenFunction?: (() => void | Promise<void>) | null
63
64
  askForCloseConfirmation?: boolean
@@ -66,7 +67,8 @@ interface DialogProps {
66
67
  }
67
68
 
68
69
  const props = withDefaults(defineProps<DialogProps>(), {
69
- clickToCloseOutside: true,
70
+ closeByClickOutside: true,
71
+ closeByEsc: true,
70
72
  beforeCloseFunction: null,
71
73
  beforeOpenFunction: null,
72
74
  askForCloseConfirmation: false,
@@ -109,8 +111,25 @@ function toggleModal() {
109
111
  }
110
112
  }
111
113
 
114
+ function onEsc(event: KeyboardEvent) {
115
+ if (event.key === 'Escape') {
116
+ if (props.closeByEsc) {
117
+ tryToHideModal();
118
+ }
119
+ }
120
+ }
121
+
122
+ onMounted(() => {
123
+ document.addEventListener('keydown', onEsc)
124
+ })
125
+
126
+ onUnmounted(() => {
127
+ document.removeEventListener('keydown', onEsc)
128
+ })
129
+
130
+
112
131
  function backdropClick(e: MouseEvent) {
113
- if (props.clickToCloseOutside && e.target === e.currentTarget) {
132
+ if (props.closeByClickOutside && e.target === e.currentTarget) {
114
133
  tryToHideModal();
115
134
  }
116
135
  }
@@ -13,7 +13,7 @@
13
13
  class="block w-full pl-3 pr-10 py-2.5 border border-lightDropownButtonsBorder rounded-md leading-5 bg-lightDropdownButtonsBackground
14
14
  placeholder-lightDropdownButtonsPlaceholderText text-lightDropdownButtonsText sm:text-sm transition duration-150 ease-in-out dark:bg-darkDropdownButtonsBackground dark:border-darkDropdownButtonsBorder dark:placeholder-darkDropdownButtonsPlaceholderText
15
15
  dark:text-darkDropdownButtonsText focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
16
- :class="{'cursor-pointer': searchDisabled}"
16
+ :class="[{'cursor-pointer': searchDisabled}, classesForInput]"
17
17
  autocomplete="off" data-custom="no-autofill"
18
18
  :placeholder="
19
19
  selectedItems.length && !multiple ? '' : (showDropdown ? $t('Search') : placeholder || $t('Select...'))
@@ -122,7 +122,7 @@ import { useElementSize } from '@vueuse/core'
122
122
  const props = defineProps({
123
123
  options: Array,
124
124
  modelValue: {
125
- type: Array as PropType<(string | number)[]>,
125
+ type: Array as PropType<(string | number)[] | (string | number)>,
126
126
  default: () => [],
127
127
  },
128
128
  multiple: {
@@ -153,6 +153,10 @@ const props = defineProps({
153
153
  type: Number,
154
154
  default: 300,
155
155
  },
156
+ classesForInput: {
157
+ type: String,
158
+ default: '',
159
+ },
156
160
  });
157
161
 
158
162
  const emit = defineEmits(['update:modelValue', 'scroll-near-end', 'search']);
@@ -1,15 +1,14 @@
1
1
  <template>
2
2
  <RouterLink
3
- :to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
3
+ :to="item.url || { name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
4
+ :target="item.isOpenInNewTab ? '_blank' : '_self'"
4
5
  class="af-menu-link flex group relative items-center w-full py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default transition-all duration-200 ease-in-out"
5
6
  :class="{
6
7
  'hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover': !['divider', 'gap', 'heading'].includes(item.type),
7
8
  'pl-6 pr-3.5': (isChild && !isSidebarIconOnly && !isSidebarHovering) || (isChild && isSidebarIconOnly && isSidebarHovering),
8
9
  'px-3.5 ': !isChild || (isSidebarIconOnly && !isSidebarHovering),
9
10
  'max-w-12': isSidebarIconOnly && !isSidebarHovering,
10
- 'bg-lightSidebarItemActive dark:bg-darkSidebarItemActive': item.resourceId ?
11
- ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
12
- ($route.name === item.path)
11
+ 'bg-lightSidebarItemActive dark:bg-darkSidebarItemActive': isItemActive(item)
13
12
  }"
14
13
  >
15
14
  <component v-if="item.icon" :is="getIcon(item.icon)"
@@ -61,6 +60,21 @@ import { Tooltip } from '@/afcl';
61
60
  import { ref, watch, computed } from 'vue';
62
61
  import { useCoreStore } from '@/stores/core';
63
62
  import { IconFileImageOutline } from '@iconify-prerendered/vue-flowbite';
63
+ import { useRoute } from 'vue-router';
64
+
65
+ const route = useRoute();
66
+
67
+ const isItemActive = (item: any) => {
68
+ if (item.url) {
69
+ return route.fullPath === item.url;
70
+ }
71
+
72
+ if (item.resourceId) {
73
+ return route.params.resourceId === item.resourceId && route.name === 'resource-list';
74
+ }
75
+
76
+ return route.name === item.path;
77
+ };
64
78
 
65
79
  const props = defineProps(['item', 'isChild', 'isSidebarIconOnly', 'isSidebarHovering']);
66
80
 
@@ -28,7 +28,7 @@
28
28
  :class="{
29
29
  'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }"
30
30
  />
31
- <img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
31
+ <img v-if="coreStore.config?.iconOnlySidebar?.logo" :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
32
32
  <span
33
33
  v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
34
34
  class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
@@ -18,7 +18,7 @@
18
18
  'hidden': !showDropdown,
19
19
  'left-0 md:left-auto': checkboxes && checkboxes.length > 0
20
20
  }"
21
- class="absolute z-30 mt-3 bg-lightThreeDotsMenuBodyBackground divide-y divide-gray-100 rounded-lg shadow w-auto max-w-64 dark:bg-darkThreeDotsMenuBodyBackground dark:divide-gray-600 right-0">
21
+ class="absolute z-30 mt-3 bg-lightThreeDotsMenuBodyBackground divide-y divide-gray-100 rounded-lg shadow w-max max-w-64 dark:bg-darkThreeDotsMenuBodyBackground dark:divide-gray-600 right-0">
22
22
  <ul class="py-2 text-sm text-lightThreeDotsMenuBodyText dark:text-darkThreeDotsMenuBodyText" aria-labelledby="dropdownMenuIconButton">
23
23
  <li v-for="(item, i) in threeDotsDropdownItems" :key="`dropdown-item-${i}`">
24
24
  <div
@@ -117,7 +117,7 @@ import timezone from 'dayjs/plugin/timezone';
117
117
  import {checkEmptyValues} from '@/utils';
118
118
  import { useRoute, useRouter } from 'vue-router';
119
119
  import { JsonViewer } from "vue3-json-viewer";
120
- import "vue3-json-viewer/dist/index.css";
120
+ import "vue3-json-viewer/dist/vue3-json-viewer.css";
121
121
  import type { AdminForthResourceColumnCommon } from '@/types/Common';
122
122
 
123
123
  import { useCoreStore } from '@/stores/core';
@@ -92,6 +92,7 @@ export const useCoreStore = defineStore('core', () => {
92
92
 
93
93
  // console.log('🔔 subscribeToMenuBadges', mi.badge, JSON.stringify(mi));
94
94
  if (mi.badge !== undefined) {
95
+ websocket.unsubscribe(`/opentopic/update-menu-badge/${mi.itemId}`);
95
96
  websocket.subscribe(`/opentopic/update-menu-badge/${mi.itemId}`, ({ badge }) => {
96
97
  mi.badge = badge;
97
98
  });
@@ -121,8 +122,6 @@ export const useCoreStore = defineStore('core', () => {
121
122
  item.badge = badge;
122
123
  }
123
124
  });
124
- // TODO: This thing was created for something. Find out why
125
- // websocket.unsubscribeAll();
126
125
  subscribeToMenuBadges();
127
126
 
128
127
  }
@@ -1206,8 +1206,8 @@ interface AdminForthInputConfigCustomization {
1206
1206
  *
1207
1207
  * ```bashcreating rec
1208
1208
  * cd custom
1209
- * npm init -y
1210
- * npm install highcharts highcharts-vue
1209
+ * pnpm init -y
1210
+ * pnpm install highcharts highcharts-vue
1211
1211
  * ```
1212
1212
  *
1213
1213
  * And specify vueUsesFile in AdminForth config:
@@ -361,6 +361,12 @@ export interface AdminForthResourceInputCommon {
361
361
  recordLabel?: (item: any) => string,
362
362
 
363
363
 
364
+ /**
365
+ * If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
366
+ * default is false
367
+ */
368
+ dontShowWarningAboutUnsavedChanges?: boolean,
369
+
364
370
  /**
365
371
  * General options for resource.
366
372
  */
@@ -1116,6 +1122,8 @@ export interface AdminForthConfigMenuItem {
1116
1122
  * Item id will be automatically generated from hashed resourceId+Path+label
1117
1123
  */
1118
1124
  itemId?: string, // todo move to runtime type
1125
+
1126
+ url?: string
1119
1127
  }
1120
1128
 
1121
1129
 
@@ -213,6 +213,3 @@ export enum AlertVariant {
213
213
  }
214
214
 
215
215
 
216
-
217
-
218
-