Merge branch 'dev'
|
@ -1,11 +1,13 @@
|
||||||
# title of the sample
|
# title of the sample
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Short summary on functionality and used technologies.
|
Short summary on functionality and used technologies.
|
||||||
|
|
||||||
[picture of the web part in action]
|
[picture of the web part in action]
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## Used SharePoint Framework Version
|
||||||
|
|
||||||
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
@ -33,25 +35,27 @@ Version|Date|Comments
|
||||||
1.0|August 29, 2025|Initial release
|
1.0|August 29, 2025|Initial release
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Minimal Path to Awesome
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
- Clone this repository
|
* Clone this repository
|
||||||
- in the command line run:
|
* in the command line run:
|
||||||
- `npm install`
|
* `npm install`
|
||||||
- `gulp serve`
|
* `gulp serve`
|
||||||
|
|
||||||
> Include any additional steps as needed.
|
> Include any additional steps as needed.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Description of the web part with possible additional details than in short summary.
|
Description of the web part with possible additional details than in short summary.
|
||||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||||
|
|
||||||
- topic 1
|
* topic 1
|
||||||
- topic 2
|
* topic 2
|
||||||
- topic 3
|
* topic 3
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
|
@ -57,4 +57,4 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
||||||
|
|
||||||
- Video Demonstration on using, building and code specifics for the sample web part:<br> http://warner.digital/dynamic-spfx-package-bundling/
|
- Video Demonstration on using, building and code specifics for the sample web part:<br> http://warner.digital/dynamic-spfx-package-bundling/
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-dynamic-bundling-libraries" />
|
||||||
|
|
|
@ -77,3 +77,4 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
||||||
* Logging.
|
* Logging.
|
||||||
* Rendering error messages.
|
* Rendering error messages.
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-employee-spotlight" />
|
||||||
|
|
|
@ -47,3 +47,5 @@ The _PowerBI Embedded_ Client-Side Web Part is built on the SharePoint Framework
|
||||||
All authentication and rendering happens client-side, there is no server-side component required.
|
All authentication and rendering happens client-side, there is no server-side component required.
|
||||||
|
|
||||||
It uses the [PowerBI Client](https://www.npmjs.com/package/powerbi-client) for rendering the PowerBI report.
|
It uses the [PowerBI Client](https://www.npmjs.com/package/powerbi-client) for rendering the PowerBI report.
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-powerbi-embedded" />
|
||||||
|
|
|
@ -63,3 +63,5 @@ gulp deploy-azure-storage
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
[Handling Multiple Editions of SPFx Solution](http://tricky-sharepoint.blogspot.com/2017/08/handling-multiple-editions-of-spfx.html)
|
[Handling Multiple Editions of SPFx Solution](http://tricky-sharepoint.blogspot.com/2017/08/handling-multiple-editions-of-spfx.html)
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-solution-editions" />
|
||||||
|
|
|
@ -67,6 +67,7 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
||||||
- Using the SharePoint Online REST API to manage Modern Experience Themes
|
- Using the SharePoint Online REST API to manage Modern Experience Themes
|
||||||
|
|
||||||
## Additional Information:
|
## Additional Information:
|
||||||
|
|
||||||
- [Office UI Fabric Theme Palette Generator](https://developer.microsoft.com/en-us/fabric#/styles/themegenerator)
|
- [Office UI Fabric Theme Palette Generator](https://developer.microsoft.com/en-us/fabric#/styles/themegenerator)
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-theme-manager" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-theme-manager" />
|
|
@ -45,3 +45,5 @@ Version|Date|Comments
|
||||||
* in the command line run:
|
* in the command line run:
|
||||||
* `npm install`
|
* `npm install`
|
||||||
* `gulp serve`
|
* `gulp serve`
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-workbench-customizer" />
|
||||||
|
|
|
@ -21,10 +21,8 @@
|
||||||
"officeFabricIconFontName": "DeveloperTools",
|
"officeFabricIconFontName": "DeveloperTools",
|
||||||
"properties": {
|
"properties": {
|
||||||
"requiresPageRefresh": false,
|
"requiresPageRefresh": false,
|
||||||
"maxWidth": true,
|
"customWorkbenchStyles": true,
|
||||||
"centerCanvas": true,
|
"previewMode": true
|
||||||
"overflow": true,
|
|
||||||
"padding": true
|
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,13 @@ import {
|
||||||
PropertyPaneToggle
|
PropertyPaneToggle
|
||||||
} from '@microsoft/sp-webpart-base';
|
} from '@microsoft/sp-webpart-base';
|
||||||
import styles from './WorkbenchCustomizerWebPart.module.scss';
|
import styles from './WorkbenchCustomizerWebPart.module.scss';
|
||||||
import { escape } from '@microsoft/sp-lodash-subset';
|
|
||||||
|
|
||||||
import * as strings from 'WorkbenchCustomizerWebPartStrings';
|
import * as strings from 'WorkbenchCustomizerWebPartStrings';
|
||||||
|
|
||||||
export interface IWorkbenchCustomizerWebPartProps {
|
export interface IWorkbenchCustomizerWebPartProps {
|
||||||
requiresPageRefresh: boolean;
|
requiresPageRefresh: boolean;
|
||||||
maxWidth: boolean;
|
customWorkbenchStyles: boolean;
|
||||||
centerCanvas: boolean;
|
previewMode: boolean;
|
||||||
overflow: boolean;
|
|
||||||
padding: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IWorkbenchCustomizerWebPartProps> {
|
export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IWorkbenchCustomizerWebPartProps> {
|
||||||
|
@ -26,30 +23,26 @@ export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IW
|
||||||
|
|
||||||
public async render(): Promise<void> {
|
public async render(): Promise<void> {
|
||||||
|
|
||||||
if (this.properties.maxWidth) {
|
if (!this.renderedOnce) {
|
||||||
await import('./styles/maxWidth.module.scss');
|
|
||||||
}
|
|
||||||
if (this.properties.centerCanvas) {
|
|
||||||
await import('./styles/centerCanvas.module.scss');
|
|
||||||
}
|
|
||||||
if (this.properties.overflow) {
|
|
||||||
await import('./styles/overflow.module.scss');
|
|
||||||
}
|
|
||||||
if (this.properties.padding) {
|
|
||||||
await import('./styles/padding.module.scss');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.domElement.innerHTML = `
|
if (this.properties.customWorkbenchStyles) {
|
||||||
|
await import('./styles/customWorkbenchStyles.module.scss');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.properties.previewMode) {
|
||||||
|
const previewBtn = document.getElementsByName("Preview")[0];
|
||||||
|
previewBtn.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domElement.innerHTML = `
|
||||||
<div class="${styles.workbenchCustomizer}">
|
<div class="${styles.workbenchCustomizer}">
|
||||||
${this.properties.requiresPageRefresh
|
${this.properties.requiresPageRefresh
|
||||||
? `<div class="${styles.redMessage}">Please refresh the page to update workbench styles</div>`
|
? `<div class="${styles.redMessage}">Please refresh the page to remove custom workbench styles</div>`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
<div>Max width enabled: ${this.properties.maxWidth}</div>
|
*** Workbench Customizer web part ***
|
||||||
<div>Center canvas zone: ${this.properties.centerCanvas}</div>
|
|
||||||
<div>Custom overflow enabled: ${this.properties.overflow}</div>
|
|
||||||
<div>Custom padding enabled: ${this.properties.padding}</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onPropertyPaneFieldChanged(path: string, oldValue: any, newValue: any): void {
|
public onPropertyPaneFieldChanged(path: string, oldValue: any, newValue: any): void {
|
||||||
|
@ -75,17 +68,11 @@ export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IW
|
||||||
{
|
{
|
||||||
groupName: strings.BasicGroupName,
|
groupName: strings.BasicGroupName,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneToggle('maxWidth', {
|
PropertyPaneToggle('customWorkbenchStyles', {
|
||||||
label: strings.MaxWidthFieldLabel
|
label: strings.CustomWorkbenchStylesFieldLabel
|
||||||
}),
|
}),
|
||||||
PropertyPaneToggle('centerCanvas', {
|
PropertyPaneToggle('previewMode', {
|
||||||
label: strings.CenterCanvasFieldLabel
|
label: strings.PreviewModeFieldLabel
|
||||||
}),
|
|
||||||
PropertyPaneToggle('overflow', {
|
|
||||||
label: strings.OverflowFieldLabel
|
|
||||||
}),
|
|
||||||
PropertyPaneToggle('padding', {
|
|
||||||
label: strings.PaddingFieldLabel
|
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,7 @@ define([], function() {
|
||||||
return {
|
return {
|
||||||
"PropertyPaneDescription": "Description",
|
"PropertyPaneDescription": "Description",
|
||||||
"BasicGroupName": "Configuration",
|
"BasicGroupName": "Configuration",
|
||||||
"MaxWidthFieldLabel": "Enable custom max width",
|
"CustomWorkbenchStylesFieldLabel": "Enable custom styles for Workbench",
|
||||||
"CenterCanvasFieldLabel": "Center canvas",
|
"PreviewModeFieldLabel": "Enable Preview mode by default",
|
||||||
"OverflowFieldLabel": "Enable custom overflow",
|
|
||||||
"PaddingFieldLabel": "Enable custom padding"
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
declare interface IWorkbenchCustomizerWebPartStrings {
|
declare interface IWorkbenchCustomizerWebPartStrings {
|
||||||
PropertyPaneDescription: string;
|
PropertyPaneDescription: string;
|
||||||
BasicGroupName: string;
|
BasicGroupName: string;
|
||||||
MaxWidthFieldLabel: string;
|
CustomWorkbenchStylesFieldLabel: string;
|
||||||
CenterCanvasFieldLabel: string;
|
PreviewModeFieldLabel: string;
|
||||||
OverflowFieldLabel: string;
|
|
||||||
PaddingFieldLabel: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'WorkbenchCustomizerWebPartStrings' {
|
declare module 'WorkbenchCustomizerWebPartStrings' {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
:global #workbenchPageContent {
|
|
||||||
.CanvasComponent {
|
|
||||||
.CanvasZoneContainer {
|
|
||||||
.CanvasZone {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
:global #workbenchPageContent {
|
|
||||||
// max-width: 1316px;
|
|
||||||
max-width: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
:global #workbenchPageContent {
|
|
||||||
&>div {
|
|
||||||
&>div {
|
|
||||||
overflow: visible;
|
|
||||||
&>div {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
:global #workbenchPageContent {
|
|
||||||
&>div {
|
|
||||||
&>div {
|
|
||||||
&>div {
|
|
||||||
padding: 0;
|
|
||||||
.CanvasComponent {
|
|
||||||
.CanvasZoneContainer {
|
|
||||||
.CanvasZone {
|
|
||||||
padding-left: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -52,4 +52,4 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
||||||
- styling components to match Fabric UI experience
|
- styling components to match Fabric UI experience
|
||||||
- creating custom Property Pane fields (custom markup, logic) based on Knockout.js framework
|
- creating custom Property Pane fields (custom markup, logic) based on Knockout.js framework
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/ko-dependent-properties" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/knockout-dependent-properties" />
|
||||||
|
|
|
@ -55,4 +55,4 @@ npm install
|
||||||
gulp serve --nobrowser
|
gulp serve --nobrowser
|
||||||
```
|
```
|
||||||
|
|
||||||
![](https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/pnp-controls)
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/pnp-controls" />
|
||||||
|
|
|
@ -69,4 +69,5 @@ Once the npm packages are installed, run the command to preview your web parts i
|
||||||
```
|
```
|
||||||
gulp serve
|
gulp serve
|
||||||
```
|
```
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/tutorials" />
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/react-3rd-party-api" />
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"@microsoft/generator-sharepoint": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"libraryName": "react-relay-service",
|
|
||||||
"libraryId": "3d8b2b8b-cc29-4cf9-b3f1-62edd5ca9b52",
|
|
||||||
"environment": "spo"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
## react-relay-service
|
|
||||||
|
|
||||||
This is where you include your WebPart documentation.
|
|
||||||
|
|
||||||
### Building the code
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone the repo
|
|
||||||
npm i
|
|
||||||
npm i -g gulp
|
|
||||||
gulp
|
|
||||||
```
|
|
||||||
|
|
||||||
This package produces the following:
|
|
||||||
|
|
||||||
* lib/* - intermediate-stage commonjs build artifacts
|
|
||||||
* dist/* - the bundled script, along with other resources
|
|
||||||
* deploy/* - all resources which should be uploaded to a CDN.
|
|
||||||
|
|
||||||
### Build options
|
|
||||||
|
|
||||||
gulp clean - TODO
|
|
||||||
gulp test - TODO
|
|
||||||
gulp serve - TODO
|
|
||||||
gulp bundle - TODO
|
|
||||||
gulp package-solution - TODO
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
|
||||||
"version": "2.0",
|
|
||||||
"bundles": {
|
|
||||||
"hello-azure-relay-service-web-part": {
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"entrypoint": "./lib/webparts/helloAzureRelayService/HelloAzureRelayServiceWebPart.js",
|
|
||||||
"manifest": "./src/webparts/helloAzureRelayService/HelloAzureRelayServiceWebPart.manifest.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"externals": {},
|
|
||||||
"localizedResources": {
|
|
||||||
"HelloAzureRelayServiceWebPartStrings": "lib/webparts/helloAzureRelayService/loc/{locale}.js"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
|
||||||
"deployCdnPath": "temp/deploy"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
|
||||||
"workingDir": "./temp/deploy/",
|
|
||||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
|
||||||
"container": "react-relay-service",
|
|
||||||
"accessKey": "<!-- ACCESS KEY -->"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
|
||||||
"solution": {
|
|
||||||
"name": "react-relay-service-client-side-solution",
|
|
||||||
"id": "3d8b2b8b-cc29-4cf9-b3f1-62edd5ca9b52",
|
|
||||||
"version": "1.0.0.0",
|
|
||||||
"includeClientSideAssets": true,
|
|
||||||
"skipFeatureDeployment": true
|
|
||||||
},
|
|
||||||
"paths": {
|
|
||||||
"zippedPackage": "solution/react-relay-service.sppkg"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
|
||||||
// Display errors as warnings
|
|
||||||
"displayAsWarning": true,
|
|
||||||
// The TSLint task may have been configured with several custom lint rules
|
|
||||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
|
||||||
// project). If true, this flag will deactivate any of these rules.
|
|
||||||
"removeExistingRules": true,
|
|
||||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
|
||||||
"useDefaultConfigAsBase": false,
|
|
||||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
|
||||||
// which are active, other than the list of rules below.
|
|
||||||
"lintConfig": {
|
|
||||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
|
||||||
"rules": {
|
|
||||||
"class-name": false,
|
|
||||||
"export-name": false,
|
|
||||||
"forin": false,
|
|
||||||
"label-position": false,
|
|
||||||
"member-access": true,
|
|
||||||
"no-arg": false,
|
|
||||||
"no-console": false,
|
|
||||||
"no-construct": false,
|
|
||||||
"no-duplicate-case": true,
|
|
||||||
"no-duplicate-variable": true,
|
|
||||||
"no-eval": false,
|
|
||||||
"no-function-expression": true,
|
|
||||||
"no-internal-module": true,
|
|
||||||
"no-shadowed-variable": true,
|
|
||||||
"no-switch-case-fall-through": true,
|
|
||||||
"no-unnecessary-semicolons": true,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-use-before-declare": true,
|
|
||||||
"no-with-statement": true,
|
|
||||||
"semicolon": true,
|
|
||||||
"trailing-comma": false,
|
|
||||||
"typedef": false,
|
|
||||||
"typedef-whitespace": false,
|
|
||||||
"use-named-parameter": true,
|
|
||||||
"valid-typeof": true,
|
|
||||||
"variable-name": false,
|
|
||||||
"whitespace": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
|
||||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
{
|
|
||||||
"name": "react-relay-service",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "gulp bundle",
|
|
||||||
"clean": "gulp clean",
|
|
||||||
"test": "gulp test"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react": "15.6.2",
|
|
||||||
"react-dom": "15.6.2",
|
|
||||||
"@types/react": "15.6.6",
|
|
||||||
"@types/react-dom": "15.5.6",
|
|
||||||
"@microsoft/sp-core-library": "~1.4.1",
|
|
||||||
"@microsoft/sp-webpart-base": "~1.4.1",
|
|
||||||
"@microsoft/sp-lodash-subset": "~1.4.1",
|
|
||||||
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
|
|
||||||
"@types/webpack-env": ">=1.12.1 <1.14.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@microsoft/sp-build-web": "~1.4.1",
|
|
||||||
"@microsoft/sp-module-interfaces": "~1.4.1",
|
|
||||||
"@microsoft/sp-webpart-workbench": "~1.4.1",
|
|
||||||
"gulp": "~3.9.1",
|
|
||||||
"@types/chai": ">=3.4.34 <3.6.0",
|
|
||||||
"@types/mocha": ">=2.2.33 <2.6.0",
|
|
||||||
"ajv": "~5.2.2"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
|
||||||
"id": "6ddbb470-dced-4bcb-ad30-2956dd91dce9",
|
|
||||||
"alias": "HelloAzureRelayServiceWebPart",
|
|
||||||
"componentType": "WebPart",
|
|
||||||
|
|
||||||
// The "*" signifies that the version should be taken from the package.json
|
|
||||||
"version": "*",
|
|
||||||
"manifestVersion": 2,
|
|
||||||
|
|
||||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
|
||||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
|
||||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
|
||||||
"requiresCustomScript": false,
|
|
||||||
|
|
||||||
"preconfiguredEntries": [{
|
|
||||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
|
||||||
"group": { "default": "Other" },
|
|
||||||
"title": { "default": "HelloAzureRelayService" },
|
|
||||||
"description": { "default": "HelloAzureRelayService description" },
|
|
||||||
"officeFabricIconFontName": "Page",
|
|
||||||
"properties": {
|
|
||||||
"description": "HelloAzureRelayService"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import * as ReactDom from 'react-dom';
|
|
||||||
import { Version } from '@microsoft/sp-core-library';
|
|
||||||
import {
|
|
||||||
BaseClientSideWebPart,
|
|
||||||
IPropertyPaneConfiguration,
|
|
||||||
PropertyPaneTextField
|
|
||||||
} from '@microsoft/sp-webpart-base';
|
|
||||||
|
|
||||||
import * as strings from 'HelloAzureRelayServiceWebPartStrings';
|
|
||||||
import HelloAzureRelayService from './components/HelloAzureRelayService';
|
|
||||||
import { IHelloAzureRelayServiceProps } from './components/IHelloAzureRelayServiceProps';
|
|
||||||
import { HttpClient, IHttpClientOptions, HttpClientResponse } from '@microsoft/sp-http';
|
|
||||||
|
|
||||||
export interface IHelloAzureRelayServiceWebPartProps {
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class HelloAzureRelayServiceWebPart extends BaseClientSideWebPart<IHelloAzureRelayServiceWebPartProps> {
|
|
||||||
private results: Array<any>;
|
|
||||||
public onInit(): Promise<any> {
|
|
||||||
debugger;
|
|
||||||
const requestHeaders: Headers = new Headers();
|
|
||||||
requestHeaders.append('Content-type', 'application/json');
|
|
||||||
requestHeaders.append('Cache-Control', 'no-cache');
|
|
||||||
const httpClientOptions: IHttpClientOptions = {
|
|
||||||
|
|
||||||
headers: requestHeaders
|
|
||||||
};
|
|
||||||
|
|
||||||
const url = "https://relayserviceproxy20180831034031.azurewebsites.net/api/Document?webID=917E82F1-FDF8-4298-AE73-1DD60E244C67";
|
|
||||||
return this.context.httpClient.get(url, HttpClient.configurations.v1, httpClientOptions).then((response) => {
|
|
||||||
response.json().then((r => {
|
|
||||||
debugger;
|
|
||||||
this.results = r;
|
|
||||||
}))
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
public render(): void {
|
|
||||||
const element: React.ReactElement<IHelloAzureRelayServiceProps> = React.createElement(
|
|
||||||
HelloAzureRelayService,
|
|
||||||
{
|
|
||||||
description: this.properties.description,
|
|
||||||
documents: this.results
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ReactDom.render(element, this.domElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get dataVersion(): Version {
|
|
||||||
return Version.parse('1.0');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
|
||||||
return {
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
header: {
|
|
||||||
description: strings.PropertyPaneDescription
|
|
||||||
},
|
|
||||||
groups: [
|
|
||||||
{
|
|
||||||
groupName: strings.BasicGroupName,
|
|
||||||
groupFields: [
|
|
||||||
PropertyPaneTextField('description', {
|
|
||||||
label: strings.DescriptionFieldLabel
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import styles from './HelloAzureRelayService.module.scss';
|
|
||||||
import { IHelloAzureRelayServiceProps } from './IHelloAzureRelayServiceProps';
|
|
||||||
import { escape } from '@microsoft/sp-lodash-subset';
|
|
||||||
|
|
||||||
export default class HelloAzureRelayService extends React.Component<IHelloAzureRelayServiceProps, {}> {
|
|
||||||
|
|
||||||
public render(): React.ReactElement<IHelloAzureRelayServiceProps> {
|
|
||||||
return (
|
|
||||||
<div className={styles.helloAzureRelayService}>
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.row}>
|
|
||||||
<div className={styles.column}>
|
|
||||||
<span className={styles.title}>Welcome to SharePoint!</span>
|
|
||||||
<p className={styles.subTitle}>Customize SharePoint experiences using Web Parts.</p>
|
|
||||||
<p className={styles.description}>{escape(this.props.description)}</p>
|
|
||||||
<a href="https://aka.ms/spfx" className={styles.button}>
|
|
||||||
<ul>
|
|
||||||
{this.props.documents.map((d, i) => {
|
|
||||||
debugger;
|
|
||||||
return <li>{d.Title}</li>;
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
export interface IHelloAzureRelayServiceProps {
|
|
||||||
description: string;
|
|
||||||
documents:Array<any>;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
declare interface IHelloAzureRelayServiceWebPartStrings {
|
|
||||||
PropertyPaneDescription: string;
|
|
||||||
BasicGroupName: string;
|
|
||||||
DescriptionFieldLabel: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'HelloAzureRelayServiceWebPartStrings' {
|
|
||||||
const strings: IHelloAzureRelayServiceWebPartStrings;
|
|
||||||
export = strings;
|
|
||||||
}
|
|
|
@ -63,10 +63,3 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-accordion" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-accordion" />
|
||||||
|
|
||||||
### Build options
|
|
||||||
|
|
||||||
gulp clean - TODO
|
|
||||||
gulp test - TODO
|
|
||||||
gulp serve - TODO
|
|
||||||
gulp bundle - TODO
|
|
||||||
gulp package-solution - TODO
|
|
||||||
|
|
|
@ -79,3 +79,4 @@ This sample web part shows how adaptive cards can be used effectively with Share
|
||||||
[figure4]: ./assets/list-sample-data.png
|
[figure4]: ./assets/list-sample-data.png
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-adaptive-cards-image-gallery" />
|
||||||
|
|
|
@ -75,7 +75,6 @@ This Web Part displays the events from multiple calendars located in various sit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Building the code
|
### Building the code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -98,3 +97,5 @@ gulp test
|
||||||
gulp serve
|
gulp serve
|
||||||
gulp bundle
|
gulp bundle
|
||||||
gulp package-solution
|
gulp package-solution
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-aggregated-calendar" />
|
||||||
|
|
|
@ -29,10 +29,13 @@ Existing list in tenant root site, with the Title "Birthdays" and columns:
|
||||||
|
|
||||||
Column Internal Name|Type|Required| comments
|
Column Internal Name|Type|Required| comments
|
||||||
--------------------|----|--------|----------
|
--------------------|----|--------|----------
|
||||||
jobTitle| Text| no|
|
JobTitle| Text| no|
|
||||||
Birthday| DateTime | true|
|
Birthday| DateTime | true|
|
||||||
userAADGUID| Text| no | required if used Azure Function to get Birthdays from AAD
|
userAADGUID| Text| no | required if used Azure Function to get Birthdays from AAD
|
||||||
Title| Text| true
|
Title| Text| true
|
||||||
|
email| Text| true
|
||||||
|
|
||||||
|
## After create a column Index on column "Brithday" - Important!
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
|
@ -51,7 +54,7 @@ Version|Date|Comments
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Minimal Path to Awesome
|
## Minimal Path to Awesome - please follow all the steps.
|
||||||
|
|
||||||
- Clone this repository
|
- Clone this repository
|
||||||
- in the command line run:
|
- in the command line run:
|
||||||
|
@ -59,6 +62,8 @@ Version|Date|Comments
|
||||||
- `gulp build`
|
- `gulp build`
|
||||||
- `gulp bundle --ship`
|
- `gulp bundle --ship`
|
||||||
- `gulp package-solution --ship`
|
- `gulp package-solution --ship`
|
||||||
|
- `Add and Deploy Package to AppCatalog `
|
||||||
|
- `Go to API Management - from SharePoint Admin Center new experience, and Approve the Permission Require to Use Graph API SCOPES`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,4 +78,5 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
|
||||||
- using @PnP/PnPjs to create a List, add, update, delete Items.
|
- using @PnP/PnPjs to create a List, add, update, delete Items.
|
||||||
|
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-birthdays" />
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,24 @@
|
||||||
"version": "1.0.0.0",
|
"version": "1.0.0.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": true,
|
"skipFeatureDeployment": true,
|
||||||
"isDomainIsolated": false
|
"isDomainIsolated": false,
|
||||||
|
"webApiPermissionRequests": [
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "User.Read.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "User.ReadBasic.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Sites.Read.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "profile"
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"zippedPackage": "solution/birdthays.sppkg"
|
"zippedPackage": "solution/birdthays.sppkg"
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class SPService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.graphClient = await this._context.msGraphClientFactory.getClient();
|
this.graphClient = await this._context.msGraphClientFactory.getClient();
|
||||||
_results = await this.graphClient.api(`sites/root/lists('${this.birthdayListTitle}')/items`)
|
_results = await this.graphClient.api(`sites/root/lists('${this.birthdayListTitle}')/items?orderby=Fields/Birthday`)
|
||||||
.version('v1.0')
|
.version('v1.0')
|
||||||
.expand('fields')
|
.expand('fields')
|
||||||
.top(upcommingDays)
|
.top(upcommingDays)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "stylelint-config-standard",
|
||||||
|
"plugins": [
|
||||||
|
"stylelint-scss"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"at-rule-no-unknown": null,
|
||||||
|
"scss/at-rule-no-unknown": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"@pnp/generator-spfx": {
|
||||||
|
"framework": "react",
|
||||||
|
"pnpFramework": "reactjs.plus",
|
||||||
|
"pnp-libraries": [
|
||||||
|
"jquery@3",
|
||||||
|
"@pnp/pnpjs",
|
||||||
|
"@pnp/spfx-property-controls",
|
||||||
|
"@pnp/spfx-controls-react"
|
||||||
|
],
|
||||||
|
"pnp-ci": [
|
||||||
|
"azure"
|
||||||
|
],
|
||||||
|
"pnp-vetting": [
|
||||||
|
"webpack-analyzer",
|
||||||
|
"stylelint"
|
||||||
|
],
|
||||||
|
"spfxenv": "spo"
|
||||||
|
},
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"environment": "spo",
|
||||||
|
"framework": "react",
|
||||||
|
"plusBeta": true,
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"version": "1.8.0",
|
||||||
|
"libraryName": "react-calendar",
|
||||||
|
"libraryId": "3a13208b-3874-4036-9262-4edd22e88187",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
# React Calendar
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
This Web Part allows you to manage events in a calendar.
|
||||||
|
Uses a list of existing calendars on any website.
|
||||||
|
The location and name of the list and the dates of the events to be displayed are defined in the properties of the web part.
|
||||||
|
|
||||||
|
The Events are created in Site TimeZone, defined in site Regional Settings.
|
||||||
|
|
||||||
|
Each category has its own color that is generated in the load.
|
||||||
|
|
||||||
|
The Web Part checks the user's permissions for the View, Add, Edit, and Delete events.
|
||||||
|
|
||||||
|
The Web Part does not show recurring events, I will work on it soon.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
![callendar](/samples/react-calendar/assets/animatevideo.gif)
|
||||||
|
|
||||||
|
|
||||||
|
## Web Part - Screenshots
|
||||||
|
![callendar](/samples/react-calendar//assets/screen1.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen1.0.jpg)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen1.1.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen1.2.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen1.3.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar//assets/screen1.4.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar//assets/screen2.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen3.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar//assets/screen4.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen5.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar//assets/screen6.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar//assets/screen7.png)
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen8.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar//assets/screen9.png)
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
|
> Update accordingly as needed.
|
||||||
|
|
||||||
|
## WebPart Properties
|
||||||
|
|
||||||
|
Property |Type|Required| comments
|
||||||
|
--------------------|----|--------|----------
|
||||||
|
Site Url of Calendar List | Text| yes|
|
||||||
|
Calendar list| Text| yes| this is filled with all list of type "event list" created
|
||||||
|
Start Date | Date | yes | Event Date
|
||||||
|
End Date| Date| yes | Event Date
|
||||||
|
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
The Web Part Use PnPjs library, Office-ui-fabric-react components. react Big-Calendar Compoment
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
Calendar Web Part|João Mendes
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0.0|April 25, 2019|Initial release
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- in the command line run:
|
||||||
|
- `npm install`
|
||||||
|
- `gulp build`
|
||||||
|
- `gulp bundle --ship`
|
||||||
|
- `gulp package-solution --ship`
|
||||||
|
- `Add to AppCatalog and deploy`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
After Width: | Height: | Size: 3.5 MiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 157 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 319 KiB |
|
@ -0,0 +1,71 @@
|
||||||
|
resources:
|
||||||
|
- repo: self
|
||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
queue:
|
||||||
|
name: Hosted VS2017
|
||||||
|
demands:
|
||||||
|
- npm
|
||||||
|
- node.js
|
||||||
|
|
||||||
|
steps:
|
||||||
|
#install node 8.x
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Use Node 8.x'
|
||||||
|
inputs:
|
||||||
|
versionSpec: 8.x
|
||||||
|
checkLatest: true
|
||||||
|
|
||||||
|
#install nodejs modules with npm
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: 'npm install'
|
||||||
|
inputs:
|
||||||
|
workingDir: '$(Build.SourcesDirectory)'
|
||||||
|
verbose: false
|
||||||
|
|
||||||
|
#start unit tests
|
||||||
|
- task: Gulp@0
|
||||||
|
displayName: 'gulp test'
|
||||||
|
inputs:
|
||||||
|
gulpFile: '$(Build.SourcesDirectory)/gulpfile.js'
|
||||||
|
targets: test
|
||||||
|
publishJUnitResults: true
|
||||||
|
testResultsFiles: '**/test-*.xml'
|
||||||
|
#publish test results
|
||||||
|
- task: PublishCodeCoverageResults@1
|
||||||
|
displayName: 'Publish Code Coverage Results $(Build.SourcesDirectory)/temp/coverage/cobertura/cobertura.xml'
|
||||||
|
inputs:
|
||||||
|
codeCoverageTool: Cobertura
|
||||||
|
summaryFileLocation: '$(Build.SourcesDirectory)/temp/coverage/cobertura/cobertura.xml'
|
||||||
|
reportDirectory: '$(Build.SourcesDirectory)/temp/coverage/cobertura'
|
||||||
|
|
||||||
|
#bundle code with gulp
|
||||||
|
- task: Gulp@0
|
||||||
|
displayName: 'gulp bundle'
|
||||||
|
inputs:
|
||||||
|
gulpFile: '$(Build.SourcesDirectory)/gulpfile.js'
|
||||||
|
targets: bundle
|
||||||
|
arguments: '--ship'
|
||||||
|
continueOnError: true
|
||||||
|
|
||||||
|
#package solution with gulp
|
||||||
|
- task: Gulp@0
|
||||||
|
displayName: 'gulp package-solution'
|
||||||
|
inputs:
|
||||||
|
gulpFile: '$(Build.SourcesDirectory)/gulpfile.js'
|
||||||
|
targets: 'package-solution'
|
||||||
|
arguments: '--ship'
|
||||||
|
|
||||||
|
#copy files to artifact repository
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: 'Copy Files to: $(build.artifactstagingdirectory)/drop'
|
||||||
|
inputs:
|
||||||
|
Contents: '**\*.sppkg'
|
||||||
|
TargetFolder: '$(build.artifactstagingdirectory)/drop'
|
||||||
|
|
||||||
|
#publish artifacts
|
||||||
|
- task: PublishBuildArtifacts@1
|
||||||
|
displayName: 'Publish Artifact: drop'
|
||||||
|
inputs:
|
||||||
|
PathtoPublish: '$(build.artifactstagingdirectory)/drop'
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"calendar-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/calendar/CalendarWebPart.js",
|
||||||
|
"manifest": "./src/webparts/calendar/CalendarWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"CalendarWebPartStrings": "lib/webparts/calendar/loc/{locale}.js",
|
||||||
|
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||||
|
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,6 @@
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
"workingDir": "./temp/deploy/",
|
"workingDir": "./temp/deploy/",
|
||||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
"container": "react-related-items",
|
"container": "react-calendar",
|
||||||
"accessKey": "<!-- ACCESS KEY -->"
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
}
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"preset": "@voitanos/jest-preset-spfx-react16",
|
||||||
|
"rootDir": "../src"
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
"use strict";
|
||||||
|
var existingKarmaConfig = require('@microsoft/sp-build-web/lib/karma/karma.config');
|
||||||
|
var junitReporter = require('karma-junit-reporter');
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
existingKarmaConfig(config);
|
||||||
|
config.reporters.push('junit');
|
||||||
|
|
||||||
|
config.set({
|
||||||
|
basePath: './..',
|
||||||
|
});
|
||||||
|
|
||||||
|
config.junitReporter = {
|
||||||
|
outputDir: 'temp/', // results will be saved as $outputDir/$browserName.xml
|
||||||
|
outputFile: 'test-results.xml', // if included, results will be saved as $outputDir/$browserName/$outputFile
|
||||||
|
suite: 'karma', // suite will become the package name attribute in xml testsuite element
|
||||||
|
useBrowserName: true, // add browser name to report and classes names
|
||||||
|
};
|
||||||
|
var coberturaSubDir = 'cobertura';
|
||||||
|
var coverageSubDir = 'lcov';
|
||||||
|
var coberturaFileName = 'cobertura.xml';
|
||||||
|
config.coverageReporter.reporters.push({type: 'cobertura', subdir: './' + coberturaSubDir, file: coberturaFileName});
|
||||||
|
config.coverageReporter.reporters.push({
|
||||||
|
type: 'lcov',
|
||||||
|
subdir: './' + coverageSubDir + '/',
|
||||||
|
file: 'lcov.info'
|
||||||
|
});
|
||||||
|
config.browserNoActivityTimeout = 60000;
|
||||||
|
config.plugins.push(junitReporter);
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "react-calendar-client-side-solution",
|
||||||
|
"id": "3a13208b-3874-4036-9262-4edd22e88187",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"skipFeatureDeployment": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-calendar.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// check if gulp dist was called
|
||||||
|
if (process.argv.indexOf('dist') !== -1) {
|
||||||
|
// add ship options to command call
|
||||||
|
process.argv.push('--ship');
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
const gulpSequence = require('gulp-sequence');
|
||||||
|
|
||||||
|
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||||
|
|
||||||
|
// Create clean distrubution package
|
||||||
|
gulp.task('dist', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||||
|
// Create clean development package
|
||||||
|
gulp.task('dev', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webpack Bundle Anlayzer
|
||||||
|
* Reference and gulp task
|
||||||
|
*/
|
||||||
|
const bundleAnalyzer = require('webpack-bundle-analyzer');
|
||||||
|
|
||||||
|
build.configureWebpack.mergeConfig({
|
||||||
|
|
||||||
|
additionalConfiguration: (generatedConfiguration) => {
|
||||||
|
const lastDirName = path.basename(__dirname);
|
||||||
|
const dropPath = path.join(__dirname, 'temp', 'stats');
|
||||||
|
generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({
|
||||||
|
openAnalyzer: false,
|
||||||
|
analyzerMode: 'static',
|
||||||
|
reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),
|
||||||
|
generateStatsFile: true,
|
||||||
|
statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),
|
||||||
|
logLevel: 'error'
|
||||||
|
}));
|
||||||
|
|
||||||
|
return generatedConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StyleLinter configuration
|
||||||
|
* Reference and custom gulp task
|
||||||
|
*/
|
||||||
|
const stylelint = require('gulp-stylelint');
|
||||||
|
|
||||||
|
/* Stylelinter sub task */
|
||||||
|
let styleLintSubTask = build.subTask('stylelint', (gulp) => {
|
||||||
|
|
||||||
|
console.log('[stylelint]: By default style lint errors will not break your build. If you want to change this behaviour, modify failAfterError parameter in gulpfile.js.');
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('src/**/*.scss')
|
||||||
|
.pipe(stylelint({
|
||||||
|
failAfterError: false,
|
||||||
|
reporters: [{
|
||||||
|
formatter: 'string',
|
||||||
|
console: true
|
||||||
|
}]
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
/* end sub task */
|
||||||
|
|
||||||
|
build.rig.addPreBuildTask(styleLintSubTask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Framework Specific gulp tasks
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continuous Integration
|
||||||
|
*/
|
||||||
|
|
||||||
|
const buildConfig = build.getConfig();
|
||||||
|
|
||||||
|
const karmaTaskCandidates = buildConfig.uniqueTasks.filter(task => task.name === 'karma');
|
||||||
|
|
||||||
|
if (karmaTaskCandidates && karmaTaskCandidates.length > 0) {
|
||||||
|
const karmaTask = karmaTaskCandidates[0];
|
||||||
|
karmaTask.taskConfig.configPath = './config/karma.config.js';
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "react-calendar",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"preversion": "node ./tools/pre-version.js",
|
||||||
|
"postversion": "gulp dist",
|
||||||
|
"test": "./node_modules/.bin/jest --config ./config/jest.config.json",
|
||||||
|
"test:watch": "./node_modules/.bin/jest --config ./config/jest.config.json --watchAll"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/rush-stack-compiler-3.2": "^0.3.6",
|
||||||
|
"@microsoft/sp-core-library": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-lodash-subset": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-property-pane": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-webpart-base": "1.8.0-plusbeta",
|
||||||
|
"@pnp/pnpjs": "^1.3.0",
|
||||||
|
"@pnp/spfx-controls-react": "1.12.0",
|
||||||
|
"@pnp/spfx-property-controls": "1.14.1",
|
||||||
|
"@types/draft-js": "^0.10.30",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@types/globalize": "0.0.34",
|
||||||
|
"@types/jquery": "^3.3.29",
|
||||||
|
"@types/react": "16.7.22",
|
||||||
|
"@types/react-big-calendar": "^0.20.13",
|
||||||
|
"@types/react-dom": "16.0.5",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"draft-js": "^0.10.5",
|
||||||
|
"draftjs-to-html": "^0.8.4",
|
||||||
|
"globalize": "^1.4.2",
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"jquery": "^3.3.1",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"moment-timezone": "^0.5.25",
|
||||||
|
"react": "16.7.0",
|
||||||
|
"react-big-calendar": "^0.20.4",
|
||||||
|
"react-dom": "16.7.0",
|
||||||
|
"react-draft-wysiwyg": "^1.13.2",
|
||||||
|
"typescript": "^3.2.4"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/react": "16.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/rush-stack-compiler-2.7": "0.4.0",
|
||||||
|
"@microsoft/sp-build-web": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-tslint-rules": "1.8.0-plusbeta",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.8.0-plusbeta",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"@types/mocha": "2.2.38",
|
||||||
|
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"gulp-sequence": "1.0.0",
|
||||||
|
"gulp-stylelint": "^8.0.0",
|
||||||
|
"jest": "^23.6.0",
|
||||||
|
"karma-junit-reporter": "^1.2.0",
|
||||||
|
"stylelint": "^9.10.1",
|
||||||
|
"stylelint-config-standard": "^18.2.0",
|
||||||
|
"stylelint-scss": "^3.5.4",
|
||||||
|
"webpack-bundle-analyzer": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.description {
|
||||||
|
border: 1px solid #a6a6a6;
|
||||||
|
padding: 10;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
height: 300px;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description:hover {
|
||||||
|
border-color: rgb( 51, 51, 51 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
height: 600px;
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eventTitle {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg10;
|
||||||
|
@include ms-xl8;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: $ms-color-themePrimary;
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
color: $ms-color-white;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-regular;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { IEventData } from '../../services/IEventData';
|
||||||
|
import { IPanelModelEnum} from './IPanelModeEnum';
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IEventProps {
|
||||||
|
event: IEventData;
|
||||||
|
panelMode: IPanelModelEnum;
|
||||||
|
onDissmissPanel: (refresh:boolean) => void;
|
||||||
|
showPanel: boolean;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
context:WebPartContext;
|
||||||
|
siteUrl: string;
|
||||||
|
listId:string;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { IEventData } from '../../services/IEventData';
|
||||||
|
import { IUserPermissions } from '../../services/IUserPermissions';
|
||||||
|
import { DayOfWeek} from 'office-ui-fabric-react/lib/DatePicker';
|
||||||
|
import { IDropdownOption } from 'office-ui-fabric-react/';
|
||||||
|
export interface IEventState {
|
||||||
|
showPanel: boolean;
|
||||||
|
eventData:IEventData;
|
||||||
|
firstDayOfWeek?: DayOfWeek;
|
||||||
|
startSelectedHour: IDropdownOption ;
|
||||||
|
startSelectedMin: IDropdownOption ;
|
||||||
|
endSelectedHour: IDropdownOption ;
|
||||||
|
endSelectedMin: IDropdownOption ;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
editorState?: any;
|
||||||
|
selectedUsers: string[];
|
||||||
|
locationLatitude: number;
|
||||||
|
locationLongitude: number;
|
||||||
|
errorMessage?:string;
|
||||||
|
hasError?:boolean;
|
||||||
|
disableButton?: boolean;
|
||||||
|
isSaving?:boolean;
|
||||||
|
isDeleting?:boolean;
|
||||||
|
displayDialog:boolean;
|
||||||
|
userPermissions?: IUserPermissions;
|
||||||
|
isloading:boolean;
|
||||||
|
siteRegionalSettings: any;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum IPanelModelEnum {
|
||||||
|
add=1,
|
||||||
|
edit=2,
|
||||||
|
delete=3
|
||||||
|
}
|
|
@ -0,0 +1,769 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Event.module.scss';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import { IEventProps } from './IEventProps';
|
||||||
|
import { IEventState } from './IEventState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||||
|
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
|
||||||
|
import {
|
||||||
|
Panel,
|
||||||
|
PanelType,
|
||||||
|
TextField,
|
||||||
|
Label,
|
||||||
|
extendComponent
|
||||||
|
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
import { EnvironmentType } from '@microsoft/sp-core-library';
|
||||||
|
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
|
||||||
|
import { IEventData } from '../../services/IEventData';
|
||||||
|
import { IUserPermissions } from '../../services/IUserPermissions';
|
||||||
|
import {
|
||||||
|
DatePicker,
|
||||||
|
DayOfWeek,
|
||||||
|
IDatePickerStrings,
|
||||||
|
Dropdown,
|
||||||
|
DropdownMenuItemType,
|
||||||
|
IDropdownStyles,
|
||||||
|
IDropdownOption,
|
||||||
|
DefaultButton,
|
||||||
|
PrimaryButton,
|
||||||
|
IPersonaProps,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize,
|
||||||
|
Dialog,
|
||||||
|
DialogType,
|
||||||
|
DialogFooter,
|
||||||
|
Toggle
|
||||||
|
|
||||||
|
}
|
||||||
|
from 'office-ui-fabric-react';
|
||||||
|
import { addMonths, addYears } from 'office-ui-fabric-react/lib/utilities/dateMath/DateMath';
|
||||||
|
import { _ComponentBaseKillSwitches } from '@microsoft/sp-component-base';
|
||||||
|
import { IPanelModelEnum } from './IPanelModeEnum';
|
||||||
|
import { EditorState, convertToRaw, ContentState } from 'draft-js';
|
||||||
|
import { Editor } from 'react-draft-wysiwyg';
|
||||||
|
import draftToHtml from 'draftjs-to-html';
|
||||||
|
import htmlToDraft from 'html-to-draftjs';
|
||||||
|
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
|
||||||
|
import spservices from '../../services/spservices';
|
||||||
|
import { Map, ICoordinates, MapType } from "@pnp/spfx-controls-react/lib/Map";
|
||||||
|
|
||||||
|
|
||||||
|
const today: Date = new Date(Date.now());
|
||||||
|
const DayPickerStrings: IDatePickerStrings = {
|
||||||
|
months: [strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July, strings.August, strings.September, strings.October, strings.November, strings.Dezember],
|
||||||
|
shortMonths: [strings.Jan, strings.Feb, strings.Mar, strings.Apr, strings.May, strings.Jun, strings.Jul, strings.Aug, strings.Sep, strings.Oct, strings.Nov, strings.Dez],
|
||||||
|
days: [strings.Sunday, strings.Monday, strings.Tuesday, strings.Wednesday, strings.Thursday, strings.Friday, strings.Saturday],
|
||||||
|
shortDays: [strings.ShortDay_S, strings.ShortDay_M, strings.ShortDay_T, strings.ShortDay_W, strings.ShortDay_Tursday, strings.ShortDay_Friday, strings.ShortDay_Saunday],
|
||||||
|
goToToday: strings.GoToDay,
|
||||||
|
prevMonthAriaLabel: strings.PrevMonth,
|
||||||
|
nextMonthAriaLabel: strings.NextMonth,
|
||||||
|
prevYearAriaLabel: strings.PrevYear,
|
||||||
|
nextYearAriaLabel: strings.NextYear,
|
||||||
|
closeButtonAriaLabel: strings.CloseDate,
|
||||||
|
isRequiredErrorMessage: strings.IsRequired,
|
||||||
|
invalidInputErrorMessage: strings.InvalidDateFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
|
private spService: spservices = null;
|
||||||
|
private attendees: IPersonaProps[] = [];
|
||||||
|
private latitude: number = 41.1931819;
|
||||||
|
private longitude: number = -8.4897452;
|
||||||
|
|
||||||
|
private categoryDropdownOption: IDropdownOption[] = [];
|
||||||
|
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
/* geolocation is available */
|
||||||
|
if ("geolocation" in navigator) {
|
||||||
|
navigator.geolocation.getCurrentPosition((position) => {
|
||||||
|
this.latitude = position.coords.latitude;
|
||||||
|
this.longitude = position.coords.longitude;
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/* geolocation IS NOT available */
|
||||||
|
console.log('browser Geolocation is not available');
|
||||||
|
}
|
||||||
|
// Initialize Map coordinates
|
||||||
|
|
||||||
|
console.log('ini', this.latitude, this.longitude);
|
||||||
|
this.state = {
|
||||||
|
showPanel: false,
|
||||||
|
eventData: this.props.event,
|
||||||
|
startSelectedHour: { key: '09', text: '00' },
|
||||||
|
startSelectedMin: { key: '00', text: '00' },
|
||||||
|
endSelectedHour: { key: '18', text: '00' },
|
||||||
|
endSelectedMin: { key: '00', text: '00' },
|
||||||
|
editorState: EditorState.createEmpty(),
|
||||||
|
selectedUsers: [],
|
||||||
|
locationLatitude: this.latitude,
|
||||||
|
locationLongitude: this.longitude,
|
||||||
|
hasError: false,
|
||||||
|
errorMessage: '',
|
||||||
|
disableButton: true,
|
||||||
|
isSaving: false,
|
||||||
|
displayDialog: false,
|
||||||
|
isloading: false,
|
||||||
|
siteRegionalSettings: undefined,
|
||||||
|
userPermissions: { hasPermissionAdd: false, hasPermissionDelete: false, hasPermissionEdit: false, hasPermissionView: false },
|
||||||
|
};
|
||||||
|
// local copia of props
|
||||||
|
this.onStartChangeHour = this.onStartChangeHour.bind(this);
|
||||||
|
this.onStartChangeMin = this.onStartChangeMin.bind(this);
|
||||||
|
this.onEndChangeHour = this.onEndChangeHour.bind(this);
|
||||||
|
this.onEndChangeMin = this.onEndChangeMin.bind(this);
|
||||||
|
this.onEditorStateChange = this.onEditorStateChange.bind(this);
|
||||||
|
this.onRenderFooterContent = this.onRenderFooterContent.bind(this);
|
||||||
|
this.onSave = this.onSave.bind(this);
|
||||||
|
this.onSelectDateEnd = this.onSelectDateEnd.bind(this);
|
||||||
|
this.onSelectDateStart = this.onSelectDateStart.bind(this);
|
||||||
|
this.onUpdateCoordinates = this.onUpdateCoordinates.bind(this);
|
||||||
|
this.onGetErrorMessageTitle = this.onGetErrorMessageTitle.bind(this);
|
||||||
|
this.getPeoplePickerItems = this.getPeoplePickerItems.bind(this);
|
||||||
|
this.hidePanel = this.hidePanel.bind(this);
|
||||||
|
this.onDelete = this.onDelete.bind(this);
|
||||||
|
this.closeDialog = this.closeDialog.bind(this);
|
||||||
|
this.confirmDelete = this.confirmDelete.bind(this);
|
||||||
|
this.onAllDayEventChange = this.onAllDayEventChange.bind(this);
|
||||||
|
this.onCategoryChanged = this.onCategoryChanged.bind(this);
|
||||||
|
this.spService = new spservices(this.props.context);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Hide Panel
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private hidePanel() {
|
||||||
|
this.props.onDissmissPanel(false);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Save Event to a list
|
||||||
|
* @private
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private async onSave() {
|
||||||
|
|
||||||
|
let eventData: IEventData = this.state.eventData;
|
||||||
|
// All Day event ?
|
||||||
|
|
||||||
|
const startDate = `${moment(this.state.startDate).format('YYYY/MM/DD')}`;
|
||||||
|
const startTime = `${this.state.startSelectedHour.key}:${this.state.startSelectedMin.key}`;
|
||||||
|
const startDateTime = `${startDate} ${startTime}`;
|
||||||
|
const start = moment(startDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
|
||||||
|
eventData.start = new Date(start);
|
||||||
|
|
||||||
|
// End Date
|
||||||
|
const endDate = `${moment(this.state.endDate).format('YYYY/MM/DD')}`;
|
||||||
|
const endTime = `${this.state.endSelectedHour.key}:${this.state.endSelectedMin.key}`;
|
||||||
|
const endDateTime = `${endDate} ${endTime}`;
|
||||||
|
const end = moment(endDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
|
||||||
|
eventData.end = new Date(end);
|
||||||
|
|
||||||
|
debugger;
|
||||||
|
// get Geolocation
|
||||||
|
|
||||||
|
eventData.geolocation = { Latitude: this.latitude, Longitude: this.longitude };
|
||||||
|
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
|
||||||
|
eventData.location = locationInfo ? locationInfo.display_name : 'N/A';
|
||||||
|
console.log('beforeupd',eventData.geolocation);
|
||||||
|
// get Attendees
|
||||||
|
if (!eventData.attendes) { //vinitialize if no attendees
|
||||||
|
eventData.attendes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Descript from RichText Compoment
|
||||||
|
eventData.Description = draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const user of this.attendees) {
|
||||||
|
|
||||||
|
const userInfo: any= await this.spService.getUserByLoginName(user.id, this.props.siteUrl);
|
||||||
|
eventData.attendes.push(parseInt(userInfo.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ isSaving: true });
|
||||||
|
|
||||||
|
switch (this.props.panelMode) {
|
||||||
|
case IPanelModelEnum.edit:
|
||||||
|
await this.spService.updateEvent(eventData, this.props.siteUrl, this.props.listId);
|
||||||
|
break;
|
||||||
|
case IPanelModelEnum.add:
|
||||||
|
await this.spService.addEvent(eventData, this.props.siteUrl, this.props.listId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ isSaving: false });
|
||||||
|
this.props.onDissmissPanel(true);
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ hasError: true, errorMessage: error.message, isSaving: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} error
|
||||||
|
* @param {*} errorInfo
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
public componentDidCatch(error: any, errorInfo: any) {
|
||||||
|
this.setState({ hasError: true, errorMessage: errorInfo.componentStack });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
public async componentDidMount() {
|
||||||
|
this.setState({ isloading: true });
|
||||||
|
let editorState:EditorState;
|
||||||
|
// Load Regional Settings
|
||||||
|
const siteRegionalSettigns = await this.spService.getSiteRegionalSettingsTimeZone(this.props.siteUrl);
|
||||||
|
// chaeck User list Permissions
|
||||||
|
const userListPermissions: IUserPermissions = await this.spService.getUserPermissions(this.props.siteUrl, this.props.listId);
|
||||||
|
// Load Categories
|
||||||
|
this.categoryDropdownOption = await this.spService.getChoiceFieldOptions(this.props.siteUrl, this.props.listId, 'Category');
|
||||||
|
// Edit Mode ?
|
||||||
|
if (this.props.panelMode == IPanelModelEnum.edit && this.props.event) {
|
||||||
|
|
||||||
|
// Get hours of event
|
||||||
|
const startHour = moment(this.props.event.start).format('HH').toString();
|
||||||
|
const startMin = moment(this.props.event.start).format('mm').toString();
|
||||||
|
const endHour = moment(this.props.event.end).format('HH').toString();
|
||||||
|
const endMin = moment(this.props.event.end).format('mm').toString();
|
||||||
|
|
||||||
|
// Get Descript and covert to RichText Control
|
||||||
|
const html = this.props.event.Description;
|
||||||
|
const contentBlock = htmlToDraft(html);
|
||||||
|
|
||||||
|
if (contentBlock) {
|
||||||
|
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
|
||||||
|
editorState = EditorState.createWithContent(contentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// testa attendees
|
||||||
|
const attendees = this.props.event.attendes;
|
||||||
|
let selectedUsers: string[] = [];
|
||||||
|
if (attendees && attendees.length > 0) {
|
||||||
|
for (const userId of attendees) {
|
||||||
|
let user: any = await this.spService.getUserById(userId, this.props.siteUrl);
|
||||||
|
if (user) {
|
||||||
|
selectedUsers.push(user.UserPrincipalName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Has geolocation ?
|
||||||
|
this.latitude = this.props.event.geolocation && this.props.event.geolocation.Latitude ? this.props.event.geolocation.Latitude : this.latitude;
|
||||||
|
this.longitude = this.props.event.geolocation && this.props.event.geolocation.Longitude ? this.props.event.geolocation.Longitude : this.longitude;
|
||||||
|
|
||||||
|
// Update Component Data
|
||||||
|
this.setState({
|
||||||
|
eventData: this.props.event,
|
||||||
|
startDate: this.props.event.start,
|
||||||
|
endDate: this.props.event.end,
|
||||||
|
startSelectedHour: { key: startHour, text: startHour },
|
||||||
|
startSelectedMin: { key: startMin, text: startMin },
|
||||||
|
endSelectedHour: { key: endHour, text: endHour },
|
||||||
|
endSelectedMin: { key: endMin, text: endMin },
|
||||||
|
editorState: editorState,
|
||||||
|
selectedUsers: selectedUsers,
|
||||||
|
userPermissions: userListPermissions,
|
||||||
|
isloading: false,
|
||||||
|
siteRegionalSettings: siteRegionalSettigns,
|
||||||
|
locationLatitude: this.latitude,
|
||||||
|
locationLongitude: this.longitude,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
editorState = EditorState.createEmpty();
|
||||||
|
this.setState({
|
||||||
|
startDate: this.props.startDate ? this.props.startDate : new Date(),
|
||||||
|
endDate: this.props.endDate ? this.props.endDate : new Date(),
|
||||||
|
editorState: editorState,
|
||||||
|
userPermissions: userListPermissions,
|
||||||
|
isloading: false,
|
||||||
|
siteRegionalSettings: siteRegionalSettigns,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
public componentWillMount() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onStartChangeHour = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ startSelectedHour: item });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onEndChangeHour = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ endSelectedHour: item });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onStartChangeMin = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ startSelectedMin: item });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {any[]} items
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private getPeoplePickerItems(items: any[]) {
|
||||||
|
|
||||||
|
this.attendees = [];
|
||||||
|
this.attendees = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {*} editorState
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onEditorStateChange(editorState) {
|
||||||
|
this.setState({
|
||||||
|
editorState,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onGetErrorMessageTitle(value: string): string {
|
||||||
|
let returnMessage: string = '';
|
||||||
|
if (value.length === 0) {
|
||||||
|
returnMessage = strings.EventTitleErrorMessage;
|
||||||
|
} else {
|
||||||
|
this.setState({ eventData: { ...this.state.eventData, title: value }, disableButton: false, errorMessage: '' });
|
||||||
|
}
|
||||||
|
return returnMessage;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onEndChangeMin(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ endSelectedMin: item });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
* @param {IDropdownOption} item
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onCategoryChanged(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ eventData: { ...this.state.eventData, Category: item.text } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLDivElement>} event
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onDelete(ev: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ displayDialog: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLDivElement>} event
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private closeDialog(ev: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ displayDialog: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async confirmDelete(ev: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
ev.preventDefault();
|
||||||
|
try {
|
||||||
|
this.setState({ isDeleting: true });
|
||||||
|
|
||||||
|
switch (this.props.panelMode) {
|
||||||
|
case IPanelModelEnum.edit:
|
||||||
|
await this.spService.deleteEvent(this.state.eventData, this.props.siteUrl, this.props.listId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.setState({ isDeleting: false });
|
||||||
|
this.props.onDissmissPanel(true);
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ hasError: true, errorMessage: error.message, isDeleting: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @returns
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onRenderFooterContent() {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
<DefaultButton onClick={this.hidePanel} style={{ marginBottom: '15px', float: 'right' }}>
|
||||||
|
{strings.CancelButtonLabel}
|
||||||
|
</DefaultButton>
|
||||||
|
{
|
||||||
|
this.props.panelMode == IPanelModelEnum.edit && this.state.userPermissions.hasPermissionDelete && (
|
||||||
|
<DefaultButton onClick={this.onDelete} style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }}>
|
||||||
|
{strings.DeleteButtonLabel}
|
||||||
|
</DefaultButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit) &&
|
||||||
|
<PrimaryButton disabled={this.state.disableButton} onClick={this.onSave} style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }}>
|
||||||
|
{strings.SaveButtonLabel}
|
||||||
|
</PrimaryButton>
|
||||||
|
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.isSaving &&
|
||||||
|
<Spinner size={SpinnerSize.medium} style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} newDate
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onSelectDateStart(newDate: Date) {
|
||||||
|
this.setState({ startDate: newDate });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Date} newDate
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private onSelectDateEnd(newDate: Date) {
|
||||||
|
this.setState({ endDate: newDate });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private onAllDayEventChange(ev: React.MouseEvent<HTMLElement>, checked: boolean) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ eventData: { ...this.state.eventData, allDayEvent: checked } });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {ICoordinates} coordinates
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private async onUpdateCoordinates(coordinates: ICoordinates) {
|
||||||
|
this.latitude = coordinates.latitude;
|
||||||
|
this.longitude = coordinates.longitude;
|
||||||
|
console.log('upcoor',this.latitude + ' ' + this.longitude);
|
||||||
|
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
|
||||||
|
this.setState({ eventData: { ...this.state.eventData, location: locationInfo.display_name } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactElement<IEventProps> {
|
||||||
|
console.log(this.state.locationLatitude + '-' + this.state.locationLongitude);
|
||||||
|
const { editorState } = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Panel
|
||||||
|
isOpen={this.props.showPanel}
|
||||||
|
onDismiss={this.hidePanel}
|
||||||
|
type={PanelType.medium}
|
||||||
|
headerText={strings.EventPanelTitle}
|
||||||
|
isFooterAtBottom={true}
|
||||||
|
onRenderFooterContent={this.onRenderFooterContent}
|
||||||
|
>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
{
|
||||||
|
this.state.hasError &&
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
{this.state.errorMessage}
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.isloading && (
|
||||||
|
<Spinner size={SpinnerSize.large} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!this.state.isloading &&
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={strings.EventTitleLabel}
|
||||||
|
value={this.state.eventData ? this.state.eventData.title : ''}
|
||||||
|
onGetErrorMessage={this.onGetErrorMessageTitle}
|
||||||
|
deferredValidationTime={500}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Dropdown
|
||||||
|
label={strings.CategoryLabel}
|
||||||
|
selectedKey={this.state.eventData && this.state.eventData.Category ? this.state.eventData.Category : ''}
|
||||||
|
onChange={this.onCategoryChanged}
|
||||||
|
options={this.categoryDropdownOption}
|
||||||
|
placeholder={strings.CategoryPlaceHolder}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
||||||
|
<DatePicker
|
||||||
|
isRequired={false}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
allowTextInput={true}
|
||||||
|
value={this.state.startDate}
|
||||||
|
label={strings.StartDateLabel}
|
||||||
|
onSelectDate={this.onSelectDateStart}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.startSelectedHour.key}
|
||||||
|
onChange={this.onStartChangeHour}
|
||||||
|
label={strings.StartHourLabel}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
options={[
|
||||||
|
{ key: '00', text: '00' },
|
||||||
|
{ key: '01', text: '01' },
|
||||||
|
{ key: '02', text: '02' },
|
||||||
|
{ key: '03', text: '03' },
|
||||||
|
{ key: '04', text: '04' },
|
||||||
|
{ key: '05', text: '05' },
|
||||||
|
{ key: '06', text: '06' },
|
||||||
|
{ key: '07', text: '07' },
|
||||||
|
{ key: '08', text: '08' },
|
||||||
|
{ key: '09', text: '09' },
|
||||||
|
{ key: '10', text: '10' },
|
||||||
|
{ key: '11', text: '11' },
|
||||||
|
{ key: '12', text: '12' },
|
||||||
|
{ key: '13', text: '13' },
|
||||||
|
{ key: '14', text: '14' },
|
||||||
|
{ key: '15', text: '15' },
|
||||||
|
{ key: '16', text: '16' },
|
||||||
|
{ key: '17', text: '17' },
|
||||||
|
{ key: '18', text: '18' },
|
||||||
|
{ key: '19', text: '19' },
|
||||||
|
{ key: '20', text: '20' },
|
||||||
|
{ key: '21', text: '21' },
|
||||||
|
{ key: '22', text: '22' },
|
||||||
|
{ key: '23', text: '23' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', }}>
|
||||||
|
<Dropdown
|
||||||
|
label={strings.StartMinLabel}
|
||||||
|
selectedKey={this.state.startSelectedMin.key}
|
||||||
|
onChange={this.onStartChangeMin}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
options={[
|
||||||
|
{ key: '00', text: '00' },
|
||||||
|
{ key: '05', text: '05' },
|
||||||
|
{ key: '10', text: '10' },
|
||||||
|
{ key: '15', text: '15' },
|
||||||
|
{ key: '20', text: '20' },
|
||||||
|
{ key: '25', text: '25' },
|
||||||
|
{ key: '30', text: '30' },
|
||||||
|
{ key: '35', text: '35' },
|
||||||
|
{ key: '40', text: '40' },
|
||||||
|
{ key: '45', text: '45' },
|
||||||
|
{ key: '50', text: '50' },
|
||||||
|
{ key: '55', text: '55' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
||||||
|
<DatePicker
|
||||||
|
isRequired={false}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.EndDatePlaceHolder}
|
||||||
|
ariaLabel={strings.EndDatePlaceHolder}
|
||||||
|
allowTextInput={true}
|
||||||
|
value={this.state.endDate}
|
||||||
|
label={strings.EndDateLabel}
|
||||||
|
onSelectDate={this.onSelectDateEnd}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.endSelectedHour.key}
|
||||||
|
onChange={this.onEndChangeHour}
|
||||||
|
label={strings.EndHourLabel}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
options={[
|
||||||
|
{ key: '00', text: '00' },
|
||||||
|
{ key: '01', text: '01' },
|
||||||
|
{ key: '02', text: '02' },
|
||||||
|
{ key: '03', text: '03' },
|
||||||
|
{ key: '04', text: '04' },
|
||||||
|
{ key: '05', text: '05' },
|
||||||
|
{ key: '06', text: '06' },
|
||||||
|
{ key: '07', text: '07' },
|
||||||
|
{ key: '08', text: '08' },
|
||||||
|
{ key: '09', text: '09' },
|
||||||
|
{ key: '10', text: '10' },
|
||||||
|
{ key: '11', text: '11' },
|
||||||
|
{ key: '12', text: '12' },
|
||||||
|
{ key: '13', text: '13' },
|
||||||
|
{ key: '14', text: '14' },
|
||||||
|
{ key: '15', text: '15' },
|
||||||
|
{ key: '16', text: '16' },
|
||||||
|
{ key: '17', text: '17' },
|
||||||
|
{ key: '18', text: '18' },
|
||||||
|
{ key: '19', text: '19' },
|
||||||
|
{ key: '20', text: '20' },
|
||||||
|
{ key: '21', text: '21' },
|
||||||
|
{ key: '22', text: '22' },
|
||||||
|
{ key: '23', text: '23' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', }}>
|
||||||
|
<Dropdown
|
||||||
|
label={strings.EndMinLabel}
|
||||||
|
selectedKey={this.state.endSelectedMin.key}
|
||||||
|
onChange={this.onEndChangeMin}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
options={[
|
||||||
|
{ key: '00', text: '00' },
|
||||||
|
{ key: '05', text: '05' },
|
||||||
|
{ key: '10', text: '10' },
|
||||||
|
{ key: '15', text: '15' },
|
||||||
|
{ key: '20', text: '20' },
|
||||||
|
{ key: '25', text: '25' },
|
||||||
|
{ key: '30', text: '30' },
|
||||||
|
{ key: '35', text: '35' },
|
||||||
|
{ key: '40', text: '40' },
|
||||||
|
{ key: '45', text: '45' },
|
||||||
|
{ key: '50', text: '50' },
|
||||||
|
{ key: '55', text: '55' },
|
||||||
|
{ key: '59', text: '59' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Label>{this.state.siteRegionalSettings ? this.state.siteRegionalSettings.Description : ''}</Label>
|
||||||
|
<br />
|
||||||
|
<Label>Event Description</Label>
|
||||||
|
|
||||||
|
<div className={styles.description}>
|
||||||
|
<Editor
|
||||||
|
editorState={editorState}
|
||||||
|
onEditorStateChange={this.onEditorStateChange}
|
||||||
|
ReadOnly={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<PeoplePicker
|
||||||
|
|
||||||
|
webAbsoluteUrl={this.props.siteUrl}
|
||||||
|
context={this.props.context}
|
||||||
|
titleText={strings.AttendeesLabel}
|
||||||
|
principalTypes={[PrincipalType.User]}
|
||||||
|
resolveDelay={1000}
|
||||||
|
showtooltip={true}
|
||||||
|
selectedItems={this.getPeoplePickerItems}
|
||||||
|
personSelectionLimit={10}
|
||||||
|
defaultSelectedUsers={this.state.selectedUsers}
|
||||||
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
value={this.state.eventData && this.state.eventData.location ? this.state.eventData.location : ''}
|
||||||
|
label={strings.LocationTextLabel}
|
||||||
|
readOnly
|
||||||
|
multiline />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Map titleText={strings.LocationLabel}
|
||||||
|
coordinates={{ latitude: this.state.locationLatitude, longitude: this.state.locationLongitude }}
|
||||||
|
enableSearch={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? true : false}
|
||||||
|
onUpdateCoordinates={this.onUpdateCoordinates}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.state.displayDialog &&
|
||||||
|
<div>
|
||||||
|
<Dialog
|
||||||
|
hidden={!this.state.displayDialog}
|
||||||
|
dialogContentProps={{
|
||||||
|
type: DialogType.normal,
|
||||||
|
title: strings.DialogConfirmDeleteTitle,
|
||||||
|
showCloseButton: false
|
||||||
|
}}
|
||||||
|
modalProps={{
|
||||||
|
isBlocking: true,
|
||||||
|
styles: { main: { maxWidth: 450 } }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label >{strings.ConfirmeDeleteMessage}</Label>
|
||||||
|
{
|
||||||
|
this.state.isDeleting &&
|
||||||
|
<Spinner size={SpinnerSize.medium} ariaLabel={strings.SpinnerDeletingLabel} />
|
||||||
|
}
|
||||||
|
<DialogFooter>
|
||||||
|
<PrimaryButton onClick={this.confirmDelete} text={strings.DialogConfirmDeleteLabel} disabled={this.state.isDeleting} />
|
||||||
|
<DefaultButton onClick={this.closeDialog} text={strings.DialogCloseButtonLabel} />
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
export interface IEventData {
|
||||||
|
id?:number;
|
||||||
|
title: string;
|
||||||
|
Description?: any;
|
||||||
|
location?:string;
|
||||||
|
start: Date;
|
||||||
|
end: Date;
|
||||||
|
color?:string;
|
||||||
|
ownerInitial?: string;
|
||||||
|
ownerPhoto?:string;
|
||||||
|
ownerEmail?:string;
|
||||||
|
ownerName?:string;
|
||||||
|
allDayEvent?: boolean;
|
||||||
|
attendes?: number[];
|
||||||
|
geolocation?: {Longitude:number, Latitude: number};
|
||||||
|
Category?: string;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IList {
|
||||||
|
ID: string;
|
||||||
|
Title: string;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface IListFields {
|
||||||
|
Title: String;
|
||||||
|
InternalName: string;
|
||||||
|
TypeAsString: string;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IUserPermissions {
|
||||||
|
hasPermissionAdd: boolean;
|
||||||
|
hasPermissionEdit: boolean;
|
||||||
|
hasPermissionDelete: boolean;
|
||||||
|
hasPermissionView: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,470 @@
|
||||||
|
// João Mendes
|
||||||
|
// March 2019
|
||||||
|
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
import { sp, Fields, Web, SearchResults, Field, PermissionKind, RegionalSettings } from '@pnp/sp';
|
||||||
|
import { graph, } from "@pnp/graph";
|
||||||
|
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions, HttpClient, MSGraphClient } from '@microsoft/sp-http';
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
import { IEventData } from './IEventData';
|
||||||
|
import { registerDefaultFontFaces } from "@uifabric/styling";
|
||||||
|
import { EventArgs } from "@microsoft/sp-core-library";
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { SiteUser } from "@pnp/sp/src/siteusers";
|
||||||
|
import { IUserPermissions } from './IUserPermissions';
|
||||||
|
import { dateAdd } from "@pnp/common";
|
||||||
|
|
||||||
|
|
||||||
|
const ADMIN_ROLETEMPLATE_ID = "62e90394-69f5-4237-9190-012177145e10"; // Global Admin TemplateRoleId
|
||||||
|
// Class Services
|
||||||
|
export default class spservices {
|
||||||
|
|
||||||
|
private graphClient: MSGraphClient = null;
|
||||||
|
|
||||||
|
constructor(private context: WebPartContext) {
|
||||||
|
// Setuo Context to PnPjs and MSGraph
|
||||||
|
sp.setup({
|
||||||
|
spfxContext: this.context
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.setup({
|
||||||
|
spfxContext: this.context
|
||||||
|
});
|
||||||
|
// Init
|
||||||
|
this.onInit();
|
||||||
|
}
|
||||||
|
// OnInit Function
|
||||||
|
private async onInit() {
|
||||||
|
//this.appCatalogUrl = await this.getAppCatalogUrl();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
private async getSiteTimeZoneHoursToUtc(siteUrl: string): Promise<number> {
|
||||||
|
let numberHours: number = 0;
|
||||||
|
let siteTimeZoneHoursToUTC: any;
|
||||||
|
let siteTimeZoneBias: number;
|
||||||
|
let siteTimeZoneDaylightBias: number;
|
||||||
|
let currentDateTimeOffSet: number = new Date().getTimezoneOffset() / 60;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const siteRegionalSettings: any = await this.getSiteRegionalSettingsTimeZone(siteUrl);
|
||||||
|
// Calculate hour to current site
|
||||||
|
siteTimeZoneBias = siteRegionalSettings.Information.Bias;
|
||||||
|
siteTimeZoneDaylightBias = siteRegionalSettings.Information.DaylightBias;
|
||||||
|
|
||||||
|
// Formula to calculate the number of hours need to get UTC Date.
|
||||||
|
numberHours = (siteTimeZoneBias / 60) + (siteTimeZoneDaylightBias / 60) - currentDateTimeOffSet;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return numberHours;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IEventData} newEvent
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async addEvent(newEvent: IEventData, siteUrl: string, listId: string) {
|
||||||
|
let results = null;
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
|
||||||
|
const siteTimeZoneHoursToUTC: number = await this.getSiteTimeZoneHoursToUtc(siteUrl);
|
||||||
|
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
||||||
|
|
||||||
|
results = await web.lists.getById(listId).items.add({
|
||||||
|
Title: newEvent.title,
|
||||||
|
Description: newEvent.Description,
|
||||||
|
Geolocation: newEvent.geolocation,
|
||||||
|
ParticipantsPickerId: { results: newEvent.attendes },
|
||||||
|
EventDate: new Date(moment(newEvent.start).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
||||||
|
EndDate: new Date(moment(newEvent.end).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
||||||
|
Location: newEvent.location,
|
||||||
|
fAllDayEvent: false,
|
||||||
|
fRecurrence: false,
|
||||||
|
Category: newEvent.Category,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IEventData} newEvent
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async updateEvent(updateEvent: IEventData, siteUrl: string, listId: string) {
|
||||||
|
let results = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
const siteTimeZoneHoursToUTC: number = await this.getSiteTimeZoneHoursToUtc(siteUrl);
|
||||||
|
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
||||||
|
results = await web.lists.getById(listId).items.getById(updateEvent.id).update({
|
||||||
|
Title: updateEvent.title,
|
||||||
|
Description: updateEvent.Description,
|
||||||
|
Geolocation: updateEvent.geolocation,
|
||||||
|
ParticipantsPickerId: { results: updateEvent.attendes },
|
||||||
|
EventDate: new Date(moment(updateEvent.start).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
||||||
|
EndDate: new Date(moment(updateEvent.end).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
||||||
|
Location: updateEvent.location,
|
||||||
|
fAllDayEvent: false,
|
||||||
|
fRecurrence: false,
|
||||||
|
Category: updateEvent.Category,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IEventData} event
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async deleteEvent(event: IEventData, siteUrl: string, listId: string) {
|
||||||
|
let results = null;
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
|
||||||
|
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
||||||
|
results = await web.lists.getById(listId).items.getById(event.id).delete();
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} userId
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @returns {Promise<SiteUser>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getUserById(userId: number, siteUrl: string): Promise<SiteUser> {
|
||||||
|
let results: SiteUser = null;
|
||||||
|
|
||||||
|
if (!userId && !siteUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
results = await web.siteUsers.getById(userId).get();
|
||||||
|
//results = await web.siteUsers.getByLoginName(userId).get();
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} loginName
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @returns {Promise<SiteUser>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getUserByLoginName(loginName: string, siteUrl: string): Promise<SiteUser> {
|
||||||
|
let results: SiteUser = null;
|
||||||
|
|
||||||
|
if (!loginName && !siteUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
await web.ensureUser(loginName);
|
||||||
|
results = await web.siteUsers.getByLoginName(loginName).get();
|
||||||
|
//results = await web.siteUsers.getByLoginName(userId).get();
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} loginName
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getUserProfilePictureUrl(loginName: string) {
|
||||||
|
let results: any = null;
|
||||||
|
try {
|
||||||
|
results = await sp.profiles.getPropertiesFor(loginName);
|
||||||
|
} catch (error) {
|
||||||
|
results = null;
|
||||||
|
}
|
||||||
|
return results.PictureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @returns {Promise<IUserPermissions>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getUserPermissions(siteUrl: string, listId: string): Promise<IUserPermissions> {
|
||||||
|
let hasPermissionAdd: boolean = false;
|
||||||
|
let hasPermissionEdit: boolean = false;
|
||||||
|
let hasPermissionDelete: boolean = false;
|
||||||
|
let hasPermissionView: boolean = false;
|
||||||
|
let userPermissions: IUserPermissions = undefined;
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
hasPermissionAdd = await web.lists.getById(listId).currentUserHasPermissions(PermissionKind.AddListItems);
|
||||||
|
hasPermissionEdit = await web.lists.getById(listId).currentUserHasPermissions(PermissionKind.EditListItems);
|
||||||
|
hasPermissionDelete = await web.lists.getById(listId).currentUserHasPermissions(PermissionKind.DeleteListItems);
|
||||||
|
hasPermissionView = await web.lists.getById(listId).currentUserHasPermissions(PermissionKind.ViewListItems);
|
||||||
|
userPermissions = { hasPermissionAdd: hasPermissionAdd, hasPermissionEdit: hasPermissionEdit, hasPermissionDelete: hasPermissionDelete, hasPermissionView: hasPermissionView };
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return userPermissions;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getSiteLists(siteUrl: string) {
|
||||||
|
|
||||||
|
let results: any[] = [];
|
||||||
|
|
||||||
|
if (!siteUrl) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
results = await web.lists.select("Title", "ID").filter('BaseTemplate eq 106').get();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async colorGenerate() {
|
||||||
|
|
||||||
|
var hexValues = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e"];
|
||||||
|
var newColor = "#";
|
||||||
|
|
||||||
|
for (var i = 0; i < 6; i++) {
|
||||||
|
var x = Math.round(Math.random() * 14);
|
||||||
|
var y = hexValues[x];
|
||||||
|
newColor += y;
|
||||||
|
}
|
||||||
|
return newColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @param {string} fieldInternalName
|
||||||
|
* @returns {Promise<{ key: string, text: string }[]>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getChoiceFieldOptions(siteUrl: string, listId: string, fieldInternalName: string): Promise<{ key: string, text: string }[]> {
|
||||||
|
let fieldOptions: { key: string, text: string }[] = [];
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
const results = await web.lists.getById(listId)
|
||||||
|
.fields
|
||||||
|
.getByInternalNameOrTitle(fieldInternalName)
|
||||||
|
.select("Title", "InternalName", "Choices")
|
||||||
|
.get();
|
||||||
|
if (results && results.Choices.length > 0) {
|
||||||
|
for (const option of results.Choices) {
|
||||||
|
fieldOptions.push({
|
||||||
|
key: option,
|
||||||
|
text: option
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return fieldOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @param {Date} eventStartDate
|
||||||
|
* @param {Date} eventEndDate
|
||||||
|
* @returns {Promise< IEventData[]>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getEvents(siteUrl: string, listId: string, eventStartDate: Date, eventEndDate: Date): Promise<IEventData[]> {
|
||||||
|
|
||||||
|
let events: IEventData[] = [];
|
||||||
|
if (!siteUrl) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Get Regional Settings TimeZone Hours to UTC
|
||||||
|
const siteTimeZoneHoursToUTC: number = await this.getSiteTimeZoneHoursToUtc(siteUrl);
|
||||||
|
// Get Category Field Choices
|
||||||
|
const categoryDropdownOption = await this.getChoiceFieldOptions(siteUrl, listId, 'Category');
|
||||||
|
let categoryColor: { category: string, color: string }[] = [];
|
||||||
|
for (const cat of categoryDropdownOption) {
|
||||||
|
categoryColor.push({ category: cat.text, color: await this.colorGenerate() });
|
||||||
|
}
|
||||||
|
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
const results = await web.lists.getById(listId).renderListDataAsStream(
|
||||||
|
{
|
||||||
|
DatesInUtc: true,
|
||||||
|
ViewXml: `<View><ViewFields><FieldRef Name='Author'/><FieldRef Name='Category'/><FieldRef Name='Description'/><FieldRef Name='ParticipantsPicker'/><FieldRef Name='Geolocation'/><FieldRef Name='ID'/><FieldRef Name='EndDate'/><FieldRef Name='EventDate'/><FieldRef Name='ID'/><FieldRef Name='Location'/><FieldRef Name='Title'/><FieldRef Name='fAllDayEvent'/></ViewFields>
|
||||||
|
<Query>
|
||||||
|
<Where>
|
||||||
|
<And>
|
||||||
|
<And>
|
||||||
|
<Geq>
|
||||||
|
<FieldRef Name='EventDate' />
|
||||||
|
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventStartDate).format('YYYY-MM-DD')}</Value>
|
||||||
|
</Geq>
|
||||||
|
<Leq>
|
||||||
|
<FieldRef Name='EventDate' />
|
||||||
|
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventEndDate).format('YYYY-MM-DD')}</Value>
|
||||||
|
</Leq>
|
||||||
|
</And>
|
||||||
|
<Eq>
|
||||||
|
<FieldRef Name='fRecurrence' />
|
||||||
|
<Value Type='Recurrence'>0</Value>
|
||||||
|
</Eq>
|
||||||
|
</And>
|
||||||
|
</Where>
|
||||||
|
</Query>
|
||||||
|
<RowLimit Paged=\"FALSE\">2000</RowLimit>
|
||||||
|
</View>`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (results && results.Row.length > 0) {
|
||||||
|
for (const event of results.Row) {
|
||||||
|
const initialsArray: string[] = event.Author[0].title.split(' ');
|
||||||
|
const initials: string = initialsArray[0].charAt(0) + initialsArray[initialsArray.length - 1].charAt(0);
|
||||||
|
const userPictureUrl = await this.getUserProfilePictureUrl(`i:0#.f|membership|${event.Author[0].email}`);
|
||||||
|
const attendees: number[] = [];
|
||||||
|
const first: number = event.Geolocation.indexOf('(') + 1;
|
||||||
|
const last: number = event.Geolocation.indexOf(')');
|
||||||
|
const geo = event.Geolocation.substring(first, last);
|
||||||
|
const geolocation = geo.split(' ');
|
||||||
|
const CategoryColorValue: any[] = categoryColor.filter((value) => {
|
||||||
|
return value.category == event.Category;
|
||||||
|
});
|
||||||
|
for (const attendee of event.ParticipantsPicker) {
|
||||||
|
attendees.push(parseInt(attendee.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
events.push({
|
||||||
|
id: event.ID,
|
||||||
|
title: event.Title,
|
||||||
|
Description: event.Description,
|
||||||
|
// start: moment(event.EventDate).utc().toDate().setUTCMinutes(this.siteTimeZoneOffSet),
|
||||||
|
start: new Date(moment(event.EventDate).subtract(siteTimeZoneHoursToUTC, 'hour').toISOString()),
|
||||||
|
// end: new Date(moment(event.EndDate).toLocaleString()),
|
||||||
|
end: new Date(moment(event.EndDate).subtract(siteTimeZoneHoursToUTC, 'hour').toISOString()),
|
||||||
|
location: event.Location,
|
||||||
|
ownerEmail: event.Author[0].email,
|
||||||
|
ownerPhoto: userPictureUrl ?
|
||||||
|
`https://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=${event.Author[0].email}&UA=0&size=HR96x96` : '',
|
||||||
|
ownerInitial: initials,
|
||||||
|
// color: await this.colorGenerate(),
|
||||||
|
color: CategoryColorValue.length > 0 ? CategoryColorValue[0].color : await this.colorGenerate,
|
||||||
|
ownerName: event.Author[0].title,
|
||||||
|
attendes: attendees,
|
||||||
|
allDayEvent: false,
|
||||||
|
geolocation: { Longitude: parseFloat(geolocation[0]), Latitude: parseFloat(geolocation[1]) },
|
||||||
|
Category: event.Category
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return Data
|
||||||
|
return events;
|
||||||
|
} catch (error) {
|
||||||
|
console.dir(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getSiteRegionalSettingsTimeZone(siteUrl: string) {
|
||||||
|
let regionalSettings: RegionalSettings;
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
regionalSettings = await web.regionalSettings.timeZone.get();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return regionalSettings;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {string} webUrl
|
||||||
|
* @param {string} siteDesignId
|
||||||
|
* @returns
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getGeoLactionName(latitude: number, longitude: number) {
|
||||||
|
try {
|
||||||
|
const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&zoom=18&addressdetails=1`;
|
||||||
|
const results = await $.ajax({
|
||||||
|
url: apiUrl,
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json;charset=utf-8',
|
||||||
|
'accept': 'application/json;odata=nometadata',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "24353da0-cf7a-4ca8-85a2-c9cc91b5b865",
|
||||||
|
"alias": "CalendarWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
// The "*" signifies that the version should be taken from the package.json
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||||
|
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||||
|
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
"supportedHosts": [
|
||||||
|
"SharePointWebPart",
|
||||||
|
"TeamsTab"
|
||||||
|
],
|
||||||
|
"preconfiguredEntries": [
|
||||||
|
{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
|
"group": {
|
||||||
|
"default": "Other"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"default": "Calendar"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"default": "Calendar Events"
|
||||||
|
},
|
||||||
|
"officeFabricIconFontName": "Calendar",
|
||||||
|
"properties": {
|
||||||
|
"title": "Calendar",
|
||||||
|
"siteUrl": "",
|
||||||
|
"list": "",
|
||||||
|
"eventStartDate": "",
|
||||||
|
"eventEndDate": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
|
||||||
|
import { BaseClientSideWebPart, PropertyPaneHorizontalRule } from '@microsoft/sp-webpart-base';
|
||||||
|
import {
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField,
|
||||||
|
PropertyPaneDropdown,
|
||||||
|
IPropertyPaneDropdownOption,
|
||||||
|
PropertyPaneLabel
|
||||||
|
|
||||||
|
} from '@microsoft/sp-property-pane';
|
||||||
|
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import Calendar from './components/Calendar';
|
||||||
|
import { ICalendarProps } from './components/ICalendarProps';
|
||||||
|
import { PropertyFieldDateTimePicker, DateConvention, TimeConvention, IDateTimeFieldValue } from '@pnp/spfx-property-controls/lib/PropertyFieldDateTimePicker';
|
||||||
|
|
||||||
|
export interface ICalendarWebPartProps {
|
||||||
|
title: string;
|
||||||
|
siteUrl: string;
|
||||||
|
list: string;
|
||||||
|
eventStartDate: IDateTimeFieldValue ;
|
||||||
|
eventEndDate: IDateTimeFieldValue;
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
||||||
|
import spservices from '../../services/spservices';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { format } from '@uifabric/utilities';
|
||||||
|
|
||||||
|
export default class CalendarWebPart extends BaseClientSideWebPart<ICalendarWebPartProps> {
|
||||||
|
|
||||||
|
private lists: IPropertyPaneDropdownOption[] = [];
|
||||||
|
private listsDropdownDisabled: boolean = true;
|
||||||
|
private spService: spservices = null;
|
||||||
|
private errorMessage: string;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
|
||||||
|
const element: React.ReactElement<ICalendarProps> = React.createElement(
|
||||||
|
Calendar,
|
||||||
|
{
|
||||||
|
title: this.properties.title,
|
||||||
|
siteUrl: this.properties.siteUrl,
|
||||||
|
list: this.properties.list,
|
||||||
|
displayMode: this.displayMode,
|
||||||
|
updateProperty: (value: string) => {
|
||||||
|
this.properties.title = value;
|
||||||
|
},
|
||||||
|
context: this.context,
|
||||||
|
eventStartDate: this.properties.eventStartDate,
|
||||||
|
eventEndDate: this.properties.eventEndDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// onInit
|
||||||
|
public async onInit(): Promise<void> {
|
||||||
|
|
||||||
|
this.spService = new spservices(this.context);
|
||||||
|
this.properties.siteUrl = this.context.pageContext.site.absoluteUrl;
|
||||||
|
if (!this.properties.eventStartDate){
|
||||||
|
this.properties.eventStartDate = { value: moment().subtract(2,'years').startOf('month').toDate(), displayValue: moment().format('ddd MMM MM YYYY')};
|
||||||
|
}
|
||||||
|
if (!this.properties.eventEndDate){
|
||||||
|
this.properties.eventEndDate = { value: moment().add(20,'years').endOf('month').toDate(), displayValue: moment().format('ddd MMM MM YYYY')};
|
||||||
|
}
|
||||||
|
if (this.properties.siteUrl && !this.properties.list) {
|
||||||
|
const _lists = await this.loadLists();
|
||||||
|
this.lists = _lists;
|
||||||
|
this.properties.list = this.lists.length > 0 ? this.lists[0].key.toString() : '';
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
protected async onPropertyPaneConfigurationStart() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.properties.siteUrl) {
|
||||||
|
const _lists = await this.loadLists();
|
||||||
|
this.lists = _lists;
|
||||||
|
this.listsDropdownDisabled = false;
|
||||||
|
// await this.loadFields(this.properties.siteUrl);
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.lists = [];
|
||||||
|
this.properties.list = '';
|
||||||
|
this.listsDropdownDisabled = false;
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {Promise<IPropertyPaneDropdownOption[]>}
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
private async loadLists(): Promise<IPropertyPaneDropdownOption[]> {
|
||||||
|
const _lists: IPropertyPaneDropdownOption[] = [];
|
||||||
|
try {
|
||||||
|
const results = await this.spService.getSiteLists(this.properties.siteUrl);
|
||||||
|
for (const list of results) {
|
||||||
|
_lists.push({ key: list.Id, text: list.Title });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.errorMessage = `${error.message} - ${strings.PropPanelSiteUrlErrorMessage}` ;
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
}
|
||||||
|
return _lists;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} date
|
||||||
|
* @returns
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
private onEventStartDateValidation(date:string){
|
||||||
|
if (date && this.properties.eventEndDate.value){
|
||||||
|
if (moment(date).isAfter(moment(this.properties.eventEndDate.value))){
|
||||||
|
return strings.SartDateValidationMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} date
|
||||||
|
* @returns
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
private onEventEndDateValidation(date:string){
|
||||||
|
if (date && this.properties.eventEndDate.value){
|
||||||
|
if (moment(date).isBefore( moment(this.properties.eventStartDate.value))){
|
||||||
|
return strings.EnDateValidationMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
|
||||||
|
private onSiteUrlGetErrorMessage(value: string) {
|
||||||
|
let returnValue: string = '';
|
||||||
|
if (value) {
|
||||||
|
returnValue = '';
|
||||||
|
} else {
|
||||||
|
const previousList: string = this.properties.list;
|
||||||
|
const previousSiteUrl: string = this.properties.siteUrl;
|
||||||
|
// reset selected item
|
||||||
|
this.properties.list = undefined;
|
||||||
|
this.properties.siteUrl = undefined;
|
||||||
|
this.lists = [];
|
||||||
|
this.listsDropdownDisabled = true;
|
||||||
|
this.onPropertyPaneFieldChanged('list', previousList, this.properties.list);
|
||||||
|
this.onPropertyPaneFieldChanged('siteUrl', previousSiteUrl, this.properties.siteUrl);
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @param {string} propertyPath
|
||||||
|
* @param {string} oldValue
|
||||||
|
* @param {string} newValue
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
protected async onPropertyPaneFieldChanged(propertyPath: string, oldValue: string, newValue: string) {
|
||||||
|
try {
|
||||||
|
// reset any error
|
||||||
|
this.properties.errorMessage = undefined;
|
||||||
|
this.errorMessage = undefined;
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
|
||||||
|
if (propertyPath === 'siteUrl' && newValue) {
|
||||||
|
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
|
||||||
|
const _oldValue = this.properties.list;
|
||||||
|
this.onPropertyPaneFieldChanged('list', _oldValue, this.properties.list);
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
const _lists = await this.loadLists();
|
||||||
|
this.lists = _lists;
|
||||||
|
this.listsDropdownDisabled = false;
|
||||||
|
this.properties.list = this.lists.length > 0 ? this.lists[0].key.toString() : undefined;
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.errorMessage = `${error.message} - ${strings.PropPanelSiteUrlErrorMessage}` ;
|
||||||
|
this.context.propertyPane.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @returns {IPropertyPaneConfiguration}
|
||||||
|
* @memberof CalendarWebPart
|
||||||
|
*/
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('siteUrl', {
|
||||||
|
label: strings.SiteUrlFieldLabel,
|
||||||
|
onGetErrorMessage: this.onSiteUrlGetErrorMessage.bind(this),
|
||||||
|
value: this.context.pageContext.site.absoluteUrl,
|
||||||
|
deferredValidationTime: 1200,
|
||||||
|
}),
|
||||||
|
PropertyPaneDropdown('list', {
|
||||||
|
label: strings.ListFieldLabel,
|
||||||
|
options: this.lists,
|
||||||
|
disabled: this.listsDropdownDisabled,
|
||||||
|
}),
|
||||||
|
PropertyPaneLabel('eventStartDate', {
|
||||||
|
text: strings.eventSelectDatesLabel
|
||||||
|
}),
|
||||||
|
PropertyFieldDateTimePicker('eventStartDate', {
|
||||||
|
label: 'From',
|
||||||
|
initialDate: this.properties.eventStartDate,
|
||||||
|
dateConvention: DateConvention.Date,
|
||||||
|
onPropertyChange: this.onPropertyPaneFieldChanged,
|
||||||
|
properties: this.properties,
|
||||||
|
onGetErrorMessage: this.onEventStartDateValidation,
|
||||||
|
deferredValidationTime: 0,
|
||||||
|
key: 'eventStartDateId'
|
||||||
|
}),
|
||||||
|
PropertyFieldDateTimePicker('eventEndDate', {
|
||||||
|
label: 'to',
|
||||||
|
initialDate: this.properties.eventEndDate,
|
||||||
|
dateConvention: DateConvention.Date,
|
||||||
|
onPropertyChange: this.onPropertyPaneFieldChanged,
|
||||||
|
properties: this.properties,
|
||||||
|
onGetErrorMessage: this.onEventEndDateValidation,
|
||||||
|
deferredValidationTime: 0,
|
||||||
|
key: 'eventEndDateId'
|
||||||
|
}),
|
||||||
|
PropertyPaneLabel('errorMessage', {
|
||||||
|
text: this.errorMessage,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
|
||||||
|
.Documentcard {
|
||||||
|
width: '100%';
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewEventIcon {
|
||||||
|
|
||||||
|
justify-content: 'center';
|
||||||
|
display: 'flex';
|
||||||
|
align-items: 'center';
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locationIcon {
|
||||||
|
|
||||||
|
justify-content: 'center';
|
||||||
|
display: 'flex';
|
||||||
|
align-items: 'top';
|
||||||
|
font-size: 22px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.location {
|
||||||
|
|
||||||
|
justify-content: 'center';
|
||||||
|
display: 'flex';
|
||||||
|
align-items: 'top';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.DocumentCardDetails{
|
||||||
|
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-style: 'solid';
|
||||||
|
border-width: 1.3px;
|
||||||
|
border-color: #e6e6e6;
|
||||||
|
height: 40px;
|
||||||
|
justify-content: 'center';
|
||||||
|
display: 'flex';
|
||||||
|
align-items: 'top';
|
||||||
|
}
|
||||||
|
|
||||||
|
.eventStyle{
|
||||||
|
background-color: white;
|
||||||
|
border-radius: '0px';
|
||||||
|
opacity: 1;
|
||||||
|
color: black;
|
||||||
|
border-width: 1.4px;
|
||||||
|
border-style: 'solid';
|
||||||
|
display: 'block'
|
||||||
|
}
|
||||||
|
|
||||||
|
.DocumentCardTitle {
|
||||||
|
font-weight: 'bold';
|
||||||
|
height: '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
.DocumentCardTitleTime{
|
||||||
|
|
||||||
|
justify-content: 'center';
|
||||||
|
display: 'flex';
|
||||||
|
align-items: 'top';
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
}
|
||||||
|
.calendar {
|
||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
height: 600px;
|
||||||
|
margin: 0px auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.plainCard {
|
||||||
|
width: 300;
|
||||||
|
height: 286;
|
||||||
|
}
|
||||||
|
.eventTitle {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg10;
|
||||||
|
@include ms-xl8;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: $ms-color-themePrimary;
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
color: $ms-color-white;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-regular;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,373 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Calendar.module.scss';
|
||||||
|
import { ICalendarProps } from './ICalendarProps';
|
||||||
|
import { ICalendarState } from './ICalendarState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import BigCalendar from 'react-big-calendar';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||||
|
require('./calendar.css');
|
||||||
|
import {
|
||||||
|
|
||||||
|
IPersonaSharedProps,
|
||||||
|
Persona,
|
||||||
|
PersonaSize,
|
||||||
|
PersonaPresence,
|
||||||
|
HoverCard, IHoverCard, IPlainCardProps, HoverCardType, DefaultButton,
|
||||||
|
DocumentCard,
|
||||||
|
DocumentCardActivity,
|
||||||
|
DocumentCardDetails,
|
||||||
|
DocumentCardPreview,
|
||||||
|
DocumentCardTitle,
|
||||||
|
IDocumentCardPreviewProps,
|
||||||
|
IDocumentCardPreviewImage,
|
||||||
|
DocumentCardType,
|
||||||
|
Label,
|
||||||
|
ImageFit,
|
||||||
|
IDocumentCardLogoProps,
|
||||||
|
DocumentCardLogo,
|
||||||
|
DocumentCardImage,
|
||||||
|
Icon,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
|
||||||
|
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
import { EnvironmentType } from '@microsoft/sp-core-library';
|
||||||
|
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
|
||||||
|
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
||||||
|
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||||
|
import spservices from '../../../services/spservices';
|
||||||
|
import { stringIsNullOrEmpty } from '@pnp/common';
|
||||||
|
import { Event } from '../../../controls/Event/event';
|
||||||
|
import { IPanelModelEnum } from '../../../controls/Event/IPanelModeEnum';
|
||||||
|
import { IEventData } from './../../../services/IEventData';
|
||||||
|
import { IUserPermissions } from './../../../services/IUserPermissions';
|
||||||
|
const localizer = BigCalendar.momentLocalizer(moment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @export
|
||||||
|
* @class Calendar
|
||||||
|
* @extends {React.Component<ICalendarProps, ICalendarState>}
|
||||||
|
*/
|
||||||
|
export default class Calendar extends React.Component<ICalendarProps, ICalendarState> {
|
||||||
|
private spService: spservices = null;
|
||||||
|
private userListPermissions: IUserPermissions = undefined;
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
showDialog: false,
|
||||||
|
eventData: [],
|
||||||
|
selectedEvent: undefined,
|
||||||
|
isloading: true,
|
||||||
|
hasError: false,
|
||||||
|
errorMessage: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onDismissPanel = this.onDismissPanel.bind(this);
|
||||||
|
this.onSelectEvent = this.onSelectEvent.bind(this);
|
||||||
|
this.onSelectSlot = this.onSelectSlot.bind(this);
|
||||||
|
this.spService = new spservices(this.props.context);
|
||||||
|
moment.locale(this.props.context.pageContext.cultureInfo.currentUICultureName);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDocumentCardClick(ev: React.SyntheticEvent<HTMLElement, Event>) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {*} event
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
private onSelectEvent(event: any) {
|
||||||
|
this.setState({ showDialog: true, selectedEvent: event, panelMode: IPanelModelEnum.edit });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {boolean} refresh
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
private async onDismissPanel(refresh: boolean) {
|
||||||
|
|
||||||
|
this.setState({ showDialog: false });
|
||||||
|
if (refresh === true) {
|
||||||
|
this.setState({ isloading: true });
|
||||||
|
await this.loadEvents();
|
||||||
|
this.setState({ isloading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
private async loadEvents() {
|
||||||
|
try {
|
||||||
|
// Teste Properties
|
||||||
|
if (!this.props.list || !this.props.siteUrl || !this.props.eventStartDate.value || !this.props.eventEndDate.value) return;
|
||||||
|
|
||||||
|
this.userListPermissions = await this.spService.getUserPermissions(this.props.siteUrl, this.props.list);
|
||||||
|
const eventsData: IEventData[] = await this.spService.getEvents(escape(this.props.siteUrl), escape(this.props.list), this.props.eventStartDate.value, this.props.eventEndDate.value);
|
||||||
|
this.setState({ eventData: eventsData, hasError: false, errorMessage: "" });
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ hasError: true, errorMessage: error.message, isloading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
public async componentDidMount() {
|
||||||
|
this.setState({ isloading: true });
|
||||||
|
await this.loadEvents();
|
||||||
|
this.setState({ isloading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} error
|
||||||
|
* @param {*} errorInfo
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
public componentDidCatch(error: any, errorInfo: any) {
|
||||||
|
this.setState({ hasError: true, errorMessage: errorInfo.componentStack });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {ICalendarProps} prevProps
|
||||||
|
* @param {ICalendarState} prevState
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
public async componentDidUpdate(prevProps: ICalendarProps, prevState: ICalendarState) {
|
||||||
|
|
||||||
|
if (!this.props.list || !this.props.siteUrl || !this.props.eventStartDate.value || !this.props.eventEndDate.value) return;
|
||||||
|
// Get Properties change
|
||||||
|
if (prevProps.list !== this.props.list || this.props.eventStartDate.value !== prevProps.eventStartDate.value || this.props.eventEndDate.value !== prevProps.eventEndDate.value) {
|
||||||
|
this.setState({ isloading: true });
|
||||||
|
await this.loadEvents();
|
||||||
|
this.setState({ isloading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {*} { event }
|
||||||
|
* @returns
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
private renderEvent({ event }) {
|
||||||
|
|
||||||
|
const previewEventIcon: IDocumentCardPreviewProps = {
|
||||||
|
previewImages: [
|
||||||
|
{
|
||||||
|
// previewImageSrc: event.ownerPhoto,
|
||||||
|
previewIconProps: { iconName: 'Calendar', styles: { root: { color: event.color } }, className: styles.previewEventIcon },
|
||||||
|
height: 43,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const EventInfo: IPersonaSharedProps = {
|
||||||
|
imageInitials: event.ownerInitial,
|
||||||
|
imageUrl: event.ownerPhoto,
|
||||||
|
text: event.title
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
const onRenderPlainCard = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className={styles.plainCard}>
|
||||||
|
<DocumentCard className={styles.Documentcard} >
|
||||||
|
<div>
|
||||||
|
<DocumentCardPreview {...previewEventIcon} />
|
||||||
|
</div>
|
||||||
|
<DocumentCardDetails>
|
||||||
|
<div className={styles.DocumentCardDetails}>
|
||||||
|
<DocumentCardTitle title={event.title} shouldTruncate={true} className={styles.DocumentCardTitle} styles={{ root: { color: event.color } }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
moment(event.start).format('YYYY/MM/DD') !== moment(event.end).format('YYYY/MM/DD') ?
|
||||||
|
<span className={styles.DocumentCardTitleTime}>{moment(event.start).format('dddd')} - {moment(event.end).format('dddd')} </span>
|
||||||
|
:
|
||||||
|
<span className={styles.DocumentCardTitleTime}>{moment(event.start).format('dddd')} </span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span className={styles.DocumentCardTitleTime}>{moment(event.start).format('HH:mm')}H - {moment(event.end).format('HH:mm')}H</span>
|
||||||
|
<Icon iconName='MapPin' className={styles.locationIcon} style={{ color: event.color }} />
|
||||||
|
<DocumentCardTitle
|
||||||
|
title={`${event.location}`}
|
||||||
|
shouldTruncate={true}
|
||||||
|
showAsSecondaryTitle={true}
|
||||||
|
className={styles.location}
|
||||||
|
/>
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<DocumentCardActivity
|
||||||
|
activity={strings.EventOwnerLabel}
|
||||||
|
people={[{ name: event.ownerName, profileImageSrc: event.ownerPhoto, initialsColor: event.color }]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DocumentCardDetails>
|
||||||
|
</DocumentCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div style={{ height: 22 }}>
|
||||||
|
<HoverCard
|
||||||
|
cardDismissDelay={1000}
|
||||||
|
type={HoverCardType.plain}
|
||||||
|
plainCardProps={{ onRenderPlainCard: onRenderPlainCard }}
|
||||||
|
onCardHide={(): void => {
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Persona
|
||||||
|
{...EventInfo}
|
||||||
|
size={PersonaSize.size24}
|
||||||
|
presence={PersonaPresence.none}
|
||||||
|
coinSize={22}
|
||||||
|
initialsColor={event.color}
|
||||||
|
/>
|
||||||
|
</HoverCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
private onConfigure() {
|
||||||
|
// Context of the web part
|
||||||
|
this.props.context.propertyPane.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} { start, end }
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
public async onSelectSlot({ start, end }) {
|
||||||
|
if (!this.userListPermissions.hasPermissionAdd) return;
|
||||||
|
this.setState({ showDialog: true, startDateSlot: start, endDateSlot: end, selectedEvent: undefined, panelMode: IPanelModelEnum.add });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
* @param {*} start
|
||||||
|
* @param {*} end
|
||||||
|
* @param {*} isSelected
|
||||||
|
* @returns {*}
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
public eventStyleGetter(event, start, end, isSelected): any {
|
||||||
|
let style: any = {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '0px',
|
||||||
|
opacity: 1,
|
||||||
|
color: 'black',
|
||||||
|
borderWidth: '1.1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: event.color,
|
||||||
|
borderLeftWidth: '5px',
|
||||||
|
display: 'block'
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
style: style
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<ICalendarProps>}
|
||||||
|
* @memberof Calendar
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<ICalendarProps> {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.calendar}>
|
||||||
|
<WebPartTitle displayMode={this.props.displayMode}
|
||||||
|
title={this.props.title}
|
||||||
|
updateProperty={this.props.updateProperty} />
|
||||||
|
{
|
||||||
|
(!this.props.list || !this.props.eventStartDate.value || !this.props.eventEndDate.value) ?
|
||||||
|
<Placeholder iconName='Edit'
|
||||||
|
iconText={strings.WebpartConfigIconText}
|
||||||
|
description={strings.WebpartConfigDescription}
|
||||||
|
buttonLabel={strings.WebPartConfigButtonLabel}
|
||||||
|
hideButton={this.props.displayMode === DisplayMode.Read}
|
||||||
|
onConfigure={this.onConfigure.bind(this)} />
|
||||||
|
:
|
||||||
|
// test if has errors
|
||||||
|
this.state.hasError ?
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
{this.state.errorMessage}
|
||||||
|
</MessageBar>
|
||||||
|
:
|
||||||
|
// show Calendar
|
||||||
|
// Test if is loading Events
|
||||||
|
<div>
|
||||||
|
{this.state.isloading ? <Spinner size={SpinnerSize.large} label={strings.LoadingEventsLabel} /> :
|
||||||
|
<div className={styles.container}>
|
||||||
|
<BigCalendar
|
||||||
|
localizer={localizer}
|
||||||
|
selectable
|
||||||
|
events={this.state.eventData}
|
||||||
|
startAccessor="start"
|
||||||
|
endAccessor="end"
|
||||||
|
eventPropGetter={this.eventStyleGetter}
|
||||||
|
onSelectSlot={this.onSelectSlot}
|
||||||
|
components={{
|
||||||
|
event: this.renderEvent
|
||||||
|
|
||||||
|
}}
|
||||||
|
onSelectEvent={this.onSelectEvent}
|
||||||
|
defaultDate={moment().startOf('day').toDate()}
|
||||||
|
messages={
|
||||||
|
{
|
||||||
|
'today': strings.todayLabel,
|
||||||
|
'previous': strings.previousLabel,
|
||||||
|
'next': strings.nextLabel,
|
||||||
|
'month': strings.monthLabel,
|
||||||
|
'week': strings.weekLabel,
|
||||||
|
'day': strings.dayLable,
|
||||||
|
'showMore': total => `+${total} ${strings.showMore}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showDialog &&
|
||||||
|
<Event
|
||||||
|
event={this.state.selectedEvent}
|
||||||
|
panelMode={this.state.panelMode}
|
||||||
|
onDissmissPanel={this.onDismissPanel}
|
||||||
|
showPanel={this.state.showDialog}
|
||||||
|
startDate={this.state.startDateSlot}
|
||||||
|
endDate={this.state.endDateSlot}
|
||||||
|
context={this.props.context}
|
||||||
|
siteUrl={this.props.siteUrl}
|
||||||
|
listId={this.props.list}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
import { IDateTimeFieldValue } from '@pnp/spfx-property-controls/lib/PropertyFieldDateTimePicker';
|
||||||
|
export interface ICalendarProps {
|
||||||
|
title: string;
|
||||||
|
siteUrl: string;
|
||||||
|
list: string;
|
||||||
|
displayMode: DisplayMode;
|
||||||
|
updateProperty: (value: string) => void;
|
||||||
|
context: WebPartContext;
|
||||||
|
eventStartDate: IDateTimeFieldValue;
|
||||||
|
eventEndDate: IDateTimeFieldValue;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { IPanelModelEnum} from '../../../controls/Event/IPanelModeEnum';
|
||||||
|
import { IEventData } from './../../../services/IEventData';
|
||||||
|
export interface ICalendarState {
|
||||||
|
showDialog: boolean;
|
||||||
|
eventData: IEventData[];
|
||||||
|
selectedEvent: IEventData;
|
||||||
|
panelMode?: IPanelModelEnum;
|
||||||
|
startDateSlot?: Date;
|
||||||
|
endDateSlot?:Date;
|
||||||
|
isloading: boolean;
|
||||||
|
hasError: boolean;
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Toolbar from 'react-big-calendar/lib/Toolbar';
|
||||||
|
|
||||||
|
export default class CalendarToolbar extends Toolbar {
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const view = this.props.view;
|
||||||
|
console.log(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="rbc-btn-group">
|
||||||
|
<button type="button" onClick={() => this.navigate('TODAY')}>today</button>
|
||||||
|
<button type="button" onClick={() => this.navigate('PREV')}>back</button>
|
||||||
|
<button type="button" onClick={() => this.navigate('NEXT')}>next</button>
|
||||||
|
</div>
|
||||||
|
<div className="rbc-toolbar-label">{this.props.label}</div>
|
||||||
|
<div className="rbc-btn-group">
|
||||||
|
<button type="button" onClick={this.view.bind(null, 'month')}>Month</button>
|
||||||
|
<button type="button" onClick={this.view.bind(null, 'week')}>Week</button>
|
||||||
|
<button type="button" onClick={this.view.bind(null, 'day')}>Day</button>
|
||||||
|
<button type="button" onClick={this.view.bind(null, 'agenda')}>Agenda</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.export = CalendarToolbar;
|
|
@ -0,0 +1,92 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.rbc-selected-cell {
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
}
|
||||||
|
.rbc-event:hover {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-today {
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.rbc-off-range-bg {
|
||||||
|
background: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-toolbar button {
|
||||||
|
color: #373a3c;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
background: none;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: .375rem 1rem;
|
||||||
|
/* border-radius: 4px; */
|
||||||
|
line-height: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.rbc-toolbar button:active {
|
||||||
|
background-image: none;
|
||||||
|
/* box-shadow: inset 0 3px 5px rgba(0,0,0,.125); */
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-color: #adadad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-toolbar button {
|
||||||
|
color: #373a3c;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
background: none;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: .375rem 1rem;
|
||||||
|
border-radius: 0px;
|
||||||
|
line-height: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.rbc-toolbar button.rbc-active {
|
||||||
|
background-image: none;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
/* background-color: #0075c7;*/
|
||||||
|
border-color: #f4f4f4;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-toolbar button.rbc-active:focus, .rbc-toolbar button.rbc-active:hover, .rbc-toolbar button:active:focus, .rbc-toolbar button:active:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
border-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-toolbar button:focus, .rbc-toolbar button:hover {
|
||||||
|
color: #f8f8f8;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
border-color: #adadad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-show-more {
|
||||||
|
background-color: hsla(0,0%,100%,.3);
|
||||||
|
z-index: 4;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 85%;
|
||||||
|
height: auto;
|
||||||
|
line-height: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-ellipsis, .rbc-event-label, .rbc-row-segment .rbc-event-content, .rbc-show-more {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
define([], function () {
|
||||||
|
return {
|
||||||
|
PropPanelSiteUrlErrorMessage:'Please verify if site url is valid',
|
||||||
|
HttpErrorMessage: "Error reading calendar events:",
|
||||||
|
CategoryPlaceHolder: "Please select category",
|
||||||
|
CategoryLabel: "Category",
|
||||||
|
EnDateValidationMessage: "start date is greater than end date",
|
||||||
|
SartDateValidationMessage: "start date is greater than end date",
|
||||||
|
eventSelectDatesLabel: "Show only the events within the following dates",
|
||||||
|
ConfirmeDeleteMessage: "Confirm delete event ?",
|
||||||
|
DialogConfirmDeleteTitle: " 'Delete Event'",
|
||||||
|
SpinnerDeletingLabel: "Deleting...",
|
||||||
|
DialogCloseButtonLabel: "Cancel",
|
||||||
|
DialogConfirmDeleteLabel: "Delete",
|
||||||
|
SaveButtonLabel: " Save",
|
||||||
|
DeleteButtonLabel: "Delete",
|
||||||
|
CancelButtonLabel: "Cancel",
|
||||||
|
LoadingEventsLabel: "Loading events...",
|
||||||
|
WebPartConfigButtonLabel: "Configure",
|
||||||
|
WebpartConfigDescription: "Please configure list calendar ",
|
||||||
|
WebpartConfigIconText: "Configure your Calendar Web Part",
|
||||||
|
EventOwnerLabel: "event owner",
|
||||||
|
InvalidDateFormat: "Invalid date format.",
|
||||||
|
IsRequired: "Field is required.",
|
||||||
|
CloseDate: "Close date picker",
|
||||||
|
NextYear: "Go to next year",
|
||||||
|
PrevYear: "Go to previous year",
|
||||||
|
NextMonth: "Go to next month",
|
||||||
|
PrevMonth: "Go to previous month",
|
||||||
|
GoToDay: "Go to today",
|
||||||
|
ShortDay_Saunday: "S",
|
||||||
|
ShortDay_Friday: "F",
|
||||||
|
ShortDay_Tursday: "T",
|
||||||
|
ShortDay_W: "W",
|
||||||
|
ShortDay_T: "T",
|
||||||
|
ShortDay_M: "M",
|
||||||
|
ShortDay_S: "S",
|
||||||
|
Saturday: "Saturday",
|
||||||
|
Friday: "Friday",
|
||||||
|
Thursday: "Thursday",
|
||||||
|
Wednesday: "Wednesday",
|
||||||
|
Tuesday: "Tuesday",
|
||||||
|
Monday: "Monday",
|
||||||
|
Sunday: "Sunday",
|
||||||
|
Jan:'Jan',
|
||||||
|
Feb:'Feb',
|
||||||
|
Mar:'Mar',
|
||||||
|
Apr:'Apr',
|
||||||
|
May:'May',
|
||||||
|
Jun:'Jun',
|
||||||
|
Jul:'Jul',
|
||||||
|
Aug:'Aug',
|
||||||
|
Sep:'Sep',
|
||||||
|
Oct:'Oct',
|
||||||
|
Nov:'Nov',
|
||||||
|
Dez:'Dez',
|
||||||
|
Dezember: "December",
|
||||||
|
November: " 'November'",
|
||||||
|
October: "October",
|
||||||
|
September: "September",
|
||||||
|
August: " 'August'",
|
||||||
|
July: "July",
|
||||||
|
June: "June",
|
||||||
|
May: "May",
|
||||||
|
April: "April",
|
||||||
|
March: "March",
|
||||||
|
February: "February",
|
||||||
|
January: "January",
|
||||||
|
LocationLabel: "Location search and Map",
|
||||||
|
LocationTextLabel: "Location",
|
||||||
|
AttendeesLabel: "Attendees",
|
||||||
|
EndMinLabel: "Min",
|
||||||
|
EndHourLabel: "Hour",
|
||||||
|
EndDateLabel: "End Date",
|
||||||
|
EndDatePlaceHolder: "Select a date...",
|
||||||
|
StartMinLabel: "Min",
|
||||||
|
StartHourLabel: "Hour",
|
||||||
|
StartDateLabel: "Start Date",
|
||||||
|
StartDatePlaceHolder: "Select a date...",
|
||||||
|
EventTitleErrorMessage: "Event Title is required.",
|
||||||
|
EventTitleLabel: "Event title",
|
||||||
|
EventPanelTitle: "Edit/Add Event",
|
||||||
|
"PropertyPaneDescription": "Calendar",
|
||||||
|
"BasicGroupName": "Properties",
|
||||||
|
SiteUrlFieldLabel: 'Site Url',
|
||||||
|
ListFieldLabel: 'Calendar List name',
|
||||||
|
weekLabel: 'Week',
|
||||||
|
dayLable: 'Day',
|
||||||
|
agenda: 'Agenda',
|
||||||
|
monthLabel: 'Month',
|
||||||
|
"todayLabel": 'Today',
|
||||||
|
"previousLabel": 'Previous',
|
||||||
|
"nextLabel": "Next",
|
||||||
|
"showMore": 'more'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,100 @@
|
||||||
|
declare interface ICalendarWebPartStrings {
|
||||||
|
HttpErrorMessage: string;
|
||||||
|
CategoryPlaceHolder: string;
|
||||||
|
CategoryLabel: string;
|
||||||
|
EnDateValidationMessage: string;
|
||||||
|
SartDateValidationMessage: string;
|
||||||
|
eventSelectDatesLabel: string;
|
||||||
|
ConfirmeDeleteMessage: string;
|
||||||
|
DialogConfirmDeleteTitle: string;
|
||||||
|
SpinnerDeletingLabel: string;
|
||||||
|
DialogCloseButtonLabel: string;
|
||||||
|
DialogConfirmDeleteLabel: string;
|
||||||
|
SaveButtonLabel: string;
|
||||||
|
DeleteButtonLabel: string;
|
||||||
|
CancelButtonLabel: string;
|
||||||
|
LoadingEventsLabel: string;
|
||||||
|
WebPartConfigButtonLabel: string;
|
||||||
|
WebpartConfigDescription: string;
|
||||||
|
WebpartConfigIconText: string;
|
||||||
|
EventOwnerLabel: string;
|
||||||
|
InvalidDateFormat: string;
|
||||||
|
IsRequired: string;
|
||||||
|
CloseDate: string;
|
||||||
|
NextYear: string;
|
||||||
|
PrevYear: string;
|
||||||
|
NextMonth: string;
|
||||||
|
PrevMonth: string;
|
||||||
|
GoToDay: string;
|
||||||
|
ShortDay_Saunday: string;
|
||||||
|
ShortDay_Friday: string;
|
||||||
|
ShortDay_Tursday: string;
|
||||||
|
ShortDay_W: string;
|
||||||
|
ShortDay_T: string;
|
||||||
|
ShortDay_M: string;
|
||||||
|
ShortDay_S: string;
|
||||||
|
Saturday: string;
|
||||||
|
Friday: string;
|
||||||
|
Thursday: string;
|
||||||
|
Wednesday: string;
|
||||||
|
Tuesday: string;
|
||||||
|
Monday: string;
|
||||||
|
Sunday: string;
|
||||||
|
Jan:string;
|
||||||
|
Feb:string;
|
||||||
|
Mar:string;
|
||||||
|
Apr:string;
|
||||||
|
May:string,
|
||||||
|
Jun:string;
|
||||||
|
Jul:string;
|
||||||
|
Aug:string;
|
||||||
|
Sep:string;
|
||||||
|
Oct:string;
|
||||||
|
Nov:string;
|
||||||
|
Dez:string;
|
||||||
|
Dezember: string;
|
||||||
|
November: string;
|
||||||
|
October: string;
|
||||||
|
September: string;
|
||||||
|
August: string;
|
||||||
|
July: string;
|
||||||
|
June: string;
|
||||||
|
May: string;
|
||||||
|
April: string;
|
||||||
|
March: string;
|
||||||
|
February: string;
|
||||||
|
January: string;
|
||||||
|
LocationLabel: string;
|
||||||
|
LocationTextLabel: string;
|
||||||
|
AttendeesLabel: string;
|
||||||
|
EndMinLabel: string;
|
||||||
|
EndHourLabel: string;
|
||||||
|
EndDateLabel: string;
|
||||||
|
EndDatePlaceHolder: string;
|
||||||
|
StartMinLabel: string;
|
||||||
|
StartHourLabel: string;
|
||||||
|
StartDateLabel: string;
|
||||||
|
StartDatePlaceHolder: string;
|
||||||
|
EventTitleErrorMessage: string;
|
||||||
|
EventTitleLabel: string;
|
||||||
|
EventPanelTitle: string;
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
SiteUrlFieldLabel: string;
|
||||||
|
ListFieldLabel: string;
|
||||||
|
monthLabel: string;
|
||||||
|
weekLabel: string;
|
||||||
|
dayLable: string;
|
||||||
|
agenda: string;
|
||||||
|
todayLabel: string;
|
||||||
|
previousLabel: string;
|
||||||
|
nextLabel: string;
|
||||||
|
showMore: string;
|
||||||
|
PropPanelSiteUrlErrorMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'CalendarWebPartStrings' {
|
||||||
|
const strings: ICalendarWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
PropPanelSiteUrlErrorMessage:'Por favor verifique se site url é valido.',
|
||||||
|
HttpErrorMessage: "Error reading calendar events:",
|
||||||
|
CategoryPlaceHolder: "Please select category",
|
||||||
|
CategoryLabel: "Category",
|
||||||
|
EnDateValidationMessage: "start date is greater than end date",
|
||||||
|
SartDateValidationMessage: "start date is greater than end date",
|
||||||
|
eventSelectDatesLabel: "Show only the events within the following dates",
|
||||||
|
ConfirmeDeleteMessage: "Confirm delete event ?",
|
||||||
|
DialogConfirmDeleteTitle: " 'Delete Event'",
|
||||||
|
SpinnerDeletingLabel: "Deleting...",
|
||||||
|
DialogCloseButtonLabel: "Cancel",
|
||||||
|
DialogConfirmDeleteLabel: "Delete",
|
||||||
|
SaveButtonLabel: " Save",
|
||||||
|
DeleteButtonLabel: "Delete",
|
||||||
|
CancelButtonLabel: "Cancel",
|
||||||
|
LoadingEventsLabel: "A carregar eventos...",
|
||||||
|
WebPartConfigButtonLabel: "Configurar",
|
||||||
|
WebpartConfigDescription: "Por favor configure configurar calendário ",
|
||||||
|
WebpartConfigIconText: "Configurar Web Part Calendarário",
|
||||||
|
EventOwnerLabel: "Organizador",
|
||||||
|
InvalidDateFormat: "Formato data inválido",
|
||||||
|
IsRequired: "Campo é obrigatório",
|
||||||
|
CloseDate: "Fechar date picker",
|
||||||
|
NextYear: "Ir para o próximo ano",
|
||||||
|
PrevYear: "Ir para o ano anterior",
|
||||||
|
NextMonth: "Ir para o próximo mês",
|
||||||
|
PrevMonth: "Ir para o mês anterior",
|
||||||
|
GoToDay: "Ir para dia de hoje",
|
||||||
|
ShortDay_Saunday: "S",
|
||||||
|
ShortDay_Friday: "S",
|
||||||
|
ShortDay_Tursday: "Q",
|
||||||
|
ShortDay_W: "Q",
|
||||||
|
ShortDay_T: "T",
|
||||||
|
ShortDay_M: "S",
|
||||||
|
ShortDay_S: "D",
|
||||||
|
Saturday: "Sábado",
|
||||||
|
Friday: "Sexta-feira",
|
||||||
|
Thursday: "Quinta-feira",
|
||||||
|
Wednesday: "Quarta",
|
||||||
|
Tuesday: "Terça",
|
||||||
|
Monday: "Segunda",
|
||||||
|
Sunday: "Domingo",
|
||||||
|
Jan:'Jan',
|
||||||
|
Feb:'Fev',
|
||||||
|
Mar:'Mar',
|
||||||
|
Apr:'Abr',
|
||||||
|
May:'Mai',
|
||||||
|
Jun:'Jun',
|
||||||
|
Jul:'Jul',
|
||||||
|
Aug:'Aug',
|
||||||
|
Sep:'Sep',
|
||||||
|
Oct:'Oct',
|
||||||
|
Nov:'Nov',
|
||||||
|
Dez:'Dez',
|
||||||
|
Dezember: "Dezembro",
|
||||||
|
November: " 'Novembro'",
|
||||||
|
October: "Outubro",
|
||||||
|
September: "Setembro",
|
||||||
|
August: 'Agosto',
|
||||||
|
July: "Julho",
|
||||||
|
June: "Junho",
|
||||||
|
May: "Maio",
|
||||||
|
April: "Abril",
|
||||||
|
March: "Março",
|
||||||
|
February: "Fevereiro",
|
||||||
|
January: "Janeiro",
|
||||||
|
LocationLabel: "Mapa",
|
||||||
|
LocationTextLabel: "Local",
|
||||||
|
AttendeesLabel: "Participantes",
|
||||||
|
EndMinLabel: "Min",
|
||||||
|
EndHourLabel: "Hora",
|
||||||
|
EndDateLabel: "Data fim",
|
||||||
|
EndDatePlaceHolder: "Seleccionar data...",
|
||||||
|
StartMinLabel: "Min",
|
||||||
|
StartHourLabel: "Hora",
|
||||||
|
StartDateLabel: "Data Início",
|
||||||
|
StartDatePlaceHolder: "Seleccionar data...",
|
||||||
|
EventTitleErrorMessage: "Título do evento é obrigatório.",
|
||||||
|
EventTitleLabel: "Título do evento",
|
||||||
|
EventPanelTitle: "Editar/Addcionar Evento",
|
||||||
|
"PropertyPaneDescription": "Calendário com marcação de eventos, mostra os eventos criados na lista calendário definida no site seleccionado",
|
||||||
|
"BasicGroupName": "Indique o Url do site e calendário",
|
||||||
|
"SiteUrlFieldLabel": 'Url do Site',
|
||||||
|
"ListFieldLabel": 'Nome da Lista Calendario',
|
||||||
|
"weekLabel": 'Semana',
|
||||||
|
"dayLable": 'Dia',
|
||||||
|
"agenda": 'Agenda',
|
||||||
|
"monthLabel": 'Mês',
|
||||||
|
"todayLabel": 'Hoje',
|
||||||
|
"previousLabel": 'Anterior',
|
||||||
|
"nextLabel": "Seguinte",
|
||||||
|
"showMore": 'mais'
|
||||||
|
}
|
||||||
|
});
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* This script updates the package-solution version analogue to the
|
||||||
|
* the package.json file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (process.env.npm_package_version === undefined) {
|
||||||
|
|
||||||
|
throw 'Package version cannot be evaluated';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// define path to package-solution file
|
||||||
|
const solution = './config/package-solution.json',
|
||||||
|
teams = './teams/manifest.json';
|
||||||
|
|
||||||
|
// require filesystem instanc
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// get next automated package version from process variable
|
||||||
|
const nextPkgVersion = process.env.npm_package_version;
|
||||||
|
|
||||||
|
// make sure next build version match
|
||||||
|
const nextVersion = nextPkgVersion.indexOf('-') === -1 ?
|
||||||
|
nextPkgVersion : nextPkgVersion.split('-')[0];
|
||||||
|
|
||||||
|
// Update version in SPFx package-solution if exists
|
||||||
|
if (fs.existsSync(solution)) {
|
||||||
|
|
||||||
|
// read package-solution file
|
||||||
|
const solutionFileContent = fs.readFileSync(solution, 'UTF-8');
|
||||||
|
// parse file as json
|
||||||
|
const solutionContents = JSON.parse(solutionFileContent);
|
||||||
|
|
||||||
|
// set property of version to next version
|
||||||
|
solutionContents.solution.version = nextVersion + '.0';
|
||||||
|
|
||||||
|
// save file
|
||||||
|
fs.writeFileSync(
|
||||||
|
solution,
|
||||||
|
// convert file back to proper json
|
||||||
|
JSON.stringify(solutionContents, null, 2),
|
||||||
|
'UTF-8');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update version in teams manifest if exists
|
||||||
|
if (fs.existsSync(teams)) {
|
||||||
|
|
||||||
|
// read package-solution file
|
||||||
|
const teamsManifestContent = fs.readFileSync(teams, 'UTF-8');
|
||||||
|
// parse file as json
|
||||||
|
const teamsContent = JSON.parse(teamsManifestContent);
|
||||||
|
|
||||||
|
// set property of version to next version
|
||||||
|
teamsContent.version = nextVersion;
|
||||||
|
|
||||||
|
// save file
|
||||||
|
fs.writeFileSync(
|
||||||
|
teams,
|
||||||
|
// convert file back to proper json
|
||||||
|
JSON.stringify(teamsContent, null, 2),
|
||||||
|
'UTF-8');
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.2/includes/tsconfig-web.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
@ -10,6 +11,9 @@
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
|
"inlineSources": false,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types",
|
"./node_modules/@types",
|
||||||
"./node_modules/@microsoft"
|
"./node_modules/@microsoft"
|