This commit is contained in:
parent
0ea30552dd
commit
5b80951e3e
|
@ -32,6 +32,7 @@ import './float-button';
|
||||||
import './icon-menu';
|
import './icon-menu';
|
||||||
import './icon-button';
|
import './icon-button';
|
||||||
import './input-number';
|
import './input-number';
|
||||||
|
import './json-value';
|
||||||
import './label-value';
|
import './label-value';
|
||||||
import './range';
|
import './range';
|
||||||
import './input-time';
|
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;
|
display: table-row;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
vn-thead, .vn-thead,
|
& > thead,
|
||||||
vn-tbody, .vn-tbody,
|
& > tbody,
|
||||||
vn-tfoot, .vn-tfoot,
|
& > tfoot,
|
||||||
thead, tbody, tfoot {
|
& > vn-thead,
|
||||||
|
& > vn-tbody,
|
||||||
|
& > vn-tfoot,
|
||||||
|
& > .vn-thead,
|
||||||
|
& > .vn-tbody,
|
||||||
|
& > .vn-tfoot {
|
||||||
& > * {
|
& > * {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
|
|
||||||
|
@ -111,14 +116,14 @@ vn-table {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.vn-tbody {
|
& > a.vn-tbody {
|
||||||
&.clickable {
|
&.clickable {
|
||||||
@extend %clickable;
|
@extend %clickable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vn-tbody > *,
|
& > vn-tbody > *,
|
||||||
.vn-tbody > *,
|
& > .vn-tbody > *,
|
||||||
tbody > * {
|
& > tbody > * {
|
||||||
border-bottom: $border-thin;
|
border-bottom: $border-thin;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|
|
@ -9,63 +9,99 @@
|
||||||
limit="20"
|
limit="20"
|
||||||
auto-load="true">
|
auto-load="true">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-data-viewer model="model" class="vn-w-xl">
|
<vn-data-viewer model="model" class="vn-w-md">
|
||||||
<vn-card>
|
<vn-card>
|
||||||
<vn-table model="model">
|
<table class="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}}
|
|
||||||
</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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th translate class="field">Field</th>
|
<th translate field="action" class="action">
|
||||||
<th translate>Before</th>
|
Action
|
||||||
<th translate>After</th>
|
</th>
|
||||||
|
<th field="changedModel">
|
||||||
|
<span ng-if="$ctrl.showModelName" translate>Model</span>
|
||||||
|
</th>
|
||||||
|
<th translate field="creationDate" class="date">
|
||||||
|
Date
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody ng-repeat="log in $ctrl.logs">
|
||||||
<tr ng-repeat="prop in ::log.props">
|
<tr class="change-header">
|
||||||
<td class="field">{{prop.name}}</td>
|
<td class="user">
|
||||||
<td class="before">{{prop.old}}</td>
|
<span ng-if="::log.user.worker.id"
|
||||||
<td class="after">{{prop.new}}</td>
|
class="link"
|
||||||
|
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)">
|
||||||
|
{{::log.user.name}}
|
||||||
|
</span>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div ng-if="log.description != null">
|
|
||||||
{{::log.description}}
|
|
||||||
</div>
|
|
||||||
</vn-td>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-tbody>
|
|
||||||
</vn-table>
|
|
||||||
<vn-pagination model="model"></vn-pagination>
|
<vn-pagination model="model"></vn-pagination>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</vn-data-viewer>
|
</vn-data-viewer>
|
||||||
|
|
|
@ -13,6 +13,12 @@ export default class Controller extends Section {
|
||||||
delete: 'Deletes',
|
delete: 'Deletes',
|
||||||
select: 'Views'
|
select: 'Views'
|
||||||
};
|
};
|
||||||
|
this.actionsClass = {
|
||||||
|
insert: 'success',
|
||||||
|
update: 'warning',
|
||||||
|
delete: 'alert',
|
||||||
|
select: 'notice'
|
||||||
|
};
|
||||||
this.filter = {
|
this.filter = {
|
||||||
include: [{
|
include: [{
|
||||||
relation: 'user',
|
relation: 'user',
|
||||||
|
@ -50,8 +56,8 @@ export default class Controller extends Section {
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
log.props.push({
|
log.props.push({
|
||||||
name: locale[prop] || prop,
|
name: locale[prop] || prop,
|
||||||
old: this.formatValue(oldValues[prop]),
|
old: this.castValue(oldValues[prop]),
|
||||||
new: this.formatValue(newValues[prop])
|
new: this.castValue(newValues[prop])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,31 +67,19 @@ export default class Controller extends Section {
|
||||||
return !(this.changedModel && this.changedModelId);
|
return !(this.changedModel && this.changedModelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatValue(value) {
|
castValue(value) {
|
||||||
let type = typeof value;
|
return typeof value === 'string' && validDate.test(value)
|
||||||
|
? new Date(value)
|
||||||
if (type === 'string' && validDate.test(value)) {
|
: value;
|
||||||
value = new Date(value);
|
|
||||||
type = typeof value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
mainVal(prop, action) {
|
||||||
case 'boolean':
|
return action == 'delete' ? prop.old : prop.new;
|
||||||
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) {
|
showWorkerDescriptor(event, workerId) {
|
||||||
|
|
|
@ -13,3 +13,4 @@ Views: Visualiza
|
||||||
System: Sistema
|
System: Sistema
|
||||||
note: nota
|
note: nota
|
||||||
Changes: Cambios
|
Changes: Cambios
|
||||||
|
No changes: No hay cambios
|
||||||
|
|
|
@ -1,66 +1,127 @@
|
||||||
@import "variables";
|
@import "variables";
|
||||||
|
|
||||||
vn-log {
|
vn-log {
|
||||||
vn-td {
|
.vn-table {
|
||||||
vertical-align: initial !important;
|
table-layout: fixed;
|
||||||
}
|
|
||||||
.changes {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
color: $color-font-secondary;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
color: $color-font;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1570px) {
|
& > thead,
|
||||||
vn-table .expendable {
|
& > tbody {
|
||||||
display: none;
|
& > tr {
|
||||||
|
td, th {
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
.changes {
|
&:last-child {
|
||||||
padding-top: 10px;
|
padding-right: 16px;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.attributes {
|
}
|
||||||
width: 100%;
|
}
|
||||||
|
& > thead > tr > th {
|
||||||
|
max-width: initial;
|
||||||
|
}
|
||||||
|
& > tbody {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
tr {
|
&:last-child {
|
||||||
height: 10px;
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
& > tr {
|
||||||
|
border-bottom: none;
|
||||||
|
height: initial;
|
||||||
|
|
||||||
& > td {
|
& > td {
|
||||||
padding: 2px;
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
&.action > .chip {
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
& > td.field,
|
|
||||||
& > th.field {
|
|
||||||
width: 20%;
|
|
||||||
color: gray;
|
|
||||||
}
|
}
|
||||||
& > td.before,
|
&.change-header > td {
|
||||||
& > th.before,
|
padding-bottom: 0;
|
||||||
& > td.after,
|
}
|
||||||
& > th.after {
|
&.change-detail > td {
|
||||||
width: 40%;
|
padding-top: 6px;
|
||||||
white-space: pre-line;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
th, td {
|
||||||
|
&.action,
|
||||||
|
&.user {
|
||||||
|
width: 90px;
|
||||||
}
|
}
|
||||||
.ellipsis {
|
&.date {
|
||||||
white-space: nowrap;
|
width: 120px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.model-value {
|
||||||
|
font-style: italic;
|
||||||
|
color: #c7bd2b;
|
||||||
|
}
|
||||||
|
.model-id {
|
||||||
|
color: $color-font-secondary;
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
|
.changes {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: rgba(255, 255, 255, .05);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: $color-font-secondary;
|
||||||
|
transition: max-height 150ms ease-in-out;
|
||||||
|
max-height: 28px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > .expand-button,
|
||||||
|
& > .shrink-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .changes-wrapper {
|
||||||
|
padding: 4px 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 400px;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
display: inline-block;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
& > .no-changes {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.json-field {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.no-ellipsize,
|
|
||||||
[no-ellipsize] {
|
|
||||||
text-overflow: '';
|
|
||||||
white-space: normal;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
.alignSpan {
|
|
||||||
overflow: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue