mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-07 16:44:20 +01:00
Read-only date-since and date-duration widgets
This commit is contained in:
parent
6d86eefc96
commit
f9ba6683be
243
api/js/etemplate/Et2Date/Et2DateDuration.ts
Normal file
243
api/js/etemplate/Et2Date/Et2DateDuration.ts
Normal file
@ -0,0 +1,243 @@
|
||||
/**
|
||||
* EGroupware eTemplate2 - Duration date widget (WebComponent)
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package etemplate
|
||||
* @subpackage api
|
||||
* @link https://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
*/
|
||||
|
||||
|
||||
import {css, LitElement} from "@lion/core";
|
||||
import {Unparseable} from "@lion/form-core";
|
||||
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
||||
|
||||
export interface formatOptions
|
||||
{
|
||||
select_unit : string;
|
||||
display_format : string;
|
||||
data_format : string;
|
||||
hours_per_day : number;
|
||||
empty_not_0 : boolean;
|
||||
number_format? : string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a number as a time duration
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {object} options
|
||||
* set 'timeFormat': "12" to specify a particular format
|
||||
* @returns {value: string, unit: string}
|
||||
*/
|
||||
export function formatDuration(value : number | string, options : formatOptions) : { value : string, unit : string }
|
||||
{
|
||||
// Handle empty / 0 / no value
|
||||
if(value === "" || value == "0" || !value)
|
||||
{
|
||||
return {value: options.empty_not_0 ? "0" : "", unit: ""};
|
||||
}
|
||||
// Make sure it's a number now
|
||||
value = typeof value == "string" ? parseInt(value) : value;
|
||||
|
||||
if(!options.select_unit)
|
||||
{
|
||||
let vals = [];
|
||||
for(let i = 0; i < options.display_format.length; ++i)
|
||||
{
|
||||
let unit = options.display_format[i];
|
||||
let val = this._unit_from_value(value, unit, i === 0);
|
||||
if(unit === 's' || unit === 'm' || unit === 'h' && options.display_format[0] === 'd')
|
||||
{
|
||||
vals.push(sprintf('%02d', val));
|
||||
}
|
||||
else
|
||||
{
|
||||
vals.push(val);
|
||||
}
|
||||
}
|
||||
return {value: vals.join(':'), unit: ''};
|
||||
}
|
||||
|
||||
// Put value into minutes for further processing
|
||||
switch(options.data_format)
|
||||
{
|
||||
case 'd':
|
||||
value *= options.hours_per_day;
|
||||
// fall-through
|
||||
case 'h':
|
||||
value *= 60;
|
||||
break;
|
||||
case 's':
|
||||
value /= 60.0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Figure out the best unit for display
|
||||
let _unit = options.display_format == "d" ? "d" : "h";
|
||||
if(options.display_format.indexOf('m') > -1 && value < 60)
|
||||
{
|
||||
_unit = 'm';
|
||||
}
|
||||
else if(options.display_format.indexOf('d') > -1 && value >= (60 * options.hours_per_day))
|
||||
{
|
||||
_unit = 'd';
|
||||
}
|
||||
let out_value = "" + (_unit == 'm' ? value : (Math.round((value / 60.0 / (_unit == 'd' ? options.hours_per_day : 1)) * 100) / 100));
|
||||
|
||||
if(out_value === '')
|
||||
{
|
||||
_unit = '';
|
||||
}
|
||||
|
||||
// use decimal separator from user prefs
|
||||
var format = options.number_format || this.egw().preference('number_format');
|
||||
var sep = format ? format[0] : '.';
|
||||
if(format && sep && sep != '.')
|
||||
{
|
||||
out_value = out_value.replace('.', sep);
|
||||
}
|
||||
|
||||
return {value: out_value, unit: _unit};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a time duration (eg: 3 days, 6 hours)
|
||||
*
|
||||
* If not specified, the time is in assumed to be minutes and will be displayed with a calculated unit
|
||||
* but this can be specified with the properties.
|
||||
*/
|
||||
export class Et2DateDuration extends Et2InputWidget(LitElement)
|
||||
{
|
||||
static get styles()
|
||||
{
|
||||
return [
|
||||
...super.styles,
|
||||
css`
|
||||
:host([focused]) ::slotted(button), :host(:hover) ::slotted(button) {
|
||||
display: inline-block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
static get properties()
|
||||
{
|
||||
return {
|
||||
...super.properties,
|
||||
|
||||
/**
|
||||
* Data format
|
||||
*
|
||||
* Units to read/store the data. 'd' = days (float), 'h' = hours (float), 'm' = minutes (int), 's' = seconds (int).
|
||||
*/
|
||||
data_format: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* Display format
|
||||
*
|
||||
* Permitted units for displaying the data.
|
||||
* 'd' = days, 'h' = hours, 'm' = minutes, 's' = seconds. Use combinations to give a choice.
|
||||
* Default is 'dh' = days or hours with selectbox.
|
||||
*/
|
||||
display_format: {
|
||||
type: String
|
||||
},
|
||||
|
||||
/**
|
||||
* Select unit or input per unit
|
||||
*
|
||||
* Display a unit-selection for multiple units, or an input field per unit.
|
||||
* Default is true
|
||||
*/
|
||||
select_unit: {
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
/**
|
||||
* Percent allowed
|
||||
*
|
||||
* Allows to enter a percentage instead of numbers
|
||||
*/
|
||||
percent_allowed: {
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
/**
|
||||
* Hours per day
|
||||
*
|
||||
* Number of hours in a day, used for converting between hours and (working) days.
|
||||
* Default 8
|
||||
*/
|
||||
hours_per_day: {type: Number},
|
||||
|
||||
/**
|
||||
* 0 or empty
|
||||
*
|
||||
* Should the widget differ between 0 and empty, which get then returned as NULL
|
||||
* Default false, empty is considered as 0
|
||||
*/
|
||||
empty_not_0: {type: Boolean},
|
||||
|
||||
/**
|
||||
* Short labels
|
||||
*
|
||||
* use d/h/m instead of day/hour/minute
|
||||
*/
|
||||
short_labels: {
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
/**
|
||||
* Step limit
|
||||
*
|
||||
* Works with the min and max attributes to limit the increments at which a numeric or date-time value can be set.
|
||||
*/
|
||||
step: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
// Property defaults
|
||||
this.data_format = "m";
|
||||
this.display_format = "dhm";
|
||||
this.select_unit = true;
|
||||
this.percent_allowed = false;
|
||||
this.hours_per_day = 8;
|
||||
this.empty_not_0 = false;
|
||||
this.short_labels = false;
|
||||
|
||||
this.formatter = formatDuration;
|
||||
}
|
||||
|
||||
getValue()
|
||||
{
|
||||
if(this.readOnly)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// The supplied value was not understandable, return null
|
||||
if(this.modelValue instanceof Unparseable || !this.modelValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.modelValue.toJSON();
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore TypeScript is not recognizing that this is a LitElement
|
||||
customElements.define("et2-date-duration", Et2DateDuration);
|
58
api/js/etemplate/Et2Date/Et2DateDurationReadonly.ts
Normal file
58
api/js/etemplate/Et2Date/Et2DateDurationReadonly.ts
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* EGroupware eTemplate2 - Readonly duration WebComponent
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package api
|
||||
* @link https://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
*/
|
||||
|
||||
|
||||
import {html} from "@lion/core";
|
||||
import {Et2DateDuration, formatOptions} from "./Et2DateDuration";
|
||||
|
||||
|
||||
/**
|
||||
* This is a stripped-down read-only widget used in nextmatch
|
||||
*/
|
||||
export class Et2DateDurationReadonly extends Et2DateDuration
|
||||
{
|
||||
render()
|
||||
{
|
||||
let parsed = this.value;
|
||||
|
||||
const format_options = <formatOptions>{
|
||||
select_unit: this.select_unit,
|
||||
display_format: this.display_format,
|
||||
data_format: this.data_format,
|
||||
number_format: this.egw().preference("number_format"),
|
||||
hours_per_day: this.hours_per_day,
|
||||
empty_not_0: this.empty_not_0
|
||||
};
|
||||
|
||||
const display = this.formatter(parsed, format_options);
|
||||
return html`
|
||||
<span ${this.id ? html`id="${this._dom_id}"` : ''}>
|
||||
${display.value}${display.unit}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
getDetachedAttributes(attrs)
|
||||
{
|
||||
attrs.push("id", "value", "class");
|
||||
}
|
||||
|
||||
getDetachedNodes() : HTMLElement[]
|
||||
{
|
||||
return [<HTMLElement><unknown>this];
|
||||
}
|
||||
|
||||
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
||||
{
|
||||
// Do nothing, since we can't actually stop being a DOM node...
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
|
||||
customElements.define("et2-date-duration_ro", Et2DateDurationReadonly);
|
133
api/js/etemplate/Et2Date/Et2DateSinceReadonly.ts
Normal file
133
api/js/etemplate/Et2Date/Et2DateSinceReadonly.ts
Normal file
@ -0,0 +1,133 @@
|
||||
/**
|
||||
* EGroupware eTemplate2 - Readonly time since WebComponent
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package api
|
||||
* @link https://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
*/
|
||||
|
||||
|
||||
import {html} from "@lion/core";
|
||||
import {parseDate, parseDateTime} from "./Et2Date";
|
||||
import {Et2DateReadonly} from "./Et2DateReadonly";
|
||||
|
||||
/**
|
||||
* Formatter for time since widget.
|
||||
*
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*/
|
||||
const formatDate = function(date : Date, options = {units: "YmdHis"})
|
||||
{
|
||||
const unit2label = {
|
||||
'Y': 'years',
|
||||
'm': 'month',
|
||||
'd': 'days',
|
||||
'H': 'hours',
|
||||
'i': 'minutes',
|
||||
's': 'seconds'
|
||||
};
|
||||
let unit2s : Object = {
|
||||
'Y': 31536000,
|
||||
'm': 2628000,
|
||||
'd': 86400,
|
||||
'H': 3600,
|
||||
'i': 60,
|
||||
's': 1
|
||||
};
|
||||
var d = new Date();
|
||||
var diff = Math.round(d.valueOf() / 1000) - Math.round(date.valueOf() / 1000);
|
||||
let display = '';
|
||||
|
||||
// limit units used to display
|
||||
let smallest = 's';
|
||||
if(options.units)
|
||||
{
|
||||
const valid = Object.entries(unit2s).filter((e) => (<string>options.units).includes(e[0]));
|
||||
unit2s = Object.fromEntries(valid);
|
||||
smallest = (valid.pop() || [])[0];
|
||||
}
|
||||
|
||||
for(var unit in unit2s)
|
||||
{
|
||||
var unit_s = unit2s[unit];
|
||||
if(diff >= unit_s || unit === smallest)
|
||||
{
|
||||
display = Math.round(diff / unit_s) + ' ' + this.egw().lang(unit2label[unit]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return display;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the elapsed time since the given date
|
||||
*
|
||||
* The time units (years, months, days, etc) will be calculated automatically to best match the
|
||||
* time scale being dealt with, unless the units property is set.
|
||||
*
|
||||
* This is a stripped-down read-only widget used in nextmatch
|
||||
*/
|
||||
export class Et2DateSinceReadonly extends Et2DateReadonly
|
||||
{
|
||||
static get properties()
|
||||
{
|
||||
return {
|
||||
...super.properties,
|
||||
/**
|
||||
* Allowed display units, default 'YmdHis', e.g. 'd' to display a value only in days"
|
||||
*/
|
||||
units: {type: String, reflect: true},
|
||||
}
|
||||
}
|
||||
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this.parser = parseDateTime;
|
||||
this.formatter = formatDate;
|
||||
}
|
||||
|
||||
set_value(value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
let parsed : Date | Boolean = this.value ? this.parser(this.value) : false
|
||||
|
||||
// Be more forgiving if time is missing
|
||||
if(!parsed && this.value)
|
||||
{
|
||||
parsed = parseDate(this.value) || false;
|
||||
}
|
||||
|
||||
return html`
|
||||
<span ${this.id ? html`id="${this._dom_id}"` : ''}
|
||||
datetime="${parsed ? (<Date>parsed).toJSON() : ""}">
|
||||
${this.value ? this.formatter(<Date>parsed, {units: this.units}) : ''}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
getDetachedAttributes(attrs)
|
||||
{
|
||||
attrs.push("id", "value", "class");
|
||||
}
|
||||
|
||||
getDetachedNodes() : HTMLElement[]
|
||||
{
|
||||
return [<HTMLElement><unknown>this];
|
||||
}
|
||||
|
||||
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
||||
{
|
||||
// Do nothing, since we can't actually stop being a DOM node...
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
|
||||
customElements.define("et2-date-since_ro", Et2DateSinceReadonly);
|
@ -26,7 +26,10 @@ import {egwIsMobile} from "../egw_action/egw_action_common.js";
|
||||
import './Et2Box/Et2Box';
|
||||
import './Et2Button/Et2Button';
|
||||
import './Et2Date/Et2Date';
|
||||
import './Et2Date/Et2DateReadonly'
|
||||
import './Et2Date/Et2DateDuration';
|
||||
import './Et2Date/Et2DateDurationReadonly';
|
||||
import './Et2Date/Et2DateReadonly';
|
||||
import './Et2Date/Et2DateSinceReadonly';
|
||||
import './Et2Date/Et2DateTime';
|
||||
import './Et2Date/Et2DateTimeReadonly';
|
||||
import './Et2Description/Et2Description';
|
||||
|
Loading…
Reference in New Issue
Block a user