added new web part emoji reactions web part

added new web part emoji reactions web part
This commit is contained in:
Siddharth Vaghasia 2021-10-04 01:08:19 +05:30
parent 822c62ec30
commit 2892ccd5a9
34 changed files with 24047 additions and 0 deletions

33
samples/react-emoji-ratings/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
release
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.12.1",
"libraryName": "react-emoji-reaction-rating",
"libraryId": "fe292bb9-a5c2-40b4-b90b-61e8510c3c09",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,91 @@
# react-emoji-ratings
## Summary
This is sample web part which can be used to take emoji based reaction or feedback for particular news/article/posts.
We all know every organizations would be using SharePoint news features for company's internal communications from HR to IT updates and some time annoucements. News created in SharePoint are created as Pages in library. Idea behind this webpart is to take employee/user's feedback as emoji reactions on particular news. This webpart can also be used on wiki articles or blog posts to take similar reactions. Webpart uses concept of 1 to 5 star based rating system(configurable), you can decide low to high based on your preference.
Ideally, we can do some automation using PnP Powershell and PowerAutomate(trigger when item is created ) to add this webpart to any every news that is published.
If you wish to directly use the package in your tenant here is [link](https://github.com/siddharth-vaghasia/public-docs/blob/master/react-emoji-reaction-rating.sppkg) to download.
![WebPart in Action](./assets/EmojiWPinAction.gif)
## Features
* Configurable header text, rating text, and images
* We can choose to show from minimum one rating to max 5 rating images and text
* Option to show total count of particular rating count as badge
* Option to show and capture comments from user when submitting feedback
* Display current user's rating selection with highlighted background
* Allow user to change rating(update rating)
* Stores ratings in SharePoint List
* Option to create list on the fly and choose list to store the ratings
* Option for admin to choose background color
## Technical Notes
* User's rating data would be stored in SP List, below are list of columns and its relevance
1. Title : To store user's display name(not required though)
2. Page Name : To store page on which rating is given by particular user, this will have absolute url of page
3. User : To store user who has given feedback for particular page/news where webpart is added
4. Comment : To store comment provided by user while submitting reaction.
5. Then there are 5 columns Rating1, Rating2, Rating3, Rating4, Rating5 which will store the rating text configured by admin in webpart... Only one of this 5 columns would be populated for single item in list, which is what user has selected as part of giving reaction
* Below is how you can configure emoji's text and its image url
![WebPart in Action](./assets/EmojisConfigurations.png)
I have added default emoji's images which can be used and uploaded to SharePoint library in the assets folder of this solution.
## Used SharePoint Framework Version
![version](https://img.shields.io/badge/version-1.12.1-green.svg)
## Applies to
- [SharePoint Framework](https://aka.ms/spfx)
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
## Prerequisites
* SharePoint Online tenant
* Permission to deploy package to Tenant or SharePoint Site collection app catalog
## Solution
Solution|Author(s)
--------|---------
react-emoji-ratings | Siddharth Vaghasia(@siddh_me)
## Version history
Version|Date|Comments
-------|----|--------
1.0|Sep 04, 2021|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
- Ensure that you are at the solution folder
- in the command-line run:
- **npm install**
- **gulp serve**
## References
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-emoji-ratings" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"react-emoji-reaction-rating-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/reactEmojiReactionRating/ReactEmojiReactionRatingWebPart.js",
"manifest": "./src/webparts/reactEmojiReactionRating/ReactEmojiReactionRatingWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ReactEmojiReactionRatingWebPartStrings": "lib/webparts/reactEmojiReactionRating/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "./release/assets/"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-emoji-reaction-rating",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-emoji-reaction-rating-client-side-solution",
"id": "fe292bb9-a5c2-40b4-b90b-61e8510c3c09",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/react-emoji-reaction-rating.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

16
samples/react-emoji-ratings/gulpfile.js vendored Normal file
View File

@ -0,0 +1,16 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
build.initialize(require('gulp'));

22662
samples/react-emoji-ratings/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
{
"name": "react-emoji-reaction-rating",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@material-ui/core": "^4.12.3",
"@microsoft/sp-core-library": "1.12.1",
"@microsoft/sp-lodash-subset": "1.12.1",
"@microsoft/sp-office-ui-fabric-core": "1.12.1",
"@microsoft/sp-property-pane": "1.12.1",
"@microsoft/sp-webpart-base": "1.12.1",
"@pnp/sp": "^2.8.0",
"@pnp/spfx-controls-react": "3.3.0",
"@pnp/spfx-property-controls": "3.2.0",
"office-ui-fabric-react": "7.156.0",
"react": "16.9.0",
"react-dom": "16.9.0"
},
"devDependencies": {
"@types/react": "16.9.36",
"@types/react-dom": "16.9.8",
"@microsoft/sp-build-web": "1.12.1",
"@microsoft/sp-tslint-rules": "1.12.1",
"@microsoft/sp-module-interfaces": "1.12.1",
"@microsoft/sp-webpart-workbench": "1.12.1",
"@microsoft/rush-stack-compiler-3.7": "0.2.3",
"gulp": "~4.0.2",
"ajv": "~5.2.2",
"@types/webpack-env": "1.13.1"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,38 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "0dbbad17-3a6b-44ed-8ebb-989a8dd906b5",
"alias": "ReactEmojiReactionRatingWebPart",
"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"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "react-emoji-reaction-rating" },
"description": { "default": "react-emoji-reaction-rating description" },
"officeFabricIconFontName": "Page",
"properties": {
"propertyRatingText": "React emoji reaction rating web part!. How does you like this news/article? ",
"propertyEmojisCollection":[
{"Title":"Wow!","ImagaeUrl":"https://mypersorg.sharepoint.com/sites/PnPSearchDemo/EmojiLibrary//RatingImg1.png"},
{"Title":"Mmm","ImagaeUrl":"https://mypersorg.sharepoint.com/sites/PnPSearchDemo/EmojiLibrary//RatingImg2.png"},
{"Title":"Hmm","ImagaeUrl":"https://mypersorg.sharepoint.com/sites/PnPSearchDemo/EmojiLibrary//RatingImg3.png"},
{"Title":"Meh","ImagaeUrl":"https://mypersorg.sharepoint.com/sites/PnPSearchDemo/EmojiLibrary//RatingImg4.png"},
{"Title":"Pff","ImagaeUrl":"https://mypersorg.sharepoint.com/sites/PnPSearchDemo/EmojiLibrary//RatingImg5.png"}
],
/* "propertyListName":"EmojiReactionRating", */
"propertyEnableComments":"True",
"propertyEnableCount":"True"
}
}]
}

