New Sample - react-multimedia-gallery - SPFx (#899)

* update docs

* Update README.md

* Update README.md

* Support to IE11

* fine tunning, added Fade on Images

* use CSS transitions

* clean extra comments
This commit is contained in:
joaojmendes 2019-07-05 13:57:15 +01:00 committed by Vesa Juvonen
parent a5a9e7e5af
commit 3e82d22c5f
45 changed files with 21594 additions and 1 deletions

View File

@ -1,6 +1,6 @@
{
"name": "react-calendar",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
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,13 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.8.2",
"libraryName": "react-image-gallery",
"libraryId": "cff1f832-50fb-4a72-bb4e-1393121dc4ad",
"environment": "spo",
"packageManager": "npm",
"framework": "react",
"isCreatingSolution": true,
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,86 @@
# React Image Gallery
## Summary
This web part show images and videos in responsive grid, on click it show images and videos in a carousel view.
It uses Microsoft Graph API to get thumbnails and image/video url and use PnPjs to load files from library the images/videos are loading in lazy mode, progressively.
##
![callendar](/samples/react-multimedia-gallery/assets/MultimediaGallery.gif)
## Web Part - Screenshots
![gallery](/samples/react-multimedia-gallery/assets/Annotation2.jpg)
![gallery](/samples/react-multimedia-gallery/assets/Annotation0.jpg)
![gallery](/samples/react-multimedia-gallery/assets/Annotation1.jpg)
![gallery](/samples/react-multimedia-gallery/assets/Screenshot1.png)
![gallery](/samples/react-multimedia-gallery/assets/Screenshot2.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.8.2-green.svg)
## Applies to
* [SharePoint Online](https:/dev.office.com/sharepoint)
* [Microsoft Teams](https://products.office.com/en-US/microsoft-teams/group-chat-software)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## WebPart Properties
Property |Type|Required| comments
--------------------|----|--------|----------
Site Url of library | Text| yes|
Library| Choice/Dropdown | yes| this is filled with all libraries
number images to load | number| yes | number between 1 and 200
## Solution
The web part Use PnPjs library, Microsoft Graph API, Office-ui-fabric-react components, react-slick Compoment
Solution|Author(s)
--------|---------
Multimedia Gallery Web Part|João Mendes
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|June 24, 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/react-multimedia-gallery" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,78 @@
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: Npm@1
displayName: 'npm test'
inputs:
command: custom
customCommand: 'test'
workingDir: '$(Build.SourcesDirectory)'
verbose: false
# Publish Test Results to Azure Pipelines/TFS
- task: PublishTestResults@2
inputs:
testResultsFiles: 'temp/test/junit/junit.xml'
searchFolder: '$(Build.SourcesDirectory)'
# publish coverage test results
- task: PublishCodeCoverageResults@1
displayName: 'Publish Code Coverage Results $(Build.SourcesDirectory)/temp/test/cobertura-coverage.xml'
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(Build.SourcesDirectory)/temp/test/cobertura-coverage.xml'
reportDirectory: '$(Build.SourcesDirectory)/temp/test/'
#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'

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"image-gallery-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/imageGallery/ImageGalleryWebPart.js",
"manifest": "./src/webparts/imageGallery/ImageGalleryWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ImageGalleryWebPartStrings": "lib/webparts/imageGallery/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"
}
}

View File

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

View File

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

View File

@ -0,0 +1 @@
{"preset":"@voitanos/jest-preset-spfx-react16","rootDir":"../src","coverageReporters":["text","json","lcov","text-summary","cobertura"],"reporters":["default","jest-junit"]}

View File

@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-image-gallery-client-side-solution",
"id": "cff1f832-50fb-4a72-bb4e-1393121dc4ad",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-image-gallery.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 -->"
}

View File

