Merge branch 'dev'
|
@ -1,11 +1,13 @@
|
|||
# title of the sample
|
||||
|
||||
## Summary
|
||||
|
||||
Short summary on functionality and used technologies.
|
||||
|
||||
[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)
|
||||
|
||||
## Applies to
|
||||
|
@ -16,7 +18,7 @@ Short summary on functionality and used technologies.
|
|||
> Update accordingly as needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
> Any special pre-requisites?
|
||||
|
||||
## Solution
|
||||
|
@ -33,25 +35,27 @@ Version|Date|Comments
|
|||
1.0|August 29, 2025|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 serve`
|
||||
* Clone this repository
|
||||
* in the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
|
||||
> Include any additional steps as needed.
|
||||
|
||||
## Features
|
||||
|
||||
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:
|
||||
|
||||
- topic 1
|
||||
- topic 2
|
||||
- topic 3
|
||||
* topic 1
|
||||
* topic 2
|
||||
* topic 3
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
|
@ -1,60 +1,60 @@
|
|||
# title of the sample
|
||||
|
||||
## Summary
|
||||
This sample illustrates how SPFx functionality and packages can be bundled in multiple '.js' files then be dynamically & asynchronously loaded into the page at execution time, such as with a button click.
|
||||
|
||||
Pre Button Click:
|
||||
![preview](./assets/WebPart-Preview-PreClick.jpg)
|
||||
|
||||
Post Button Click that imports jQuery and additional functionality:
|
||||
![preview](./assets/WebPart-Preview-PostjQueryClick.jpg)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.7.0-orange.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)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
js-dynamic-bundling-libaries | David Warner II ([@DavidWarnerII](https://twitter.com/davidwarnerii) / [Warner Digital](http://warner.digital))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|September 21, 2018|Initial release
|
||||
1.1|December 3, 2018|Updated for SPFx 1.7.0
|
||||
|
||||
## 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 serve`
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- How to separate SPFx functionality into multiple bundled files
|
||||
- How to asynchronously load the seperate bundled files at execution time
|
||||
- Including a library in the separate bundled file.
|
||||
|
||||
## Additional Information:
|
||||
- [Dynamic loading of packages in SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-loading)
|
||||
|
||||
- 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" />
|
||||
# title of the sample
|
||||
|
||||
## Summary
|
||||
This sample illustrates how SPFx functionality and packages can be bundled in multiple '.js' files then be dynamically & asynchronously loaded into the page at execution time, such as with a button click.
|
||||
|
||||
Pre Button Click:
|
||||
![preview](./assets/WebPart-Preview-PreClick.jpg)
|
||||
|
||||
Post Button Click that imports jQuery and additional functionality:
|
||||
![preview](./assets/WebPart-Preview-PostjQueryClick.jpg)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.7.0-orange.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)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
js-dynamic-bundling-libaries | David Warner II ([@DavidWarnerII](https://twitter.com/davidwarnerii) / [Warner Digital](http://warner.digital))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|September 21, 2018|Initial release
|
||||
1.1|December 3, 2018|Updated for SPFx 1.7.0
|
||||
|
||||
## 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 serve`
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- How to separate SPFx functionality into multiple bundled files
|
||||
- How to asynchronously load the seperate bundled files at execution time
|
||||
- Including a library in the separate bundled file.
|
||||
|
||||
## Additional Information:
|
||||
- [Dynamic loading of packages in SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-loading)
|
||||
|
||||
- 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/js-dynamic-bundling-libraries" />
|
||||
|
|
|
@ -77,3 +77,4 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
|||
* Logging.
|
||||
* Rendering error messages.
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-employee-spotlight" />
|
||||
|
|
|
@ -46,4 +46,6 @@ Please refer to [this blog post](http://rolandoldengarm.com/index.php/2016/09/13
|
|||
The _PowerBI Embedded_ Client-Side Web Part is built on the SharePoint Framework using React and uses [PowerBI Embedded](https://azure.microsoft.com/en-us/services/power-bi-embedded/) to securely display a report.
|
||||
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" />
|
||||
|
|
|
@ -62,4 +62,6 @@ gulp deploy-azure-storage
|
|||
```
|
||||
|
||||
## 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
|
||||
|
||||
## Additional Information:
|
||||
|
||||
- [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:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-workbench-customizer" />
|
||||
|
|
|
@ -21,10 +21,8 @@
|
|||
"officeFabricIconFontName": "DeveloperTools",
|
||||
"properties": {
|
||||
"requiresPageRefresh": false,
|
||||
"maxWidth": true,
|
||||
"centerCanvas": true,
|
||||
"overflow": true,
|
||||
"padding": true
|
||||
"customWorkbenchStyles": true,
|
||||
"previewMode": true
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -5,16 +5,13 @@ import {
|
|||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import styles from './WorkbenchCustomizerWebPart.module.scss';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import * as strings from 'WorkbenchCustomizerWebPartStrings';
|
||||
|
||||
export interface IWorkbenchCustomizerWebPartProps {
|
||||
requiresPageRefresh: boolean;
|
||||
maxWidth: boolean;
|
||||
centerCanvas: boolean;
|
||||
overflow: boolean;
|
||||
padding: boolean;
|
||||
customWorkbenchStyles: boolean;
|
||||
previewMode: boolean;
|
||||
}
|
||||
|
||||
export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IWorkbenchCustomizerWebPartProps> {
|
||||
|
@ -26,30 +23,26 @@ export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IW
|
|||
|
||||
public async render(): Promise<void> {
|
||||
|
||||
if (this.properties.maxWidth) {
|
||||
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');
|
||||
}
|
||||
if (!this.renderedOnce) {
|
||||
|
||||
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}">
|
||||
${this.properties.requiresPageRefresh
|
||||
? `<div class="${styles.redMessage}">Please refresh the page to update workbench styles</div>`
|
||||
: ''
|
||||
}
|
||||
<div>Max width enabled: ${this.properties.maxWidth}</div>
|
||||
<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 class="${styles.redMessage}">Please refresh the page to remove custom workbench styles</div>`
|
||||
: ''
|
||||
}
|
||||
*** Workbench Customizer web part ***
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
public onPropertyPaneFieldChanged(path: string, oldValue: any, newValue: any): void {
|
||||
|
@ -75,17 +68,11 @@ export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IW
|
|||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneToggle('maxWidth', {
|
||||
label: strings.MaxWidthFieldLabel
|
||||
PropertyPaneToggle('customWorkbenchStyles', {
|
||||
label: strings.CustomWorkbenchStylesFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle('centerCanvas', {
|
||||
label: strings.CenterCanvasFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle('overflow', {
|
||||
label: strings.OverflowFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle('padding', {
|
||||
label: strings.PaddingFieldLabel
|
||||
PropertyPaneToggle('previewMode', {
|
||||
label: strings.PreviewModeFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ define([], function() {
|
|||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Configuration",
|
||||
"MaxWidthFieldLabel": "Enable custom max width",
|
||||
"CenterCanvasFieldLabel": "Center canvas",
|
||||
"OverflowFieldLabel": "Enable custom overflow",
|
||||
"PaddingFieldLabel": "Enable custom padding"
|
||||
"CustomWorkbenchStylesFieldLabel": "Enable custom styles for Workbench",
|
||||
"PreviewModeFieldLabel": "Enable Preview mode by default",
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
declare interface IWorkbenchCustomizerWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
MaxWidthFieldLabel: string;
|
||||
CenterCanvasFieldLabel: string;
|
||||
OverflowFieldLabel: string;
|
||||
PaddingFieldLabel: string;
|
||||
CustomWorkbenchStylesFieldLabel: string;
|
||||
PreviewModeFieldLabel: string;
|
||||
}
|
||||
|
||||
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
|
||||
- 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
|
||||
```
|
||||
|
||||
![](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
|
||||
```
|
||||
<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" />
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
<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
|
||||
|
||||
```bash
|
||||
|
@ -98,3 +97,5 @@ gulp test
|
|||
gulp serve
|
||||
gulp bundle
|
||||
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
|
||||
--------------------|----|--------|----------
|
||||
jobTitle| Text| no|
|
||||
JobTitle| Text| no|
|
||||
Birthday| DateTime | true|
|
||||
userAADGUID| Text| no | required if used Azure Function to get Birthdays from AAD
|
||||
Title| Text| true
|
||||
email| Text| true
|
||||
|
||||
## After create a column Index on column "Brithday" - Important!
|
||||
|
||||
## Solution
|
||||
|
||||
|
@ -51,7 +54,7 @@ Version|Date|Comments
|
|||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
## Minimal Path to Awesome - please follow all the steps.
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
|
@ -59,6 +62,8 @@ Version|Date|Comments
|
|||
- `gulp build`
|
||||
- `gulp bundle --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.
|
||||
|
||||
|
||||
<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",
|
||||
"includeClientSideAssets": 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": {
|
||||
"zippedPackage": "solution/birdthays.sppkg"
|
||||
|
|
|
@ -37,7 +37,7 @@ export class SPService {
|
|||
}
|
||||
}
|
||||
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')
|
||||
.expand('fields')
|
||||
.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",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-related-items",
|
||||
"container": "react-calendar",
|
||||
"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": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
@ -10,6 +11,9 @@
|
|||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|