View File

@ -0,0 +1,189 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneButton,
PropertyPaneButtonType,
PropertyPaneDropdown,
PropertyPaneTextField,
PropertyPaneToggle
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { sp } from "@pnp/sp";
import * as strings from 'ReactEmojiReactionRatingWebPartStrings';
import ReactEmojiReactionRating from './components/ReactEmojiReactionRating';
import { IReactEmojiReactionRatingProps } from './components/IReactEmojiReactionRatingProps';
import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData';
import { PropertyFieldColorPicker, PropertyFieldColorPickerStyle } from '@pnp/spfx-property-controls/lib/PropertyFieldColorPicker';
import spService from './components/services/spService';
import { result } from 'lodash';
export interface IReactEmojiReactionRatingWebPartProps {
propertyRatingText: string;
propertyEmojisCollection: any[];
propertyEnableComments: boolean;
propertyEnableCount: boolean;
propertySelectedColor: string;
propertyListName: string;
propertyListOperationMessage: string;
}
export default class ReactEmojiReactionRatingWebPart extends BaseClientSideWebPart<IReactEmojiReactionRatingWebPartProps> {
private _spService: spService = null;
public render(): void {
const element: React.ReactElement<IReactEmojiReactionRatingProps> = React.createElement(
ReactEmojiReactionRating,
{
ratingText: this.properties.propertyRatingText,
emojisCollection: this.properties.propertyEmojisCollection,
context: this.context,
enableComments: this.properties.propertyEnableComments,
enableCount: this.properties.propertyEnableCount,
selectedColor: this.properties.propertySelectedColor,
listName: this.properties.propertyListName,
displayMode: this.displayMode,
listMessage: this.properties.propertyListOperationMessage,
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
private validateListName(value: string): string {
if (value === null ||
value.trim().length === 0) {
return 'Provide a list name';
}
else {
console.log("List Name: ", value);
return '';
}
}
public onInit(): Promise<void> {
this._spService = new spService(this.context);
return Promise.resolve();
}
private btnListSchemaCreation(val: any): any {
const colListColumns = ['Pagename', 'User', 'Rating1', 'Rating2', 'Rating3', 'Rating4', 'Rating5', 'Comments'];
console.log("colListColumns: ", colListColumns);
let listName = this.properties.propertyListName;
console.log("listName: ", listName);
this._spService._createListwithColumns(listName, colListColumns).then((result) => {
console.log(result);
//this.properties.propertyListOperationMessage = result;
//this.context.propertyPane.refresh();
alert(result);
}).catch(error => {
console.log("Something went wrong! please contact admin for more information.", error);
// this.properties.propertyListOperationMessage = "Something went wrong! please contact admin for more information."
// this.context.propertyPane.refresh();
let errMessage = (error.mesaage || error.Mesaage);
alert("Something went wrong! please contact admin for more information. "+ errMessage);
});
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('propertyListName', {
label: strings.ListNameFieldLabel,
description: "If you want to create new list, please enter list name and click on create list button",
validateOnFocusOut: true,
onGetErrorMessage: this.validateListName.bind(this),
}),
PropertyPaneButton('propertyListSchemaButton',
{
text: strings.ListSchemaFieldLablel,
buttonType: PropertyPaneButtonType.Primary,
onClick: this.btnListSchemaCreation.bind(this)
}),
PropertyPaneTextField('propertyRatingText', {
label: strings.RatingTextFieldLabel
}),
PropertyFieldCollectionData("propertyEmojisCollection", {
key: "emojisCollection",
label: strings.EmojisCollectionFieldLabel,//"Emojis Collection",
panelHeader: "Emojis collection panel header",
manageBtnLabel: "Manage emojis collection",
value: this.properties.propertyEmojisCollection,
fields: [
{
id: "Title",
title: "Title",
type: CustomCollectionFieldType.string,
required: true
},
{
id: "ImagaeUrl",
title: "ImagaeUrl",
type: CustomCollectionFieldType.url,
required: true
}
],
disabled: false,
disableItemCreation: true,
disableItemDeletion: true,
}),
PropertyPaneToggle('propertyEnableComments', {
label: strings.EnableCommentsFieldLablel
}),
PropertyPaneToggle('propertyEnableCount', {
label: strings.EnableCountFieldLablel
}),
PropertyFieldColorPicker('propertySelectedColor', {
label: 'Select background color',
selectedColor: this.properties.propertySelectedColor,
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
disabled: false,
debounce: 1000,
isHidden: false,
alphaSliderHidden: false,
style: PropertyFieldColorPickerStyle.Full,
iconName: 'Precipitation',
key: 'colorFieldId'
}),
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,13 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface IReactEmojiReactionRatingProps {
ratingText: string;
emojisCollection: any[];
context: WebPartContext;
enableComments: boolean;
enableCount: boolean;
selectedColor: string;
listName: string;
displayMode: any;
listMessage: string;
}

View File

@ -0,0 +1,15 @@
export interface IReactEmojiReactionRatingState {
selectedRatingIndex: Number | null;
selectedRatingValue: string | null;
0: Number | null;
1: Number | null;
2: Number | null;
3: Number | null;
4: Number | null;
RatingComments: string;
CustomMessage: string;
configLoaded: boolean;
isDialogHidden: boolean;
dialogTitle: string;
dialogBody: string;
}

View File

@ -0,0 +1,135 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.reactEmojiReactionRating {
.container {
max-width: 700px;
margin: 0px auto;
/* box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); */
}
.row {
@include ms-Grid-row;
@include ms-fontColor-gray200;
//background-color: $ms-color-themeDark;
/* background-color: #F5EFDF !important; */
padding: 20px;
}
.column10 {
@include ms-Grid-col;
@include ms-lg10;
}
/* @include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1; */
.column2 {
@include ms-Grid-col;
@include ms-lg2;
}
.title {
/* @include ms-font-xl;
@include ms-fontColor-gray200; */
}
.selectedEmoji {
background-color: firebrick !important;
border-radius: 14px !important;
cursor: pointer !important;
}
.subTitle {
/* @include ms-font-l;
@include ms-fontColor-gray200; */
}
.description {
/* @include ms-font-l;
@include ms-fontColor-gray200; */
font-weight: 300;
/* font-size: large; */
}
.lblCountClass {
background-color: #ddd;
border: none;
color: black;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px 2px;
border-radius: 16px;
}
.btnCountClass {
background-color: #ddd;
border: none;
color: black;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px 2px;
border-radius: 16px;
cursor: default !important;
}
.txtArea {
color: grey !important;
}
.txtArea label {
color: grey !important;
}
.stackImage {
cursor: 'pointer' !important;
border-radius: 20px !important;
}
.labelClass {
text-align: center !important;
color: grey !important;
}
.button1 {
// 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;
}
}
}

View File

@ -0,0 +1,478 @@
import * as React from 'react';
import styles from './ReactEmojiReactionRating.module.scss';
import { IReactEmojiReactionRatingProps } from './IReactEmojiReactionRatingProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { IReactEmojiReactionRatingState } from './IReactEmojiReactionRatingState';
import { DisplayMode, Environment, EnvironmentType } from '@microsoft/sp-core-library';
import spService from './services/spService';
import { getItemStyles } from 'office-ui-fabric-react/lib/components/ContextualMenu/ContextualMenu.classNames';
import {
FocusZone, IRectangle, List, mergeStyleSets, getTheme,
ITheme, getFocusStyle, ImageFit, TextField, Stack, Label, Rectangle, Button, DefaultButton, PrimaryButton
} from '@microsoft/office-ui-fabric-react-bundle';
import { IRatingNewItem } from './models/IRatingNewItem';
import Badge from '@material-ui/core/Badge/Badge';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { Dialog, DialogFooter, DialogType } from 'office-ui-fabric-react';
export default class ReactEmojiReactionRating extends React.Component<IReactEmojiReactionRatingProps, IReactEmojiReactionRatingState> {
private _spService: spService = null;
private _message = null;
private _currentContext = null;
private listName = this.props.listName ? this.props.listName : "EmojiReactionRating";
private existingRating: any;
constructor(prop: IReactEmojiReactionRatingProps, state: IReactEmojiReactionRatingState) {
super(prop);
this.state = {
selectedRatingIndex: null,
selectedRatingValue: "",
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
RatingComments: "",
CustomMessage: "",
configLoaded: false,
isDialogHidden: true,
dialogTitle: "",
dialogBody: "",
};
this._currentContext = this.props.context;
this._spService = new spService(this.props.context);
if (Environment.type === EnvironmentType.SharePoint) {
// this.getItems();
// console.log("items: ", items);
}
else if (Environment.type === EnvironmentType.Local) {
this._message = <div>Whoops! you are using local host...</div>;
}
this.selectedRating = this.selectedRating.bind(this);
this.submitRating = this.submitRating.bind(this);
this.handleChange = this.handleChange.bind(this);
this._onConfigure = this._onConfigure.bind(this);
this.closeDialog = this.closeDialog.bind(this);
}
componentDidMount() {
if (this.props.enableCount) {
this.getItems();
}
if (this.props.listName && (this.props.emojisCollection.length > 0)) {
this.setState({ configLoaded: true });
}
}
componentDidUpdate(previousProps, previousState) {
if (previousProps.listName !== this.props.listName) {
if (this.props.listName && (this.props.emojisCollection.length > 0)) {
this.setState({ configLoaded: true });
//this.getItems();
}
}
if (previousProps.listMessage !== this.props.listMessage) {
this.ShowDialogMessage("List Creation", this.props.listMessage);
}
}
submitRating(event) {
let ratingCommnets = this.state.RatingComments ? (this.state.RatingComments).trim() : "";
//let selectedRatingIndex = parseInt(event.target.id);
//let selectedRatingIndex = event.target.tabIndex;
// let selectedRatingValue = event.target.title;
let selectedRatingIndex = this.state.selectedRatingIndex;
let selectedRatingValue = this.state.selectedRatingValue;
let ratingField;
switch (selectedRatingIndex) {
case 0:
ratingField = "Rating1";
break;
case 1:
ratingField = "Rating2";
break;
case 2:
ratingField = "Rating3";
break;
case 3:
ratingField = "Rating4";
break;
case 4:
ratingField = "Rating5";
break;
}
if (!(ratingField)) {
console.log("Something went wrong! Please check with Admin.");
return false;
}
//let pageName = window.location.pathname.substring(window.location.pathname.lastIndexOf("/") + 1);
/* let pageName = window.location.href;
let body: IRatingNewItem = {
Title: this._currentContext.pageContext.user.displayName,
Pagename: pageName ? pageName : window.location.href,
User: this._currentContext?.pageContext.user.loginName,
Comment: this.props.enableComments ? ratingCommnets : "",
}
//adding the right rating column and value
body[ratingColumn] = selectedRatingValue;
console.log("body object is: ", body); */
let pageName = window.location.href;
let body: any = {
Title: this._currentContext.pageContext.user.displayName,
Pagename: pageName ? pageName : window.location.href,
User: this._currentContext?.pageContext.user.loginName,
Comments: this.props.enableComments ? ratingCommnets : "",
Rating1: "",
Rating2: "",
Rating3: "",
Rating4: "",
Rating5: "",
}
body[ratingField] = selectedRatingValue;
console.log("body object is: ", body);
if (!this.existingRating) {
this._spService._addRatingItem(this.listName, body)
.then(value => {
// alert("Rating submitted successfully, thank you!");
//this.setState({ CustomMessage: "Rating submitted successfully, thank you!" });
console.log("Rating submitted successfully, thank you!", value.data.id);
this.ShowDialogMessage("Rating submitted", "Your rating submitted successfully, thank you!");
})
.catch(error => {
console.log("Something went wrong! please contact admin for more information.");
this.ShowDialogError(error, "Something went wrong! please contact admin for more information.")
});
}
else {
this._spService._updateRatingItem(this.listName, body, this.existingRating.Id)
.then(value => {
console.log("Rating updated successfully, thank you!", value.data.id);
this.ShowDialogMessage("Rating updated", "Your rating updated successfully, thank you!");
})
.catch(error => {
console.log("Something went wrong! please contact admin for more information.");
this.ShowDialogError(error, "Something went wrong! please contact admin for more information.")
});
}
}
async getItems() {
console.log("getItems: ", this._currentContext);
let ratingItems = await this._spService.getRatingListItems(this.listName);
console.log("ratingItems: ", ratingItems);
console.log("this.props.emojisCollection: ", this.props.emojisCollection.length);
let column1ValIndex, column2ValIndex, column3ValIndex, column4ValIndex, column5ValIndex;
for (let i = this.props.emojisCollection.length; i > 0; i--) {
switch (i) {
case 5:
column1ValIndex = this.props.emojisCollection.length - i;
break;
case 4:
column2ValIndex = this.props.emojisCollection.length - i;
break;
case 3:
column3ValIndex = this.props.emojisCollection.length - i;
break;
case 2:
column4ValIndex = this.props.emojisCollection.length - i;
break;
case 1:
column5ValIndex = this.props.emojisCollection.length - i;
break;
}
}
let pageRatings = await ratingItems.filter(function (element) {
return (element["Pagename"] == window.location.href
)
});
Promise.all([
this.getRatingCount(pageRatings, 'Rating1', this.props.emojisCollection[column1ValIndex].Title),
this.getRatingCount(pageRatings, 'Rating2', this.props.emojisCollection[column2ValIndex].Title),
this.getRatingCount(pageRatings, 'Rating3', this.props.emojisCollection[column3ValIndex].Title),
this.getRatingCount(pageRatings, 'Rating4', this.props.emojisCollection[column4ValIndex].Title),
this.getRatingCount(pageRatings, 'Rating5', this.props.emojisCollection[column5ValIndex].Title),
])
.then(results => {
console.log("countRating1: ", results[0]);
console.log("countRating2: ", results[1]);
console.log("countRating3: ", results[2]);
console.log("countRating4: ", results[3]);
console.log("countRating5: ", results[4]);
this.setState(
{
0: results[0],
1: results[1],
2: results[2],
3: results[3],
4: results[4]
}
);
})
.catch(error => {
console.log("Error in getting the rating count!");
});
let userLogin = this._currentContext.pageContext.user.loginName;
let userSelectedRating = await ratingItems.filter(function (element) {
return (element["Pagename"] == window.location.href
&& (element["User"] == userLogin))
});
console.log("userSelectedRating: ", userSelectedRating[0]);
this.existingRating = userSelectedRating[0];
let currentUserRatingVal = "";
let currentUserRatingColumn = "";
//this.props.emojisCollection
if (userSelectedRating[0]["Rating1"]) {
currentUserRatingVal = userSelectedRating[0]["Rating1"];
currentUserRatingColumn = "Rating1";
}
else if (userSelectedRating[0]["Rating2"]) {
currentUserRatingVal = userSelectedRating[0]["Rating2"];
currentUserRatingColumn = "Rating2";
}
else if (userSelectedRating[0]["Rating3"]) {
currentUserRatingVal = userSelectedRating[0]["Rating3"];
currentUserRatingColumn = "Rating3";
}
else if (userSelectedRating[0]["Rating4"]) {
currentUserRatingVal = userSelectedRating[0]["Rating4"];
currentUserRatingColumn = "Rating4";
}
else if (userSelectedRating[0]["Rating5"]) {
currentUserRatingVal = userSelectedRating[0]["Rating5"];
currentUserRatingColumn = "Rating5";
}
let userSelectedRatingIndex;
await this.props.emojisCollection.filter(function (element, tabIndex) {
if ((element["Title"] == currentUserRatingVal)) {
userSelectedRatingIndex = tabIndex;
return tabIndex;
}
});
console.log("userSelectedRatingIndex: ", userSelectedRatingIndex);
this.setState({
selectedRatingIndex: userSelectedRatingIndex,
selectedRatingValue: currentUserRatingVal
});
}
async getRatingCount(items: any[], colName: string, colValue: string) {
let ratingCount = await items.filter(function (element) {
return element[colName] == colValue;
}).length
return ratingCount;
}
selectedRating(event) {
let selectedRatingIndex = event.target.tabIndex;
let selectedRatingValue = event.target.title;
this.setState({
selectedRatingIndex: selectedRatingIndex,
selectedRatingValue: selectedRatingValue
})
}
private handleChange(event: any, newValue: string) {
let partialState = {};
partialState[event.target.name] = newValue || "";
this.setState(partialState);
}
private _onConfigure = () => {
this.props.context.propertyPane.open();
}
private closeDialog(e: any) {
this.setState({ isDialogHidden: true });
window.location.reload();
}
private ShowDialogMessage(title: string, body: string) {
this.setState({
//isRedirectDialogHidden: false,
isDialogHidden: false,
dialogTitle: title,
dialogBody: body,
});
}
private ShowDialogError(
error: any,
customErrorMessage: string
) {
if (!customErrorMessage) {
customErrorMessage = "";
}
this.setState({
isDialogHidden: false,
dialogTitle: "Error: Request failed.",
dialogBody: customErrorMessage.concat(
" Error message: ",
(error.Message || error.message)
),
});
}
public render(): React.ReactElement<IReactEmojiReactionRatingProps> {
/* if (this.props.listMessage) {
this.ShowDialogMessage("List Creation", this.props.listMessage);
} */
return !(this.state.configLoaded) ? (
<Placeholder iconName='Edit'
iconText='Configure your web part'
description='Please configure the web part.'
buttonLabel='Configure'
hideButton={this.props.displayMode === DisplayMode.Read}
onConfigure={this._onConfigure} />
) : (this.props.listMessage ? (
<div>{this.props.listMessage}</div>
) :
(
<div className={styles.reactEmojiReactionRating} style={{
backgroundColor: `${this.props.selectedColor
}`
}}>
<div className={styles.container}>
<div className={styles.row}>
<Stack>
<div className={styles.description}>{this.props.ratingText ? this.props.ratingText : ''}</div>
</Stack>
</div>
<div className={styles.row}>
<Stack horizontal verticalAlign="center" horizontalAlign="space-between">
{this.props.emojisCollection ? this.props.emojisCollection.map((ratingItem, tabIndex) => (
<Stack.Item>
{this.props.enableCount ? (
<>
<Badge color="secondary" overlap="circular" badgeContent={this.state[tabIndex]}>
<img src={ratingItem.ImagaeUrl}
className={this.state.selectedRatingIndex == tabIndex ? styles.selectedEmoji : styles.stackImage}
title={ratingItem.Title}
tabIndex={tabIndex}
id={tabIndex.toString()}
alt={ratingItem.Title}
onClick={this.selectedRating}
/>
</Badge>
<Label className={styles.labelClass}>{ratingItem.Title}</Label>
</>) : (
<>
<img src={ratingItem.ImagaeUrl}
className={this.state.selectedRatingIndex == tabIndex ? styles.selectedEmoji : styles.stackImage}
title={ratingItem.Title}
tabIndex={tabIndex}
id={tabIndex.toString()}
alt={ratingItem.Title}
onClick={this.selectedRating} />
<Label className={styles.labelClass}>{ratingItem.Title}</Label>
</>
)
}
</Stack.Item>
)) :
<Label>Rating list is empty...</Label>}
</Stack>
</div>
{this.props.enableComments ? (
<div className={styles.row}>
<TextField
label={"Enter comments"}
value={this.state.RatingComments}
onChange={this.handleChange}
name="RatingComments"
rows={6}
multiline={true}
width={80}
className={styles.txtArea}
/>
{this.state.RatingComments}
</div>
) : ""
}
{this.state.CustomMessage ? (
<div className={styles.row}>
{this.state.CustomMessage}
</div>
) : ""
}
{this._message}
<div className={styles.row}>
<div className={styles.column10}>
</div>
<div className={styles.column2}>
<PrimaryButton text="Submit" onClick={this.submitRating} disabled={this.state.selectedRatingValue ? false : true} />
</div>
</div>
<Dialog
hidden={this.state.isDialogHidden}
onDismiss={this.closeDialog}
dialogContentProps={{
type: DialogType.normal,
title: this.state.dialogTitle,
subText: this.state.dialogBody
}}
modalProps={{
isBlocking: true,
styles: { main: { maxWidth: 450 } }
}}
>
<DialogFooter>
<PrimaryButton onClick={this.closeDialog} text="OK" />
</DialogFooter>
</Dialog>
</div >
</div >
)
);
}
}

View File

@ -0,0 +1,13 @@
export interface IRatingNewItem {
Id?: number;
Title: string;
Pagename: string;
User: string;
Comment: string;
Rating1?: string;
Rating2?: string;
Rating3?: string;
Rating4?: string;
Rating5?: string;
}

View File

@ -0,0 +1,153 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { sp } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/fields";
import "@pnp/sp/views";
import { IItemAddResult } from "@pnp/sp/items";
import { IRatingNewItem } from "../models/IRatingNewItem";
// Class Services
export default class spService {
constructor(private context: WebPartContext) {
// Setup Context to PnPjs
sp.setup({
spfxContext: this.context
});
// Init
this.onInit();
}
// OnInit Function
private async onInit() {
}
public async _getListItems(listName: string) {
const allItems: any[] = await sp.web.lists.getByTitle(listName).items.getAll();
console.log(allItems);
return allItems;
}
public getRatingListItems(listName: string): Promise<any[]> {
let promise: Promise<any[]> = new Promise<any[]>(
(resolve, reject) => {
sp.web.lists
.getByTitle(listName)
.items
.getAll(4000)
.then((data: any[]) => {
resolve(data);
})
.catch((error: any) => {
reject(error);
});
}
);
return promise;
}
public async _addRatingItemPnP(listName: string, RatingRequest: IRatingNewItem) {
await sp.web.lists.getByTitle(listName).items.add(RatingRequest);
}
public _addRatingItem(listName: string, RatingRequest: IRatingNewItem): Promise<IItemAddResult> {
let promise: Promise<IItemAddResult> = new Promise<IItemAddResult>(
(resolve, reject) => {
sp.web.lists
.getByTitle(listName)
.items.add(
RatingRequest
)
.then((iar: IItemAddResult) => {
resolve(iar);
})
.catch(error => {
reject(error);
});
}
);
return promise;
}
public _updateRatingItem(listName: string, RatingRequest: IRatingNewItem, ExistingID: any): Promise<IItemAddResult> {
let promise: Promise<IItemAddResult> = new Promise<IItemAddResult>(
(resolve, reject) => {
sp.web.lists
.getByTitle(listName)
.items.getById(ExistingID).update(
RatingRequest
)
.then((iar: IItemAddResult) => {
resolve(iar);
})
.catch(error => {
reject(error);
});
}
);
return promise;
}
public async _createListwithColumns(listName: string, colListColumns: any[]) {
let listExist = await this._checkList(listName);
console.log("List exist: ", listExist);
if (!listExist) {
const listAddResult = await sp.web.lists.add(listName);
const list = await listAddResult.list.get();
const newList = await sp.web.lists.getByTitle(listName);
const view = await newList.defaultView;
//checking columns are added to the collection or not
if (colListColumns.length > 0) {
const batch = sp.web.createBatch();
colListColumns.forEach(fieldName => {
if (fieldName == "Comments") {
newList.fields.inBatch(batch).addMultilineText(fieldName, 6);
}
else {
newList.fields.inBatch(batch).addText(fieldName, 255);
}
});
colListColumns.forEach(fieldName => {
view.fields.inBatch(batch).add(fieldName);
});
batch.execute().then(_result => {
console.log('List with columns created.');
}).catch(error => {
console.log(error);
});
return "List with required columns created."
}
}
else {
return "List alreay exist"
}
}
public async _checkList(listName: string) {
let filterList = `Title eq '${listName}'`
let boolResult: boolean = false;
let getList = await sp.web.lists.filter(filterList).get()
if (getList.length > 0) {
return boolResult = true;
}
else {
return boolResult;
}
}
}

View File

@ -0,0 +1,14 @@
define([], function() {
return {
"PropertyPaneDescription": "This is a webpart to collect page ratings!",
"BasicGroupName": "Rating WebPart Configuration",
"RatingTextFieldLabel": "Rating text",
"EmojisCollectionFieldLabel":"Emojis collection",
"EnableCommentsFieldLablel":"Enable comments",
"EnableCountFieldLablel":"Enable count",
"ListSchemaFieldLablel":"Create List",
"ListColumnsFieldLabel":"List Columns",
"ListNameFieldLabel":"List Name",
}
});

View File

@ -0,0 +1,17 @@
declare interface IReactEmojiReactionRatingWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
RatingTextFieldLabel: string;
EmojisCollectionFieldLabel:string;
EnableCommentsFieldLablel:string;
EnableCountFieldLablel:string;
ListSchemaFieldLablel:string;
ListColumnsFieldLabel:string;
ListNameFieldLabel:string;
ListSchemaFieldLabel:string;
}
declare module 'ReactEmojiReactionRatingWebPartStrings' {
const strings: IReactEmojiReactionRatingWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,35 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
"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-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,
"variable-name": false,
"whitespace": false
}
}