mirror of https://github.com/OpenVidu/openvidu.git
ov-components: Improves recording activity UI
Refactors the recording activity component's template and styles to use cards for displaying recording information. Enhances the display of recording metadata, including duration, size, and date, with appropriate icons. Adds visual cues for active recordings and improves overall responsiveness of the recording list.master
parent
e486665efd
commit
181c5f0789
|
@ -160,108 +160,106 @@
|
||||||
|
|
||||||
<!-- Recording list -->
|
<!-- Recording list -->
|
||||||
@if (recordingList.length > 0) {
|
@if (recordingList.length > 0) {
|
||||||
<div class="item recording-list-container">
|
<div class="recording-list-container">
|
||||||
<mat-list>
|
@for (recording of recordingList; track trackByRecordingId($index, recording)) {
|
||||||
@for (recording of recordingList; track trackByRecordingId($index, recording)) {
|
<div class="recording-card" [class.recording-active]="recording.status === recStatusEnum.STARTED">
|
||||||
<mat-list-item class="recording-item" [class.blink]="recording.status === recStatusEnum.STARTED">
|
<!-- Recording header with status indicator and info -->
|
||||||
<!-- Recording icon -->
|
<div class="recording-header">
|
||||||
<mat-icon class="recording-icon" matListItemIcon>video_file</mat-icon>
|
<div class="recording-status-indicator">
|
||||||
|
|
||||||
<!-- Recording title -->
|
|
||||||
<div matListItemTitle>
|
|
||||||
<span class="recording-name">{{ recording.filename }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recording status/progress -->
|
|
||||||
<div matListItemLine class="time-container">
|
|
||||||
@if (recording.status === recStatusEnum.STARTED) {
|
@if (recording.status === recStatusEnum.STARTED) {
|
||||||
<span>
|
<div class="status-dot recording-live"></div>
|
||||||
{{ 'PANEL.RECORDING.IN_PROGRESS' | translate }}
|
|
||||||
</span>
|
|
||||||
} @else {
|
} @else {
|
||||||
<span class="recording-duration">{{ recording.duration | duration }}</span>
|
<div class="status-dot recording-ready"></div>
|
||||||
<span class="recording-size">| {{ recording.size / 1024 / 1024 | number: '1.1-2' }} MBs</span>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recording date -->
|
<div class="recording-info">
|
||||||
@if (recording.status !== recStatusEnum.STARTED) {
|
<div class="recording-name">{{ recording.filename || 'Recording' }}</div>
|
||||||
<div matListItemLine class="recording-date">
|
|
||||||
{{ recording.startedAt | date: 'HH:mm - dd/MM/yyyy' }}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Recording action buttons -->
|
@if (recording.status === recStatusEnum.STARTED) {
|
||||||
@if (!isReadOnlyMode) {
|
<div class="recording-status-text recording-live-text">
|
||||||
@if (recording.status !== recStatusEnum.STARTED) {
|
{{ 'PANEL.RECORDING.IN_PROGRESS' | translate }}
|
||||||
<div id="recording-action-buttons" class="recording-actions">
|
</div>
|
||||||
@if (showControls.play) {
|
} @else {
|
||||||
<button
|
<div class="recording-metadata">
|
||||||
mat-icon-button
|
<span class="metadata-item">
|
||||||
(click)="play(recording)"
|
<mat-icon class="metadata-icon">schedule</mat-icon>
|
||||||
id="play-recording-btn"
|
{{ formatDuration(recording.duration) }}
|
||||||
matTooltip="{{ 'PANEL.RECORDING.PLAY' | translate }}"
|
</span>
|
||||||
class="action-button play-button"
|
<span class="metadata-item">
|
||||||
>
|
<mat-icon class="metadata-icon">storage</mat-icon>
|
||||||
<mat-icon>play_arrow</mat-icon>
|
{{ formatFileSize(recording.size) }}
|
||||||
</button>
|
</span>
|
||||||
}
|
<span class="metadata-item">
|
||||||
|
<mat-icon class="metadata-icon">today</mat-icon>
|
||||||
@if (showControls.externalView) {
|
{{ recording.startedAt | date: 'MMM d, y' }}
|
||||||
<button
|
</span>
|
||||||
mat-icon-button
|
|
||||||
(click)="onViewRecordingClicked.emit(recording.id)"
|
|
||||||
id="watch-recording-btn"
|
|
||||||
matTooltip="{{ 'PANEL.RECORDING.WATCH' | translate }}"
|
|
||||||
class="action-button watch-button"
|
|
||||||
>
|
|
||||||
<mat-icon>open_in_new</mat-icon>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (showControls.download) {
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
(click)="download(recording)"
|
|
||||||
id="download-recording-btn"
|
|
||||||
matTooltip="{{ 'PANEL.RECORDING.DOWNLOAD' | translate }}"
|
|
||||||
class="action-button download-button"
|
|
||||||
>
|
|
||||||
<mat-icon>download</mat-icon>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (showControls.delete) {
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
(click)="deleteRecording(recording)"
|
|
||||||
id="delete-recording-btn"
|
|
||||||
matTooltip="{{ 'PANEL.RECORDING.DELETE' | translate }}"
|
|
||||||
class="action-button delete-button"
|
|
||||||
>
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
} @else {
|
</div>
|
||||||
@if (recording.status !== recStatusEnum.STARTED) {
|
</div>
|
||||||
<div id="recording-action-buttons" class="recording-actions">
|
|
||||||
|
<!-- Actions menu row -->
|
||||||
|
@if (recording.status !== recStatusEnum.STARTED) {
|
||||||
|
<div class="recording-actions-menu">
|
||||||
|
@if (!isReadOnlyMode) {
|
||||||
|
@if (showControls.play) {
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="play(recording)"
|
||||||
|
matTooltip="{{ 'PANEL.RECORDING.PLAY' | translate }}"
|
||||||
|
class="action-btn action-play"
|
||||||
|
>
|
||||||
|
<mat-icon>play_circle</mat-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (showControls.externalView) {
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="onViewRecordingClicked.emit(recording.id)"
|
(click)="onViewRecordingClicked.emit(recording.id)"
|
||||||
id="watch-recording-btn"
|
|
||||||
matTooltip="{{ 'PANEL.RECORDING.WATCH' | translate }}"
|
matTooltip="{{ 'PANEL.RECORDING.WATCH' | translate }}"
|
||||||
class="action-button watch-button"
|
class="action-btn action-view"
|
||||||
>
|
>
|
||||||
<mat-icon>open_in_new</mat-icon>
|
<mat-icon>open_in_new</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
|
@if (showControls.download) {
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="download(recording)"
|
||||||
|
matTooltip="{{ 'PANEL.RECORDING.DOWNLOAD' | translate }}"
|
||||||
|
class="action-btn action-download"
|
||||||
|
>
|
||||||
|
<mat-icon>download</mat-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (showControls.delete) {
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="deleteRecording(recording)"
|
||||||
|
matTooltip="{{ 'PANEL.RECORDING.DELETE' | translate }}"
|
||||||
|
class="action-btn action-delete"
|
||||||
|
>
|
||||||
|
<mat-icon>delete_outline</mat-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="onViewRecordingClicked.emit(recording.id)"
|
||||||
|
matTooltip="{{ 'PANEL.RECORDING.WATCH' | translate }}"
|
||||||
|
class="action-btn action-view"
|
||||||
|
>
|
||||||
|
<mat-icon>visibility</mat-icon>
|
||||||
|
</button>
|
||||||
}
|
}
|
||||||
}
|
</div>
|
||||||
</mat-list-item>
|
}
|
||||||
}
|
</div>
|
||||||
</mat-list>
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -99,8 +99,236 @@
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modern recording list styles
|
||||||
.recording-list-container {
|
.recording-list-container {
|
||||||
margin-top: 10px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
max-height: 500px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--ov-accent-action-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-card {
|
||||||
|
background: var(--ov-surface-background-color);
|
||||||
|
border: 1px solid rgba(0, 102, 204, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.recording-active {
|
||||||
|
border-color: var(--ov-primary-action-color);
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
rgba(255, 87, 34, 0.05) 0%,
|
||||||
|
transparent 50%);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--ov-primary-action-color);
|
||||||
|
animation: pulse-border 2s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 5px;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-status-indicator {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-top: 2px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.recording-live {
|
||||||
|
background: var(--ov-primary-action-color);
|
||||||
|
box-shadow: 0 0 0 4px rgba(255, 87, 34, 0.2);
|
||||||
|
animation: pulse-dot 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.recording-ready {
|
||||||
|
background: var(--ov-accent-action-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
height: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-status-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.recording-live-text {
|
||||||
|
color: var(--ov-primary-action-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-metadata {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 4px;
|
||||||
|
height: auto;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
|
opacity: 0.7;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.metadata-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-actions-menu {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 1;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
height: 32px;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.action-play {
|
||||||
|
color: var(--ov-accent-action-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 102, 204, 0.1);
|
||||||
|
color: var(--ov-accent-action-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.action-view {
|
||||||
|
color: var(--ov-accent-action-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 102, 204, 0.1);
|
||||||
|
color: var(--ov-accent-action-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.action-download {
|
||||||
|
color: #4CAF50;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(76, 175, 80, 0.1);
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.action-delete {
|
||||||
|
color: var(--ov-error-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(244, 67, 54, 0.1);
|
||||||
|
color: var(--ov-error-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animations
|
||||||
|
@keyframes pulse-dot {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-border {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-actions {
|
.recording-actions {
|
||||||
|
@ -116,6 +344,62 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile responsive design for new recording cards
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.recording-list-container {
|
||||||
|
padding-top: 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-card {
|
||||||
|
padding: 8px;
|
||||||
|
height: 100px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-header {
|
||||||
|
gap: 8px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-info {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-metadata {
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-item {
|
||||||
|
font-size: 11px;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
.metadata-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-actions-menu {
|
||||||
|
opacity: 1; // Always visible on mobile
|
||||||
|
gap: 6px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.recording-message {
|
.recording-message {
|
||||||
color: var(--ov-text-surface-color);
|
color: var(--ov-text-surface-color);
|
||||||
}
|
}
|
||||||
|
@ -124,19 +408,44 @@
|
||||||
color: var(--ov-error-color);
|
color: var(--ov-error-color);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disable-recording-btn {
|
.disable-recording-btn {
|
||||||
background-color: var(--ov-secondary-action-color) !important;
|
background-color: var(--ov-secondary-action-color) !important;
|
||||||
color: var(--ov-text-surface-color) !important;
|
color: var(--ov-text-surface-color) !important;
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
.recording-name {
|
|
||||||
font-size: 14px;
|
// Enhanced empty state
|
||||||
font-weight: bold;
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 32px 16px;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-date {
|
.empty-state-icon {
|
||||||
font-size: 12px !important;
|
margin-bottom: 16px;
|
||||||
font-style: italic;
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
color: var(--ov-accent-action-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: var(--ov-text-surface-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0.7;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-allowed-message {
|
.not-allowed-message {
|
||||||
|
|
|
@ -304,6 +304,40 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
||||||
this.onViewRecordingsClicked.emit();
|
this.onViewRecordingsClicked.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* Format duration in seconds to a readable format (e.g., "2m 30s")
|
||||||
|
*/
|
||||||
|
formatDuration(seconds: number): string {
|
||||||
|
if (!seconds || seconds < 0) return '0s';
|
||||||
|
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const remainingSeconds = Math.floor(seconds % 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h ${minutes}m`;
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return `${minutes}m ${remainingSeconds}s`;
|
||||||
|
} else {
|
||||||
|
return `${remainingSeconds}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* Format file size in bytes to a readable format (e.g., "2.5 MB")
|
||||||
|
*/
|
||||||
|
formatFileSize(bytes: number): string {
|
||||||
|
if (!bytes || bytes < 0) return '0 B';
|
||||||
|
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
|
const size = bytes / Math.pow(1024, i);
|
||||||
|
|
||||||
|
return `${size.toFixed(1)} ${sizes[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
private subscribeToConfigChanges() {
|
private subscribeToConfigChanges() {
|
||||||
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
|
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
|
||||||
this.isReadOnlyMode = readOnly;
|
this.isReadOnlyMode = readOnly;
|
||||||
|
|
Loading…
Reference in New Issue