bfg-common 1.5.707 → 1.5.709

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.
@@ -8,7 +8,8 @@
8
8
  >
9
9
  <atoms-the-icon name="arrow" />
10
10
  </button>
11
- <div v-else class="h-full">
11
+
12
+ <div v-else class="h-full flex-direction-column">
12
13
  <div class="navbar__header">
13
14
  <p class="title">{{ props.title }}</p>
14
15
  <button
@@ -19,15 +20,17 @@
19
20
  <atoms-the-icon name="close" />
20
21
  </button>
21
22
  </div>
23
+
22
24
  <div class="navbar__content content-links">
23
25
  <a
24
26
  v-for="item in props.items"
25
27
  :key="item.text"
26
28
  :data-id="item.testId"
27
29
  :href="item.to"
28
- :class="['link-item', { active: route.hash === item.to }]"
29
- >{{ item.text }}</a
30
+ :class="['link-item', { active: activeHash === item.to }]"
30
31
  >
32
+ {{ item.text }}
33
+ </a>
31
34
  </div>
32
35
  </div>
33
36
  </div>
@@ -41,39 +44,96 @@ const props = defineProps<{
41
44
  items: UI_I_ContentNavbarItem[]
42
45
  }>()
43
46
 
44
- const route = useRoute()
45
-
46
47
  const isShow = ref<boolean>(true)
48
+ const activeHash = ref<string>('')
47
49
 