@ -0,0 +1,29 @@
'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'));
/**
* Custom Framework Specific gulp tasks
*/
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
{
"name": "react-image-gallery",
"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/sp-core-library": "1.8.2",
"@microsoft/sp-lodash-subset": "1.8.2",
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
"@microsoft/sp-property-pane": "1.8.2",
"@microsoft/sp-webpart-base": "1.8.2",
"@pnp/pnpjs": "^1.3.2",
"@pnp/polyfill-ie11": "^1.0.1",
"@pnp/spfx-controls-react": "1.13.1",
"@pnp/spfx-property-controls": "1.14.1",
"@types/es6-promise": "0.0.33",
"@types/jquery": "^3.3.29",
"@types/react": "16.7.22",
"@types/react-dom": "16.8.0",
"@types/webpack-env": "1.13.1",
"@uifabric/fluent-theme": "^0.16.9",
"jquery": "^3.4.1",
"lightbox-react": "^0.3.7",
"office-ui-fabric-react": "^6.182.0",
"react": "16.7.0",
"react-dom": "16.7.0",
"react-grid-gallery": "^0.5.4",
"react-responsive-carousel": "^3.1.49",
"react-slick": "^0.24.0",
"slick-carousel": "^1.8.1",
"video-react": "^0.13.9"
},
"resolutions": {
"@types/react": "16.7.22"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.3": "~0.2.x",
"@microsoft/sp-build-web": "1.8.2",
"@microsoft/sp-module-interfaces": "1.8.2",
"@microsoft/sp-tslint-rules": "1.8.2",
"@microsoft/sp-webpart-workbench": "1.8.2",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"@types/react": "^16.7.22",
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"gulp-sequence": "1.0.0",
"jest": "^23.6.0",
"jest-junit": "^6.3.0",
"typescript": "~3.3.x"
},
"jest-junit": {
"output": "temp/test/junit/junit.xml",
"usePathForSuiteName": "true"
}
}

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,4 @@
export interface IList {
ID: string;
Title: string;
}

View File

@ -0,0 +1,83 @@
// João Mendes
// March 2019
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { sp, Fields, Web, SearchResults, Field, PermissionKind, RegionalSettings, PagedItemCollection } from '@pnp/sp';
import { graph, } from "@pnp/graph";
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions, HttpClient, MSGraphClient } from '@microsoft/sp-http';
import * as $ from 'jquery';
import { registerDefaultFontFaces } from "@uifabric/styling";
import * as moment from 'moment';
import { SiteUser } from "@pnp/sp/src/siteusers";
import { dateAdd } from "@pnp/common";
import { escape, update } from '@microsoft/sp-lodash-subset';
// 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() {
}
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 101 or BaseTemplate eq 109')
.usingCaching()
.get();
} catch (error) {
return Promise.reject(error);
}
return results;
}
public async getImages(siteUrl: string, listId: string, numberImages: number): Promise<any[]> {
let results: any[] = [];
try {
const web = new Web(siteUrl);
results = await web.lists
.getById(listId).items
.select('Title','File_x0020_Type', 'FileSystemObjectType','File/Name', 'File/ServerRelativeUrl', 'File/Title', 'File/Id', 'File/TimeLastModified')
.top(numberImages)
.expand('File')
.filter((`File_x0020_Type eq 'jpg' or File_x0020_Type eq 'png' or File_x0020_Type eq 'jpeg' or File_x0020_Type eq 'gif' or File_x0020_Type eq 'mp4'`))
.orderBy('Modified', false)
.usingCaching()
.get();
} catch (error) {
return Promise.reject(error);
}
return results;
}
public async getImagesNextPage(results: PagedItemCollection<any[]>): Promise<PagedItemCollection<any[]>> {
return results.getNext();
}
}

View File

@ -0,0 +1,37 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "b698e561-a0d5-4e5f-b1c2-c57c4f43753d",
"alias": "ImageGalleryWebPart",
"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", "SharePointFullPage"
],
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "SPFx Web Parts"
},
"title": {
"default": "Image Gallery"
},
"description": {
"default": "Image Gallery"
},
"officeFabricIconFontName": "PhotoCollection",
"properties": {
"title": "Image Gallery",
"siteUrl": "",
"list": "",
"numberImages": 20
}
}
]
}

View File

