This commit is contained in:
parent
0ea30552dd
commit
5b80951e3e
|
@ -32,6 +32,7 @@ import './float-button';
|
|||
import './icon-menu';
|
||||
import './icon-button';
|
||||
import './input-number';
|
||||
import './json-value';
|
||||
import './label-value';
|
||||
import './range';
|
||||
import './input-time';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<span></span>
|
|
@ -0,0 +1,56 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from 'core/lib/component';
|
||||
import './style.scss';
|
||||
|
||||
const maxStrLen = 25;
|
||||
|
||||
/**
|
||||
* Displays pretty JSON value.
|
||||
*
|
||||
* @property {*} value The value
|
||||
*/
|
||||
export default class Controller extends Component {
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
this._value = value;
|
||||
const span = this.element;
|
||||
const formattedValue = this.formatValue(value);
|
||||
span.textContent = formattedValue;
|
||||
span.title = typeof value == 'string' && value.length > maxStrLen ? value : '';
|
||||
span.className = `js-${value == null ? 'null' : typeof value}`;
|
||||
}
|
||||
|
||||
formatValue(value) {
|
||||
if (value == null) return '∅';
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
return value ? '✓' : '✗';
|
||||
case 'string':
|
||||
return value.length <= maxStrLen
|
||||
? value
|
||||
: value.substring(0, maxStrLen) + '...';
|
||||
case 'object':
|
||||
if (value instanceof Date) {
|
||||
const hasZeroTime =
|
||||
value.getHours() === 0 &&
|
||||
value.getMinutes() === 0 &&
|
||||
value.getSeconds() === 0;
|
||||
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
|
||||
return this.$filter('date')(value, format);
|
||||
} else
|
||||
return value;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnJsonValue', {
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
value: '<?'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
vn-json-value {
|
||||
display: inline;
|
||||
|
||||
&.js-string {
|
||||
color: #d172cc;
|
||||
}
|
||||
&.js-object {
|
||||
/*color: #d1a572;*/
|
||||
color: #d172cc;
|
||||
}
|
||||
&.js-number {
|
||||
color: #85d0ff;
|
||||
}
|
||||
&.js-boolean {
|
||||
color: #7dc489;
|
||||
}
|
||||
&.js-null {
|
||||
color: #cd7c7c;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
|
@ -41,10 +41,15 @@ vn-table {
|
|||
display: table-row;
|
||||
height: 48px;
|
||||
}
|
||||
vn-thead, .vn-thead,
|
||||
vn-tbody, .vn-tbody,
|
||||
vn-tfoot, .vn-tfoot,
|
||||
thead, tbody, tfoot {
|
||||
& > thead,
|
||||
& > tbody,
|
||||
& > tfoot,
|
||||
& > vn-thead,
|
||||
& > vn-tbody,
|
||||
& > vn-tfoot,
|
||||
& > .vn-thead,
|
||||
& > .vn-tbody,
|
||||
& > .vn-tfoot {
|
||||
& > * {
|
||||
display: table-row;
|
||||
|
||||
|
@ -111,14 +116,14 @@ vn-table {
|
|||
color: inherit;
|
||||
}
|
||||
}
|
||||
a.vn-tbody {
|
||||
& > a.vn-tbody {
|
||||
&.clickable {
|
||||
@extend %clickable;
|
||||
}
|
||||
}
|
||||
vn-tbody > *,
|
||||
.vn-tbody > *,
|
||||
tbody > * {
|
||||
& > vn-tbody > *,
|
||||
& > .vn-tbody > *,
|
||||
& > tbody > * {
|
||||
border-bottom: $border-thin;
|
||||
|
||||
&:last-child {
|
||||
|
|
|
@ -9,63 +9,99 @@
|
|||
limit="20"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-data-viewer model="model" class="vn-w-xl">
|
||||
<vn-data-viewer model="model" class="vn-w-md">
|
||||
<vn-card>
|
||||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="creationDate">Date</vn-th>
|
||||
<vn-th field="userFk" shrink>User</vn-th>
|
||||
<vn-th field="changedModel" ng-if="$ctrl.showModelName" shrink>Model</vn-th>
|
||||
<vn-th field="action" shrink>Action</vn-th>
|
||||
<vn-th field="changedModelValue" ng-if="$ctrl.showModelName">Name</vn-th>
|
||||
<vn-th expand>Changes</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="log in $ctrl.logs">
|
||||
<vn-td shrink-datetime>
|
||||
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
|
||||
translate>{{::log.user.name || 'System' | translate}}
|
||||
<table class="vn-table" model="model">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate field="action" class="action">
|
||||
Action
|
||||
</th>
|
||||
<th field="changedModel">
|
||||
<span ng-if="$ctrl.showModelName" translate>Model</span>
|
||||
</th>
|
||||
<th translate field="creationDate" class="date">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ng-repeat="log in $ctrl.logs">
|
||||
<tr class="change-header">
|
||||
<td class="user">
|
||||
<span ng-if="::log.user.worker.id"
|
||||
class="link"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)">
|
||||
{{::log.user.name}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td ng-if="$ctrl.showModelName">
|
||||
{{::log.changedModel}}
|
||||
</vn-td>
|
||||
<vn-td shrink translate>
|
||||
{{::$ctrl.actionsText[log.action]}}
|
||||
</vn-td>
|
||||
<vn-td ng-if="$ctrl.showModelName">
|
||||
{{::log.changedModelValue}}
|
||||
</vn-td>
|
||||
<vn-td expand>
|
||||
<table class="attributes">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate class="field">Field</th>
|
||||
<th translate>Before</th>
|
||||
<th translate>After</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="prop in ::log.props">
|
||||
<td class="field">{{prop.name}}</td>
|
||||
<td class="before">{{prop.old}}</td>
|
||||
<td class="after">{{prop.new}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="log.description != null">
|
||||
{{::log.description}}
|
||||
<span ng-if="::!log.user.worker.id" translate>
|
||||
System
|
||||
</span>
|
||||
</td>
|
||||
<td title="{{::log.changedModelValue}}">
|
||||
<span class="model-name" ng-if="::$ctrl.showModelName">
|
||||
{{::log.changedModel}}
|
||||
</span>
|
||||
<span class="model-value">
|
||||
{{::log.changedModelValue}}
|
||||
</span>
|
||||
<span class="model-id">
|
||||
#{{::log.changedModelId}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="date">
|
||||
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="change-detail">
|
||||
<td class="action">
|
||||
<span class="chip {{::$ctrl.actionsClass[log.action]}}" translate>
|
||||
{{::$ctrl.actionsText[log.action]}}
|
||||
</span>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<div vn-id="changes" class="changes {{::log.props.length ? 'props' : 'no-props'}}">
|
||||
<vn-icon icon="visibility"
|
||||
class="expand-button"
|
||||
ng-click="$ctrl.toggleAttributes(log, changes, true)">
|
||||
</vn-icon>
|
||||
<vn-icon icon="visibility_off"
|
||||
class="shrink-button"
|
||||
ng-click="$ctrl.toggleAttributes(log, changes, false)">
|
||||
</vn-icon>
|
||||
<div class="changes-wrapper">
|
||||
<span ng-if="::log.props.length"
|
||||
class="attributes">
|
||||
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
|
||||
class="basic-json">
|
||||
<span class="json-field">{{::prop.name}}:</span>
|
||||
<vn-json-value value="$ctrl.mainVal(prop, log.action)"></vn-json-value><span ng-if="::!$last">,</span>
|
||||
</span>
|
||||
<div ng-if="log.expand"
|
||||
class="expanded-json">
|
||||
<div ng-repeat="prop in ::log.props">
|
||||
<span class="json-field">{{::prop.name}}:</span>
|
||||
<vn-json-value value="$ctrl.mainVal(prop, log.action)"></vn-json-value>
|
||||
<span ng-if="::log.action == 'update'">
|
||||
← <vn-json-value value="prop.old"></vn-json-value>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span ng-if="::!log.props.length"
|
||||
class="description">
|
||||
{{::log.description}}
|
||||
</span>
|
||||
<span ng-if="!log.description && !log.props.length"
|
||||
class="no-changes"
|
||||
translate>
|
||||
No changes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<vn-pagination model="model"></vn-pagination>
|
||||
</vn-card>
|
||||
</vn-data-viewer>
|
||||
|
|
|
@ -13,6 +13,12 @@ export default class Controller extends Section {
|
|||
delete: 'Deletes',
|
||||
select: 'Views'
|
||||
};
|
||||
this.actionsClass = {
|
||||
insert: 'success',
|
||||
update: 'warning',
|
||||
delete: 'alert',
|
||||
select: 'notice'
|
||||
};
|
||||
this.filter = {
|
||||
include: [{
|
||||
relation: 'user',
|
||||
|
@ -50,8 +56,8 @@ export default class Controller extends Section {
|
|||
for (const prop of props) {
|
||||
log.props.push({
|
||||
name: locale[prop] || prop,
|
||||
old: this.formatValue(oldValues[prop]),
|
||||
new: this.formatValue(newValues[prop])
|
||||
old: this.castValue(oldValues[prop]),
|
||||
new: this.castValue(newValues[prop])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -61,31 +67,19 @@ export default class Controller extends Section {
|
|||
return !(this.changedModel && this.changedModelId);
|
||||
}
|
||||
|
||||
formatValue(value) {
|
||||
let type = typeof value;
|
||||
castValue(value) {
|
||||
return typeof value === 'string' && validDate.test(value)
|
||||
? new Date(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
if (type === 'string' && validDate.test(value)) {
|
||||
value = new Date(value);
|
||||
type = typeof value;
|
||||
}
|
||||
mainVal(prop, action) {
|
||||
return action == 'delete' ? prop.old : prop.new;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'boolean':
|
||||
return value ? '✓' : '✗';
|
||||
case 'object':
|
||||
if (value instanceof Date) {
|
||||
const hasZeroTime =
|
||||
value.getHours() === 0 &&
|
||||
value.getMinutes() === 0 &&
|
||||
value.getSeconds() === 0;
|
||||
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
|
||||
return this.$filter('date')(value, format);
|
||||
}
|
||||
else
|
||||
return value;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
toggleAttributes(log, changesEl, force) {
|
||||
log.expand = force;
|
||||
changesEl.classList.toggle('expanded', force);
|
||||
}
|
||||
|
||||
showWorkerDescriptor(event, workerId) {
|
||||
|
|
|
@ -13,3 +13,4 @@ Views: Visualiza
|
|||
System: Sistema
|
||||
note: nota
|
||||
Changes: Cambios
|
||||
No changes: No hay cambios
|
||||
|
|
|
@ -1,66 +1,127 @@
|
|||
@import "variables";
|
||||
|
||||
vn-log {
|
||||
vn-td {
|
||||
vertical-align: initial !important;
|
||||
.vn-table {
|
||||
table-layout: fixed;
|
||||
|
||||
& > thead,
|
||||
& > tbody {
|
||||
& > tr {
|
||||
td, th {
|
||||
&:first-child {
|
||||
padding-left: 16px;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
& > thead > tr > th {
|
||||
max-width: initial;
|
||||
}
|
||||
& > tbody {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
& > tr {
|
||||
border-bottom: none;
|
||||
height: initial;
|
||||
|
||||
& > td {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
&.action > .chip {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
&.change-header > td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
&.change-detail > td {
|
||||
padding-top: 6px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
}
|
||||
th, td {
|
||||
&.action,
|
||||
&.user {
|
||||
width: 90px;
|
||||
}
|
||||
&.date {
|
||||
width: 120px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
.model-value {
|
||||
font-style: italic;
|
||||
color: #c7bd2b;
|
||||
}
|
||||
.model-id {
|
||||
color: $color-font-secondary;
|
||||
font-size: .9em;
|
||||
}
|
||||
.changes {
|
||||
display: none;
|
||||
}
|
||||
.label {
|
||||
overflow: hidden;
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
border-radius: 4px;
|
||||
color: $color-font-secondary;
|
||||
}
|
||||
.value {
|
||||
color: $color-font;
|
||||
}
|
||||
transition: max-height 150ms ease-in-out;
|
||||
max-height: 28px;
|
||||
position: relative;
|
||||
|
||||
@media screen and (max-width: 1570px) {
|
||||
vn-table .expendable {
|
||||
& > .expand-button,
|
||||
& > .shrink-button {
|
||||
display: none;
|
||||
}
|
||||
.changes {
|
||||
padding-top: 10px;
|
||||
display: block;
|
||||
&.props {
|
||||
padding-right: 24px;
|
||||
|
||||
& > .expand-button,
|
||||
& > .shrink-button {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 8px;
|
||||
font-size: inherit;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
& > .expand-button {
|
||||
display: block;
|
||||
}
|
||||
&.expanded {
|
||||
max-height: 500px;
|
||||
padding-right: 0;
|
||||
|
||||
& > .changes-wrapper {
|
||||
text-overflow: initial;
|
||||
white-space: initial;
|
||||
}
|
||||
& > .shrink-button {
|
||||
display: block;
|
||||
}
|
||||
& > .expand-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.attributes {
|
||||
width: 100%;
|
||||
& > .changes-wrapper {
|
||||
padding: 4px 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
tr {
|
||||
height: 10px;
|
||||
|
||||
& > td {
|
||||
padding: 2px;
|
||||
& > .no-changes {
|
||||
font-style: italic;
|
||||
}
|
||||
& > td.field,
|
||||
& > th.field {
|
||||
width: 20%;
|
||||
color: gray;
|
||||
}
|
||||
& > td.before,
|
||||
& > th.before,
|
||||
& > td.after,
|
||||
& > th.after {
|
||||
width: 40%;
|
||||
white-space: pre-line;
|
||||
.json-field {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 400px;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
}
|
||||
.no-ellipsize,
|
||||
[no-ellipsize] {
|
||||
text-overflow: '';
|
||||
white-space: normal;
|
||||
overflow: auto;
|
||||
}
|
||||
.alignSpan {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue