Merge pull request #577 from StfBauer/dev

Dev
This commit is contained in:
Patrick Rodgers 2018-07-24 14:47:33 -04:00 committed by GitHub
commit 10af3ba114
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 18461 additions and 0 deletions

View File

@ -0,0 +1,99 @@
## Local Azure Function and SPFx Web Part Development to consume third party APIs
This sample shows how to consume third-party APIs through an Azure Functions by a Web Part. In this scenario, Vimeo is the representative third party API.
This project contains two separate project folders:
* [VimeoRequest](./VimeoRequest) - contain the Azure Function written in JavaScript
* [VimeoWebPart](./VimeoWebPart) - contain the web part consuming the local running Azure Function
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.4.1-green.svg)
## Applies to
* [SharePoint Framework Developer](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)
## Solution
Solution|Author(s)
--------|---------
react-azfunc-vimeo | Stefan Bauer - n8d ([@stfbauer](https://twitter.com/stfbauer))
## Version history
Version|Date|Comments
-------|----|--------
1.0|July 24, 2018|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.**
## Build Azure Function
To install and run the Azure Function navigate to the folder: [VimeoRequest](./VimeoRequest) and execute the following command.
```sh
npm install
```
This will install all the required NPM packages to run the Azure function
### Additional Configuration Vimeo Azure Function
The folder [VimeoRequest](./VimeoRequest) contains a Azure Function generated with the [Azure Function Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local).
The access videos stored on this platform first a new application needs to be created first. To create a new Application navigate to the following website [https://developer.vimeo.com/apps/new?source=topnav](https://developer.vimeo.com/apps/new?source=topnav)
After the creation of a new Application, the created AppID and Secret must get stored in the local Azure function configuration. This configuration file can be found in the root folder of the Azure Function Host and is named [local.settings.json](./VimeoRequest/local.settings.json).
```jS
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"VimeoAPI": "<Enter APP ID here>",
"VimeoSecret": "<Enter App Secret here>",
"VimeoEndPoint": "https://api.vimeo.com"
}
}
```
A detailed step by step guide on this Azure Function can be found on the blog post: [An Azure Function for a Web Part](https://n8d.at/blog/an-azure-function-for-an-smart-stupid-web-part-part-2/).
#### Run Azure Function
To run this Azure Function execute the following command from inside the VimeoRequest folder.
```sh
func start --useHttps --cert server.pfx --password 'password' --cors '*'
```
To check if the Azure Function is running and returns a result navigate to the following URL in your browser.
```
https://localhost:7071/api/Search?q=Hello%20World
```
#### Run Vimeo Web Part
To run the web part execute the following command:
```bash
git clone the repo
npm i
npm i -g gulp
gulp serve
```
This package produces the following:
* lib/* - intermediate-stage commonjs build artifacts
* dist/* - the bundled script, along with other resources
* deploy/* - all resources which should be uploaded to a CDN.
## Further information
- Overall Overview: [A smart stupid web part for SharePoint - Part 1](https://n8d.at/blog/smart-stupid-web-parts-with-sharepoint-framework-part-1/)
- Details to Azure function implementation: [An Azure Function for an smart stupid web part Part 2](https://n8d.at/blog/an-azure-function-for-an-smart-stupid-web-part-part-2/)
- Web Part Details: [A smart stupid web part consumes a third party API through Azure Functions Part 3](https://n8d.at/blog/a-smart-stupid-web-part-consumes-a-third-party-api-through-azure-functions-part-3/)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-azfunc-vimeo" />

View File

@ -0,0 +1,26 @@
bin
obj
csx
.vs
edge
Publish
*.user
*.suo
*.cscfg
*.Cache
project.lock.json
/packages
/TestResults
/tools/NuGet.exe
/App_Data
/secrets
/data
.secrets
appsettings.json
local.settings.json
node_modules

View File

@ -0,0 +1,16 @@
{
"disabled": false,
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}

View File

@ -0,0 +1,143 @@
'use strict';
var Vimeo = require('vimeo').Vimeo;
const VimeoAPIKey = process.env.VimeoAPI,
VimeoAPISecret = process.env.VimeoSecret,
VimeoEndPoint = process.env.VimeoEndPoint;
var _handleError = {
noQuery: (context) => {
// return error context
context.res = {
status: 400,
body: "No search query has been passed in please specifiy ?q=Hello World"
};
context.done();
// throw exception
throw "No query was specified please use ?q=VideoToFind";
},
APIError: (context, message) => {
// return error context
context.res = {
status: 400,
body: "API Error: " + message
};
context.done();
// throw exception
throw "API Error" + message;
}
}
async function queryVimeo(search, client) {
return new Promise((resolve, reject) => {
client.request({
// This returns the first page of videos containing the term "vimeo staff".
// These videos will be sorted by most relevant to least relevant.
path: '/videos',
query: {
page: 1,
per_page: 10,
query: search,
sort: 'relevant',
direction: 'asc'
}
}, (error, body, statusCode, headers) => {
console.log(error);
if (error) {
reject();
throw error;
}
resolve({
header: headers,
body: body
});
})
})
};
module.exports = (context, req) => {
if (Object.keys(req.query).length === 0) {
throw _handleError.noQuery(context);
}
if (req.query.q === undefined) {
throw _handleError.noQuery(context);
}
try {
var client = new Vimeo(VimeoAPIKey, VimeoAPISecret);
client.generateClientCredentials(["public"], (err, response) => {
console.log(response);
if (err) {
throw err;
context.done();
}
client.setAccessToken(response.access_token);
try {
queryVimeo(req.query.q, client)
.then((searchResponse) => {
console.log(searchResponse.header);
context.res = {
body: searchResponse.body
};
context.done();
}).catch(
(error) => {
throw error;
context.done();
}
)
} catch (err) {
context.log('error', err);
}
})
} catch (error) {
context.res = {
body: error
};
}
};

View File

@ -0,0 +1,47 @@
{
"name": "vimeo-search-api",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"buffer-from": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
"integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="
},
"extend": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"tus-js-client": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-1.5.1.tgz",
"integrity": "sha512-qBSNXpc6ZPe6stn4NSkQ1dnVhVblPAtQo6037g5Qr5zr9gGX1gr+8e0+HtQMBp22Ouo6LYesWMdKbcOR5sRj5A==",
"requires": {
"buffer-from": "^0.1.1",
"extend": "^3.0.0",
"lodash.throttle": "^4.1.1",
"resolve-url": "^0.2.1"
}
},
"vimeo": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/vimeo/-/vimeo-2.0.2.tgz",
"integrity": "sha512-BAxfH2B787TcCHDN3MipluLPV92NK0yF5PYIXpXnvt1w7yy/UsI9Cxo5boL50QQjC038ULLNeneGGTskES6SmA==",
"requires": {
"tus-js-client": "^1.4.5"
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"name": "vimeo-search-api",
"version": "0.0.1",
"description": "Vimeo Search Api for use in Web Parts",
"private": true,
"main": "index.js",
"dependencies": {
"vimeo": "^2.0.2"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Stefan Bauer",
"license": "ISC"
}

View File

@ -0,0 +1,3 @@
{
"name": "Azure"
}

View File

@ -0,0 +1,2 @@
#!/bin/bash
func start --useHttps --cert server.pfx --password 'password' --cors '*'

View File

@ -0,0 +1 @@
{ }

View File

@ -0,0 +1,9 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"VimeoAPI": "<Enter your APPID here>",
"VimeoSecret": "<Enter your Secret here>",
"VimeoEndPoint": "https://api.vimeo.com"
}
}

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDQDCCAigCCQCmyduWRZNakzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJB
VDEPMA0GA1UECAwGVmllbm5hMQ8wDQYDVQQHDAZWaWVubmExDDAKBgNVBAoMA044
RDEPMA0GA1UECwwGRGVzaWduMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwNTE2
MTEyOTExWhcNMjAwNTE1MTEyOTExWjBiMQswCQYDVQQGEwJBVDEPMA0GA1UECAwG
Vmllbm5hMQ8wDQYDVQQHDAZWaWVubmExDDAKBgNVBAoMA044RDEPMA0GA1UECwwG
RGVzaWduMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQC/NnBOEbCJPEmWtLj2zibQsmfutHoinTivGxNhG8C+DvNU0NZD
wRKztmHdal/Z3oSgGYiKdADkWR/rk4ZemIpezRq/IdL3N6kJ0B73X8tGjfUqnmGE
9ocoYKuRd8QwSyaV3GhFhIIJajBno9V2GILfTpywa7YTSVrqY11qK1q6lluC5zLK
SreYhRO1fj74Y2bYz+s0I7GEomfse0Y3/Ju+oxWWItgWzmkUPhDCLAbvq3x3dt2P
aWrXGGh62vrlosq9uz7IVLVaN8nkhjLjiZagsD2T+MXfwkPeNfXAzOQiMSKZeslU
ItmzfmbNMIh++En705EMZXakpudLGapHq1ZZAgMBAAEwDQYJKoZIhvcNAQELBQAD
ggEBAAt3c5i26yuBAogGpJn3pQfKulBSRkWtosm8rlHwdiWcUG/Gqi82VNuc7Yyf
gcNBHT+91kutP5lWWnm2waCGqRR9gyeBv3oKLYzay3CP/Pp2gsPUOn5kg5Tf4G6T
hFQgF+S6wR38Tgrw2IedwiXO2ayW4Rar6pBy9hVUfBOo3TxZWS05EJWKsgVo0ixN
GbJDFlbGToieX0tsOTjfOH+mZI6tGxtFAQ6HwmzEvxMoJeG6cENdaxasIpLbtirN
k8FAI9GVZVl89/lPgnTZXp6MN1k6EwbjmJWN4LYHllKuYOoszuJcrhYPWgZ4WIUM
QPtXr505dSOBUn31a/PR0YlJdoE=
-----END CERTIFICATE-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0G
A1UEBwwGVmllbm5hMQwwCgYDVQQKDANOOEQxDzANBgNVBAsMBkRlc2lnbjESMBAG
A1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
vzZwThGwiTxJlrS49s4m0LJn7rR6Ip04rxsTYRvAvg7zVNDWQ8ESs7Zh3Wpf2d6E
oBmIinQA5Fkf65OGXpiKXs0avyHS9zepCdAe91/LRo31Kp5hhPaHKGCrkXfEMEsm
ldxoRYSCCWowZ6PVdhiC306csGu2E0la6mNdaitaupZbgucyykq3mIUTtX4++GNm
2M/rNCOxhKJn7HtGN/ybvqMVliLYFs5pFD4QwiwG76t8d3bdj2lq1xhoetr65aLK
vbs+yFS1WjfJ5IYy44mWoLA9k/jF38JD3jX1wMzkIjEimXrJVCLZs35mzTCIfvhJ
+9ORDGV2pKbnSxmqR6tWWQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAJiDgXVA
dWGQ23MheP0LL0BSDV9YvPDQIv2enO0dQlTACdQ7d36kmKEvciEQPbIN+auIdDzU
6DzGDr4DeBWujMta3VaEHzVLL97isPAvQeZlAGqIcxGj8FXxZLdjb0lhKdCHobLP
QyrVRvdCYpzRk31fNMN2ce9w53lonNY0DHAM52V/QblAJtsSjCfJiszlFumKzGJJ
Z2rc6JZVr3yGLzJ8GsMmiNZI/i0ZXYbm+/0dyQP5duxagxbvN0kNssQUcRB5HBPW
/w0pmnj9CXDnMc6LFqrawV1OUbTq8zZxKr/Z5J01TDXdE0mu2C4/VbhlYFtauzwI
hnIRBCFiLyoK3Vo=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,8AEF0DAC87FA102A
j+ubxX2TjHxuJpSpEwfUdjMZcnq2WT8cCTnoCB2fj7JlKpbGWOenFBhbSAlg+3eY
bjkoVlZxV4w1M73c5mLy+AIxUmrPE27y/2axhviYsxCLhtmHBSPflJUou5emtAmk
ExpuIkk2SvPPiwUAICvYxW1Tt1eGLSMme98vczBm4berap5AbNf+jtTFsU9kzRig
ItfbadPepPKSp9ERh5lChbuVADshwpppy5kvcGkD5Vg4US8CMSWU4f9cWFbIU+Md
l8Rfk8iYRRynSm4y1dlKhd4rh0JwDH9nlTAQEOpBGQUdyhS0wcYwO1rRo8wQefAe
K+/im/f9Gp+AhopeRj/+FRZGN9Vwg/+s1Is0QbEPSztUPFeI8i5zBjjsIkEjvnlN
Ces7QmutEUtx+hLvskvDChuP1HTsI7ji9m/aLHq4cuswm+FPuwUDGiauyTpczfeN
dEWdxTravBcrhzOqRTgAXXx+FXhdSgn9ZPYbBJuX3O0kJ+xcqEWuz0FE4d1lB4bR
5qI81+fsOVxa4wU9n7PSLG2pHVu8nC3P+cRQCofmz77LRl4KmXNpIFOKUgvI8GMe
UDkmOaIYIefYrlWzds7OQxRtIK22ktX3zzFeEMjutkcyS7pRogPJ2uwlmJ8TB9dH
BztjobA5LvZ32KcfwFJoV2HS1bVqJYvsO1+8mUnO/9Or4KEi8TLMTdwfGkgNjByZ
ioR8PktNZjPsBAeslP7z4IVPBngsTKV1xqen2p/twJcozuW1dA+fOFFVaihb6ERn
hicx+b9MWnZP9zMfn3YhGEeCL17WIIrO/UDPaApVgKmQmxjujZ9opuABWgteJ2mR
/M5cMig/uqgv6vNfCjEBmesckD2XGQpSgVSwalxQkKMRpgYsl0t5eAGiQne2FswF
2u6WomgN18eDgQoqSV5KBdyDhMIuHjEHnLJ8+oCq6nQ6QH27x1N7I252PhQ70fts
NS6AoupTuToQEeQXxruLa2BZhOLZyCnqAaSO7pBMrR861F68R/Vw5KLHau+CpiVF
Mr86eloiHjl/lVeuBcwmXvqTTsonuolHIC/4Qi6FuEu5GNWhlkhoT1wwf+bcAfir
shGQCAgLFVNJJbSiuWnmnNLpgJ1rJi2Nv5FM2gMrZ/VVYXgsVq/b14lJHc6lZQF7
8Y8JQGDYWVYdnU4g2Puc+S9OZgb1vJpp9rdbX1D8vrHe8NW0O2p9Esk3Yum/fbaD
pQXsiovFXhfstUCbBiMtBRYzpim7wLutpCeAfWNtdF1CGXHjNnT18vwaOm0T3kB3
pc1xnm0R9kgT9Xo+kBboQ8YZtpstQpagSy2UraBVY0he340vrsjusyxVWhepi8GF
9yitPwd2hwi7BlGC0rvLetFHbLwgZTMzUvwEwL6UFzBryo1Oa3WVmL4azQzgBr9g
UkEbzuNrYE1EXamz10+uWJp0ht81j8OMJRWQ49AB9Qlx+OPH4GjYAbTtXu72k/h0
v9CnABfLUfVnuEvNb2aLIPIfytGxp6YJKJDX2c8NAdFh5rAc3+7h+eTpfYR1N3Bi
VvzVRUSnC0QtS6yYDk3lp+4etIjf4XIkoAL9rhVkHw86r0ZWNoAPShP/D8WDJjD4
-----END RSA PRIVATE KEY-----

Binary file not shown.

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,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.4.1",
"libraryName": "vimeo-web-part",
"libraryId": "46352013-f007-40a8-80a1-5e4247dbe22d",
"environment": "spo"
}
}

View File

@ -0,0 +1,18 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"vimeo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/vimeo/VimeoWebPart.js",
"manifest": "./src/webparts/vimeo/VimeoWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"VimeoWebPartStrings": "lib/webparts/vimeo/loc/{locale}.js"
}
}

View File

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

View File

@ -0,0 +1,7 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "vimeo-web-part",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,12 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "vimeo-web-part-client-side-solution",
"id": "46352013-f007-40a8-80a1-5e4247dbe22d",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/vimeo-web-part.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://dev.office.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,45 @@
{
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

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

View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
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.`);
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
{
"name": "vimeo-web-part",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"react": "15.6.2",
"react-dom": "15.6.2",
"@types/react": "15.6.6",
"@types/react-dom": "15.5.6",
"@microsoft/sp-core-library": "~1.4.1",
"@microsoft/sp-webpart-base": "~1.4.1",
"@microsoft/sp-lodash-subset": "~1.4.1",
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
"@types/webpack-env": ">=1.12.1 <1.14.0"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.1",
"@microsoft/sp-module-interfaces": "~1.4.1",
"@microsoft/sp-webpart-workbench": "~1.4.1",
"gulp": "~3.9.1",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"ajv": "~5.2.2"
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "8937220d-c8ec-4411-af5d-1ff527a65953",
"alias": "VimeoWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "Vimeo" },
"description": { "default": "Vimeo Web part" },
"officeFabricIconFontName": "Page",
"properties": {
"VimeoUrl": "Vimeo"
}
}]
}

View File

@ -0,0 +1,76 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
IWebPartEvent,
WebPartContext
} from '@microsoft/sp-webpart-base';
import * as strings from 'VimeoWebPartStrings';
import Vimeo from './components/Vimeo';
import { IVimeoProps } from './components/IVimeoProps';
import { HttpClient } from '@microsoft/sp-http';
export interface IVimeoWebPartProps {
VimeoUrl: string;
httpClient: HttpClient;
onSave: any;
properties: any;
editMode: boolean;
}
export default class VimeoWebPart extends BaseClientSideWebPart<IVimeoWebPartProps> {
public render(): void {
const element: React.ReactElement<IVimeoProps> = React.createElement(
Vimeo,
{
VimeoUrl: this.properties.VimeoUrl,
httpClient: this.context.httpClient,
onSave: this.onSave,
properties: this.properties,
editMode: this.displayMode === 2 ? true : false
}
);
ReactDom.render(element, this.domElement);
}
private onSave(item) {
this.properties.VimeoUrl = item.url;
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('VimeoUrl', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,36 @@
export interface IVimeoPitctures{
active: boolean;
resource_key: string;
sizes: Array<any>;
type: string;
uri: string;
}
export interface IVimeoUser{
account: string;
bio: string;
created_time: string;
link: string;
location: string;
metadata: any;
name: any;
pictures: any;
resource_key: string;
uri: string;
websites: any;
}
// export interface IVimeoEntryProps{
// title?: string;
// description: string;
// author?: IVimeoUser;
// url?: string;
// playerProps?: string;
// pictures?: IVimeoPitctures;
// showVideo: boolean;
// onClick?: any;
// }
export interface IVimeoEntryProps{
item: any;
onShowVideo: any;
}

View File

@ -0,0 +1,6 @@
export interface IVimeoProps {
VimeoUrl: string;
httpClient: any;
onSave: Function;
editMode: boolean;
}

View File

@ -0,0 +1,4 @@
export interface IVimeoSearchProps {
httpClient: any;
onSave: Function;
}

View File

@ -0,0 +1,3 @@
.test {
color: black
}

View File

@ -0,0 +1,155 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.vimeo {
display: block;
}
.videoEntry {
background-color: transparent;
}
.videoContainer {
background-color: transparent;
}
.videoInfo {
background-color: transparent;
}
.videoEntry {
background-color: transparent;
}
// .videoPlayer {
// border: 0;
// border-collapse: collapse;
// width: 50%;
// height: auto;
// }
.videoPlayerOuter {
position: relative;
padding: 56.25% 0 0 0;
}
.videoPlayer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
}
.videoPreviewPane {
border-top: 1px black solid;
border-bottom: 1px black solid;
margin: 0.5em 0;
}
.videoPlayerToolbar {
background-color: white;
padding-bottom: 0.5em;
padding-top: 0.5em;
}
.videoPlayerHeader {
@extend .videoPlayerToolbar;
@include ms-font-xl;
line-height: 200%;
padding: 0;
}
.videoSearchResult {
background-color: yellow;
}
.videoResult {
&Item {
background-color: black;
display: flex;
justify-items: stretch;
flex-wrap: none;
}
&Preview {
display: inline-block;
width: auto;
height: auto;
}
&Info {
background-color: white;
box-sizing: border-box;
flex: 1;
padding-left: 1em;
padding-right: 1em;
}
&Title {
@include ms-fontSize-xl;
margin-bottom: 9px;
}
&Description {
// background-color: yellow;
@include ms-fontSize-m;
max-height: 5*14px*1.1;
overflow: hidden;
}
&Author {
background-color: white;
a {
display: flex;
justify-content: flex-start;
flex-wrap: nowrap;
width: auto;
flex-direction: row;
text-decoration: none;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
&Image {
border-radius: 50%;
border: 1px gray solid;
display: inline-block;
}
&Name {
@include ms-fontSize-mPlus;
line-height: 30px;
display: inline-block;
align-self: flex-start;
margin-left: 0.5em;
color: gray;
}
}
}
// .videoSearchBar {
// background-color: transparent;
// display: flex-box;
// margin-bottom: 0.5em;
// :global {
// .n8d-searchbox {
// width: calc(100% - 90px);
// display: inline-block;
// }
// .n8d-searchbutton {
// width: 90px;
// margin: 0;
// }
// }
// }
.videoSearchBar {
display: flex;
margin-bottom: 0.5em;
}
.n8dSearchbox {
width: calc(100% - 90px);
display: inline-block;
}
.n8dSearchbutton {
width: 90px;
margin: 0;
}
.n8dSavebutton {
margin: 0;
white-space: nowrap;
}

View File

@ -0,0 +1,55 @@
import * as React from 'react';
import styles from './Vimeo.module.scss';
import { IVimeoProps } from './IVimeoProps';
import { escape } from '@microsoft/sp-lodash-subset';
import VimeoEntry from './VimeoEntry';
import VimeoSearch from './VimeoSearch';
import VimeoPlayer from './VimeoPlayer';
export default class Vimeo extends React.Component<IVimeoProps, {}> {
constructor(props) {
super(props);
}
private onSave(item) {
this.props.onSave(item);
}
private handleModes(editMode) {
if (editMode) {
return (
<div>
<h1>Search Vimeo</h1>
<VimeoSearch httpClient={this.props.httpClient} onSave={this.onSave.bind(this)} />
</div>
);
} else {
return (<VimeoPlayer playbackVideoUrl={this.props.VimeoUrl} />);
}
}
public render(): React.ReactElement<IVimeoProps> {
let modeContent = this.handleModes(this.props.editMode);
return (
<div className={styles.vimeo}>
{modeContent}
</div>
);
}
}

View File

@ -0,0 +1,13 @@
export interface IConstants{
VIMEO_PLAYER: string;
VIMEO_PREVIEW: string;
}
var Constants: IConstants = {
VIMEO_PLAYER: "HELLO WORLD",
VIMEO_PREVIEW: "https://www.vimeo.com"
};
export default Constants;

View File

@ -0,0 +1,116 @@
import * as React from 'react';
import styles from './Vimeo.module.scss';
import { IVimeoEntryProps } from './IVimeoEntryProps';
import { escape } from '@microsoft/sp-lodash-subset';
import VimeoConstants from './VimeoConstants';
export interface IStateVideoEntry {
showVideo: any;
videoPlayer: any;
editMode: boolean;
}
export default class VimeoEntry extends React.Component<IVimeoEntryProps, {}> {
private showVideo: boolean;
public state: IStateVideoEntry;
constructor(props) {
super(props);
this.state = {
showVideo: this.props,
videoPlayer: null,
editMode: true
};
}
private previewPicture(): string {
let pictureUrl;
if (this.props !== undefined && this.props.item.pictures !== undefined && this.props.item.pictures.uri !== undefined) {
pictureUrl = this.props.item.pictures.sizes[2].link;
} else {
pictureUrl = "";
}
return pictureUrl;
}
private userInfo(): React.ReactElement<{}> {
let authorUrl = "https://www.vimeo.com/" + this.props.item.author.uri.replace('s/', '');
return (
<div className={styles.videoResultAuthor}>
<a href={authorUrl} target="_blank">
<img src={this.props.item.author.pictures.sizes[0].link} alt="" className={styles.videoResultAuthorImage} />
<div className={styles.videoResultAuthorName}>{this.props.item.author.name}</div>
</a>
</div>
);
}
public showVideos() {
this.props.onShowVideo(this.props.item);
let curShowVideo = this.state.showVideo;
this.state.showVideo = !curShowVideo;
this.state.videoPlayer = this.renderVideo(!curShowVideo);
this.setState(this.state);
}
public renderVideo(showVideo) {
if (showVideo) {
return (
<div>Hide Videos</div>
);
} else {
return (
<div>Show Videos</div>
);
}
}
public render(): React.ReactElement<IVimeoEntryProps> {
if (this.state.editMode) {
return (
<div className={styles.videoResultItem} onClick={this.showVideos.bind(this)}>
<div className={styles.videoResultPreview}>
<img src={this.previewPicture()} />
</div>
<div className={styles.videoResultInfo}>
<div className={styles.videoResultTitle}>{this.props.item.name}</div>
{this.userInfo()}
<div className={styles.videoResultDescription}>{this.props.item.description}</div>
</div>
</div>
);
}
}
}

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import styles from './Vimeo.module.scss';
import { escape } from '@microsoft/sp-lodash-subset';
import VimeoConstants from './VimeoConstants';
export interface IVimeoPlayerProps {
playbackVideoUrl: any;
}
export default class VimeoEntry extends React.Component<IVimeoPlayerProps, {}> {
public render(): React.ReactElement<{ IVimeoPlayerProps }> {
if (this.props.playbackVideoUrl !== null) {
return (
<div className={styles.videoPlayerOuter}>
<iframe src={this.props.playbackVideoUrl} frameBorder="0" allowFullScreen className={styles.videoPlayer} ref='videoSource'></iframe>
</div>
);
} else {
return (<span />);
}
}
}

View File

@ -0,0 +1,42 @@
import * as React from 'react';
import styles from './Vimeo.module.scss';
import { escape } from '@microsoft/sp-lodash-subset';
import VimeoPlayer from './VimeoPlayer';
import VimeoConstants from './VimeoConstants';
import { PrimaryButton } from 'office-ui-fabric-react/lib/components/Button';
export interface IVimeoPlayerPreviewProps {
playbackVideo: any;
inEditMode?: boolean;
onAdd: Function;
}
export default class VimeoPlayerPreviewEntry extends React.Component<IVimeoPlayerPreviewProps, {}> {
private onSaveItem(){
this.props.onAdd(this.props.playbackVideo);
}
public render(): React.ReactElement<{ IVimeoPlayerPreviewProps }> {
if (this.props.playbackVideo !== null) {
return (
<div className={styles.videoPreviewPane}>
<div className={styles.videoPlayerHeader}>Video Preview</div>
<VimeoPlayer playbackVideoUrl={ this.props.playbackVideo.url } />.
<div className={styles.videoPlayerToolbar}>&nbsp;</div>
<PrimaryButton text="Use this video" className={styles.n8dSavebutton}
onClick={this.onSaveItem.bind(this)}
/>
</div>
);
} else {
return (<span />);
}
}
}

View File

@ -0,0 +1,157 @@
import * as React from 'react';
import styles from './Vimeo.module.scss';
import { IVimeoSearchProps } from './IVimeoSearchProps';
import { IVimeoProps } from './IVimeoProps';
import { escape } from '@microsoft/sp-lodash-subset';
import {
TextField
} from 'office-ui-fabric-react/lib/TextField';
import {
PrimaryButton
} from 'office-ui-fabric-react/lib/Button';
import { HttpClient, IHttpClientOptions, HttpClientResponse } from '@microsoft/sp-http';
import { IVimeoEntryProps } from './IVimeoEntryProps';
import VimeoEntry from './VimeoEntry';
import VimeoPreviewPlayer from './VimeoPreviewPlayer';
export interface IVideoSearchState {
searchValue?: string;
videos: Array<IVimeoEntryProps>;
currentVideo?: any;
playerHidden: string;
}
export default class VimeoSearch extends React.Component<IVimeoSearchProps, IVideoSearchState> {
public state: IVideoSearchState;
private reqOptions: IHttpClientOptions;
constructor(props) {
super(props);
console.log(props.httpClient);
this.state = {
searchValue: '',
videos: [],
currentVideo: null,
playerHidden: 'hidden'
};
this.searchVideos = this.searchVideos.bind(this);
// this.updateSearch = this.updateSearch.bind(this);
const reqHeaders = new Headers();
reqHeaders.append('Content-type', 'application/json');
this.reqOptions = {
headers: reqHeaders
};
}
private handleSearch(value) {
this.state.searchValue = value;
this.setState(
this.state
);
}
private searchVideos(evt) {
this.props.httpClient
.get(
'https://localhost:7071/api/Search?q=' + this.state.searchValue,
HttpClient.configurations.v1,
this.reqOptions)
.then(
(response: Response): Promise<HttpClientResponse> => {
console.log("REST API response received.");
return response.json();
})
.then(
(result: any) => {
console.log(result.data);
this.state.videos = result.data.map((videoResultItem) => {
var videoPlayerUri = videoResultItem.uri.replace('videos', 'video');
return {
title: videoResultItem.name,
description: videoResultItem.description,
url: 'https://player.vimeo.com' + videoPlayerUri,
author: videoResultItem.user,
pictures: videoResultItem.pictures
};
});
this.setState(this.state);
}
);
}
private onShowVideo(item) {
this.state.currentVideo = item;
this.state.playerHidden = 'show';
this.setState(
this.state
);
}
public onSave(item){
this.props.onSave(item);
}
public render(): React.ReactElement<IVimeoSearchProps> {
var videoResult = this.state.videos.map((item, index) => {
return (
<VimeoEntry
key={index}
item={item}
onShowVideo={this.onShowVideo.bind(this)}
/>
);
});
return (
<div>
<div className={styles.videoSearchBar}>
<TextField id="HelloWorld"
className={styles.n8dSearchbox}
value={this.state.searchValue}
onChanged={this.handleSearch.bind(this)}
autoComplete="off"
/>
<PrimaryButton text="Search" className={styles.n8dSearchbutton}
onClick={this.searchVideos}
/>
</div>
<VimeoPreviewPlayer playbackVideo={this.state.currentVideo} onAdd={this.onSave.bind(this)}></VimeoPreviewPlayer>
<div className={styles.videoSearchResult}>
{videoResult}
</div>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "VimeoUrl"
}
});

View File

@ -0,0 +1,10 @@
declare interface IVimeoWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'VimeoWebPartStrings' {
const strings: IVimeoWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
}
}