@ -0,0 +1,210 @@
import '@pnp/polyfill-ie11';
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown,
IPropertyPaneDropdownOption,
PropertyPaneLabel
} from '@microsoft/sp-property-pane';
import * as strings from 'ImageGalleryWebPartStrings';
import ImageGallery from './components/ImageGallery/ImageGallery';
import { ImageGalleryProps } from './components/ImageGallery/ImageGalleryProps';
import spservices from '../../services/spservices';
import { PropertyFieldNumber } from '@pnp/spfx-property-controls/lib/PropertyFieldNumber';
export interface IImageGalleryWebPartProps {
title: string;
siteUrl: string;
list: string;
errorMessage: string;
numberImages: number;
}
export default class ImageGalleryWebPart extends BaseClientSideWebPart<IImageGalleryWebPartProps> {
private lists: IPropertyPaneDropdownOption[] = [];
private listsDropdownDisabled: boolean = true;
private spService: spservices = null;
private errorMessage: string;
// onInit
public async onInit(): Promise<void> {
this.spService = new spservices(this.context);
this.properties.siteUrl = this.properties.siteUrl ? this.properties.siteUrl : this.context.pageContext.site.absoluteUrl;
if (this.properties.siteUrl && !this.properties.list) {
const _lists = await this.loadLists();
if ( _lists.length > 0 ){
this.lists = _lists;
this.properties.list = this.lists[0].key.toString();
}
}
return Promise.resolve();
}
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 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 });
}
// push new item value
} catch (error) {
this.errorMessage = `${ escape(error.message.toString())} - please check if site url if valid.` ;
this.context.propertyPane.refresh();
}
return _lists;
}
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 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} - please check if site url if valid.` ;
this.context.propertyPane.refresh();
}
}
public render(): void {
const element: React.ReactElement<ImageGalleryProps > = React.createElement(
ImageGallery,
{
title: this.properties.title,
siteUrl: this.properties.siteUrl,
list: this.properties.list,
numberImages: this.properties.numberImages,
context: this.context,
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
},
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(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('title', {
label: strings.TitleLabel,
value: this.properties.title,
}),
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('errorMessage', {
text: this.errorMessage,
}),
PropertyFieldNumber("numberImages", {
key: "numberValue",
label: "Number of images to load",
description: "Number between 01 and 200",
value: this.properties.numberImages,
maxValue: 200,
minValue: 1,
disabled: false
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,10 @@
export interface IGalleryImages {
imageUrl: string;
ServerRelativeUrl: string;
thumbnail:string;
thumbnailWidth: number;
thumbnailHeight: number;
caption: string;
mediaType: string;
customOverlay: any;
}

View File

@ -0,0 +1,111 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.carousel {
background-color: white;
}
.slideLoading {
display:none !important;
}
.overlay {
position: absolute;
bottom: 0;
background: rgb(0, 0, 0);
background: rgba(0, 0, 0, 0.5); /* Black see-through */
color: #f1f1f1;
width: 100%;
transition: .5s ease;
opacity:0;
color: white;
font-size: 20px;
padding: 10px;
text-align: center;
}
.card {
width: 250px;
float: left;
vertical-align: top;
margin:3px;
}
.container {
padding: 10px;
}
.imageGallery {
.webpartTitle{
color: white !important;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
overflow: hidden
}
.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;
margin-bottom: 25px;
}
.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;
}
}
}

View File

@ -0,0 +1,422 @@
import * as React from 'react';
import styles from './ImageGallery.module.scss';
import { ImageGalleryProps } from './ImageGalleryProps';
import { ImageGalleryState } from './ImageGalleryState';
import { escape } from '@microsoft/sp-lodash-subset';
import spservices from '../../../../services/spservices';
import Gallery from 'react-grid-gallery';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import RenderImage from '../RenderImage/RenderImage';
import {
Spinner,
SpinnerSize,
MessageBar,
MessageBarType,
Label,
Icon,
DocumentCard,
DefaultButton,
PrimaryButton,
ImageFit,
Image,
Dialog,
DialogType,
DialogFooter,
ActionButton,
IButtonProps,
IconButton,
CommandBarButton,
ImageLoadState,
Panel, PanelType
} from 'office-ui-fabric-react';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { DisplayMode } from '@microsoft/sp-core-library';
import { FontSizes, } from '@uifabric/fluent-theme/lib/fluent/FluentType';
import { CommunicationColors } from '@uifabric/fluent-theme/lib/fluent/FluentColors';
import * as strings from 'ImageGalleryWebPartStrings';
import * as microsoftTeams from '@microsoft/teams-js';
import "react-responsive-carousel/lib/styles/carousel.min.css";
import { Carousel } from 'react-responsive-carousel';
import 'video-react/dist/video-react.css'; // import css
import { Player, BigPlayButton } from 'video-react';
import './carousel.scss';
import { IGalleryImages } from './IGalleryImages';
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import * as $ from 'jquery';
/**
*
*
* @export
* @class ImageGallery
* @extends {React.Component<ImageGalleryProps, ImageGalleryState>}
*/
export default class ImageGallery extends React.Component<ImageGalleryProps, ImageGalleryState> {
private spService: spservices = null;
private images: any;
private galleryImages: IGalleryImages[] = [];
private _teamsContext: microsoftTeams.Context;
private _teamsTheme: string = '';
private _carouselImages: any;
private _slider: any = null;
constructor(props: ImageGalleryProps) {
super(props);
this.spService = new spservices(this.props.context);
if (this.props.context.microsoftTeams) {
this.props.context.microsoftTeams.getContext(context => {
this._teamsContext = context;
console.log('ctt', this._teamsContext.theme);
this.setState({ teamsTheme: this._teamsContext.theme });
});
}
this.state = {
images: [],
isLoading: false,
errorMessage: '',
hasError: false,
teamsTheme: 'default',
isPlaying: true,
showLithbox: false,
photoIndex: 0,
isloadingCarousel: false,
carouselImages: [],
autoplay: true,
};
this.onPlayResume = this.onPlayResume.bind(this);
}
/**
*
*
* @protected
* @returns {Promise<any>}
* @memberof ImageGallery
*/
/**
*
*
* @private
* @memberof ImageGallery
*/
private async loadPictures() {
this.setState({ isLoading: true, hasError: false });
const tenantUrl = `https://${location.host}`;
try {
this.images = await this.spService.getImages(this.props.siteUrl, this.props.list, this.props.numberImages);
for (const image of this.images) {
const pURL = `${tenantUrl}/_api/v2.0/sharePoint:${image.File.ServerRelativeUrl}:/driveItem/thumbnails/0/large/content?preferNoRedirect=true `;
const thumbnailUrl = `${tenantUrl}/_api/v2.0/sharePoint:${image.File.ServerRelativeUrl}:/driveItem/thumbnails/0/c240x240/content?preferNoRedirect=true `;
let mediaType: string = '';
switch (image.File_x0020_Type) {
case ('jpg' || 'jpeg' || 'png' || 'tiff' || 'gif'):
mediaType = 'image';
break;
case ('mp4'):
mediaType = 'video';
break;
default:
continue;
break;
}
this.galleryImages.push(
{
imageUrl: pURL,
mediaType: mediaType,
ServerRelativeUrl: image.File.ServerRelativeUrl,
thumbnail: thumbnailUrl,
thumbnailWidth: 240,
thumbnailHeight: 180,
caption: image.Title ? image.Title : image.File.Name,
// thumbnailCaption: image.File.Name,
customOverlay:
<Label style={{ fontSize: FontSizes.size18, bottom: 0, transition: '.5s ease', textAlign: 'center', width: '100%', position: 'absolute', background: 'rgba(0, 0, 0, 0.5)', color: '#f1f1f1', padding: '10px' }}>
{image.Title ? image.Title : image.File.Name}
</Label>
},
);
// Create Carousel Slides from Images
this._carouselImages = this.galleryImages.map((GalleryImage, i) => {
return (
<div className='slideLoading'>
<div style={{}} >
<Label
style={{ fontSize: FontSizes.size18, textAlign: 'center', width: '100%', padding: '5px' }}>
<ActionButton
data-automation-id="test"
iconProps={{ iconName: 'Download', styles: { root: { fontSize: FontSizes.size18, } } }}
allowDisabledFocus={true}
style={{ padding: '10px', fontSize: FontSizes.size18 }}
checked={true}
href={`${this.props.context.pageContext.legacyPageContext.siteAbsoluteUrl}/_layouts/download.aspx?SourceUrl=${GalleryImage.ServerRelativeUrl}`}
>
{GalleryImage.caption}
</ActionButton>
</Label>
</div>
{GalleryImage.mediaType == 'video' ?
<div >
<Player
poster={GalleryImage.imageUrl}
style={{ width: '100%', height: '640px' }}
>
<BigPlayButton position="center" />
<source src={GalleryImage.ServerRelativeUrl}
/>
</Player>
</div>
:
<div style={{ maxWidth: '100%' }}>
<Image src={GalleryImage.imageUrl}
style={{ height: 'auto', overflow: 'hidden', maxHeight: '100%' }}
onLoadingStateChange={async (loadState: ImageLoadState) => {
console.log('imageload Status ' + i, loadState, GalleryImage.imageUrl);
if (loadState == ImageLoadState.loaded) {
this.setState({ isloadingCarousel: false });
}
}}
height={'400px'}
imageFit={ImageFit.contain}
/>
</div>
}
</div>
);
}
);
}
} catch (error) {
this.setState({ hasError: true, errorMessage: decodeURIComponent(error.message) });
}
}
public async componentDidMount() {
await this.loadPictures();
this.setState({ images: this.galleryImages, carouselImages: this._carouselImages, isLoading: false, isloadingCarousel: false });
}
/**
*
*
* @param {ImageGalleryProps} prevProps
* @returns
* @memberof ImageGallery
*/
public async componentDidUpdate(prevProps: ImageGalleryProps) {
if (!this.props.list || !this.props.siteUrl) return;
// Get Properties change
if (prevProps.list !== this.props.list || prevProps.numberImages !== this.props.numberImages) {
this.galleryImages = [];
this._carouselImages = [];
await this.loadPictures();
this.setState({ images: this.galleryImages, carouselImages: this._carouselImages, isLoading: false });
}
}
/**
*
*
* @private
* @memberof ImageGallery
*/
private onConfigure() {
// Context of the web part
this.props.context.propertyPane.open();
}
private handleclick = event => {
const value = event.currentTarget.attributes.getNamedItem("data-i").value;
this.setState({ showLithbox: true, photoIndex: Number(value), isloadingCarousel: true });
}
private onNext = () => {
this._slider.slickNext();
}
private onPrev = () => {
this._slider.slickPrev();
}
private async onPlayResume() {
const { isPlaying } = this.state;
if (isPlaying) {
this._slider.slickPause();
} else {
this._slider.slickPlay();
}
this.setState({
isPlaying: !isPlaying
});
}
private onDialogClose = (ev: React.MouseEvent<HTMLButtonElement>) => {
this.setState({ showLithbox: false });
}
/**
*
*
* @returns {React.ReactElement<ImageGalleryProps>}
* @memberof ImageGallery
*/
public render(): React.ReactElement<ImageGalleryProps> {
console.log('theme', this.state.teamsTheme);
const sliderSettings = {
dots: false,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
lazyLoad: 'progressive',
autoplaySpeed: 3000,
initialSlide: this.state.photoIndex,
arrows: false,
draggable: false,
adaptiveHeight: true,
useCSS: true,
useTransform: true,
};
return (
<div className={styles.container}>
<div>
{
this.state.teamsTheme == 'dark' ?
<Label style={{color:'white', fontSize: FontSizes.size24}}>{this.props.title}</Label>
:
<WebPartTitle displayMode={this.props.displayMode}
title={this.props.title}
updateProperty={this.props.updateProperty}
/>
}
</div>
{
(!this.props.list) ?
<Placeholder iconName='Edit'
iconText={strings.WebpartConfigIconText}
description={strings.WebpartConfigDescription}
buttonLabel={strings.WebPartConfigButtonLabel}
hideButton={this.props.displayMode === DisplayMode.Read}
onConfigure={this.onConfigure.bind(this)} />
:
this.state.hasError ?
<MessageBar messageBarType={MessageBarType.error}>
{this.state.errorMessage}
</MessageBar>
:
this.state.isLoading ?
<Spinner size={SpinnerSize.large} label='loading images...' />
:
this.state.images.length == 0 ?
<div style={{ width: '300px', margin: 'auto' }}>
<Icon iconName="PhotoCollection"
style={{ fontSize: '250px', color: '#d9d9d9' }} />
<Label style={{ width: '250px', margin: 'auto', fontSize: FontSizes.size20 }}>No images in the library</Label>
</div>
:
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
{
this.state.images.map((item, i) => {
let v: boolean = true;
return (
<div
onClick={this.handleclick}
data-i={i}
id={i.toString()}
style={{ width: '230px', display: 'inline-block', verticalAlign: 'top', margin: '2px' }}>
<DocumentCard>
<RenderImage displayCaption={v} image={item} context={this.props.context} />
</DocumentCard>
</div>
);
})
}
<Panel
isOpen={this.state.showLithbox}
onDismiss={this.onDialogClose}
headerText="Images - slides"
type={PanelType.custom}
customWidth="700px"
onRenderFooterContent={() => {
return (
<div style={{ float: 'right', paddingBottom: '20px' }}>
<PrimaryButton text="Close" onClick={this.onDialogClose} />
</div>
);
}}
>
<div style={{ marginBottom: 25, verticalAlign: 'Top', width: '100%' }}>
<Slider
ref={c => (this._slider = c)}
{...sliderSettings}
autoplay={this.state.autoplay}
onReInit={() => {
if (!this.state.isloadingCarousel)
$(".slideLoading").removeClass("slideLoading");
}}
>
{
this.state.carouselImages
}
</Slider>
</div>
{
!this.state.isloadingCarousel ?
<div style={{ textAlign: 'center', width: '100%' }}>
<CommandBarButton
iconProps={{ iconName: 'TriangleSolidLeft12', styles: { root: { fontSize: FontSizes.size16, padding: '10px', color: CommunicationColors.primary } } }}
allowDisabledFocus={true}
style={{ fontSize: FontSizes.size16, marginRight: '10px' }}
title='Prev'
onClick={this.onPrev}>
</CommandBarButton>
<CommandBarButton
iconProps={{ iconName: this.state.isPlaying ? 'Pause' : 'PlayResume', styles: { root: { fontSize: FontSizes.size18, padding: '10px', color: CommunicationColors.primary } } }}
allowDisabledFocus={true}
style={{ fontSize: FontSizes.size18, marginRight: '10px' }}
title={this.state.isPlaying ? 'Pause' : 'Play'}
onClick={this.onPlayResume}>
</CommandBarButton>
<CommandBarButton
iconProps={{ iconName: 'TriangleSolidRight12', styles: { root: { fontSize: FontSizes.size16, padding: '10px', color: CommunicationColors.primary } } }}
allowDisabledFocus={true}
style={{ fontSize: FontSizes.size16 }}
title='Next'
onClick={this.onNext}>
</CommandBarButton>
</div>
:
<Spinner size={SpinnerSize.large} label={'Loading...'} style={{ fontSize: FontSizes.size18, color: CommunicationColors.primary }}></Spinner>
}
</Panel>
</div>
}
</div>
);
}
}