48
- const checkActiveLink = (): void => {
49
- const links = document.querySelectorAll('.content-links .link-item')
50
+ let observer: IntersectionObserver | null = null
51
+ let rootElement: Element | null = null
50
52
 
51
- if (!links.length) return
53
+ const setActiveHashFromLocation = () => {
54
+ // decodeURIComponent чтобы корректно читать хэши с кириллицей/спецсимволами
55
+ activeHash.value =
56
+ typeof window !== 'undefined'
57
+ ? decodeURIComponent(window.location.hash || '')
58
+ : ''
59
+ }
60
+
61
+ const observeLinks = (): void => {
62
+ const headingsSelector =
63
+ '.help .content-renderer h1, .help .content-renderer h2, .help .content-renderer h3, .help .content-renderer h4, .help .content-renderer h5, .help .content-renderer h6'
52
64
 
53
- links.forEach((item) => {
54
- const linkElement = item as HTMLLinkElement
65
+ const headingNodes = Array.from(
66
+ document.querySelectorAll<HTMLElement>(headingsSelector)
67
+ ).filter((h) => !!h.id)
55
68
 
56
- linkElement.classList.remove('active')
57
- const linkHref = linkElement.getAttribute('href')
58
- if (linkHref && route.hash === linkHref) linkElement.classList.add('active')
59
- else if (!route.hash) links[0].classList.add('active')
60
- })
69
+ if (headingNodes.length) {
70
+ const options: IntersectionObserverInit = {
71
+ root: rootElement || null,
72
+ rootMargin: '0px 0px -99% 0px', // активна пока верх секции у top root
73
+ threshold: 0,
74
+ }
75
+
76
+ observer = new IntersectionObserver((entries) => {
77
+ entries.forEach((entry) => {
78
+ if (!entry.isIntersecting) return
79
+ const id = entry.target.getAttribute('id')
80
+ if (!id) return
81
+ const newHash = `#${id}`
82
+
83
+ // Если уже такой hash — ничего не делаем
84
+ if (decodeURIComponent(window.location.hash || '') === newHash) {
85
+ activeHash.value = newHash
86
+ return
87
+ }
88
+
89
+ // Обновляем URL без скролла
90
+ history.replaceState(null, '', newHash)
91
+ activeHash.value = newHash
92
+ })
93
+ }, options)
94
+
95
+ headingNodes.forEach((h) => observer?.observe(h))
96
+ }
61
97
  }
62
98
 
63
- const parentCurrentScrollPosition = (): void => {
64
- const parentElement = document.querySelector('.help__content')
65
- if (!parentElement || route.hash) return
99
+ const init = (): void => {
100
+ rootElement = document.querySelector('.help__content')
101
+
102
+ setActiveHashFromLocation()
66
103
 
67
- parentElement.scrollTop = 0
104
+ if (props.items?.length) {
105
+ const first = props.items[0]?.to ?? ''
106
+ if (first) {
107
+ history.replaceState(null, '', first)
108
+ setActiveHashFromLocation()
109
+ }
110
+ }
111
+
112
+ observeLinks()
68
113
  }
69
114
 
115
+ onMounted(() => {
116
+ init()
117
+ })
118
+ let isRouteUpdated = false
70
119
  onUpdated(() => {
71
- parentCurrentScrollPosition()
72
- checkActiveLink()
120
+ if (isRouteUpdated) {
121
+ init()
122
+ isRouteUpdated = false
123
+ }
73
124
  })
74
125
 
75
- onMounted(() => {
76
- checkActiveLink()
126
+ // cleanup при unmount
127
+ onUnmounted(() => {
128
+ observer?.disconnect()
129
+ })
130
+ onBeforeRouteUpdate((to, from, next) => {
131
+ // обработка оснавной навигации, делаем cleanup
132
+ if (from.path !== to.path) {
133
+ observer?.disconnect()
134
+ isRouteUpdated = true
135
+ }
136
+ next()
77
137
  })
78
138
  </script>
79
139
 
@@ -101,6 +161,7 @@ onMounted(() => {
101
161
  cursor: pointer;
102
162
  margin: 12px 6px;
103
163
  }
164
+
104
165
  &__header {
105
166
  display: flex;
106
167
  justify-content: space-between;
@@ -128,10 +189,12 @@ onMounted(() => {
128
189
  }
129
190
  }
130
191
  }
192
+
131
193
  &__content {
132
194
  height: 100%;
133
195
  overflow: auto;
134
196
  }
197
+
135
198
  .link-item {
136
199
  display: block;
137
200
  color: var(--global-font-color);
@@ -1,191 +1,186 @@
1
- <template>
2
- <common-layout-the-header-new
3
- v-if="props.newView"
4
- v-model:security="security"
5
- :is-show-main-menu="props.isShowMainMenu"
6
- :company-name="props.companyName"
7
- :beta-text="props.betaText"
8
- :global-refresh-loading="props.globalRefreshLoading"
9
- :hostname="props.hostname"
10
- :project-name="props.projectName"
11
- :is-preference="props.isPreference"
12
- :selected-language-type="props.selectedLanguageType"
13
- :selected-lang="props.selectedLang"
14
- :new-view="props.newView"
15
- :new-view-local="props.newViewLocal"
16
- :time-format="props.timeFormat"
17
- :project="props.project"
18
- :is-dark-theme="props.isDarkTheme"
19
- :remote-console="props.remoteConsole"
20
- :vm-cluster="props.vmCluster"
21
- :security-loader="props.securityLoader"
22
- :recovery="props.recovery"
23
- @toggle-main-menu="emits('toggle-main-menu')"
24
- @show-preference="emits('show-preference')"
25
- @reset-preference="emits('reset-preference')"
26
- @hide-preference="emits('hide-preference')"
27
- @global-refresh="emits('global-refresh')"
28
- @update-time-format="emits('update-time-format', $event)"
29
- @change-theme-mode="emits('change-theme-mode')"
30
- @update-language="emits('update-language', $event)"
31
- @update-is-browser="emits('update-is-browser', $event)"
32
- @update-is-new-view="emits('update-is-new-view', $event)"
33
- @update-remote-console="emits('update-remote-console', $event)"
34
- @update-vm-clusters="emits('update-vm-clusters', $event)"
35
- @security-confirm="emits('security-confirm', $event)"
36
- @submit-preferences="emits('submit-preferences')"
37
- />
38
- <common-layout-the-header-old
39
- v-else
40
- v-model:security="security"
41
- :company-name="props.companyName"
42
- :beta-text="props.betaText"
43
- :global-refresh-loading="props.globalRefreshLoading"
44
- :hostname="props.hostname"
45
- :project-name="props.projectName"
46
- :is-preference="props.isPreference"
47
- :selected-language-type="props.selectedLanguageType"
48
- :selected-lang="props.selectedLang"
49
- :new-view="props.newView"
50
- :new-view-local="props.newViewLocal"
51
- :time-format="props.timeFormat"
52
- :project="props.project"
53
- :remote-console="props.remoteConsole"
54
- :vm-cluster="props.vmCluster"
55
- :security-loader="props.securityLoader"
56
- :recovery="props.recovery"
57
- @toggle-main-menu="emits('toggle-main-menu')"
58
- @show-preference="emits('show-preference')"
59
- @hide-preference="emits('hide-preference')"
60
- @global-refresh="emits('global-refresh')"
61
- @update-time-format="emits('update-time-format', $event)"
62
- @change-theme-mode="emits('change-theme-mode')"
63
- @update-language="emits('update-language', $event)"
64
- @update-is-browser="emits('update-is-browser', $event)"
65
- @update-is-new-view="emits('update-is-new-view', $event)"
66
- @update-remote-console="emits('update-remote-console', $event)"
67
- @update-vm-clusters="emits('update-vm-clusters', $event)"
68
- @security-confirm="emits('security-confirm', $event)"
69
- @submit-preferences="emits('submit-preferences')"
70
- />
71
-
72
- <common-layout-the-header-modals-reconnect />
73
- <common-layout-the-header-modals-redirect-login />
74
- </template>
75
-
76
- <script setup lang="ts">
77
- import type { UI_I_Dropdown } from '~/node_modules/bfg-uikit/components/ui/dropdown/models/interfaces'
78
- import type { UI_T_Project } from '~/lib/models/types'
79
- import type { UI_T_TimeValue } from '~/components/common/layout/theHeader/userMenu/modals/preferences/timeFormat/lib/models/types'
80
- import type {
81
- UI_I_Recovery
82
- } from "~/components/common/layout/theHeader/userMenu/modals/preferences/security/lib/models/interfaces";
83
- import { checkIsTokenExpired } from '~/lib/utils/token'
84
-
85
- const security = defineModel<boolean>('security')
86
-
87
- const props = withDefaults(
88
- defineProps<{
89
- isShowMainMenu: boolean
90
- companyName: string
91
- betaText: string
92
- globalRefreshLoading: boolean
93
- project: UI_T_Project
94
- projectName: string
95
- hostname: string
96
- isPreference: boolean
97
- selectedLanguageType: string
98
- selectedLang: UI_I_Dropdown
99
- remoteConsole: string
100
- vmCluster: boolean
101
- newView: boolean
102
- newViewLocal: boolean
103
- timeFormat: string
104
- isDarkTheme: boolean
105
- isPauseReconnect: boolean
106
- expireTimeFromState: number
107
- isShowReconnectModal: boolean
108
- isShowRedirectLoginModal: boolean
109
- securityLoader?: boolean // для Сферы
110
- recovery?: UI_I_Recovery | null // для Сферы
111
- }>(),
112
- {
113
- securityLoader: false,
114
- recovery: null
115
- }
116
- )
117
-
118
- const emits = defineEmits<{
119
- (event: 'toggle-main-menu'): void
120
- (event: 'global-refresh'): void
121
- (event: 'show-preference'): void
122
- (event: 'reset-preference'): void
123
- (event: 'hide-preference'): void
124
- (event: 'change-theme-mode'): void
125
- (event: 'update-time-format', value: UI_T_TimeValue): void
126
- (event: 'update-language', value: UI_I_Dropdown): void
127
- (event: 'update-is-browser', value: string): void
128
- (event: 'update-is-new-view', value: boolean): void
129
- (event: 'update-remote-console', value: string): void
130
- (event: 'update-vm-clusters', value: boolean): void
131
- (event: 'security-confirm', value: boolean): void
132
- (event: 'submit-preferences'): void
133
- (event: 'show-redirect-login-modal', value: boolean): void
134
- (event: 'show-reconnect-modal', value: boolean): void
135
- }>()
136
-
137
- const inactivityTimer = ref<NodeJS.Timeout | undefined>(undefined)
138
- const inactivityTimeout = ref<number>(10 * 60 * 1000)
139
-
140
- const startInactivityTimer = (): void => {
141
- inactivityTimer.value = setTimeout(() => {
142
- // Проверяем если isPauseReconnect истина то не показываем модальное окно переподключения,
143
- // к примеру если загружаем файл и он загружается дольше чем срабатывает таймаут
144
-
145
- if (props.isPauseReconnect) {
146
- startInactivityTimer()
147
- return
148
- }
149
-
150
- const expireTime =
151
- props.expireTimeFromState || useCookie('token-expired-time').value
152
-
153
- const isExpiredToken = checkIsTokenExpired(expireTime)
154
-
155
- if (isExpiredToken) {
156
- emits('show-redirect-login-modal', true)
157
-
158
- if (props.isShowReconnectModal) {
159
- emits('show-reconnect-modal', false)
160
- }
161
- }
162
-
163
- if (!isExpiredToken && !props.isShowRedirectLoginModal) {
164
- emits('show-reconnect-modal', true)
165
- startInactivityTimer()
166
- }
167
-
168
- console.log('Пользователь бездействует!')
169
- }, inactivityTimeout.value)
170
- }
171
-
172
- const resetInactivityTimer = (): void => {
173
- // Сброс таймера бездействия при каждом действии пользователя
174
- inactivityTimer.value !== null && clearTimeout(inactivityTimer.value)
175
- startInactivityTimer()
176
- }
177
-
178
- onMounted(() => {
179
- document.addEventListener('mousemove', resetInactivityTimer)
180
-
181
- startInactivityTimer()
182
- })
183
-
184
- onBeforeUnmount(() => {
185
- document.removeEventListener('mousemove', resetInactivityTimer)
186
-
187
- inactivityTimer.value !== null && clearTimeout(inactivityTimer.value)
188
- })
189
- </script>
190
-
191
- <style scoped lang="scss"></style>
1
+ <template>
2
+ <common-layout-the-header-new
3
+ v-if="props.newView"
4
+ v-model:security="security"
5
+ v-model:new-view-local="newViewLocal"
6
+ :is-show-main-menu="props.isShowMainMenu"
7
+ :company-name="props.companyName"
8
+ :beta-text="props.betaText"
9
+ :global-refresh-loading="props.globalRefreshLoading"
10
+ :hostname="props.hostname"
11
+ :project-name="props.projectName"
12
+ :is-preference="props.isPreference"
13
+ :selected-language-type="props.selectedLanguageType"
14
+ :selected-lang="props.selectedLang"
15
+ :new-view="props.newView"
16
+ :time-format="props.timeFormat"
17
+ :project="props.project"
18
+ :is-dark-theme="props.isDarkTheme"
19
+ :remote-console="props.remoteConsole"
20
+ :vm-cluster="props.vmCluster"
21
+ :security-loader="props.securityLoader"
22
+ :recovery="props.recovery"
23
+ @toggle-main-menu="emits('toggle-main-menu')"
24
+ @show-preference="emits('show-preference')"
25
+ @reset-preference="emits('reset-preference')"
26
+ @hide-preference="emits('hide-preference')"
27
+ @global-refresh="emits('global-refresh')"
28
+ @update-time-format="emits('update-time-format', $event)"
29
+ @change-theme-mode="emits('change-theme-mode')"
30
+ @update-language="emits('update-language', $event)"
31
+ @update-is-browser="emits('update-is-browser', $event)"
32
+ @update-remote-console="emits('update-remote-console', $event)"
33
+ @update-vm-clusters="emits('update-vm-clusters', $event)"
34
+ @security-confirm="emits('security-confirm', $event)"
35
+ @submit-preferences="emits('submit-preferences')"
36
+ />
37
+ <common-layout-the-header-old
38
+ v-else
39
+ v-model:security="security"
40
+ v-model:new-view-local="newViewLocal"
41
+ :company-name="props.companyName"
42
+ :beta-text="props.betaText"
43
+ :global-refresh-loading="props.globalRefreshLoading"
44
+ :hostname="props.hostname"
45
+ :project-name="props.projectName"
46
+ :is-preference="props.isPreference"
47
+ :selected-language-type="props.selectedLanguageType"
48
+ :selected-lang="props.selectedLang"
49
+ :new-view="props.newView"
50
+ :time-format="props.timeFormat"
51
+ :project="props.project"
52
+ :remote-console="props.remoteConsole"
53
+ :vm-cluster="props.vmCluster"
54
+ :security-loader="props.securityLoader"
55
+ :recovery="props.recovery"
56
+ @toggle-main-menu="emits('toggle-main-menu')"
57
+ @show-preference="emits('show-preference')"
58
+ @hide-preference="emits('hide-preference')"
59
+ @global-refresh="emits('global-refresh')"
60
+ @update-time-format="emits('update-time-format', $event)"
61
+ @change-theme-mode="emits('change-theme-mode')"
62
+ @update-language="emits('update-language', $event)"
63
+ @update-is-browser="emits('update-is-browser', $event)"
64
+ @update-remote-console="emits('update-remote-console', $event)"
65
+ @update-vm-clusters="emits('update-vm-clusters', $event)"
66
+ @security-confirm="emits('security-confirm', $event)"
67
+ @submit-preferences="emits('submit-preferences')"
68
+ />
69
+
70
+ <common-layout-the-header-modals-reconnect />
71
+ <common-layout-the-header-modals-redirect-login />
72
+ </template>
73
+
74
+ <script setup lang="ts">
75
+ import type { UI_I_Dropdown } from '~/node_modules/bfg-uikit/components/ui/dropdown/models/interfaces'
76
+ import type { UI_T_Project } from '~/lib/models/types'
77
+ import type { UI_T_TimeValue } from '~/components/common/layout/theHeader/userMenu/modals/preferences/timeFormat/lib/models/types'
78
+ import type { UI_I_Recovery } from '~/components/common/layout/theHeader/userMenu/modals/preferences/security/lib/models/interfaces'
79
+ import { checkIsTokenExpired } from '~/lib/utils/token'
80
+
81
+ const security = defineModel<boolean>('security')
82
+ const newViewLocal = defineModel<boolean>('newViewLocal')
83
+
84
+ const props = withDefaults(
85
+ defineProps<{
86
+ isShowMainMenu: boolean
87
+ companyName: string
88
+ betaText: string
89
+ globalRefreshLoading: boolean
90
+ project: UI_T_Project
91
+ projectName: string
92
+ hostname: string
93
+ isPreference: boolean
94
+ selectedLanguageType: string
95
+ selectedLang: UI_I_Dropdown
96
+ remoteConsole: string
97
+ vmCluster: boolean
98
+ newView: boolean
99
+ timeFormat: string
100
+ isDarkTheme: boolean
101
+ isPauseReconnect: boolean
102
+ expireTimeFromState: number
103
+ isShowReconnectModal: boolean
104
+ isShowRedirectLoginModal: boolean
105
+ securityLoader?: boolean // для Сферы
106
+ recovery?: UI_I_Recovery | null // для Сферы
107
+ }>(),
108
+ {
109
+ securityLoader: false,
110
+ recovery: null,
111
+ }
112
+ )
113
+
114
+ const emits = defineEmits<{
115
+ (event: 'toggle-main-menu'): void
116
+ (event: 'global-refresh'): void
117
+ (event: 'show-preference'): void
118
+ (event: 'reset-preference'): void
119
+ (event: 'hide-preference'): void
120
+ (event: 'change-theme-mode'): void
121
+ (event: 'update-time-format', value: UI_T_TimeValue): void
122
+ (event: 'update-language', value: UI_I_Dropdown): void
123
+ (event: 'update-is-browser', value: string): void
124
+ (event: 'update-remote-console', value: string): void
125
+ (event: 'update-vm-clusters', value: boolean): void
126
+ (event: 'security-confirm', value: boolean): void
127
+ (event: 'submit-preferences'): void
128
+ (event: 'show-redirect-login-modal', value: boolean): void
129
+ (event: 'show-reconnect-modal', value: boolean): void
130
+ }>()
131
+
132
+ const inactivityTimer = ref<NodeJS.Timeout | undefined>(undefined)
133
+ const inactivityTimeout = ref<number>(10 * 60 * 1000)
134
+
135
+ const startInactivityTimer = (): void => {
136
+ inactivityTimer.value = setTimeout(() => {
137
+ // Проверяем если isPauseReconnect истина то не показываем модальное окно переподключения,
138
+ // к примеру если загружаем файл и он загружается дольше чем срабатывает таймаут
139
+
140
+ if (props.isPauseReconnect) {
141
+ startInactivityTimer()
142
+ return
143
+ }
144
+
145
+ const expireTime =
146
+ props.expireTimeFromState || useCookie('token-expired-time').value
147
+
148
+ const isExpiredToken = checkIsTokenExpired(expireTime)
149
+
150
+ if (isExpiredToken) {
151
+ emits('show-redirect-login-modal', true)
152
+
153
+ if (props.isShowReconnectModal) {
154
+ emits('show-reconnect-modal', false)
155
+ }
156
+ }
157
+
158
+ if (!isExpiredToken && !props.isShowRedirectLoginModal) {
159
+ emits('show-reconnect-modal', true)
160
+ startInactivityTimer()
161
+ }
162
+
163
+ console.log('Пользователь бездействует!')
164
+ }, inactivityTimeout.value)
165
+ }
166
+
167
+ const resetInactivityTimer = (): void => {
168
+ // Сброс таймера бездействия при каждом действии пользователя
169
+ inactivityTimer.value !== null && clearTimeout(inactivityTimer.value)
170
+ startInactivityTimer()
171
+ }
172
+
173
+ onMounted(() => {
174
+ document.addEventListener('mousemove', resetInactivityTimer)
175
+
176
+ startInactivityTimer()
177
+ })
178
+
179
+ onBeforeUnmount(() => {
180
+ document.removeEventListener('mousemove', resetInactivityTimer)
181
+
182
+ inactivityTimer.value !== null && clearTimeout(inactivityTimer.value)
183
+ })
184
+ </script>
185
+
186
+ <style scoped lang="scss"></style>