fix: RTL support in call view

Signed-off-by: Dorra Jaouad <dorra.jaoued7@gmail.com>
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
This commit is contained in:
Dorra Jaouad 2025-01-29 15:14:03 +01:00 committed by Maksim Sukharev
parent 9815309a80
commit d19a764143
11 changed files with 77 additions and 59 deletions

View file

@ -806,7 +806,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../assets/variables'; @import '../../assets/variables';
/* stylelint-disable csstools/use-logical */
#call-container { #call-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -867,7 +867,7 @@ export default {
.local-video { .local-video {
position: absolute; position: absolute;
right: 0; inset-inline-end: 0;
bottom: 0; bottom: 0;
width: 300px; width: 300px;
height: 250px; height: 250px;

View file

@ -12,10 +12,10 @@
:aria-label="stripeButtonTitle" :aria-label="stripeButtonTitle"
@click="handleClickStripeCollapse"> @click="handleClickStripeCollapse">
<template #icon> <template #icon>
<ChevronDown v-if="stripeOpen" <IconChevronDown v-if="stripeOpen"
fill-color="#ffffff" fill-color="#ffffff"
:size="20" /> :size="20" />
<ChevronUp v-else <IconChevronUp v-else
fill-color="#ffffff" fill-color="#ffffff"
:size="20" /> :size="20" />
</template> </template>
@ -29,7 +29,8 @@
:aria-label="t('spreed', 'Previous page of videos')" :aria-label="t('spreed', 'Previous page of videos')"
@click="handleClickPrevious"> @click="handleClickPrevious">
<template #icon> <template #icon>
<ChevronLeft fill-color="#ffffff" <IconChevronLeft class="bidirectional-icon"
fill-color="#ffffff"
:size="20" /> :size="20" />
</template> </template>
</NcButton> </NcButton>
@ -90,7 +91,8 @@
:aria-label="t('spreed', 'Next page of videos')" :aria-label="t('spreed', 'Next page of videos')"
@click="handleClickNext"> @click="handleClickNext">
<template #icon> <template #icon>
<ChevronRight fill-color="#ffffff" <IconChevronRight class="bidirectional-icon"
fill-color="#ffffff"
:size="20" /> :size="20" />
</template> </template>
</NcButton> </NcButton>
@ -111,7 +113,10 @@
aria-label="Toggle screenshot mode" aria-label="Toggle screenshot mode"
@click="screenshotMode = !screenshotMode"> @click="screenshotMode = !screenshotMode">
<template #icon> <template #icon>
<ChevronLeft v-if="!screenshotMode" fill-color="#00FF41" :size="20" /> <IconChevronLeft v-if="!screenshotMode"
class="bidirectional-icon"
fill-color="#00FF41"
:size="20" />
</template> </template>
</NcButton> </NcButton>
<div v-if="!screenshotMode" class="dev-mode__data"> <div v-if="!screenshotMode" class="dev-mode__data">
@ -143,10 +148,10 @@
import debounce from 'debounce' import debounce from 'debounce'
import { inject, ref } from 'vue' import { inject, ref } from 'vue'
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue' import IconChevronDown from 'vue-material-design-icons/ChevronDown.vue'
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue' import IconChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue' import IconChevronRight from 'vue-material-design-icons/ChevronRight.vue'
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue' import IconChevronUp from 'vue-material-design-icons/ChevronUp.vue'
import { subscribe, unsubscribe } from '@nextcloud/event-bus' import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state' import { loadState } from '@nextcloud/initial-state'
@ -179,10 +184,10 @@ export default {
NcButton, NcButton,
TransitionWrapper, TransitionWrapper,
VideoBottomBar, VideoBottomBar,
ChevronRight, IconChevronDown,
ChevronLeft, IconChevronLeft,
ChevronUp, IconChevronRight,
ChevronDown, IconChevronUp,
}, },
props: { props: {
@ -958,7 +963,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.grid-main-wrapper { .grid-main-wrapper {
--navigation-position: calc(var(--default-grid-baseline) * 2); --navigation-position: calc(var(--default-grid-baseline) * 2);
position: relative; position: relative;
@ -978,7 +982,7 @@ export default {
display: flex; display: flex;
position: relative; position: relative;
bottom: 0; bottom: 0;
left: 0; inset-inline-start: 0;
} }
.grid { .grid {
@ -1028,6 +1032,7 @@ export default {
.dev-mode__title { .dev-mode__title {
position: absolute; position: absolute;
/* stylelint-disable-next-line csstools/use-logical */
left: var(--default-clickable-area); left: var(--default-clickable-area);
color: #00FF41; color: #00FF41;
z-index: 100; z-index: 100;
@ -1040,14 +1045,17 @@ export default {
.dev-mode__toggle { .dev-mode__toggle {
position: fixed !important; position: fixed !important;
/* stylelint-disable-next-line csstools/use-logical */
left: 20px; left: 20px;
top: calc(2 * var(--header-height)); top: calc(2 * var(--header-height));
} }
.dev-mode__data { .dev-mode__data {
direction: ltr;
font-family: monospace; font-family: monospace;
position: fixed; position: fixed;
color: #00FF41; color: #00FF41;
/* stylelint-disable-next-line csstools/use-logical */
left: 20px; left: 20px;
top: calc(2 * var(--header-height) + 40px); top: calc(2 * var(--header-height) + 40px);
padding: 5px; padding: 5px;
@ -1082,11 +1090,11 @@ export default {
top: calc(50% - var(--default-clickable-area) / 2); top: calc(50% - var(--default-clickable-area) / 2);
&__previous { &__previous {
left: calc(var(--default-grid-baseline) * 2); inset-inline-start: calc(var(--default-grid-baseline) * 2);
} }
&__next { &__next {
right: calc(var(--default-grid-baseline) * 2); inset-inline-end: calc(var(--default-grid-baseline) * 2);
} }
} }
@ -1095,11 +1103,11 @@ export default {
top: calc(var(--navigation-position) + var(--grid-gap)); top: calc(var(--navigation-position) + var(--grid-gap));
&__previous { &__previous {
left: var(--navigation-position); inset-inline-start: var(--navigation-position);
} }
&__next { &__next {
right: calc(var(--navigation-position) + var(--grid-gap)); inset-inline-end: calc(var(--navigation-position) + var(--grid-gap));
} }
} }
} }
@ -1107,7 +1115,7 @@ export default {
.stripe--collapse { .stripe--collapse {
position: absolute !important; position: absolute !important;
top: calc(-1 * (var(--default-clickable-area) + var(--navigation-position) / 2)); top: calc(-1 * (var(--default-clickable-area) + var(--navigation-position) / 2));
right: calc(var(--navigation-position) / 2) ; inset-inline-end: calc(var(--navigation-position) / 2) ;
} }
.stripe--collapse, .stripe--collapse,

View file

@ -218,6 +218,6 @@ export default {
.popover-hint { .popover-hint {
padding: calc(3 * var(--default-grid-baseline)); padding: calc(3 * var(--default-grid-baseline));
max-width: 300px; max-width: 300px;
text-align: left; text-align: start;
} }
</style> </style>

View file

@ -360,7 +360,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.not-connected { .not-connected {
video, video,
.avatar-container { .avatar-container {
@ -422,7 +421,7 @@ export default {
.video-loading { .video-loading {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; inset-inline-end: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
@ -451,7 +450,7 @@ export default {
height: 100%; height: 100%;
width: 100%; width: 100%;
top: 0; top: 0;
left: 0; inset-inline-start: 0;
border-radius: var(--border-radius-element, calc(var(--default-clickable-area) / 2)); border-radius: var(--border-radius-element, calc(var(--default-clickable-area) / 2));
} }
@ -508,7 +507,7 @@ export default {
.presenter-icon__hide { .presenter-icon__hide {
position: absolute; position: absolute;
color: white; color: white;
left: calc(50% - var(--default-clickable-area) / 2); inset-inline-start: calc(50% - var(--default-clickable-area) / 2);
top: calc(100% - var(--default-grid-baseline) - var(--default-clickable-area)); top: calc(100% - var(--default-grid-baseline) - var(--default-clickable-area));
opacity: 0.7; opacity: 0.7;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);

View file

@ -10,7 +10,7 @@
:resizable="false" :resizable="false"
:h="presenterOverlaySize" :h="presenterOverlaySize"
:w="presenterOverlaySize" :w="presenterOverlaySize"
:x="10" :x="isRTL ? parentWidth - presenterOverlaySize - 10 : 10"
:y="10" :y="10"
@dragging="isDragging = true" @dragging="isDragging = true"
@dragstop="isDragging = false"> @dragstop="isDragging = false">
@ -50,11 +50,12 @@
<script> <script>
import { ref } from 'vue'
import VueDraggableResizable from 'vue-draggable-resizable' import VueDraggableResizable from 'vue-draggable-resizable'
import AccountBox from 'vue-material-design-icons/AccountBoxOutline.vue' import AccountBox from 'vue-material-design-icons/AccountBoxOutline.vue'
import { t } from '@nextcloud/l10n' import { t, isRTL } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
@ -106,6 +107,14 @@ export default {
emits: ['click'], emits: ['click'],
setup() {
const parentWidth = ref(document.getElementById('videos').getBoundingClientRect().width)
return {
parentWidth,
isRTL,
}
},
data() { data() {
return { return {
resizeObserver: null, resizeObserver: null,
@ -150,10 +159,10 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.presenter-overlay { .presenter-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
/* stylelint-disable-next-line csstools/use-logical */
left: 0; left: 0;
} }
@ -181,7 +190,7 @@ export default {
position: absolute !important; position: absolute !important;
opacity: .7; opacity: .7;
bottom: 48px; bottom: 48px;
right: 0; inset-inline-end: 0;
#call-container:hover & { #call-container:hover & {
background-color: rgba(0, 0, 0, 0.1) !important; background-color: rgba(0, 0, 0, 0.1) !important;

View file

@ -216,11 +216,10 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.toaster { .toaster {
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
left: 0; inset-inline-start: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
@ -231,7 +230,7 @@ export default {
.toast { .toast {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: var(--horizontal-offset, 0); inset-inline-start: var(--horizontal-offset, 0);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;

View file

@ -204,7 +204,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.screenContainer { .screenContainer {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -215,7 +214,7 @@ export default {
height: 100%; height: 100%;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; inset-inline-start: 0;
&--fit { &--fit {
object-fit: contain; object-fit: contain;
} }

View file

@ -40,10 +40,9 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.video-background { .video-background {
position: absolute; position: absolute;
left: 0; inset-inline-start: 0;
top: 0; top: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -55,7 +54,7 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
top: 0; top: 0;
left: 0; inset-inline-start: 0;
} }
} }
</style> </style>

View file

@ -628,7 +628,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.not-connected { .not-connected {
video, video,
.avatar-container { .avatar-container {
@ -693,7 +692,7 @@ export default {
& > .dev-mode-video--presenter { & > .dev-mode-video--presenter {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; inset-inline-start: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
object-fit: cover; object-fit: cover;
@ -704,7 +703,7 @@ export default {
.video-loading { .video-loading {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; inset-inline-end: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
@ -742,7 +741,7 @@ export default {
height: 100%; height: 100%;
width: 100%; width: 100%;
top: 0; top: 0;
left: 0; inset-inline-start: 0;
border-radius: var(--border-radius-element, calc(var(--default-clickable-area) / 2)); border-radius: var(--border-radius-element, calc(var(--default-clickable-area) / 2));
} }
@ -765,7 +764,7 @@ export default {
.presenter-icon__hide { .presenter-icon__hide {
position: absolute; position: absolute;
color: white; color: white;
left: calc(50% - var(--default-clickable-area) / 2); inset-inline-start: calc(50% - var(--default-clickable-area) / 2);
top: calc(100% - var(--default-grid-baseline) - var(--default-clickable-area)); top: calc(100% - var(--default-grid-baseline) - var(--default-clickable-area));
opacity: 0.7; opacity: 0.7;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);

View file

@ -8,10 +8,7 @@
<Portal> <Portal>
<!-- Add .app-talk to use Talk icon classes outside of #content-vue --> <!-- Add .app-talk to use Talk icon classes outside of #content-vue -->
<div class="viewer-overlay app-talk" <div class="viewer-overlay app-talk"
:style="{ :style="computedStyle">
right: position.right + 'px',
bottom: position.bottom + 'px'
}">
<div class="viewer-overlay__collapse" <div class="viewer-overlay__collapse"
:class="{ collapsed: isCollapsed }"> :class="{ collapsed: isCollapsed }">
<NcButton type="secondary" <NcButton type="secondary"
@ -105,7 +102,7 @@ import ArrowExpand from 'vue-material-design-icons/ArrowExpand.vue'
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue' import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue' import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
import { t } from '@nextcloud/l10n' import { t, isRTL } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
@ -194,6 +191,7 @@ export default {
observer: null, observer: null,
position: { position: {
right: 0, right: 0,
left: 0,
bottom: 0, bottom: 0,
}, },
} }
@ -211,6 +209,13 @@ export default {
showLocalScreen() { showLocalScreen() {
return this.hasLocalScreen && this.screens[0] === localCallParticipantModel.attributes.peerId return this.hasLocalScreen && this.screens[0] === localCallParticipantModel.attributes.peerId
}, },
computedStyle() {
return {
[isRTL() ? 'left' : 'right']: this.position[isRTL() ? 'left' : 'right'] + 'px',
bottom: this.position.bottom + 'px'
}
},
}, },
mounted() { mounted() {
@ -233,8 +238,12 @@ export default {
}, },
updatePosition() { updatePosition() {
const { right, bottom } = this.$refs.ghost.getBoundingClientRect() const { left, right, bottom } = this.$refs.ghost.getBoundingClientRect()
this.position.right = window.innerWidth - right if (isRTL()) {
this.position.left = left
} else {
this.position.right = window.innerWidth - right
}
this.position.bottom = window.innerHeight - bottom this.position.bottom = window.innerHeight - bottom
}, },
}, },
@ -242,12 +251,10 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* stylelint-disable csstools/use-logical */
.viewer-overlay-ghost { .viewer-overlay-ghost {
position: absolute; position: absolute;
bottom: 8px; bottom: 8px;
right: 8px; inset-inline: 0 8px;
left: 0;
} }
.viewer-overlay { .viewer-overlay {
@ -270,7 +277,7 @@ export default {
.viewer-overlay__collapse { .viewer-overlay__collapse {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; inset-inline-end: 8px;
z-index: 100; z-index: 100;
} }
@ -286,7 +293,7 @@ export default {
.video-overlay__top-bar { .video-overlay__top-bar {
position: absolute; position: absolute;
top: 8px; top: 8px;
left: 8px; inset-inline-start: 8px;
z-index: 100; z-index: 100;
} }
@ -309,13 +316,13 @@ export default {
max-height: calc(var(--max-width) * var(--aspect-ratio)); max-height: calc(var(--max-width) * var(--aspect-ratio));
/* Note: because of transition it always has position absolute on animation */ /* Note: because of transition it always has position absolute on animation */
bottom: 0; bottom: 0;
right: 0; inset-inline-end: 0;
} }
.viewer-overlay__local-video { .viewer-overlay__local-video {
position: absolute; position: absolute;
bottom: 8px; bottom: 8px;
right: 8px; inset-inline-end: 8px;
width: 25%; width: 25%;
height: 25%; height: 25%;
overflow: hidden; overflow: hidden;

View file

@ -18,7 +18,6 @@ stylelintConfig.plugins.push('stylelint-use-logical')
stylelintConfig.rules['csstools/use-logical'] = [ stylelintConfig.rules['csstools/use-logical'] = [
'always', 'always',
{ {
severity: 'warning',
// Only lint LTR-RTL properties for now // Only lint LTR-RTL properties for now
except: [ except: [
// Position properties // Position properties