View File

@ -0,0 +1,11 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { DisplayMode } from '@microsoft/sp-core-library';
export interface ImageGalleryProps {
title: string;
siteUrl: string;
list: string;
context: WebPartContext;
numberImages: number;
updateProperty: (value: string) => void;
displayMode: DisplayMode;
}

View File

@ -0,0 +1,16 @@
import { IGalleryImages } from './IGalleryImages';
export interface ImageGalleryState {
images: IGalleryImages[];
isLoading: boolean;
errorMessage: string;
hasError: boolean;
teamsTheme: string;
showLithbox: boolean;
photoIndex: number;
isloadingCarousel:boolean;
carouselImages: any[];
isPlaying:boolean;
autoplay: boolean;
}

View File

@ -0,0 +1,227 @@
import * as React from 'react';
import styles from './ImageGallery.module.scss';
import { ImageGalleryProps } from './ImageGalleryProps';
import { ImageGalleryState } from './ImageGalleryState';
import { escape } from '@microsoft/sp-lodash-subset';
import spservices from '../../../../services/spservices';
import Gallery from 'react-grid-gallery';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import RenderImage from '../RenderImage/RenderImage';
import {
Spinner,
SpinnerSize,
MessageBar,
MessageBarType,
Label,
Icon,
DocumentCard,
DocumentCardActivity,
DocumentCardPreview,
DocumentCardTitle,
IDocumentCardPreviewProps,
ImageFit,
Image,
} from 'office-ui-fabric-react';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { DisplayMode } from '@microsoft/sp-core-library';
import { FontSizes } from '@uifabric/fluent-theme/lib/fluent/FluentType';
import * as strings from 'ImageGalleryWebPartStrings';
import * as microsoftTeams from '@microsoft/teams-js';
import { Root } from '@pnp/graph';
import Lightbox from 'lightbox-react';
import 'lightbox-react/style.css'; // This only needs to be imported once in your app
/**
*
*
* @export
* @class ImageGallery
* @extends {React.Component<ImageGalleryProps, ImageGalleryState>}
*/
export default class ImageGallery extends React.Component<ImageGalleryProps, ImageGalleryState> {
private spService: spservices = null;
private images: any;
private galleryImages: any[] = [];
private _teamsContext: microsoftTeams.Context;
private _teamsTheme: string = '';
private lithboxMedia: any[] = [];
constructor(props: ImageGalleryProps) {
super(props);
this.spService = new spservices(this.props.context);
if (this.props.context.microsoftTeams) {
this.props.context.microsoftTeams.getContext(context => {
this._teamsContext = context;
this._teamsTheme = this._teamsContext.theme;
console.log('ctt', this._teamsContext);
this.setState({ teamsTheme: this._teamsTheme });
});
}
this.state = {
images: [],
isLoading: false,
errorMessage: '',
hasError: false,
teamsTheme: this._teamsTheme,
onOver: false,
showLithbox: false,
lithboxMedia: [],
photoIndex: 0,
};
}
/**
*
*
* @protected
* @returns {Promise<any>}
* @memberof ImageGallery
*/
/**
*
*
* @private
* @memberof ImageGallery
*/
private async loadPictures() {
this.setState({ isLoading: true, hasError: false });
const tenantUrl = `https://${location.host}`;
try {
this.images = await this.spService.getImages(this.props.siteUrl, this.props.list, this.props.numberImages);
// const el = <Label style={{fontSize: FontSizes.size18, bottom:0,transition:'.5s ease', textAlign: 'center', width:'100%', position:'absolute', background: 'rgba(0, 0, 0, 0.5)', color: '#f1f1f1', padding: '10px'}}>Teste</Label>;
const el = <Label className={styles.overlay}>Teste</Label>;
for (const image of this.images) {
if (image.FileSystemObjectType == 1) continue; // by pass folder item
const pURL = `${tenantUrl}/_api/v2.0/sharePoint:${image.File.ServerRelativeUrl}:/driveItem/thumbnails/0/large/content?preferNoRedirect=true `;
const thumbnailUrl = `${tenantUrl}/_api/v2.0/sharePoint:${image.File.ServerRelativeUrl}:/driveItem/thumbnails/0/c240x240/content?preferNoRedirect=true `;
this.lithboxMedia.push(pURL);
this.galleryImages.push(
{
src: pURL,
thumbnail: thumbnailUrl,
thumbnailWidth: 240,
thumbnailHeight: 180,
caption: image.Title ? image.Title : image.File.Name,
// thumbnailCaption: image.File.Name,
customOverlay:
<Label style={{ fontSize: FontSizes.size18, bottom: 0, transition: '.5s ease', textAlign: 'center', width: '100%', position: 'absolute', background: 'rgba(0, 0, 0, 0.5)', color: '#f1f1f1', padding: '10px' }}>
{image.Title ? image.Title : image.File.Name}
</Label>
},
);
}
} catch (error) {
this.setState({ hasError: true, errorMessage: decodeURIComponent(error.message) });
}
}
public async componentDidMount() {
await this.loadPictures();
this.setState({ images: this.galleryImages, lithboxMedia: this.lithboxMedia, isLoading: false });
}
/**
*
*
* @param {ImageGalleryProps} prevProps
* @returns
* @memberof ImageGallery
*/
public async componentDidUpdate(prevProps: ImageGalleryProps) {
if (!this.props.list || !this.props.siteUrl) return;
// Get Properties change
if (prevProps.list !== this.props.list || prevProps.numberImages !== this.props.numberImages) {
this.galleryImages = [];
await this.loadPictures();
this.setState({ images: this.galleryImages, isLoading: false });
}
}
/**
*
*
* @private
* @memberof ImageGallery
*/
private onConfigure() {
// Context of the web part
this.props.context.propertyPane.open();
}
/**
*
*
* @returns {React.ReactElement<ImageGalleryProps>}
* @memberof ImageGallery
*/
public render(): React.ReactElement<ImageGalleryProps> {
console.log('th', (this._teamsContext && this._teamsContext.theme == 'dark') ? styles.webpartTitle : '');
return (
<div className={styles.imageGallery}>
<div >
{
(this._teamsContext && this._teamsContext.theme == 'dark') ?
<Label className={styles.title}>{this.props.title}</Label>
:
<WebPartTitle displayMode={this.props.displayMode}
title={this.props.title}
updateProperty={this.props.updateProperty}
className={(this._teamsContext && this._teamsContext.theme == 'dark') && styles.webpartTitle}
/>
}
</div>
{
(!this.props.list) ?
<Placeholder iconName='Edit'
iconText={strings.WebpartConfigIconText}
description={strings.WebpartConfigDescription}
buttonLabel={strings.WebPartConfigButtonLabel}
hideButton={this.props.displayMode === DisplayMode.Read}
onConfigure={this.onConfigure.bind(this)} />
:
this.state.hasError ?
<MessageBar messageBarType={MessageBarType.error}>
{this.state.errorMessage}
</MessageBar>
:
this.state.isLoading ?
<Spinner size={SpinnerSize.large} label='loading images...' />
:
this.state.images.length == 0 ?
<div style={{ width: '300px', margin: 'auto' }}>
<Icon iconName="PhotoCollection"
style={{ fontSize: '250px', color: '#d9d9d9' }} />
<Label style={{ width: '250px', margin: 'auto', fontSize: FontSizes.size20 }}>No images in the library</Label>
</div>
:
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
<Gallery images={this.state.images} enableImageSelection={false} rowHeight={180} />
</div>
}
</div>
);
}
}

