diff --git a/package-lock.json b/package-lock.json
index 040c0cb3f..54b4cafb8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -131,6 +131,14 @@
"tslib": "1.10.0",
"typescript": "3.5.3",
"webpack-sources": "1.4.3"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
+ "dev": true
+ }
}
},
"@angular-devkit/build-webpack": {
@@ -2026,8 +2034,7 @@
"dependencies": {
"rxjs": {
"version": "6.4.0",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
- "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+ "bundled": true,
"requires": {
"tslib": "^1.9.0"
}
@@ -2173,9 +2180,9 @@
}
},
"@types/selenium-webdriver": {
- "version": "3.0.16",
- "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz",
- "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==",
+ "version": "3.0.17",
+ "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz",
+ "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==",
"dev": true
},
"@types/source-list-map": {
@@ -2454,9 +2461,9 @@
}
},
"ajv": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz",
- "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==",
+ "version": "6.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
+ "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -3748,9 +3755,9 @@
"dev": true
},
"compare-versions": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.1.tgz",
- "integrity": "sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
+ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
"dev": true
},
"component-bind": {
@@ -4623,9 +4630,9 @@
"dev": true
},
"electron-to-chromium": {
- "version": "1.3.349",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.349.tgz",
- "integrity": "sha512-uEb2zs6EJ6OZIqaMsCSliYVgzE/f7/s1fLWqtvRtHg/v5KBF2xds974fUnyatfxIDgkqzQVwFtam5KExqywx0Q==",
+ "version": "1.3.360",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.360.tgz",
+ "integrity": "sha512-RE1pv2sjQiDRRN1nI0fJ0eQHZ9le4oobu16OArnwEUV5ycAU5SNjFyvzjZ1gPUAqBa2Ud1XagtW8j3ZXfHuQHA==",
"dev": true
},
"elliptic": {
@@ -5266,9 +5273,9 @@
"dev": true
},
"figures": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz",
- "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
@@ -5385,9 +5392,9 @@
}
},
"make-dir": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz",
- "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
+ "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
"dev": true,
"requires": {
"semver": "^6.0.0"
@@ -6304,9 +6311,9 @@
"dev": true
},
"ipaddr.js": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
- "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==",
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"dev": true
},
"is-absolute-url": {
@@ -6839,9 +6846,9 @@
"integrity": "sha512-/yBqMepJNHxy+yS4Llt0KQPsHGkMi1FVwcE77cpiJZh2RwoNyGBXOwFz4Am5FNZYBF3oiL39FMzHRwwf6yl6xg=="
},
"jdnconvertiblecalendardateadapter": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/jdnconvertiblecalendardateadapter/-/jdnconvertiblecalendardateadapter-0.0.10.tgz",
- "integrity": "sha512-qqbWdSe5owgGtRu24QV64IvR4zeE0icnVnRF9eC1Gz+L6wPmOpjPfUfkj+Hs/z14KK0/ADTl7HcslYbO0N2vtw==",
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/jdnconvertiblecalendardateadapter/-/jdnconvertiblecalendardateadapter-0.0.12.tgz",
+ "integrity": "sha512-dQ1PNHH0LnUjj1ZYiWH7ELdMgd2RpQR1TpqDY2afC07lB61B+Y9Jmdu5mSppgOI5v8foJCxgt9uRsamRQ0ya3w==",
"requires": {
"tslib": "^1.9.0"
}
@@ -8027,9 +8034,9 @@
}
},
"make-error": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
- "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"make-fetch-happen": {
@@ -8624,9 +8631,9 @@
}
},
"node-releases": {
- "version": "1.1.49",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.49.tgz",
- "integrity": "sha512-xH8t0LS0disN0mtRCh+eByxFPie+msJUBL/lJDBuap53QGiYPa9joh83K4pCZgWJ+2L4b9h88vCVdXQ60NO2bg==",
+ "version": "1.1.50",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz",
+ "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==",
"dev": true,
"requires": {
"semver": "^6.3.0"
@@ -8735,9 +8742,9 @@
}
},
"npm-registry-fetch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz",
- "integrity": "sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz",
+ "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==",
"dev": true,
"requires": {
"JSONStream": "^1.3.4",
@@ -9453,9 +9460,9 @@
}
},
"postcss-value-parser": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz",
- "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
+ "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
"dev": true
},
"prepend-http": {
@@ -9669,13 +9676,13 @@
}
},
"proxy-addr": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
- "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+ "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"dev": true,
"requires": {
"forwarded": "~0.1.2",
- "ipaddr.js": "1.9.0"
+ "ipaddr.js": "1.9.1"
}
},
"prr": {
@@ -10109,9 +10116,9 @@
"dev": true
},
"regjsparser": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz",
- "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz",
+ "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==",
"dev": true,
"requires": {
"jsesc": "~0.5.0"
@@ -11196,9 +11203,9 @@
},
"dependencies": {
"readable-stream": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz",
- "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
@@ -11815,9 +11822,9 @@
"dev": true
},
"tslib": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
- "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.0.tgz",
+ "integrity": "sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg=="
},
"tslint": {
"version": "5.15.0",
diff --git a/package.json b/package.json
index 52d6607e4..c0a1b6b5d 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"@angular/router": "~8.2.14",
"@knora/api": "file:.yalc/@knora/api",
"jdnconvertiblecalendar": "^0.0.5",
- "jdnconvertiblecalendardateadapter": "^0.0.10",
+ "jdnconvertiblecalendardateadapter": "^0.0.12",
"ngx-color-picker": "^8.2.0",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
diff --git a/projects/knora-ui/package.json b/projects/knora-ui/package.json
index b70fc88f7..93b9d09ec 100644
--- a/projects/knora-ui/package.json
+++ b/projects/knora-ui/package.json
@@ -6,6 +6,8 @@
"@angular/core": "^8.2.14",
"@angular/material": "^8.2.3",
"@angular/cdk": "^8.2.3",
- "@knora/api": "file:.yalc/@knora/api"
+ "@knora/api": "file:.yalc/@knora/api",
+ "jdnconvertiblecalendar": "^0.0.5",
+ "jdnconvertiblecalendardateadapter": "^0.0.12"
}
}
diff --git a/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.html b/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.html
index fd7d88c90..c999b38b0 100644
--- a/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.html
+++ b/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.html
@@ -23,6 +23,7 @@
+
{{displayValue.strval}}
diff --git a/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts b/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts
index a6c9a9081..af83f1838 100644
--- a/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts
+++ b/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts
@@ -183,6 +183,16 @@ class TestGeonameValueComponent {
}
+@Component({
+ selector: `kui-date-value`,
+ template: ``
+})
+class TestDateValueComponent {
+ @Input() mode;
+
+ @Input() displayValue;
+}
+
/**
* Test host component to simulate parent component.
*/
@@ -253,7 +263,8 @@ describe('DisplayEditComponent', () => {
TestDecimalValueComponent,
TestGeonameValueComponent,
TestTimeValueComponent,
- TestColorValueComponent
+ TestColorValueComponent,
+ TestDateValueComponent
],
providers: [
{
diff --git a/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts b/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts
index 572526f29..342caa0e9 100644
--- a/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts
+++ b/projects/knora-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts
@@ -40,7 +40,7 @@ export class DisplayEditComponent implements OnInit {
editModeActive = false;
shouldShowCommentToggle: boolean;
-
+
// type of given displayValue
// or knora-api-js-lib class representing the value
valueTypeOrClass: string;
@@ -135,7 +135,7 @@ export class DisplayEditComponent implements OnInit {
checkCommentToggleVisibility() {
this.shouldShowCommentToggle = (this.mode === 'read' && this.displayValue.valueHasComment !== '' && this.displayValue.valueHasComment !== undefined);
}
-
+
/**
* Given a value, determines the type or class representing it.
*
@@ -174,7 +174,6 @@ export class DisplayEditComponent implements OnInit {
isReadOnly(valueTypeOrClass: string): boolean {
return valueTypeOrClass === this.readTextValueAsHtml ||
valueTypeOrClass === this.readTextValueAsXml ||
- valueTypeOrClass === this.constants.DateValue ||
valueTypeOrClass === this.constants.GeomValue;
}
}
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.html b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.html
new file mode 100644
index 000000000..b87e646a4
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.html
@@ -0,0 +1,4 @@
+
+
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.scss b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.scss
new file mode 100644
index 000000000..2bea1c416
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.scss
@@ -0,0 +1,6 @@
+:host {
+ .mat-select.kui-calendar-header {
+ margin: 16px 16px 0 16px !important;
+ width: calc(100% - 32px) !important;
+ }
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.spec.ts b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.spec.ts
new file mode 100644
index 000000000..4f8733394
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.spec.ts
@@ -0,0 +1,143 @@
+import {async, ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
+
+import {CalendarHeaderComponent} from './calendar-header.component';
+import {ACTIVE_CALENDAR, JDNConvertibleCalendarDateAdapter} from 'jdnconvertiblecalendardateadapter';
+import {MatSelectModule} from '@angular/material/select';
+import {DateAdapter, MatOptionModule} from '@angular/material/core';
+import {FormControl, ReactiveFormsModule} from '@angular/forms';
+import {MatCalendar, MatDatepickerContent} from '@angular/material/datepicker';
+import {BehaviorSubject} from 'rxjs';
+import {Component, DebugElement} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {JDNConvertibleCalendarModule} from 'jdnconvertiblecalendar/dist/src/JDNConvertibleCalendar';
+import {CalendarDate, CalendarPeriod, JulianCalendarDate} from 'jdnconvertiblecalendar';
+import GregorianCalendarDate = JDNConvertibleCalendarModule.GregorianCalendarDate;
+
+@Component({
+ selector: `mat-calendar-header`,
+ template: ``
+})
+class TestMatCalendarHeaderComponent {
+
+}
+
+describe('CalendarHeaderComponent', () => {
+ let component: CalendarHeaderComponent;
+ let fixture: ComponentFixture>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ ReactiveFormsModule,
+ MatSelectModule,
+ MatOptionModule,
+ BrowserAnimationsModule
+ ],
+ declarations: [CalendarHeaderComponent, TestMatCalendarHeaderComponent],
+ providers: [
+ {
+ provide: MatCalendar, useValue: {
+ activeDate: new GregorianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 3, 17), new CalendarDate(2020, 3, 17))),
+ updateTodaysDate: () => {
+ }
+ }
+ },
+ {provide: DateAdapter, useClass: JDNConvertibleCalendarDateAdapter},
+ {provide: ACTIVE_CALENDAR, useValue: new BehaviorSubject('Gregorian')},
+ {
+ provide: MatDatepickerContent, useValue: {
+ datepicker: {
+ select: () => {
+ }
+ }
+ }
+ }
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CalendarHeaderComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should init the selected value and options correctly', fakeAsync(() => {
+
+ expect(component.formControl.value).toEqual('Gregorian');
+
+ // https://github.com/angular/components/blob/941b5a3529727f583b76068835e07e412e69f4f7/src/material/select/select.spec.ts#L1674-L1692
+ component.formControl = new FormControl('Gregorian');
+ fixture.detectChanges();
+
+ const compDe = fixture.debugElement;
+
+ const selectValueDebugElement = compDe.query(By.css('.mat-select-value'));
+
+ const selectDebugElement = compDe.query(By.css('.mat-select'));
+
+ expect(selectValueDebugElement.nativeElement.textContent).toEqual('Gregorian');
+
+ const trigger = compDe.query(By.css('.mat-select-trigger')).nativeElement;
+ trigger.click();
+ fixture.detectChanges();
+ flush();
+
+ const options: DebugElement[] = selectDebugElement.queryAll(By.css('mat-option'));
+
+ expect(options.length).toEqual(2);
+
+ expect(options[0].nativeElement.innerText).toEqual('Gregorian');
+
+ expect(options[1].nativeElement.innerText).toEqual('Julian');
+
+ }));
+
+ it('should perform a calendar conversion when the selection is changed', () => {
+
+ const dateAdapter = TestBed.get(DateAdapter);
+
+ const dateAdapterSpy = spyOn(dateAdapter, 'convertCalendar').and.callFake(
+ (date, calendar) => {
+ return new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 3, 4), new CalendarDate(2020, 3, 4)));
+ });
+
+ const matCal = TestBed.get(MatCalendar);
+
+ const matCalendarSpy = spyOn(matCal, 'updateTodaysDate').and.stub();
+
+ const datepickerContent = TestBed.get(MatDatepickerContent);
+
+ const datepickerContentSpy = spyOn(datepickerContent.datepicker, 'select').and.stub();
+
+ component.formControl.setValue('Julian');
+
+ expect(dateAdapterSpy).toHaveBeenCalledTimes(1);
+
+ expect(dateAdapterSpy).toHaveBeenCalledWith(new GregorianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 3, 17), new CalendarDate(2020, 3, 17))), 'Julian');
+
+ expect(matCal.activeDate).toEqual(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 3, 4), new CalendarDate(2020, 3, 4))));
+
+ expect(datepickerContentSpy).toHaveBeenCalledTimes(1);
+
+ expect(matCalendarSpy).toHaveBeenCalledTimes(1);
+
+ });
+
+ it('should unsubscribe from value changes subscription when the component is destroyed', () => {
+
+ expect(component.valueChangesSubscription.closed).toEqual(false);
+
+ component.ngOnDestroy();
+
+ expect(component.valueChangesSubscription.closed).toEqual(true);
+
+ });
+
+});
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.ts b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.ts
new file mode 100644
index 000000000..764e6e596
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/calendar-header/calendar-header.component.ts
@@ -0,0 +1,85 @@
+/** Custom header component containing a calendar format switcher */
+import {JDNConvertibleCalendarDateAdapter} from 'jdnconvertiblecalendardateadapter';
+import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
+import {JDNConvertibleCalendar} from 'jdnconvertiblecalendar';
+import {MatCalendar, MatDatepickerContent} from '@angular/material/datepicker';
+import {DateAdapter} from '@angular/material/core';
+import {Component, Host, Inject, OnDestroy, OnInit} from '@angular/core';
+import {Subscription} from 'rxjs';
+
+@Component({
+ selector: 'kui-calendar-header',
+ templateUrl: './calendar-header.component.html',
+ styleUrls: ['./calendar-header.component.scss']
+})
+export class CalendarHeaderComponent implements OnInit, OnDestroy {
+ constructor(@Host() private _calendar: MatCalendar,
+ private _dateAdapter: DateAdapter,
+ private _datepickerContent: MatDatepickerContent,
+ @Inject(FormBuilder) private fb: FormBuilder) {
+ }
+
+ form: FormGroup;
+ formControl: FormControl;
+ valueChangesSubscription: Subscription;
+
+ // a list of supported calendars (Gregorian and Julian)
+ supportedCalendars = ['Gregorian', 'Julian'];
+
+ ngOnInit() {
+
+ let activeCal;
+
+ // get the currently active calendar from the date adapter
+ if (this._dateAdapter instanceof JDNConvertibleCalendarDateAdapter) {
+ activeCal = this._dateAdapter.activeCalendar;
+ } else {
+ console.log('date adapter is expected to be an instance of JDNConvertibleCalendarDateAdapter');
+ }
+
+ this.formControl = new FormControl(activeCal, Validators.required);
+
+ // build a form for the calendar format selection
+ this.form = this.fb.group({
+ calendar: this.formControl
+ });
+
+ // do the conversion when the user selects another calendar format
+ this.valueChangesSubscription = this.form.valueChanges.subscribe((data) => {
+ // pass the target calendar format to the conversion method
+ this.convertDate(data.calendar);
+ });
+
+ }
+
+ ngOnDestroy(): void {
+ if (this.valueChangesSubscription !== undefined) {
+ this.valueChangesSubscription.unsubscribe();
+ }
+ }
+
+ /**
+ * Converts the date into the target format.
+ *
+ * @param calendar the target calendar format.
+ */
+ convertDate(calendar: 'Gregorian' | 'Julian') {
+
+ if (this._dateAdapter instanceof JDNConvertibleCalendarDateAdapter) {
+
+ // convert the date into the target calendar format
+ const convertedDate = this._dateAdapter.convertCalendar(this._calendar.activeDate, calendar);
+
+ // set the new date
+ this._calendar.activeDate = convertedDate;
+
+ // select the new date in the datepicker UI
+ this._datepickerContent.datepicker.select(convertedDate);
+
+ // update view after calendar format conversion
+ this._calendar.updateTodaysDate();
+ } else {
+ console.log('date adapter is expected to be an instance of JDNConvertibleCalendarDateAdapter');
+ }
+ }
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.html b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.html
new file mode 100644
index 000000000..32ba3248a
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.html
@@ -0,0 +1,28 @@
+
+
+ {{startDateControl.value?.calendarName}}
+
+
+
+ Start date is required
+
+
+ In a period, start and end date must use the same calendar
+
+
+ In a period, start must be before end
+
+
+
+
+
+
+
+ {{endDateControl.value?.calendarName}}
+
+
+
+
+
+
+
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.scss b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.scss
new file mode 100644
index 000000000..671f9859c
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.scss
@@ -0,0 +1,14 @@
+/**
+ * CSS changed here must also be changed in each child component of a wrapped component
+ * time-input.component.scss, interval-input.component.scss, etc.
+ */
+
+.child-input-component {
+ display: inline-block;
+ vertical-align: bottom;
+ width: 49%;
+}
+
+.child-input-component:nth-child(2) {
+ padding-left: 2%;
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts
new file mode 100644
index 000000000..50268ed0a
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts
@@ -0,0 +1,251 @@
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {DateInputComponent} from './date-input.component';
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms';
+import {KnoraDate, KnoraPeriod} from '@knora/api';
+import {JDNDatepickerDirective} from '../../jdn-datepicker-directive/jdndatepicker.directive';
+import {MatFormFieldModule} from '@angular/material/form-field';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {MatInputModule} from '@angular/material/input';
+import {MatDatepickerModule} from '@angular/material/datepicker';
+import {MatJDNConvertibleCalendarDateAdapterModule} from 'jdnconvertiblecalendardateadapter';
+import {MatCheckboxModule} from '@angular/material/checkbox';
+import {CalendarDate, GregorianCalendarDate, CalendarPeriod, JulianCalendarDate} from 'jdnconvertiblecalendar';
+import {By} from '@angular/platform-browser';
+
+/**
+ * Test host component to simulate parent component.
+ */
+@Component({
+ template: `
+
+
+
+
+
`
+})
+class TestHostComponent implements OnInit {
+
+ @ViewChild('dateInput', {static: false}) dateInputComponent: DateInputComponent;
+
+ form: FormGroup;
+
+ readonly = false;
+
+ constructor(private fb: FormBuilder) {
+ }
+
+ ngOnInit(): void {
+
+ this.form = this.fb.group({
+ date: [new KnoraDate('JULIAN', 'CE', 2018, 5, 19)]
+ });
+
+ }
+}
+
+describe('DateInputComponent', () => {
+ let testHostComponent: TestHostComponent;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatDatepickerModule,
+ MatCheckboxModule,
+ MatJDNConvertibleCalendarDateAdapterModule,
+ BrowserAnimationsModule
+ ],
+ declarations: [DateInputComponent, TestHostComponent, JDNDatepickerDirective]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ });
+
+ it('should initialize a date correctly', () => {
+
+ expect(testHostComponent.dateInputComponent.value instanceof KnoraDate).toBe(true);
+ expect(testHostComponent.dateInputComponent.value)
+ .toEqual(new KnoraDate('JULIAN', 'CE', 2018, 5, 19));
+
+ expect(testHostComponent.dateInputComponent.startDateControl.value)
+ .toEqual(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2018, 5, 19), new CalendarDate(2018, 5, 19))));
+
+ expect(testHostComponent.dateInputComponent.isPeriodControl.value).toBe(false);
+
+ expect(testHostComponent.dateInputComponent.endDateControl.value).toBe(null);
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(true);
+
+ });
+
+ it('should initialize a period correctly', () => {
+
+ testHostComponent.form.controls.date.setValue(new KnoraPeriod(new KnoraDate('JULIAN', 'CE', 2018, 5, 19), new KnoraDate('JULIAN', 'CE', 2019, 5, 19)));
+
+ expect(testHostComponent.dateInputComponent.value instanceof KnoraPeriod).toBe(true);
+ expect(testHostComponent.dateInputComponent.value)
+ .toEqual(new KnoraPeriod(new KnoraDate('JULIAN', 'CE', 2018, 5, 19), new KnoraDate('JULIAN', 'CE', 2019, 5, 19)));
+
+ expect(testHostComponent.dateInputComponent.startDateControl.value)
+ .toEqual(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2018, 5, 19), new CalendarDate(2018, 5, 19))));
+
+ expect(testHostComponent.dateInputComponent.isPeriodControl.value).toBe(true);
+
+ expect(testHostComponent.dateInputComponent.endDateControl.value)
+ .toEqual(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2019, 5, 19), new CalendarDate(2019, 5, 19))));
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(true);
+
+ });
+
+ it('should propagate changes made by the user for a single date', () => {
+
+ testHostComponent.dateInputComponent.form.controls.dateStart.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2019, 5, 19), new CalendarDate(2019, 5, 19))));
+
+ testHostComponent.dateInputComponent._handleInput();
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(true);
+
+ expect(testHostComponent.form.controls.date.value).toEqual(new KnoraDate('JULIAN', 'CE', 2019, 5, 19));
+ });
+
+ it('should propagate changes made by the user for a period', () => {
+
+ testHostComponent.dateInputComponent.form.controls.dateStart.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2019, 5, 19), new CalendarDate(2019, 5, 19))));
+
+ testHostComponent.dateInputComponent.form.controls.isPeriod.setValue(true);
+
+ testHostComponent.dateInputComponent.form.controls.dateEnd.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 5, 19), new CalendarDate(2020, 5, 19))));
+
+ testHostComponent.dateInputComponent._handleInput();
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(true);
+
+ expect(testHostComponent.form.controls.date.value).toEqual(new KnoraPeriod(new KnoraDate('JULIAN', 'CE', 2019, 5, 19), new KnoraDate('JULIAN', 'CE', 2020, 5, 19)));
+ });
+
+ it('should return "null" for an invalid user input (start date greater than end date)', () => {
+
+ testHostComponent.dateInputComponent.form.controls.dateStart.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2021, 5, 19), new CalendarDate(2021, 5, 19))));
+
+ testHostComponent.dateInputComponent.form.controls.isPeriod.setValue(true);
+
+ testHostComponent.dateInputComponent.form.controls.dateEnd.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 5, 19), new CalendarDate(2020, 5, 19))));
+
+ testHostComponent.dateInputComponent._handleInput();
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(false);
+
+ expect(testHostComponent.dateInputComponent.value).toEqual(null);
+
+ });
+
+ it('should return "null" for an invalid user input (start date and end date have different calendars)', () => {
+
+ testHostComponent.dateInputComponent.form.controls.dateStart.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2021, 5, 19), new CalendarDate(2021, 5, 19))));
+
+ testHostComponent.dateInputComponent.form.controls.isPeriod.setValue(true);
+
+ testHostComponent.dateInputComponent.form.controls.dateEnd.setValue(new GregorianCalendarDate(new CalendarPeriod(new CalendarDate(2022, 5, 19), new CalendarDate(2022, 5, 19))));
+
+ testHostComponent.dateInputComponent._handleInput();
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(false);
+
+ expect(testHostComponent.dateInputComponent.value).toEqual(null);
+
+ });
+
+ it('should return "null" for an invalid user input (start date is "null")', () => {
+
+ testHostComponent.dateInputComponent.form.controls.dateStart.setValue(null);
+
+ testHostComponent.dateInputComponent.form.controls.isPeriod.setValue(true);
+
+ testHostComponent.dateInputComponent.form.controls.dateEnd.setValue(new JulianCalendarDate(new CalendarPeriod(new CalendarDate(2020, 5, 19), new CalendarDate(2020, 5, 19))));
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(false);
+
+ expect(testHostComponent.dateInputComponent.value).toEqual(null);
+
+ });
+
+ it('should initialize the date with an empty value', () => {
+
+ testHostComponent.form.controls.date.setValue(null);
+
+ expect(testHostComponent.dateInputComponent.form.controls.dateStart.value).toBe(null);
+ expect(testHostComponent.dateInputComponent.form.controls.isPeriod.value).toBe(false);
+ expect(testHostComponent.dateInputComponent.form.controls.dateEnd.value).toBe(null);
+
+ expect(testHostComponent.dateInputComponent.form.valid).toBe(false);
+
+ });
+
+ it('should show the toggle when not in readonly mode', () => {
+
+ expect(testHostComponent.dateInputComponent.readonly).toBe(false);
+
+ testHostComponent.dateInputComponent.form.controls.isPeriod.setValue(true);
+
+ testHostFixture.detectChanges();
+
+ const hostCompDe = testHostFixture.debugElement;
+ const dateInputComponentDe = hostCompDe.query(By.directive(DateInputComponent));
+
+ const startDateToggle = dateInputComponentDe.query(By.css('.start mat-datepicker-toggle'));
+
+ expect(startDateToggle).not.toBe(null);
+
+ const endDateToggle = dateInputComponentDe.query(By.css('.end mat-datepicker-toggle'));
+
+ expect(endDateToggle).not.toBe(null);
+ });
+
+ it('should not show the toggle when in readonly mode', () => {
+
+ testHostComponent.readonly = true;
+
+ testHostComponent.dateInputComponent.form.controls.isPeriod.setValue(true);
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.dateInputComponent.readonly).toBe(true);
+
+ const hostCompDe = testHostFixture.debugElement;
+ const dateInputComponentDe = hostCompDe.query(By.directive(DateInputComponent));
+
+ const startDateToggle = dateInputComponentDe.query(By.css('.start mat-datepicker-toggle'));
+
+ expect(startDateToggle).toBe(null);
+
+ const endDateToggle = dateInputComponentDe.query(By.css('.end mat-datepicker-toggle'));
+
+ expect(endDateToggle).toBe(null);
+
+ });
+
+ it('should show the calendar of a date', () => {
+
+ const hostCompDe = testHostFixture.debugElement;
+ const dateInputComponentDe = hostCompDe.query(By.directive(DateInputComponent));
+
+ const startDateCalendar = dateInputComponentDe.query(By.css('.start span.calendar'));
+
+ expect(startDateCalendar).not.toBe(null);
+
+ });
+
+});
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.ts b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.ts
new file mode 100644
index 000000000..6ec1416e3
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-input/date-input.component.ts
@@ -0,0 +1,332 @@
+import {Component, DoCheck, ElementRef, HostBinding, Input, OnDestroy, Optional, Self} from '@angular/core';
+import {
+ AbstractControl,
+ ControlValueAccessor,
+ FormBuilder,
+ FormControl,
+ FormGroup,
+ FormGroupDirective,
+ NgControl,
+ NgForm,
+ ValidatorFn,
+ Validators
+} from '@angular/forms';
+import {MatFormFieldControl} from '@angular/material/form-field';
+import {KnoraDate, KnoraPeriod} from '@knora/api';
+import {CanUpdateErrorState, CanUpdateErrorStateCtor, ErrorStateMatcher, mixinErrorState} from '@angular/material/core';
+import {Subject} from 'rxjs';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+import {FocusMonitor} from '@angular/cdk/a11y';
+import {
+ CalendarDate,
+ CalendarPeriod,
+ GregorianCalendarDate,
+ JDNConvertibleCalendar,
+ JulianCalendarDate
+} from 'jdnconvertiblecalendar';
+import {CalendarHeaderComponent} from '../calendar-header/calendar-header.component';
+
+/** Error when invalid control is dirty, touched, or submitted. */
+export class DateInputErrorStateMatcher implements ErrorStateMatcher {
+ isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
+ const isSubmitted = form && form.submitted;
+ return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
+ }
+}
+
+/** If a period is defined, start and end date must have the same calendar */
+export function sameCalendarValidator(isPeriod: FormControl, endDate: FormControl): ValidatorFn {
+ return (control: AbstractControl): { [key: string]: any } | null => {
+
+ if (isPeriod.value) {
+
+ let invalid = true;
+ if (control.value instanceof JDNConvertibleCalendar && endDate.value instanceof JDNConvertibleCalendar) {
+ invalid = control.value.calendarName !== endDate.value.calendarName;
+ }
+
+ return invalid ? {'sameCalendarRequired': {value: control.value}} : null;
+ }
+
+ return null;
+ };
+}
+
+/** If a period is defined, start date must be before end date */
+export function periodStartEndValidator(isPeriod: FormControl, endDate: FormControl): ValidatorFn {
+ return (control: AbstractControl): { [key: string]: any } | null => {
+
+ if (isPeriod.value) {
+ let invalid = true;
+
+ if (control.value instanceof JDNConvertibleCalendar && endDate.value instanceof JDNConvertibleCalendar) {
+
+ // check if start is before end
+ const startAsJdnPeriod = (control.value as JDNConvertibleCalendar).toJDNPeriod();
+ const endAsJdnPeriod = (endDate.value as JDNConvertibleCalendar).toJDNPeriod();
+
+ // check for start after end
+ invalid = startAsJdnPeriod.periodStart >= endAsJdnPeriod.periodStart;
+ }
+
+ return invalid ? {'periodStartEnd': {value: control.value}} : null;
+ }
+
+ return null;
+ };
+}
+
+class MatInputBase {
+ constructor(public _defaultErrorStateMatcher: ErrorStateMatcher,
+ public _parentForm: NgForm,
+ public _parentFormGroup: FormGroupDirective,
+ public ngControl: NgControl) {
+ }
+}
+
+const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
+ mixinErrorState(MatInputBase);
+
+@Component({
+ selector: 'kui-date-input',
+ templateUrl: './date-input.component.html',
+ styleUrls: ['./date-input.component.scss'],
+ providers: [{provide: MatFormFieldControl, useExisting: DateInputComponent}]
+})
+export class DateInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl, DoCheck, CanUpdateErrorState, OnDestroy {
+
+ static nextId = 0;
+
+ form: FormGroup;
+ stateChanges = new Subject();
+ @HostBinding() id = `kui-date-input-${DateInputComponent.nextId++}`;
+ focused = false;
+ errorState = false;
+ controlType = 'kui-date-input';
+ matcher = new DateInputErrorStateMatcher();
+
+ calendarHeaderComponent = CalendarHeaderComponent;
+ startDateControl: FormControl;
+ endDateControl: FormControl;
+ isPeriodControl: FormControl;
+
+ onChange = (_: any) => {
+ };
+ onTouched = () => {
+ };
+
+ get empty() {
+ const userInput = this.form.value;
+ return !userInput.start && !userInput.end;
+ }
+
+ @HostBinding('class.floating')
+ get shouldLabelFloat() {
+ return this.focused || !this.empty;
+ }
+
+ @Input()
+ get required() {
+ return this._required;
+ }
+
+ set required(req) {
+ this._required = coerceBooleanProperty(req);
+ this.stateChanges.next();
+ }
+
+ private _required = false;
+
+ @Input()
+ get disabled(): boolean {
+ return this._disabled;
+ }
+
+ set disabled(value: boolean) {
+ this._disabled = coerceBooleanProperty(value);
+ this._disabled ? this.form.disable() : this.form.enable();
+ this.stateChanges.next();
+ }
+
+ private _disabled = false;
+
+ @Input()
+ get placeholder() {
+ return this._placeholder;
+ }
+
+ set placeholder(plh) {
+ this._placeholder = plh;
+ this.stateChanges.next();
+ }
+
+ private _placeholder: string;
+
+ @Input() readonly = false;
+
+ @HostBinding('attr.aria-describedby') describedBy = '';
+
+ setDescribedByIds(ids: string[]) {
+ this.describedBy = ids.join(' ');
+ }
+
+ @Input()
+ get value(): KnoraDate | KnoraPeriod | null {
+
+ if (!this.form.valid) {
+ return null;
+ }
+
+ const userInput = this.form.value;
+
+ if (!this.isPeriodControl.value) {
+ // single date
+ if (userInput.dateStart !== null) {
+ return new KnoraDate(userInput.dateStart.calendarName.toUpperCase(), 'CE', userInput.dateStart.calendarStart.year, userInput.dateStart.calendarStart.month, userInput.dateStart.calendarStart.day);
+ } else {
+ return null;
+ }
+ } else {
+ // period
+ if (userInput.dateStart !== null && userInput.dateEnd !== null) {
+
+ const start = new KnoraDate(userInput.dateStart.calendarName.toUpperCase(), 'CE', userInput.dateStart.calendarStart.year, userInput.dateStart.calendarStart.month, userInput.dateStart.calendarStart.day);
+ const end = new KnoraDate(userInput.dateEnd.calendarName.toUpperCase(), 'CE', userInput.dateEnd.calendarStart.year, userInput.dateEnd.calendarStart.month, userInput.dateEnd.calendarStart.day);
+
+ return new KnoraPeriod(start, end);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ set value(date: KnoraDate | KnoraPeriod | null) {
+ if (date !== null) {
+ if (date instanceof KnoraDate) {
+ // single date
+
+ this.form.setValue({
+ dateStart: this.createCalendarDate(date),
+ dateEnd: null,
+ isPeriod: false
+ });
+
+ this.startDateControl.updateValueAndValidity();
+
+ } else {
+ // period
+ const period = date as KnoraPeriod;
+
+ this.form.setValue({
+ dateStart: this.createCalendarDate(period.start),
+ dateEnd: this.createCalendarDate(period.end),
+ isPeriod: true
+ });
+
+ this.startDateControl.updateValueAndValidity();
+
+ }
+ } else {
+ this.form.setValue({dateStart: null, dateEnd: null, isPeriod: false});
+
+ this.startDateControl.updateValueAndValidity();
+ }
+ this.stateChanges.next();
+ }
+
+ /**
+ * Given a `KnoraDate`, creates a Gregorian or Julian calendar date.
+ *
+ * @param date the given KnoraDate.
+ */
+ createCalendarDate(date: KnoraDate): GregorianCalendarDate | JulianCalendarDate {
+
+ const calDate = new CalendarDate(date.year, date.month, date.day);
+ const period = new CalendarPeriod(calDate, calDate);
+
+ // determine calendar
+ if (date.calendar === 'GREGORIAN') {
+ return new GregorianCalendarDate(period);
+ } else if (date.calendar === 'JULIAN') {
+ return new JulianCalendarDate(period);
+ } else {
+ throw new Error('Unsupported calendar: ' + date.calendar);
+ }
+ }
+
+ @Input() errorStateMatcher: ErrorStateMatcher;
+
+ constructor(fb: FormBuilder,
+ @Optional() @Self() public ngControl: NgControl,
+ private fm: FocusMonitor,
+ private elRef: ElementRef,
+ @Optional() _parentForm: NgForm,
+ @Optional() _parentFormGroup: FormGroupDirective,
+ _defaultErrorStateMatcher: ErrorStateMatcher) {
+
+ super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
+
+ this.endDateControl = new FormControl(null);
+ this.isPeriodControl = new FormControl(null);
+ this.startDateControl
+ = new FormControl(
+ null,
+ [Validators.required, sameCalendarValidator(this.isPeriodControl, this.endDateControl), periodStartEndValidator(this.isPeriodControl, this.endDateControl)]
+ );
+
+ this.form = fb.group({
+ dateStart: this.startDateControl,
+ dateEnd: this.endDateControl,
+ isPeriod: this.isPeriodControl
+ });
+
+
+ fm.monitor(elRef.nativeElement, true).subscribe(origin => {
+ this.focused = !!origin;
+ this.stateChanges.next();
+ });
+
+ if (this.ngControl != null) {
+ this.ngControl.valueAccessor = this;
+ }
+ }
+
+ ngDoCheck() {
+ if (this.ngControl) {
+ this.updateErrorState();
+ }
+ }
+
+ ngOnDestroy() {
+ this.stateChanges.complete();
+ }
+
+ onContainerClick(event: MouseEvent) {
+ if ((event.target as Element).tagName.toLowerCase() != 'input') {
+ this.elRef.nativeElement.querySelector('input').focus();
+ }
+ }
+
+ writeValue(date: KnoraDate | KnoraPeriod | null): void {
+ this.value = date;
+ }
+
+ registerOnChange(fn: any): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ }
+
+ _handleInput(): void {
+ // trigger evaluation of validators defined for start date
+ this.startDateControl.updateValueAndValidity();
+ this.onChange(this.value);
+ }
+
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.html b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.html
new file mode 100644
index 000000000..0b9484aaa
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.scss b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.scss
new file mode 100644
index 000000000..36fb61ae9
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.scss
@@ -0,0 +1,11 @@
+:host ::ng-deep .child-value-component {
+ .mat-form-field-underline {
+ display: none;
+ }
+ .mat-form-field-infix{
+ border-top: 0.2em solid transparent !important;
+ .mat-form-field-underline{
+ display: block;
+ }
+ }
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.spec.ts b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.spec.ts
new file mode 100644
index 000000000..2828880ef
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.spec.ts
@@ -0,0 +1,492 @@
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {DateValueComponent} from './date-value.component';
+import {Component, DebugElement, forwardRef, Input, OnInit, ViewChild} from '@angular/core';
+import {CreateDateValue, KnoraDate, KnoraPeriod, MockResource, ReadDateValue, UpdateDateValue} from '@knora/api';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule} from '@angular/forms';
+import {MatFormFieldControl} from '@angular/material/form-field';
+import {Subject} from 'rxjs';
+import {ErrorStateMatcher} from '@angular/material/core';
+import {By} from '@angular/platform-browser';
+import {MatInputModule} from '@angular/material/input';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+
+@Component({
+ selector: `kui-date-input`,
+ template: ``,
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ multi: true,
+ useExisting: forwardRef(() => TestDateInputComponent),
+ },
+ {provide: MatFormFieldControl, useExisting: TestDateInputComponent}
+ ]
+})
+class TestDateInputComponent implements ControlValueAccessor, MatFormFieldControl {
+
+ @Input() readonly = false;
+ @Input() value;
+ @Input() disabled: boolean;
+ @Input() empty: boolean;
+ @Input() placeholder: string;
+ @Input() required: boolean;
+ @Input() shouldLabelFloat: boolean;
+ @Input() errorStateMatcher: ErrorStateMatcher;
+
+ errorState = false;
+ focused = false;
+ id = 'testid';
+ ngControl: NgControl | null;
+ onChange = (_: any) => {
+ };
+ stateChanges = new Subject();
+
+ writeValue(date: KnoraDate | KnoraPeriod | null): void {
+ this.value = date;
+ }
+
+ registerOnChange(fn: any): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ }
+
+ onContainerClick(event: MouseEvent): void {
+ }
+
+ setDescribedByIds(ids: string[]): void {
+ }
+
+ _handleInput(): void {
+ this.onChange(this.value);
+ }
+
+}
+
+/**
+ * Test host component to simulate parent component.
+ */
+@Component({
+ template: `
+ `
+})
+class TestHostDisplayValueComponent implements OnInit {
+
+ @ViewChild('inputVal', {static: false}) inputValueComponent: DateValueComponent;
+
+ displayInputVal: ReadDateValue;
+
+ mode: 'read' | 'update' | 'create' | 'search';
+
+ ngOnInit() {
+
+ MockResource.getTestthing().subscribe(res => {
+ const inputVal: ReadDateValue =
+ res[0].getValuesAs('http://0.0.0.0:3333/ontology/0001/anything/v2#hasDate', ReadDateValue)[0];
+
+ this.displayInputVal = inputVal;
+
+ this.mode = 'read';
+ });
+
+ }
+}
+
+/**
+ * Test host component to simulate parent component.
+ */
+@Component({
+ template: `
+ `
+})
+class TestHostCreateValueComponent implements OnInit {
+
+ @ViewChild('inputVal', {static: false}) inputValueComponent: DateValueComponent;
+
+ mode: 'read' | 'update' | 'create' | 'search';
+
+ ngOnInit() {
+
+ this.mode = 'create';
+
+ }
+}
+
+describe('DateValueComponent', () => {
+ let component: DateValueComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ ReactiveFormsModule,
+ MatInputModule,
+ BrowserAnimationsModule
+ ],
+ declarations: [
+ DateValueComponent,
+ TestDateInputComponent,
+ TestHostDisplayValueComponent,
+ TestHostCreateValueComponent]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DateValueComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ describe('display and edit a date value', () => {
+ let testHostComponent: TestHostDisplayValueComponent;
+ let testHostFixture: ComponentFixture;
+
+ let valueComponentDe: DebugElement;
+ let commentTextareaDebugElement: DebugElement;
+ let commentTextareaNativeElement;
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostDisplayValueComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ expect(testHostComponent.inputValueComponent).toBeTruthy();
+
+ const hostCompDe = testHostFixture.debugElement;
+
+ valueComponentDe = hostCompDe.query(By.directive(DateValueComponent));
+
+ });
+
+ it('should display an existing value', () => {
+
+ expect(testHostComponent.inputValueComponent.displayValue.date).toEqual(new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('read');
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.readonly).toEqual(true);
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13));
+
+ });
+
+ it('should make an existing value editable', () => {
+
+ testHostComponent.mode = 'update';
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('update');
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.readonly).toEqual(false);
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13));
+
+ // simulate user input
+ const newKnoraDate = new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13);
+
+ testHostComponent.inputValueComponent.dateInputComponent.value = newKnoraDate;
+ testHostComponent.inputValueComponent.dateInputComponent._handleInput();
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.valueFormControl.value).toBeTruthy();
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();
+
+ const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue();
+
+ expect(updatedValue instanceof UpdateDateValue).toBeTruthy();
+
+ expect((updatedValue as UpdateDateValue).calendar).toEqual('GREGORIAN');
+ expect((updatedValue as UpdateDateValue).startYear).toEqual(2019);
+ expect((updatedValue as UpdateDateValue).endYear).toEqual(2019);
+ expect((updatedValue as UpdateDateValue).startMonth).toEqual(5);
+ expect((updatedValue as UpdateDateValue).endMonth).toEqual(5);
+ expect((updatedValue as UpdateDateValue).startDay).toEqual(13);
+ expect((updatedValue as UpdateDateValue).endDay).toEqual(13);
+
+ });
+
+ it('should validate an existing value with an added comment', () => {
+
+ testHostComponent.mode = 'update';
+
+ testHostFixture.detectChanges();
+
+ commentTextareaDebugElement = valueComponentDe.query(By.css('textarea.comment'));
+ commentTextareaNativeElement = commentTextareaDebugElement.nativeElement;
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('update');
+
+ expect(testHostComponent.inputValueComponent.displayValue.date).toEqual(new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.readonly).toEqual(false);
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ commentTextareaNativeElement.value = 'this is a comment';
+
+ commentTextareaNativeElement.dispatchEvent(new Event('input'));
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();
+
+ const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue();
+
+ expect(updatedValue instanceof UpdateDateValue).toBeTruthy();
+
+ expect((updatedValue as UpdateDateValue).valueHasComment).toEqual('this is a comment');
+
+ });
+
+ it('should not return an invalid update value', () => {
+
+ testHostComponent.mode = 'update';
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('update');
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.readonly).toEqual(false);
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ testHostComponent.inputValueComponent.dateInputComponent.value = null;
+ testHostComponent.inputValueComponent.dateInputComponent._handleInput();
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue();
+
+ expect(updatedValue).toBeFalsy();
+
+ });
+
+ it('should restore the initially displayed value', () => {
+
+ testHostComponent.mode = 'update';
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('update');
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.readonly).toEqual(false);
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ // simulate user input
+ const newKnoraDate = new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13);
+
+ testHostComponent.inputValueComponent.dateInputComponent.value = newKnoraDate;
+ testHostComponent.inputValueComponent.dateInputComponent._handleInput();
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.valueFormControl.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13));
+
+ testHostComponent.inputValueComponent.resetFormControl();
+
+ expect(testHostComponent.inputValueComponent.valueFormControl.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ });
+
+ it('should set a new display value', done => {
+
+ MockResource.getTestthing().subscribe(res => {
+ const newDate: ReadDateValue =
+ res[0].getValuesAs('http://0.0.0.0:3333/ontology/0001/anything/v2#hasDate', ReadDateValue)[0];
+
+ newDate.id = 'updatedId';
+
+ newDate.date = new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13);
+
+ testHostComponent.displayInputVal = newDate;
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.valueFormControl.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13));
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();
+
+ done();
+ });
+
+ });
+
+ it('should unsubscribe when destroyed', () => {
+ expect(testHostComponent.inputValueComponent.valueChangesSubscription.closed).toBeFalsy();
+
+ testHostComponent.inputValueComponent.ngOnDestroy();
+
+ expect(testHostComponent.inputValueComponent.valueChangesSubscription.closed).toBeTruthy();
+
+ });
+
+ it('should compare two dates', () => {
+
+ expect(testHostComponent.inputValueComponent.sameDate(
+ new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13),
+ new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13))).toEqual(true);
+
+ expect(testHostComponent.inputValueComponent.sameDate(
+ new KnoraDate('JULIAN', 'CE', 2018, 5, 13),
+ new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13))).toEqual(false);
+
+ expect(testHostComponent.inputValueComponent.sameDate(
+ new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13),
+ new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13))).toEqual(false);
+
+ });
+
+ it('should correctly populate an UpdateValue from a KnoraDate', () => {
+
+ const date = new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13);
+
+ const updateVal = new UpdateDateValue();
+
+ testHostComponent.inputValueComponent.populateValue(updateVal, date);
+
+ expect(updateVal.calendar).toEqual('GREGORIAN');
+ expect(updateVal.startEra).toEqual('CE');
+ expect(updateVal.startDay).toEqual(13);
+ expect(updateVal.startMonth).toEqual(5);
+ expect(updateVal.startYear).toEqual(2018);
+ expect(updateVal.endEra).toEqual('CE');
+ expect(updateVal.endDay).toEqual(13);
+ expect(updateVal.endMonth).toEqual(5);
+ expect(updateVal.endYear).toEqual(2018);
+
+ });
+
+ it('should correctly populate an UpdateValue from a KnoraPeriod', () => {
+
+ const dateStart = new KnoraDate('GREGORIAN', 'CE', 2018, 5, 13);
+ const dateEnd = new KnoraDate('GREGORIAN', 'CE', 2019, 6, 14);
+
+ const updateVal = new UpdateDateValue();
+
+ testHostComponent.inputValueComponent.populateValue(updateVal, new KnoraPeriod(dateStart, dateEnd));
+
+ expect(updateVal.calendar).toEqual('GREGORIAN');
+ expect(updateVal.startEra).toEqual('CE');
+ expect(updateVal.startDay).toEqual(13);
+ expect(updateVal.startMonth).toEqual(5);
+ expect(updateVal.startYear).toEqual(2018);
+ expect(updateVal.endEra).toEqual('CE');
+ expect(updateVal.endDay).toEqual(14);
+ expect(updateVal.endMonth).toEqual(6);
+ expect(updateVal.endYear).toEqual(2019);
+
+ });
+
+ });
+
+ describe('create a date value', () => {
+
+ let testHostComponent: TestHostCreateValueComponent;
+ let testHostFixture: ComponentFixture;
+
+ let valueComponentDe: DebugElement;
+ let commentTextareaDebugElement: DebugElement;
+ let commentTextareaNativeElement;
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostCreateValueComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ expect(testHostComponent.inputValueComponent).toBeTruthy();
+
+ const hostCompDe = testHostFixture.debugElement;
+
+ valueComponentDe = hostCompDe.query(By.directive(DateValueComponent));
+ commentTextareaDebugElement = valueComponentDe.query(By.css('textarea.comment'));
+ commentTextareaNativeElement = commentTextareaDebugElement.nativeElement;
+ });
+
+ it('should create a value', () => {
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(null);
+
+ // simulate user input
+ const newKnoraDate = new KnoraDate('JULIAN', 'CE', 2019, 5, 13);
+
+ testHostComponent.inputValueComponent.dateInputComponent.value = newKnoraDate;
+ testHostComponent.inputValueComponent.dateInputComponent._handleInput();
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('create');
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();
+
+ const newValue = testHostComponent.inputValueComponent.getNewValue();
+
+ expect(newValue instanceof CreateDateValue).toBeTruthy();
+
+ expect((newValue as CreateDateValue).calendar).toEqual('JULIAN');
+
+ expect((newValue as CreateDateValue).startDay).toEqual(13);
+ expect((newValue as CreateDateValue).endDay).toEqual(13);
+ expect((newValue as CreateDateValue).startMonth).toEqual(5);
+ expect((newValue as CreateDateValue).endMonth).toEqual(5);
+ expect((newValue as CreateDateValue).startYear).toEqual(2019);
+ expect((newValue as CreateDateValue).endYear).toEqual(2019);
+
+ });
+
+ it('should reset form after cancellation', () => {
+
+ // simulate user input
+ const newKnoraDate = new KnoraDate('JULIAN', 'CE', 2019, 5, 13);
+
+ testHostComponent.inputValueComponent.dateInputComponent.value = newKnoraDate;
+ testHostComponent.inputValueComponent.dateInputComponent._handleInput();
+
+ testHostFixture.detectChanges();
+
+ commentTextareaNativeElement.value = 'created comment';
+
+ commentTextareaNativeElement.dispatchEvent(new Event('input'));
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.inputValueComponent.mode).toEqual('create');
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();
+
+ testHostComponent.inputValueComponent.resetFormControl();
+
+ expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy();
+
+ expect(testHostComponent.inputValueComponent.dateInputComponent.value).toEqual(null);
+
+ expect(commentTextareaNativeElement.value).toEqual('');
+
+ });
+
+ });
+
+
+});
diff --git a/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.ts b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.ts
new file mode 100644
index 000000000..d3deb120e
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/date-value/date-value.component.ts
@@ -0,0 +1,197 @@
+import {Component, Inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild} from '@angular/core';
+import {CreateDateValue, KnoraDate, KnoraPeriod, ReadDateValue, UpdateDateValue} from '@knora/api';
+import {
+ AbstractControl,
+ FormBuilder,
+ FormControl,
+ FormGroup,
+ FormGroupDirective,
+ NgForm,
+ ValidatorFn
+} from '@angular/forms';
+import {Subscription} from 'rxjs';
+import {BaseValueComponent} from '../base-value.component';
+import {ErrorStateMatcher} from '@angular/material';
+import {DateInputComponent} from './date-input/date-input.component';
+
+/** Error when invalid control is dirty, touched, or submitted. */
+export class DateErrorStateMatcher implements ErrorStateMatcher {
+ isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
+ const isSubmitted = form && form.submitted;
+ return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
+ }
+}
+
+@Component({
+ selector: 'kui-date-value',
+ templateUrl: './date-value.component.html',
+ styleUrls: ['./date-value.component.scss']
+})
+export class DateValueComponent extends BaseValueComponent implements OnInit, OnChanges, OnDestroy {
+
+ @ViewChild('dateInput', {static: false}) dateInputComponent: DateInputComponent;
+
+ @Input() displayValue?: ReadDateValue;
+
+ valueFormControl: FormControl;
+ commentFormControl: FormControl;
+
+ form: FormGroup;
+
+ valueChangesSubscription: Subscription;
+
+ customValidators = [];
+
+ matcher = new DateErrorStateMatcher();
+
+ constructor(@Inject(FormBuilder) private fb: FormBuilder) {
+ super();
+ }
+
+ /**
+ * Returns true if both dates are the same.
+ *
+ * @param date1 date for comparison with date2
+ * @param date2 date for comparison with date 1
+ */
+ sameDate(date1: KnoraDate, date2: KnoraDate): boolean {
+ return (date1.calendar === date2.calendar && date1.year === date2.year && date1.month === date2.month && date1.day === date2.day);
+ }
+
+ standardValidatorFunc: (val: any, comment: string, commentCtrl: FormControl) => ValidatorFn
+ = (initValue: any, initComment: string, commentFormControl: FormControl): ValidatorFn => {
+ return (control: AbstractControl): { [key: string]: any } | null => {
+
+ let sameValue: boolean;
+ if (initValue instanceof KnoraDate && control.value instanceof KnoraDate) {
+ sameValue = this.sameDate(initValue, control.value);
+ } else if (initValue instanceof KnoraPeriod && control.value instanceof KnoraPeriod) {
+ sameValue = this.sameDate(initValue.start, control.value.start) && this.sameDate(initValue.end, control.value.end);
+ } else {
+ // init value and current value have different types
+ sameValue = false;
+ }
+
+ const invalid = (sameValue && initValue.end === control.value.end)
+ && (initComment === commentFormControl.value || (initComment === null && commentFormControl.value === ''));
+
+ return invalid ? {valueNotChanged: {value: control.value}} : null;
+ };
+ };
+
+ getInitValue(): KnoraDate | KnoraPeriod | null {
+ if (this.displayValue !== undefined) {
+ return this.displayValue.date;
+ } else {
+ return null;
+ }
+ }
+
+ ngOnInit() {
+ // initialize form control elements
+ this.valueFormControl = new FormControl(null);
+
+ this.commentFormControl = new FormControl(null);
+
+ // subscribe to any change on the comment and recheck validity
+ this.valueChangesSubscription = this.commentFormControl.valueChanges.subscribe(
+ data => {
+ this.valueFormControl.updateValueAndValidity();
+ }
+ );
+
+ this.form = this.fb.group({
+ dateValue: this.valueFormControl,
+ comment: this.commentFormControl
+ });
+
+ this.resetFormControl();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.resetFormControl();
+ }
+
+ // unsubscribe when the object is destroyed to prevent memory leaks
+ ngOnDestroy(): void {
+ this.unsubscribeFromValueChanges();
+ }
+
+ /**
+ * Given a value and a period or Date, populates the value.
+ *
+ * @param value the value to be populated.
+ * @param dateOrPeriod the date or period to read from.
+ */
+ populateValue(value: UpdateDateValue | CreateDateValue, dateOrPeriod: KnoraDate | KnoraPeriod) {
+
+ if (dateOrPeriod instanceof KnoraDate) {
+
+ value.calendar = dateOrPeriod.calendar;
+ value.startEra = dateOrPeriod.era;
+ value.startDay = dateOrPeriod.day;
+ value.startMonth = dateOrPeriod.month;
+ value.startYear = dateOrPeriod.year;
+
+ value.endEra = value.startEra;
+ value.endDay = value.startDay;
+ value.endMonth = value.startMonth;
+ value.endYear = value.startYear;
+
+ } else if (dateOrPeriod instanceof KnoraPeriod) {
+
+ value.calendar = dateOrPeriod.start.calendar;
+
+ value.startEra = dateOrPeriod.start.era;
+ value.startDay = dateOrPeriod.start.day;
+ value.startMonth = dateOrPeriod.start.month;
+ value.startYear = dateOrPeriod.start.year;
+
+ value.endEra = dateOrPeriod.end.era;
+ value.endDay = dateOrPeriod.end.day;
+ value.endMonth = dateOrPeriod.end.month;
+ value.endYear = dateOrPeriod.end.year;
+
+ }
+ }
+
+ getNewValue(): CreateDateValue | false {
+ if (this.mode !== 'create' || !this.form.valid) {
+ return false;
+ }
+
+ const newDateValue = new CreateDateValue();
+
+ const dateOrPeriod = this.valueFormControl.value;
+
+ this.populateValue(newDateValue, dateOrPeriod);
+
+ if (this.commentFormControl.value !== null && this.commentFormControl.value !== '') {
+ newDateValue.valueHasComment = this.commentFormControl.value;
+ }
+
+ return newDateValue;
+ }
+
+ getUpdatedValue(): UpdateDateValue | false {
+ if (this.mode !== 'update' || !this.form.valid) {
+ return false;
+ }
+
+ const updatedDateValue = new UpdateDateValue();
+
+ updatedDateValue.id = this.displayValue.id;
+
+ const dateOrPeriod = this.valueFormControl.value;
+
+ this.populateValue(updatedDateValue, dateOrPeriod);
+
+ // add the submitted comment to updatedIntValue only if user has added a comment
+ if (this.commentFormControl.value !== null && this.commentFormControl.value !== '') {
+ updatedDateValue.valueHasComment = this.commentFormControl.value;
+ }
+
+ return updatedDateValue;
+ }
+
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/int-value/int-value.component.spec.ts b/projects/knora-ui/src/lib/viewer/values/int-value/int-value.component.spec.ts
index 4bcea25e8..4ca346a9f 100644
--- a/projects/knora-ui/src/lib/viewer/values/int-value/int-value.component.spec.ts
+++ b/projects/knora-ui/src/lib/viewer/values/int-value/int-value.component.spec.ts
@@ -6,7 +6,6 @@ import { OnInit, Component, ViewChild, DebugElement } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { $ } from 'protractor';
import { By } from '@angular/platform-browser';
/**
diff --git a/projects/knora-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts b/projects/knora-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts
index f06044ca6..a206334da 100644
--- a/projects/knora-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts
+++ b/projects/knora-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts
@@ -41,7 +41,7 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
selector: 'kui-interval-input',
templateUrl: './interval-input.component.html',
styleUrls: ['./interval-input.component.scss'],
- providers: [{provide: MatFormFieldControl, useExisting: IntervalInputComponent}],
+ providers: [{provide: MatFormFieldControl, useExisting: IntervalInputComponent}]
})
export class IntervalInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl, DoCheck, CanUpdateErrorState, OnDestroy {
static nextId = 0;
diff --git a/projects/knora-ui/src/lib/viewer/values/jdn-datepicker-directive/jdndatepicker.directive.spec.ts b/projects/knora-ui/src/lib/viewer/values/jdn-datepicker-directive/jdndatepicker.directive.spec.ts
new file mode 100644
index 000000000..74e0ac319
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/jdn-datepicker-directive/jdndatepicker.directive.spec.ts
@@ -0,0 +1,115 @@
+import {JDNDatepickerDirective} from './jdndatepicker.directive';
+import {Component, OnInit, ViewChild} from "@angular/core";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
+import {ACTIVE_CALENDAR, JDNConvertibleCalendarDateAdapter} from "jdnconvertiblecalendardateadapter";
+import {DateAdapter} from "@angular/material/core";
+
+/**
+ * Test host component to simulate parent component.
+ */
+@Component({
+ template: `
+ `
+})
+class TestHostComponent implements OnInit {
+
+ @ViewChild(JDNDatepickerDirective, {static: false}) jdnDir;
+
+ activeCalendar: string;
+
+ ngOnInit() {
+ this.activeCalendar = 'Gregorian';
+ }
+}
+
+
+describe('JDNDatepickerDirective', () => {
+ let testHostComponent: TestHostComponent;
+ let testHostFixture: ComponentFixture;
+
+ let testBehaviourSubject;
+ let setNextCalSpy;
+ let setCompleteSpy;
+
+ let testBehaviourSubjSpy;
+
+ beforeEach(async(() => {
+
+ testBehaviourSubject = jasmine.createSpyObj('ACTIVE_CALENDAR', ['next', 'complete']);
+
+ setNextCalSpy = testBehaviourSubject.next.and.stub();
+ setCompleteSpy = testBehaviourSubject.complete.and.stub();
+
+ TestBed.configureTestingModule({
+ declarations: [
+ JDNDatepickerDirective,
+ TestHostComponent
+ ],
+ providers: [
+ {
+ provide: DateAdapter, useValue: {}},
+ {
+ provide: ACTIVE_CALENDAR, useValue: testBehaviourSubject
+ }
+ ],
+ imports: [
+ BrowserAnimationsModule
+ ],
+ })
+ .compileComponents();
+
+ // overrides the injection token defined in JDNDatepickerDirective's metadata
+ TestBed.overrideProvider(ACTIVE_CALENDAR, {useValue: testBehaviourSubject});
+ TestBed.overrideProvider(DateAdapter, {useValue: {}});
+
+ testBehaviourSubjSpy = TestBed.get(ACTIVE_CALENDAR);
+ }));
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ expect(testHostComponent.jdnDir).toBeTruthy();
+ });
+
+ it('should create an instance', () => {
+ expect(testBehaviourSubjSpy.next).toHaveBeenCalledTimes(1);
+ expect(testBehaviourSubjSpy.next).toHaveBeenCalledWith('Gregorian');
+ });
+
+ it('should update the calendar when the input changes', () => {
+ testHostComponent.activeCalendar = 'Julian';
+ testHostFixture.detectChanges();
+
+ expect(testBehaviourSubjSpy.next).toHaveBeenCalledTimes(2);
+
+ expect(testBehaviourSubjSpy.next.calls.all()[0].args).toEqual(['Gregorian']);
+ expect(testBehaviourSubjSpy.next.calls.all()[1].args).toEqual(['Julian']);
+
+ });
+
+ it('should set the calendar to Gregorian when called with null', () => {
+ testHostComponent.activeCalendar = null;
+ testHostFixture.detectChanges();
+
+ expect(testBehaviourSubjSpy.next).toHaveBeenCalledTimes(2);
+
+ expect(testBehaviourSubjSpy.next.calls.all()[0].args).toEqual(['Gregorian']);
+ expect(testBehaviourSubjSpy.next.calls.all()[1].args).toEqual(['Gregorian']);
+
+ });
+
+ it('should complete the BehaviourSubject when destroyed', () => {
+
+ expect(setCompleteSpy).toHaveBeenCalledTimes(0);
+
+ testHostComponent.jdnDir.ngOnDestroy();
+
+ expect(setCompleteSpy).toHaveBeenCalledTimes(1);
+
+ });
+
+});
diff --git a/projects/knora-ui/src/lib/viewer/values/jdn-datepicker-directive/jdndatepicker.directive.ts b/projects/knora-ui/src/lib/viewer/values/jdn-datepicker-directive/jdndatepicker.directive.ts
new file mode 100644
index 000000000..1c693134a
--- /dev/null
+++ b/projects/knora-ui/src/lib/viewer/values/jdn-datepicker-directive/jdndatepicker.directive.ts
@@ -0,0 +1,46 @@
+import {Directive, Inject, Input, OnChanges, OnDestroy, SimpleChanges} from '@angular/core';
+import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material';
+import {JDNConvertibleCalendar} from 'jdnconvertiblecalendar';
+import {ACTIVE_CALENDAR, JDNConvertibleCalendarDateAdapter} from 'jdnconvertiblecalendardateadapter';
+import {BehaviorSubject} from 'rxjs';
+
+export function makeCalendarToken() {
+ return new BehaviorSubject('Gregorian');
+}
+
+@Directive({
+ selector: 'kui-jdn-datepicker',
+ providers: [
+ {provide: DateAdapter, useClass: JDNConvertibleCalendarDateAdapter, deps: [MAT_DATE_LOCALE, ACTIVE_CALENDAR]},
+ {provide: ACTIVE_CALENDAR, useFactory: makeCalendarToken}
+ ]
+})
+export class JDNDatepickerDirective implements OnChanges, OnDestroy {
+
+ private _activeCalendar: 'Gregorian' | 'Julian' | 'Islamic';
+
+ @Input()
+ set activeCalendar(value: 'Gregorian' | 'Julian' | 'Islamic' | null) {
+ if (value !== null && value !== undefined) {
+ this._activeCalendar = value;
+ } else {
+ this._activeCalendar = 'Gregorian';
+ }
+ }
+
+ get activeCalendar() {
+ return this._activeCalendar;
+ }
+
+ constructor(@Inject(ACTIVE_CALENDAR) private activeCalendarToken, private adapter: DateAdapter) {
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.activeCalendarToken.next(this.activeCalendar);
+ }
+
+ ngOnDestroy(): void {
+ this.activeCalendarToken.complete();
+ }
+
+}
diff --git a/projects/knora-ui/src/lib/viewer/values/time-value/jdn-datepicker-directive/jdndatepicker.directive.spec.ts b/projects/knora-ui/src/lib/viewer/values/time-value/jdn-datepicker-directive/jdndatepicker.directive.spec.ts
deleted file mode 100644
index 674517186..000000000
--- a/projects/knora-ui/src/lib/viewer/values/time-value/jdn-datepicker-directive/jdndatepicker.directive.spec.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { JDNDatepickerDirective } from './jdndatepicker.directive';
-
-describe('JDNDatepickerDirective', () => {
-
-});
\ No newline at end of file
diff --git a/projects/knora-ui/src/lib/viewer/values/time-value/jdn-datepicker-directive/jdndatepicker.directive.ts b/projects/knora-ui/src/lib/viewer/values/time-value/jdn-datepicker-directive/jdndatepicker.directive.ts
deleted file mode 100644
index 44c50d4d5..000000000
--- a/projects/knora-ui/src/lib/viewer/values/time-value/jdn-datepicker-directive/jdndatepicker.directive.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import {Directive} from '@angular/core';
-import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material';
-import {JDNConvertibleCalendar} from 'jdnconvertiblecalendar';
-import {JDNConvertibleCalendarDateAdapter} from 'jdnconvertiblecalendardateadapter';
-
-@Directive({
- selector: 'kuiJDNDatepicker',
- providers: [{provide: DateAdapter, useClass: JDNConvertibleCalendarDateAdapter, deps: [MAT_DATE_LOCALE]}]
-})
-export class JDNDatepickerDirective {
-
- constructor(private adapter: DateAdapter) {
- }
-
-}
\ No newline at end of file
diff --git a/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html b/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html
index 4334aa44d..a990ad49a 100644
--- a/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html
+++ b/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html
@@ -1,8 +1,10 @@
+
+
diff --git a/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts b/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts
index 076e6b79e..49b3bece5 100644
--- a/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts
+++ b/projects/knora-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts
@@ -1,14 +1,15 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
-import { TimeInputComponent, DateTime } from './time-input.component';
-import { Component, OnInit, ViewChild, DebugElement } from '@angular/core';
-import { FormGroup, FormBuilder, ReactiveFormsModule } from '@angular/forms';
-import { MatFormFieldModule, MatInputModule } from '@angular/material';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { By } from '@angular/platform-browser';
-import { MatDatepickerModule } from '@angular/material/datepicker'
-import { GregorianCalendarDate, CalendarPeriod, CalendarDate } from 'jdnconvertiblecalendar';
-import { MatJDNConvertibleCalendarDateAdapterModule } from 'jdnconvertiblecalendardateadapter';
+import {TimeInputComponent, DateTime} from './time-input.component';
+import {Component, OnInit, ViewChild, DebugElement} from '@angular/core';
+import {FormGroup, FormBuilder, ReactiveFormsModule} from '@angular/forms';
+import {MatFormFieldModule, MatInputModule} from '@angular/material';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {By} from '@angular/platform-browser';
+import {MatDatepickerModule} from '@angular/material/datepicker'
+import {GregorianCalendarDate, CalendarPeriod, CalendarDate} from 'jdnconvertiblecalendar';
+import {MatJDNConvertibleCalendarDateAdapterModule} from 'jdnconvertiblecalendardateadapter';
+import {JDNDatepickerDirective} from "../../jdn-datepicker-directive/jdndatepicker.directive";
/**
@@ -54,8 +55,14 @@ describe('TimeInputComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatDatepickerModule, MatJDNConvertibleCalendarDateAdapterModule, BrowserAnimationsModule],
- declarations: [TimeInputComponent, TestHostComponent]
+ imports: [
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatDatepickerModule,
+ MatJDNConvertibleCalendarDateAdapterModule,
+ BrowserAnimationsModule],
+ declarations: [TimeInputComponent, TestHostComponent, JDNDatepickerDirective]
})
.compileComponents();
}));
@@ -84,7 +91,7 @@ describe('TimeInputComponent', () => {
it('should initialize the date correctly', () => {
expect(dateInputNativeElement.value).toEqual('06-08-2019');
-
+
expect(timeInputNativeElement.value).toEqual('14:00');
});
@@ -127,7 +134,7 @@ describe('TimeInputComponent', () => {
expect(dateTime.date.toCalendarPeriod().periodStart.month).toEqual(10);
expect(dateTime.date.toCalendarPeriod().periodStart.day).toEqual(10);
- expect (dateTime.time).toEqual('12:00');
+ expect(dateTime.time).toEqual('12:00');
});
-});
\ No newline at end of file
+});
diff --git a/projects/knora-ui/src/lib/viewer/viewer.module.ts b/projects/knora-ui/src/lib/viewer/viewer.module.ts
index 497dcc998..0b17bb103 100644
--- a/projects/knora-ui/src/lib/viewer/viewer.module.ts
+++ b/projects/knora-ui/src/lib/viewer/viewer.module.ts
@@ -3,12 +3,11 @@ import {TextValueAsStringComponent} from './values/text-value/text-value-as-stri
import {ReactiveFormsModule} from '@angular/forms';
import {MatInputModule} from '@angular/material/input';
import {MatCheckboxModule} from '@angular/material/checkbox';
-import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {CommonModule} from '@angular/common';
import {ColorPickerModule} from 'ngx-color-picker';
-import { MatMenuModule } from '@angular/material/menu';
-
+import {MatMenuModule} from '@angular/material/menu';
import {IntValueComponent} from './values/int-value/int-value.component';
+import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {LinkValueComponent} from './values/link-value/link-value.component';
import {DisplayEditComponent} from './operations/display-edit/display-edit.component';
import {BooleanValueComponent} from './values/boolean-value/boolean-value.component';
@@ -25,9 +24,14 @@ import {TimeValueComponent} from './values/time-value/time-value.component';
import {TimeInputComponent} from './values/time-value/time-input/time-input.component';
import {MatDatepickerModule} from '@angular/material';
import {MatJDNConvertibleCalendarDateAdapterModule} from 'jdnconvertiblecalendardateadapter';
-import { JDNDatepickerDirective } from './values/time-value/jdn-datepicker-directive/jdndatepicker.directive';
-import { ColorPickerComponent } from './values/color-value/color-picker/color-picker.component';
+import {ColorPickerComponent} from './values/color-value/color-picker/color-picker.component';
+import {MatOptionModule} from '@angular/material/core';
+import {MatSelectModule} from '@angular/material/select';
+import {DateValueComponent} from './values/date-value/date-value.component';
+import {CalendarHeaderComponent} from './values/date-value/calendar-header/calendar-header.component';
+import {DateInputComponent} from './values/date-value/date-input/date-input.component';
import {MatIconModule} from '@angular/material/icon';
+import {JDNDatepickerDirective} from './values/jdn-datepicker-directive/jdndatepicker.directive';
@NgModule({
declarations: [
@@ -36,18 +40,18 @@ import {MatIconModule} from '@angular/material/icon';
IntValueComponent,
DisplayEditComponent,
BooleanValueComponent,
- ColorValueComponent,
- ColorPickerComponent,
- LinkValueComponent,
DecimalValueComponent,
UriValueComponent,
IntervalValueComponent,
IntervalInputComponent,
TimeValueComponent,
TimeInputComponent,
- JDNDatepickerDirective,
ColorValueComponent,
ColorPickerComponent,
+ DateValueComponent,
+ DateInputComponent,
+ JDNDatepickerDirective,
+ CalendarHeaderComponent,
LinkValueComponent,
ListValueComponent,
SublistValueComponent,
@@ -59,13 +63,15 @@ import {MatIconModule} from '@angular/material/icon';
MatInputModule,
MatAutocompleteModule,
MatCheckboxModule,
- MatMenuModule,
+ MatOptionModule,
+ MatSelectModule,
MatDatepickerModule,
MatIconModule,
MatJDNConvertibleCalendarDateAdapterModule,
+ MatMenuModule,
ColorPickerModule
- ],
-
+ ],
+ entryComponents: [CalendarHeaderComponent],
exports: [
TextValueAsStringComponent,
TextValueAsHtmlComponent,
@@ -77,6 +83,7 @@ import {MatIconModule} from '@angular/material/icon';
UriValueComponent,
IntervalValueComponent,
TimeValueComponent,
+ DateValueComponent,
LinkValueComponent,
ListValueComponent,
SublistValueComponent,
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index c6305ccc1..f957e1f67 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,9 +1,17 @@
-import { Component, Inject, OnInit, ViewChild } from '@angular/core';
-import { ApiResponseData, IHasProperty, KnoraApiConnection, LoginResponse, ReadResource, ReadValue, ResourcePropertyDefinition } from '@knora/api';
-import { PropertyDefinition } from '@knora/api/src/models/v2/ontologies/property-definition';
-import { KnoraApiConnectionToken } from 'knora-ui';
-import { DisplayEditComponent } from 'knora-ui/lib/viewer/operations/display-edit/display-edit.component';
-import { mergeMap } from 'rxjs/operators';
+import {Component, Inject, OnInit, ViewChild} from '@angular/core';
+import {
+ ApiResponseData,
+ IHasProperty,
+ KnoraApiConnection,
+ LoginResponse,
+ ReadResource,
+ ReadValue,
+ ResourcePropertyDefinition
+} from '@knora/api';
+import {mergeMap} from 'rxjs/operators';
+import {DisplayEditComponent} from 'knora-ui/lib/viewer/operations/display-edit/display-edit.component';
+import {KnoraApiConnectionToken} from 'knora-ui';
+import {PropertyDefinition} from '@knora/api/src/models/v2/ontologies/property-definition';
// object of property information from ontology class, properties and property values
export interface PropertyInfoValues {
@@ -65,4 +73,4 @@ export class AppComponent implements OnInit {
});
}
-}
\ No newline at end of file
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index e15d50713..2f447160b 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,11 +1,12 @@
-import { BrowserModule } from '@angular/platform-browser';
+import {BrowserModule} from '@angular/platform-browser';
import {APP_INITIALIZER, NgModule} from '@angular/core';
-import { AppRoutingModule } from './app-routing.module';
-import { AppComponent } from './app.component';
-import { KnoraApiConfigToken, KnoraApiConnectionToken, KuiConfigToken, ViewerModule} from 'knora-ui';
+import {AppRoutingModule} from './app-routing.module';
+import {AppComponent} from './app.component';
+import {KnoraApiConfigToken, KnoraApiConnectionToken, KuiConfigToken, ViewerModule} from 'knora-ui';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AppInitService} from './app-init.service';
+import {MatJDNConvertibleCalendarDateAdapterModule} from 'jdnconvertiblecalendardateadapter';
export function initializeApp(appInitService: AppInitService) {
return (): Promise => {
@@ -21,7 +22,8 @@ export function initializeApp(appInitService: AppInitService) {
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
- ViewerModule
+ ViewerModule,
+ MatJDNConvertibleCalendarDateAdapterModule
],
providers: [
AppInitService,
@@ -46,4 +48,5 @@ export function initializeApp(appInitService: AppInitService) {
],
bootstrap: [AppComponent]
})
-export class AppModule { }
+export class AppModule {
+}
diff --git a/yalc.lock b/yalc.lock
index 21bd1b0c0..f116b9f21 100644
--- a/yalc.lock
+++ b/yalc.lock
@@ -2,7 +2,7 @@
"version": "v1",
"packages": {
"@knora/api": {
- "signature": "cbf63807338d8cbc462923ad349eb3b4",
+ "signature": "3208f93884cf4115a1c97ef116855f3f",
"file": true
}
}