diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e7c59836e..443045703 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -56,7 +56,21 @@ When you are submitting a new sample, it has to follow up below guidelines
Please see following wiki post from the GitHub repository wiki for exact steps on submitting new pull requests.
* How to submit a PR to SharePoint repository? - Step-by-step with commands and pictures coming soon
-
+## Meging your existing github projects with this repository
+If the sample you wish to contrubute is tored in your own Github repository, you can use the following steps to merge it with the Psp-dev-fx-webparts repository:
+ - Fork the sp-dev-fx-webparts repository om hithub
+ - create a local git rpository
+ md sp-dev-fx-webparts
+ cd sp-dev-fx-webparts
+ git init
+ - pull your forked copy of sp-dev-fx-webparts into your local repository
+ git remote add origin https://github.com/yourgitaccount/sp-dev-fx-webparts.git
+ git pull origin dev
+ - pull your other project from github into the samples folder of your local copy of sp-dev-fx-webparts
+ git subtree add --prefix=samples/projectname https://github.com/yourgitaccount/projectname.git master
+ - push the changes up to your forked repository
+ git push orgin dev
+
## Signing the CLA
Before we can accept your pull requests you will be asked to sign electronically Contributor License Agreement (CLA), which is prerequisite for any contributions to PnP repository. This will be one time process, so for any future contributions you will not be asked to re-sign anything. After the CLA has been signed, our PnP core team members will have a look on your submission for final verification of the submission. Please do not delete your development branch until the submission has been closed.
diff --git a/samples/react-videolibrary/.editorconfig b/samples/react-videolibrary/.editorconfig
new file mode 100644
index 000000000..8ffcdc4ec
--- /dev/null
+++ b/samples/react-videolibrary/.editorconfig
@@ -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
\ No newline at end of file
diff --git a/samples/react-videolibrary/.gitattributes b/samples/react-videolibrary/.gitattributes
new file mode 100644
index 000000000..212566614
--- /dev/null
+++ b/samples/react-videolibrary/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/samples/react-videolibrary/.gitignore b/samples/react-videolibrary/.gitignore
new file mode 100644
index 000000000..63c4ae010
--- /dev/null
+++ b/samples/react-videolibrary/.gitignore
@@ -0,0 +1,32 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Dependency directories
+node_modules
+
+# Build generated files
+dist
+lib
+solution
+temp
+*.spapp
+
+# 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
diff --git a/samples/react-videolibrary/.npmignore b/samples/react-videolibrary/.npmignore
new file mode 100644
index 000000000..2c93a9384
--- /dev/null
+++ b/samples/react-videolibrary/.npmignore
@@ -0,0 +1,14 @@
+# Folders
+.vscode
+coverage
+node_modules
+sharepoint
+src
+temp
+
+# Files
+*.csproj
+.git*
+.yo-rc.json
+gulpfile.js
+tsconfig.json
diff --git a/samples/react-videolibrary/.vscode/settings.json b/samples/react-videolibrary/.vscode/settings.json
new file mode 100644
index 000000000..ccdda087e
--- /dev/null
+++ b/samples/react-videolibrary/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "typescript.check.workspaceVersion": false
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/.yo-rc.json b/samples/react-videolibrary/.yo-rc.json
new file mode 100644
index 000000000..b5618d026
--- /dev/null
+++ b/samples/react-videolibrary/.yo-rc.json
@@ -0,0 +1,7 @@
+{
+ "@microsoft/generator-sharepoint": {
+ "libraryName": "videotst",
+ "libraryId": "841958c7-1d50-408b-9df4-feac7165d4a0",
+ "framework": "react"
+ }
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/README.md b/samples/react-videolibrary/README.md
new file mode 100644
index 000000000..a1af2b362
--- /dev/null
+++ b/samples/react-videolibrary/README.md
@@ -0,0 +1,84 @@
+# React Video Library
+
+## Summary
+A set of 3 SPFX webparts that use different open-source carousels (react-3d-carousel, reactjs-coverface, and react-slick)
+to display videos stored on an O365 Video Channel. The idea being to display a carousel of the thumbnail images, and then
+when a user clicks on one of the thumbnails, replace the tumbnail with a video player and start the video up.
+
+The first webpart used react-3d-carousel. The carousel looks great, but i found no way to swap out the image and replace
+it with a video player. This carousel would be fine for displayin a picture library though,
+
+The second webpart used react-slick. The carousel is not as fancy as react-3d-carousel, but i was able to to swap out the
+image and replace it with a video player once a user clicked it. I had trouble with the css and getting the next and previous
+buttons to show. If you run the webpart, the buttons are there, they are just not visible.
+
+Finally I tried reactjs-coverface. It has nice scrolling through the images withe the mousweheel, and some cool 3d effects.
+It was also simple to swap the image with a video player once a user clicked it (same code as react-slick). This is the best
+of the three for my purposes.
+
+
+In the future I want to modify this webpart to link a Sharepoint list with the video channel so that users can enter additional
+metadata for the video and be anle to search/filter the videos using this metadata.
+
+See also https://github.com/russgove/O365VideoSync. It's a console app that you can schedule to run to synchronize an Office 365 Video Channel with a sharepoint list (on prem or otherwise).
+
+
+
+
+
+
+
+## Used SharePoint Framework Version
+
+
+## Applies to
+
+* [SharePoint Framework Developer Preview](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
+* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
+
+
+
+## Prerequisites
+
+> React, react-3d-carousel reactjs-coverface react-slick
+
+## Solution
+
+Solution|Author(s)
+--------|---------
+ react-VideoLibrary | Russell Gove
+
+## Version history
+
+Version|Date|Comments
+-------|----|--------
+0.1|December 31, 2016|Initial version
+
+
+## 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`
+ - `tsd install`
+ - `gulp serve`
+
+> Include any additional steps as needed.
+
+## Features
+A set of 3 SPFX webparts that use different open-source carousels (react-3d-carousel, reactjs-coverface, and react-slick)
+to display videos stored on an O365 Video Channel.
+
+Usage:
+
+
+
+
+
+
+
diff --git a/samples/react-videolibrary/config/config.json b/samples/react-videolibrary/config/config.json
new file mode 100644
index 000000000..b3055eeef
--- /dev/null
+++ b/samples/react-videolibrary/config/config.json
@@ -0,0 +1,27 @@
+{
+ "entries": [
+ {
+ "entry": "./lib/webparts/videoLibrary/VideoLibraryWebPart.js",
+ "manifest": "./src/webparts/videoLibrary/VideoLibraryWebPart.manifest.json",
+ "outputPath": "./dist/video-library.bundle.js"
+ },
+ {
+ "entry": "./lib/webparts/videoLibraryReact3DCarousel/VideoLibraryReact3DCarouselWebPart.js",
+ "manifest": "./src/webparts/videoLibraryReact3DCarousel/VideoLibraryReact3DCarouselWebPart.manifest.json",
+ "outputPath": "./dist/video-library-react-3-d-carousel.bundle.js"
+ },
+ {
+ "entry": "./lib/webparts/videoLibraryCoverFlow/VideoLibraryCoverFlowWebPart.js",
+ "manifest": "./src/webparts/videoLibraryCoverFlow/VideoLibraryCoverFlowWebPart.manifest.json",
+ "outputPath": "./dist/video-library-cpver-flow.bundle.js"
+ }
+ ],
+ "externals": {
+ "@microsoft/sp-module-loader": "node_modules/@microsoft/sp-module-loader/dist/sp-module-loader.js"
+ },
+ "localizedResources": {
+ "videoLibraryStrings": "webparts/videoLibrary/loc/{locale}.js",
+ "videoLibraryReact3DCarouselStrings": "webparts/videoLibraryReact3DCarousel/loc/{locale}.js",
+ "videoLibraryCoverFlowStrings": "webparts/videoLibraryCoverFlow/loc/{locale}.js"
+ }
+}
diff --git a/samples/react-videolibrary/config/deploy-azure-storage.json b/samples/react-videolibrary/config/deploy-azure-storage.json
new file mode 100644
index 000000000..9d69c4fec
--- /dev/null
+++ b/samples/react-videolibrary/config/deploy-azure-storage.json
@@ -0,0 +1,6 @@
+{
+ "workingDir": "./temp/deploy/",
+ "account": "",
+ "container": "videotst",
+ "accessKey": ""
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/config/package-solution.json b/samples/react-videolibrary/config/package-solution.json
new file mode 100644
index 000000000..26ec34279
--- /dev/null
+++ b/samples/react-videolibrary/config/package-solution.json
@@ -0,0 +1,10 @@
+{
+ "solution": {
+ "name": "videotst-client-side-solution",
+ "id": "841958c7-1d50-408b-9df4-feac7165d4a0",
+ "version": "1.0.0.0"
+ },
+ "paths": {
+ "zippedPackage": "solution/videotst.spapp"
+ }
+}
diff --git a/samples/react-videolibrary/config/prepare-deploy.json b/samples/react-videolibrary/config/prepare-deploy.json
new file mode 100644
index 000000000..6aca63656
--- /dev/null
+++ b/samples/react-videolibrary/config/prepare-deploy.json
@@ -0,0 +1,3 @@
+{
+ "deployCdnPath": "temp/deploy"
+}
diff --git a/samples/react-videolibrary/config/serve.json b/samples/react-videolibrary/config/serve.json
new file mode 100644
index 000000000..087899637
--- /dev/null
+++ b/samples/react-videolibrary/config/serve.json
@@ -0,0 +1,9 @@
+{
+ "port": 4321,
+ "initialPage": "https://localhost:5432/workbench",
+ "https": true,
+ "api": {
+ "port": 5432,
+ "entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
+ }
+}
diff --git a/samples/react-videolibrary/config/tslint.json b/samples/react-videolibrary/config/tslint.json
new file mode 100644
index 000000000..0010b6105
--- /dev/null
+++ b/samples/react-videolibrary/config/tslint.json
@@ -0,0 +1,50 @@
+{
+ // 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,
+ "label-undefined": false,
+ "member-access": true,
+ "no-arg": false,
+ "no-console": false,
+ "no-construct": false,
+ "no-duplicate-case": true,
+ "no-duplicate-key": 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-unused-imports": true,
+ "no-unused-variable": true,
+ "no-unreachable": 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,
+ "prefer-const": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/config/write-manifests.json b/samples/react-videolibrary/config/write-manifests.json
new file mode 100644
index 000000000..18b0a8cc9
--- /dev/null
+++ b/samples/react-videolibrary/config/write-manifests.json
@@ -0,0 +1,3 @@
+{
+ "cdnBasePath": "https://rgove3.sharepoint.com/sites/cdn/spfxapps/"
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/gulpfile.js b/samples/react-videolibrary/gulpfile.js
new file mode 100644
index 000000000..7d36ddb1c
--- /dev/null
+++ b/samples/react-videolibrary/gulpfile.js
@@ -0,0 +1,6 @@
+'use strict';
+
+const gulp = require('gulp');
+const build = require('@microsoft/sp-build-web');
+
+build.initialize(gulp);
diff --git a/samples/react-videolibrary/package.json b/samples/react-videolibrary/package.json
new file mode 100644
index 000000000..267ee0314
--- /dev/null
+++ b/samples/react-videolibrary/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "videotst",
+ "version": "0.0.1",
+ "private": true,
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "dependencies": {
+ "@microsoft/sp-client-base": "~0.7.0",
+ "@microsoft/sp-core-library": "~0.1.2",
+ "@microsoft/sp-webpart-base": "~0.4.0",
+ "@types/webpack-env": ">=1.12.1 <1.14.0",
+ "@types/react": "0.14.46",
+ "@types/react-addons-shallow-compare": "0.14.17",
+ "@types/react-addons-test-utils": "0.14.15",
+ "@types/react-addons-update": "0.14.14",
+ "@types/react-dom": "0.14.18",
+ "office-ui-fabric-react": "0.69.0",
+ "react": "0.14.8",
+ "react-3d-carousel": "0.0.6",
+ "react-dom": "0.14.8",
+ "react-slick": "^0.14.5",
+ "reactjs-coverflow": "^1.0.5"
+ },
+ "devDependencies": {
+ "@microsoft/sp-build-web": "~0.9.0",
+ "@microsoft/sp-module-interfaces": "~0.7.0",
+ "@microsoft/sp-webpart-workbench": "~0.8.0",
+ "gulp": "~3.9.1",
+ "@types/chai": ">=3.4.34 <3.6.0",
+ "@types/mocha": ">=2.2.33 <2.6.0"
+ },
+ "scripts": {
+ "build": "gulp bundle",
+ "clean": "gulp clean",
+ "test": "gulp test"
+ }
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/src/tests.js b/samples/react-videolibrary/src/tests.js
new file mode 100644
index 000000000..cb4bb5cf2
--- /dev/null
+++ b/samples/react-videolibrary/src/tests.js
@@ -0,0 +1,5 @@
+var context = require.context('.', true, /.+\.test\.js?$/);
+
+context.keys().forEach(context);
+
+module.exports = context;
diff --git a/samples/react-videolibrary/src/webparts/O365VUtilities.ts b/samples/react-videolibrary/src/webparts/O365VUtilities.ts
new file mode 100644
index 000000000..323a2a483
--- /dev/null
+++ b/samples/react-videolibrary/src/webparts/O365VUtilities.ts
@@ -0,0 +1,167 @@
+import {Promise} from "es6-promise"; // added fro rc0
+import { IWebPartContext } from '@microsoft/sp-webpart-base';
+import { SPHttpClient ,SPHttpClientConfigurations} from '@microsoft/sp-http';
+export class VideoServiceSettings {
+ public ChannelUrlTemplate: string;
+ public IsVideoPortalEnabled: string;
+ public PlayerUrlTemplate: string;
+ public VideoPortalLayoutsUrl: string;
+ public VideoPortalUrl: string;
+ public O365VideoPageUrl: string;
+}
+export class VideoChannel {
+ public Description: string;
+ public Id: string;
+ public ServerRelativeUrl: string;
+ public TileHtmlColor: string;
+ public Title: string;
+ public YammerEnabled: string;
+}
+export class Video {
+ public ChannelID: string;
+ /** CreatedDate -- The date the video was originally uploaded. */
+ public CreatedDate: string;
+ public Description: string;
+ public DisplayFormUrl: string;
+ public FileName: string;
+ public ID: string;
+ public OwnerName: string;
+ public ServerRelativeUrl: string;
+ /**ThumbnailURL -- The URL of the thumbnail image of the video. */
+ public ThumbnailUrl: string;
+ /**Title -- The title of the video. */
+ public Title: string;
+ public Url: string;
+ public VideoDownloadUrl: string;
+ /**Title -- The title of the video. */
+ public VideoDurationInSeconds: number;
+ public VideoProcessingStatus: number;
+ public ViewCount: number;
+ public YammerObjectUrl: string;
+}
+export enum VideoProcessingStatus {
+ /** 0 -- (default) -- The video has not yet been processed for playback. */
+ NotProcessd = 0,
+ /**1 -- The video has been picked up and is being processed. */
+ BeingProcessed = 1,
+ /**2 -- The video is ready to play. */
+ Ready = 2,
+ /**3 -- The video encountered an error while it was being uploaded to Azure Media Services for processing. */
+ AzureError = 3,
+ /**4 -- Error -- Generic error--Unable to process the video for streaming. */
+ GenericError = 4,
+ /**5 -- Error -- Timeout error--Unable to process the video for streaming. */
+ TimeoutError = 5,
+ /**6 -- Error -- Unsupported format --The video file type is not supported for streaming playback by Azure Media Services. */
+ UnsupportedFormatError = 6
+}
+
+export class O365Video {
+ public videoServiceSettings: VideoServiceSettings;
+ public isInitialized: boolean;
+ public videoChannels: Array;
+ public httpClient: SPHttpClient;
+ public siteAbsoluteUrl: string;
+ constructor(context: IWebPartContext) {
+ this.httpClient = context.spHttpClient;
+ this.isInitialized = false;
+ this.siteAbsoluteUrl = context.pageContext.site.absoluteUrl;
+ }
+ public Initialize(): Promise {
+
+ const url = this.siteAbsoluteUrl + "/_api/VideoService.Discover";
+ // return this.httpClient.get(url).then(response => { //pre rc0
+ return this.httpClient.get(url,SPHttpClient.configurations.v1).then(response => {
+ if (response.ok) {
+ console.log("Returned OK from httpClient");
+ debugger;
+ const results = response.json().then(settings => {
+ this.videoServiceSettings = new VideoServiceSettings();
+ this.videoServiceSettings.ChannelUrlTemplate = settings.ChannelUrlTemplate;
+ this.videoServiceSettings.IsVideoPortalEnabled = settings.IsVideoPortalEnabled;
+ this.videoServiceSettings.PlayerUrlTemplate = settings.PlayerUrlTemplate;
+ this.videoServiceSettings.VideoPortalLayoutsUrl = settings.VideoPortalLayoutsUrl;
+ this.videoServiceSettings.VideoPortalUrl = settings.VideoPortalUrl;
+ this.videoServiceSettings.O365VideoPageUrl = settings.O365VideoPageUrl;
+ return this.videoServiceSettings;
+ });
+ this.isInitialized = true;
+ return results;
+ } else {
+ this.isInitialized = true;
+ console.log("WARNING - failed to hit URL " + url + ". Error = " + response.statusText);
+ throw "Error " + response.statusText;
+ }
+ });
+ }
+ public getChannels(): Promise> {
+
+ const url = this.videoServiceSettings.VideoPortalUrl + "/_api/VideoService/Channels";
+ return this.httpClient.get(url,SPHttpClient.configurations.v1).then(response => {
+
+ if (response.ok) {
+ console.log("Returned OK from httpClient");
+
+ return response.json().then(channels => {
+
+ this.videoChannels = channels.value.map(c => {
+ const channel = new VideoChannel();
+ channel.Description = c.Description;
+ channel.Id = c.Id;
+ channel.ServerRelativeUrl = c.ServerRelativeUrl;
+ channel.TileHtmlColor = c.TileHtmlColor;
+ channel.Title = c.Title;
+ channel.YammerEnabled = c.YammerEnabled;
+ return channel;
+ });
+ return this.videoChannels;
+ });
+ } else {
+ console.log("WARNING - failed to hit URL " + url + ". Error = " + response.statusText);
+ throw "Error " + response.statusText;
+ }
+ });
+ }
+
+ public GetVideos(ChannelId: string): Promise> {
+ const url = this.videoServiceSettings.VideoPortalUrl + "/_api/VideoService/Channels('" + ChannelId + "')/Videos";
+ return this.httpClient.get(url,SPHttpClient.configurations.v1).then(response => {
+
+ if (response.ok) {
+ return response.json().then(v => {
+
+ const videos = v.value.map(c => {
+ let video = new Video();
+ video.ChannelID = c.ChannelID;
+ video.Description = c.Description;
+ video.DisplayFormUrl = c.DisplayFormUrl;
+ video.FileName = c.FileName;
+ video.ID = c.ID;
+ video.OwnerName = c.OwnerName;
+ video.ServerRelativeUrl = c.ServerRelativeUrl;
+ video.Title = c.Title;
+ video.ThumbnailUrl = c.ThumbnailUrl;
+ video.Url = c.Url;
+ video.VideoDownloadUrl = c.VideoDownloadUrl;
+ video.VideoDurationInSeconds = c.VideoDurationInSeconds;
+ video.VideoProcessingStatus = c.VideoProcessingStatus;
+ video.ViewCount = c.ViewCount;
+ video.YammerObjectUrl = c.YammerObjectUrl;
+ return video;
+ });
+ return videos;
+ });
+ } else {
+ console.log("WARNING - failed to hit URL " + url + ". Error = " + response.statusText);
+ throw "Error " + response.statusText;
+ }
+ });
+ }
+ public GetChannelByName(ChannelTitle: string): Promise {
+ return this.getChannels().then(channels => {
+ const matches = channels.filter((value, index, array) => { return value.Title === ChannelTitle; });
+ return matches[0];
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/src/webparts/videoLibrary/IVideoLibraryWebPartProps.ts b/samples/react-videolibrary/src/webparts/videoLibrary/IVideoLibraryWebPartProps.ts
new file mode 100644
index 000000000..277100b75
--- /dev/null
+++ b/samples/react-videolibrary/src/webparts/videoLibrary/IVideoLibraryWebPartProps.ts
@@ -0,0 +1,9 @@
+import { O365Video } from "../O365VUtilities";
+export interface IVideoLibraryWebPartProps {
+ description: string;
+ videoChannel: string;
+ o365Video: O365Video;
+ layout:string;
+ duration:number;
+ panels:number;
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibrary.module.scss b/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibrary.module.scss
new file mode 100644
index 000000000..bd68444ce
--- /dev/null
+++ b/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibrary.module.scss
@@ -0,0 +1,119 @@
+.slick-slide {
+ height: 80vh;
+ background: #2196f3;
+ text-align: center;
+ color: white;
+ font-size: 20px;
+ display: table !important;
+}
+
+.slick-slide div {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.slide-0 {
+ background: red;
+}
+
+.slide-1 {
+ background: orange;
+}
+
+.slide-2 {
+ background: green;
+}
+
+.slide-3{
+ background: black;
+}
+
+.hide {
+ display: none;
+}
+
+.footer1 {
+ height: 100px;
+ color: white;
+ background: blue;
+}
+
+.footer2 {
+ height: 100px;
+ color: white;
+ background: green;
+}
+
+.footer-container {
+ height: 100px;
+ position: relative;
+ text-align: center;
+ font-size: 20px;
+}
+
+.footer-container div {
+ padding: 2em;
+}
+
+.example-enter {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ opacity: 0.01;
+}
+
+.example-enter.example-enter-active {
+ opacity: 1;
+ transition: opacity .5s ease-in;
+}
+.example-leave {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ opacity: 1;
+}
+
+.example-leave.example-leave-active {
+ opacity: 0.01;
+ transition: opacity .5s ease-in;
+}
+.videoLibrary {
+ .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 {
+ padding: 20px;
+ }
+
+ .listItem {
+ max-width: 715px;
+ margin: 5px auto 5px auto;
+ box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
+ }
+
+ .button {
+ text-decoration: none;
+ }
+}
+.slides {
+ position: relative;
+ .slick-prev, .slick-next {
+ position: absolute;
+ top: 50%;
+ }
+ .slick-prev {
+ left: 5%;
+ }
+ .slick-next {
+ right: 5%;
+ }
+}
\ No newline at end of file
diff --git a/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibraryWebPart.manifest.json b/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibraryWebPart.manifest.json
new file mode 100644
index 000000000..19730861b
--- /dev/null
+++ b/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibraryWebPart.manifest.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
+
+ "id": "f0d034d3-6b7d-4907-a6c1-357c2e2e03af",
+ "alias": "VideoLibraryWebPart",
+ "componentType": "WebPart",
+ "version": "0.0.1",
+ "manifestVersion": 2,
+
+ "preconfiguredEntries": [{
+ "groupId": "f0d034d3-6b7d-4907-a6c1-357c2e2e03af",
+ "group": { "default": "Under Development" },
+ "title": { "default": "React-slick Video Library" },
+ "description": {
+ "default": "VideoLibrary implemented using react-slick -- Shows videos from an O365 Video Channel"
+ },
+ "officeFabricIconFontName": "Ribbon",
+ "properties": {
+ "description": "React-slick Video Library",
+ "layout":"prism",
+ "duration":199
+ }
+ }]
+}
diff --git a/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibraryWebPart.ts b/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibraryWebPart.ts
new file mode 100644
index 000000000..9baea2f10
--- /dev/null
+++ b/samples/react-videolibrary/src/webparts/videoLibrary/VideoLibraryWebPart.ts
@@ -0,0 +1,116 @@
+import * as React from "react";
+import * as ReactDom from "react-dom";
+import {
+ BaseClientSideWebPart,
+ IPropertyPaneConfiguration ,
+ PropertyPaneTextField,
+ PropertyPaneDropdown, IPropertyPaneDropdownOption,
+ PropertyPaneSlider
+} from "@microsoft/sp-webpart-base";
+import { O365Video } from "../O365VUtilities";
+import * as strings from "videoLibraryStrings";
+import VideoLibrary, { IVideoLibraryProps } from "./components/VideoLibrary";
+import { IVideoLibraryWebPartProps } from "./IVideoLibraryWebPartProps";
+import {SPComponentLoader} from "@microsoft/sp-loader";
+
+export default class VideoLibraryWebPart extends BaseClientSideWebPart {
+ private O365Video: O365Video;
+ private channels: Array;
+ private channelsFetched: boolean;
+
+ public onInit(): Promise {
+ SPComponentLoader.loadCss("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css");
+ SPComponentLoader.loadCss("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css");
+ this.O365Video = new O365Video(this.context);
+ return Promise.resolve(null);
+ }
+ public render(): void {
+
+ const props: IVideoLibraryProps = {
+ description: this.properties.description,
+ videoChannel: this.properties.videoChannel,
+ o365Video: this.O365Video,
+ layout: this.properties.layout,
+ duration: this.properties.duration,
+ panels: this.properties.panels
+ };
+ const element: React.ReactElement = React.createElement(VideoLibrary, props);
+
+ ReactDom.render(element, this.domElement);
+ }
+
+ public getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
+
+debugger;
+ if (!this.O365Video.isInitialized) {
+ this.O365Video.Initialize().then(x => {
+ this.O365Video.getChannels().then(channels => {
+ this.context.propertyPane.refresh();
+ });
+
+ });
+ }
+ if (!this.channelsFetched && this.O365Video.isInitialized) {
+ this.O365Video.getChannels().then(channels => {
+ this.channels = channels.map((c, i, a) => {
+ let opt: IPropertyPaneDropdownOption = {
+ key: c.Id,
+ text: c.Title,
+ index: i,
+
+ };
+ return opt;
+ });
+ this.channelsFetched = true;
+ this.context.propertyPane.refresh();
+ });
+
+
+ }
+ return {
+ pages: [
+ {
+ header: {
+ description: strings.PropertyPaneDescription
+ },
+ groups: [
+ {
+ groupName: strings.BasicGroupName,
+ groupFields: [
+ PropertyPaneTextField("description", {
+ label: strings.DescriptionFieldLabel
+ }),
+ PropertyPaneDropdown("videoChannel", {
+ label: strings.VideoChannelFieldLabel,
+ options: this.channels,
+
+ }),
+ PropertyPaneDropdown("layout", {
+ label: strings.LayoutFieldLabel,
+ options: [
+ { key: "prism", text: "prism" },
+ { key: "clssic", text: "classic" }
+ ]
+
+ }),
+ PropertyPaneSlider("duration", {
+ label: strings.DurationFieldLabel,
+ min: 1,
+ max: 1000
+ }),
+ PropertyPaneSlider("panels", {
+ label: strings.PanelsFieldLabel,
+ min: 1,
+ max: 5
+ }),
+
+
+
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ }
+}
diff --git a/samples/react-videolibrary/src/webparts/videoLibrary/components/VideoLibrary.tsx b/samples/react-videolibrary/src/webparts/videoLibrary/components/VideoLibrary.tsx
new file mode 100644
index 000000000..2da5d2e7f
--- /dev/null
+++ b/samples/react-videolibrary/src/webparts/videoLibrary/components/VideoLibrary.tsx
@@ -0,0 +1,84 @@
+import * as React from "react";
+import { css } from "office-ui-fabric-react";
+var Slick = require("react-slick");
+import { IVideoLibraryWebPartProps } from "../IVideoLibraryWebPartProps";
+import { Video} from "../../O365VUtilities";
+export interface IVideoLibraryProps extends IVideoLibraryWebPartProps {
+
+}
+export interface IVideoLibraryState {
+ ease: string;
+ width: number;
+ playerUrlTemplate: string;
+ videos: Array