View File

@ -0,0 +1,7 @@
.carousel .slide {
background-color: white;
}
.slideLoading {
display:none !important;
}

View File

@ -0,0 +1,8 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { DisplayMode } from '@microsoft/sp-core-library';
import { IGalleryImages } from '../ImageGallery/IGalleryImages';
export interface IImageProps {
image: IGalleryImages;
context: WebPartContext;
displayCaption: boolean;
}

View File

@ -0,0 +1,4 @@
export interface IImageState {
onHover:boolean;
}

View File

@ -0,0 +1,97 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.overlay {
position: absolute;
bottom: 0;
background: rgb(0, 0, 0);
background: rgba(0, 0, 0, 0.5); /* Black see-through */
color: #f1f1f1;
width: 100%;
transition: .5s ease;
opacity:0;
color: white;
font-size: 20px;
padding: 10px;
text-align: center;
}
.search {
.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);
}
.webpartTitle{
color: white !important;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
overflow: hidden
}
.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;
margin-bottom: 25px;
}
.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;
}
}
}

View File

@ -0,0 +1,76 @@
import * as React from 'react';
import { IImageProps } from './IRenderImageProps';
import { IImageState } from './IRenderImageState';
import {
Spinner,
SpinnerSize,
MessageBar,
MessageBarType,
Label,
Icon,
DocumentCard,
DocumentCardActivity,
DocumentCardPreview,
DocumentCardTitle,
IDocumentCardPreviewProps,
ImageFit,
Image
} from 'office-ui-fabric-react';
import { FontSizes } from '@uifabric/fluent-theme/lib/fluent/FluentType';
import 'video-react/dist/video-react.css'; // import css
import { Player, BigPlayButton } from 'video-react';
export default class RenderImage extends React.Component<IImageProps, IImageState> {
constructor(props: IImageProps) {
super(props);
this.state = { onHover: false };
}
public render(): React.ReactElement<IImageProps> {
return (
<div
>
<div
onMouseOver={(ev) => {
ev.preventDefault();
this.setState({ onHover: !this.state.onHover });
}}
onMouseOut={(ev) => {
ev.preventDefault();
this.setState({ onHover: !this.state.onHover });
}}
>
<Image imageFit={ImageFit.centerCover} src={this.props.image.thumbnail}
width={230}
height={180}
/>
{
this.props.image.mediaType == 'video' && (
<Icon iconName='MSNVideosSolid'
style={{ pointerEvents: "none", display: 'block' , fontSize: FontSizes.size42, textAlign: 'center', width: '50px', height: '50px', position: 'absolute', top: '40%', left: '40%' }}
/>
)
}
</div>
{
this.state.onHover &&
<div>
<Label
style={{ pointerEvents: "none", display: 'block', zIndex: 1000, fontSize: FontSizes.size18, bottom: 0, textAlign: 'center', width: '100%', position: 'absolute', background: 'rgba(0, 0, 0, 0.5)', color: '#f1f1f1', padding: '10px' }}
>
{this.props.image.caption}
</Label>
</div>
}
</div>
);
}
}

View File

@ -0,0 +1,12 @@
define([], function() {
return {
"PropertyPaneDescription": "Please select site and library to show in Image Gallery",
"BasicGroupName": "Properties",
"ListFieldLabel": "Library",
"SiteUrlFieldLabel": 'Site Url',
"WebPartConfigButtonLabel": "Configure",
"WebpartConfigDescription": "Please configure Image Library ",
"WebpartConfigIconText": "Configure your Image Gallery Web Part",
"TitleLabel": 'Web Part Title'
}
});

View File

@ -0,0 +1,15 @@
declare interface IImageGalleryWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
SiteUrlFieldLabel: string;
ListFieldLabel: string;
WebPartConfigButtonLabel: string;
WebpartConfigDescription: string;
WebpartConfigIconText: string;
TitleLabel:string;
}
declare module 'ImageGalleryWebPartStrings' {
const strings: IImageGalleryWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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');
}

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/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": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@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
}
}