gui: Show folder/device status on small screens (#8643)

gui: Show folder/device status on small screens

On larger screens, folder and device status is shown in a textual form
directly next to folder and device titles. However, on small screens,
only icons are currently shown, which may be ambiguous to new users, who
cannot possibly know what a specific icon means (see [1]). Thus, on
small screens only, display a new entry in folder/device info that
contains the same textual information that is shown in the title on
larger screens.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
Co-authored-by: André Colomb <src@andre.colomb.de>
This commit is contained in:
tomasz1986 2023-12-10 15:14:10 +01:00 committed by GitHub
parent a28de73031
commit d42fff1016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 49 deletions

View File

@ -180,7 +180,7 @@ input[type="checkbox"].extended-attributes-filter-rule-checkbox {
margin-right: .14285715em; margin-right: .14285715em;
} }
.remote-devices-panel { .inline-icon {
display: inline-block; display: inline-block;
} }
@ -460,15 +460,17 @@ ul.three-columns li, ul.two-columns li {
} }
@media (max-width: 419px) { @media (max-width: 419px) {
/* the selectors are build to target only the content of folder and device /* The selectors are build to target only the content of folder and device
panels as it would "destroy" e.g. out of sync or recent changes listings */ panels as it would "destroy" e.g. out of sync or recent changes listings.
The !important is needed to override .visible-xs that sets display to a
specific table element instead of block. */
div[id^='device-'].panel-collapse table, div[id^='device-'].panel-collapse table,
div[id^='folder-'].panel-collapse table, div[id^='folder-'].panel-collapse table,
div[id^='device-'].panel-collapse tbody, div[id^='device-'].panel-collapse tbody,
div[id^='folder-'].panel-collapse tbody, div[id^='folder-'].panel-collapse tbody,
div[id^='device-'].panel-collapse tr, div[id^='device-'].panel-collapse tr,
div[id^='folder-'].panel-collapse tr { div[id^='folder-'].panel-collapse tr {
display: block; display: block !important;
} }
div[id^='device-'].panel-collapse th, div[id^='device-'].panel-collapse th,
div[id^='folder-'].panel-collapse th, div[id^='folder-'].panel-collapse th,

View File

@ -99,6 +99,7 @@
"Device ID": "Device ID", "Device ID": "Device ID",
"Device Identification": "Device Identification", "Device Identification": "Device Identification",
"Device Name": "Device Name", "Device Name": "Device Name",
"Device Status": "Device Status",
"Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password", "Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password",
"Device rate limits": "Device rate limits", "Device rate limits": "Device rate limits",
"Device that last modified the item": "Device that last modified the item", "Device that last modified the item": "Device that last modified the item",
@ -168,6 +169,7 @@
"Folder ID": "Folder ID", "Folder ID": "Folder ID",
"Folder Label": "Folder Label", "Folder Label": "Folder Label",
"Folder Path": "Folder Path", "Folder Path": "Folder Path",
"Folder Status": "Folder Status",
"Folder Type": "Folder Type", "Folder Type": "Folder Type",
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder type \"{{receiveEncrypted}}\" can only be set when adding a new folder.", "Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder type \"{{receiveEncrypted}}\" can only be set when adding a new folder.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.", "Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.",

View File

@ -395,37 +395,12 @@
<span ng-if="folder.type == 'receiveencrypted'" class="fas fa-fw fa-lock"></span> <span ng-if="folder.type == 'receiveencrypted'" class="fas fa-fw fa-lock"></span>
</div> </div>
<div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)"> <div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs" aria-label="{{'Paused' | translate}}"><i class="fas fa-fw fa-pause"></i></span></span> <span class="hidden-xs">{{folderStatusText(folder)}}</span>
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs" aria-label="{{'Unknown' | translate}}"><i class="fas fa-fw fa-question-circle"></i></span></span> <span ng-switch-when="scanning" ng-if="scanPercentage(folder.id) != undefined">({{scanPercentage(folder.id) | percent}})</span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs" aria-label="{{'Unshared' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span> <span ng-switch-when="syncing">({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>
<span ng-switch-when="scan-waiting"><span class="hidden-xs" translate>Waiting to Scan</span><span class="visible-xs" aria-label="{{'Waiting to Scan' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span></span> <span class="inline-icon">
<span ng-switch-when="cleaning"><span class="hidden-xs" translate>Cleaning Versions</span><span class="visible-xs" aria-label="{{'Cleaning Versions' | translate}}"><i class="fas fa-fw fa-recycle"></i></span></span> <span class="visible-xs fa fa-fw {{folderStatusIcon(folder)}}" aria-label="{{folderStatusText(folder)}}"></span>
<span ng-switch-when="clean-waiting"><span class="hidden-xs" translate>Waiting to Clean</span><span class="visible-xs" aria-label="{{'Waiting to Clean' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span></span> </span>
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs" aria-label="{{'Stopped' | translate}}"><i class="fas fa-fw fa-stop"></i></span></span>
<span ng-switch-when="scanning">
<span class="hidden-xs" translate>Scanning</span>
<span class="hidden-xs" ng-if="scanPercentage(folder.id) != undefined">
({{scanPercentage(folder.id) | percent}})
</span>
<span class="visible-xs" aria-label="{{'Scanning' | translate}}"><i class="fas fa-fw fa-search"></i></span>
</span>
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="localadditions"><span class="hidden-xs" translate>Local Additions</span><span class="visible-xs" aria-label="{{'Local Additions' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="sync-waiting">
<span class="hidden-xs" translate>Waiting to Sync</span>
<span class="visible-xs" aria-label="{{'Waiting to Sync' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span>
</span>
<span ng-switch-when="sync-preparing">
<span class="hidden-xs" translate>Preparing to Sync</span>
<span class="visible-xs" aria-label="{{'Preparing to Sync' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span>
</span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span>
<span>({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>
</span>
<span ng-switch-when="outofsync"><span class="hidden-xs" translate>Out of Sync</span><span class="visible-xs" aria-label="{{'Out of Sync' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
<span ng-switch-when="faileditems"><span class="hidden-xs" translate>Failed Items</span><span class="visible-xs" aria-label="{{'Failed Items' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
<span ng-switch-when="localunencrypted"><span class="hidden-xs">{{'Unexpected Items' | translate}}</span><span class="visible-xs" aria-label="{{'Unexpected Items' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
</div> </div>
<div class="panel-title-text"> <div class="panel-title-text">
<span tooltip data-original-title="{{folder.label.length != 0 ? folder.id : ''}}">{{folder.label.length != 0 ? folder.label : folder.id}}</span> <span tooltip data-original-title="{{folder.label.length != 0 ? folder.id : ''}}">{{folder.label.length != 0 ? folder.label : folder.id}}</span>
@ -436,6 +411,10 @@
<div class="panel-body"> <div class="panel-body">
<table class="table table-condensed table-striped table-auto"> <table class="table table-condensed table-striped table-auto">
<tbody> <tbody>
<tr class="visible-xs">
<th><span class="fa fa-fw {{folderStatusIcon(folder)}}"></span>&nbsp;<span translate>Folder Status</span></th>
<td class="text-right">{{folderStatusText(folder)}}</td>
</tr>
<tr ng-show="folder.label != undefined && folder.label.length > 0"> <tr ng-show="folder.label != undefined && folder.label.length > 0">
<th><span class="fas fa-fw fa-info-circle"></span>&nbsp;<span translate>Folder ID</span></th> <th><span class="fas fa-fw fa-info-circle"></span>&nbsp;<span translate>Folder ID</span></th>
<td class="text-right no-overflow-ellipse">{{folder.id}}</td> <td class="text-right no-overflow-ellipse">{{folder.id}}</td>
@ -794,23 +773,16 @@
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | percent}}"></div> <div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | percent}}"></div>
<h4 class="panel-title"> <h4 class="panel-title">
<identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon> <identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon>
<span class="pull-right text-{{deviceClass(deviceCfg)}}"> <div class="panel-status pull-right text-{{deviceClass(deviceCfg)}}" ng-switch="deviceStatus(deviceCfg)">
<span ng-switch="deviceStatus(deviceCfg)" class="remote-devices-panel"> <span class="hidden-xs">{{deviceStatusText(deviceCfg)}}</span>
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span> <span ng-switch-when="syncing">({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)</span>
<span ng-switch-when="unused-insync"><span class="hidden-xs" translate>Connected (Unused)</span><span class="visible-xs" aria-label="{{'Connected (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span> <span class="inline-icon">
<span ng-switch-when="syncing"> <span class="visible-xs fa fa-fw {{deviceStatusIcon(deviceCfg)}}" aria-label="{{deviceStatusText(deviceCfg)}}"></span>
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)
</span>
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs" aria-label="{{'Paused' | translate}}"><i class="fas fa-fw fa-pause"></i></span></span>
<span ng-switch-when="unused-paused"><span class="hidden-xs" translate>Paused (Unused)</span><span class="visible-xs" aria-label="{{'Paused (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs" aria-label="{{'Disconnected' | translate}}"><i class="fas fa-fw fa-power-off"></i></span></span>
<span ng-switch-when="disconnected-inactive"><span class="hidden-xs" translate>Disconnected (Inactive)</span><span class="visible-xs" aria-label="{{'Disconnected (Inactive)' | translate}}"><i class="fas fa-fw fa-power-off"></i></span></span>
<span ng-switch-when="unused-disconnected"><span class="hidden-xs" translate>Disconnected (Unused)</span><span class="visible-xs" aria-label="{{'Disconnected (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
</span> </span>
<span class="remote-devices-panel"> <span class="inline-icon">
<span ng-class="rdConnTypeIcon(rdConnType(deviceCfg.deviceID))" class="reception reception-theme"></span> <span ng-class="rdConnTypeIcon(rdConnType(deviceCfg.deviceID))" class="reception reception-theme"></span>
</span> </span>
</span> </div>
<div class="panel-title-text">{{deviceName(deviceCfg)}}</div> <div class="panel-title-text">{{deviceName(deviceCfg)}}</div>
</h4> </h4>
</button> </button>
@ -818,6 +790,10 @@
<div class="panel-body"> <div class="panel-body">
<table class="table table-condensed table-striped table-auto"> <table class="table table-condensed table-striped table-auto">
<tbody> <tbody>
<tr class="visible-xs">
<th><span class="fa fa-fw {{deviceStatusIcon(deviceCfg)}}"></span>&nbsp;<span translate>Device Status</span></th>
<td class="text-right">{{deviceStatusText(deviceCfg)}}</td>
</tr>
<tr ng-if="!connections[deviceCfg.deviceID].connected"> <tr ng-if="!connections[deviceCfg.deviceID].connected">
<th><span class="fas fa-fw fa-eye"></span>&nbsp;<span translate>Last seen</span></th> <th><span class="fas fa-fw fa-eye"></span>&nbsp;<span translate>Last seen</span></th>
<td class="text-right"> <td class="text-right">

View File

@ -1151,6 +1151,113 @@ angular.module('syncthing.core')
} }
}; };
$scope.deviceStatusIcon = function(cfg) {
switch ($scope.deviceStatus(cfg)) {
case 'disconnected':
case 'disconnected-inactive':
return 'fa-power-off';
case 'insync':
return 'fa-check';
case 'paused':
return 'fa-pause';
case 'syncing':
return 'fa-sync';
case 'unused-disconnected':
case 'unused-insync':
case 'unused-paused':
return 'fa-unlink';
}
};
$scope.deviceStatusText = function(device) {
switch ($scope.deviceStatus(device)) {
case 'disconnected':
return $translate.instant('Disconnected');
case 'disconnected-inactive':
return $translate.instant('Disconnected (Inactive)');
case 'insync':
return $translate.instant('Up to Date');
case 'paused':
return $translate.instant('Paused');
case 'syncing':
return $translate.instant('Syncing');
case 'unused-disconnected':
return $translate.instant('Disconnected (Unused)');
case 'unused-insync':
return $translate.instant('Connected (Unused)');
case 'unused-paused':
return $translate.instant('Paused (Unused)');
}
};
$scope.folderStatusIcon = function(cfg) {
switch ($scope.folderStatus(cfg)) {
case 'clean-waiting':
case 'scan-waiting':
case 'sync-preparing':
case 'sync-waiting':
return 'fa-hourglass-half';
case 'cleaning':
return 'fa-recycle';
case 'faileditems':
case 'localunencrypted':
case 'outofsync':
return 'fa-exclamation-circle';
case 'idle':
case 'localadditions':
return 'fa-check';
case 'paused':
return 'fa-pause';
case 'scanning':
return 'fa-search';
case 'stopped':
return 'fa-stop';
case 'syncing':
return 'fa-sync';
case 'unknown':
return 'fa-question-circle';
case 'unshared':
return 'fa-unlink';
}
};
$scope.folderStatusText = function(folder) {
switch ($scope.folderStatus(folder)) {
case 'clean-waiting':
return $translate.instant('Waiting to Clean');
case 'cleaning':
return $translate.instant('Cleaning Versions');
case 'faileditems':
return $translate.instant('Failed Items');
case 'idle':
return $translate.instant('Up to Date');
case 'localadditions':
return $translate.instant('Local Additions');
case 'localunencrypted':
return $translate.instant('Unexpected Items');
case 'outofsync':
return $translate.instant('Out of Sync');
case 'paused':
return $translate.instant('Paused');
case 'scan-waiting':
return $translate.instant('Waiting to Scan');
case 'scanning':
return $translate.instant('Scanning');
case 'stopped':
return $translate.instant('Stopped');
case 'sync-preparing':
return $translate.instant('Preparing to Sync');
case 'sync-waiting':
return $translate.instant('Waiting to Sync');
case 'syncing':
return $translate.instant('Syncing');
case 'unknown':
return $translate.instant('Unknown');
case 'unshared':
return $translate.instant('Unshared');
}
};
$scope.deviceClass = function (deviceCfg) { $scope.deviceClass = function (deviceCfg) {
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') { if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
return 'info'; return 'info';