Initial Commit of Azure Function Web Part

This commit is contained in:
Stefan Bauer 2018-07-24 16:55:56 +02:00
parent 84a89814c9
commit e997302372
43 changed files with 18478 additions and 0 deletions

View File

@ -0,0 +1,99 @@
## Local Azure Function and SPFx Web Part Development
This sample shows how to consume third party APIs through an Azure Functions by a Web Part. In this scenario Vimeo was choose as an example for 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 web site [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 needs to be entered in the local Azure function configuration.
```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 an 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-azfunct-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,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,26 @@
## vimeo-web-part
This is where you include your WebPart documentation.
### Building the code
```bash
git clone the repo
npm i
npm i -g gulp
gulp
```
This package produces the following:
* lib/* - intermediate-stage commonjs build artifacts
* dist/* - the bundled script, along with other resources
* deploy/* - all resources which should be uploaded to a CDN.
### Build options
gulp clean - TODO
gulp test - TODO
gulp serve - TODO
gulp bundle - TODO
gulp package-solution - TODO

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"
]
}
}