Added Web Part from Olivier Carpentier around JQuery, Photopile.JS & Office UI Fabric Client-Side Web Part

This commit is contained in:
Vesa Juvonen 2016-09-17 19:02:35 +03:00
parent 05fcbeda4a
commit d6fbb21a4d
138 changed files with 34947 additions and 0 deletions

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 @@
* text=auto

35
samples/jquery-photopile/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# Logs
logs
*.log
npm-debug.log*
# Yeoman configuration files
.yo-rc.json
# 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

View File

@ -0,0 +1,14 @@
# Folders
.vscode
coverage
node_modules
solution
src
temp
# Files
*.csproj
.git*
.yo-rc.json
gulpfile.js
tsconfig.json

View File

@ -0,0 +1,21 @@
{
// The number of spaces a tab is equal to.
"editor.tabSize": 2,
// When enabled, will trim trailing whitespace when you save a file.
"files.trimTrailingWhitespace": true,
// Controls if the editor should automatically close brackets after opening them
"editor.autoClosingBrackets": false,
// Configure glob patterns for excluding files and folders.
"search.exclude": {
"**/bower_components": true,
"**/node_modules": true,
"coverage": true,
"dist": true,
"lib-amd": true,
"lib": true,
"temp": true
}
}

View File

@ -0,0 +1,34 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"command": "gulp",
"isShellCommand": true,
"showOutput": "always",
"args": [
"--no-color"
],
"tasks": [
{
"taskName": "bundle",
"isBuildCommand": true,
"problemMatcher": [
"$tsc"
]
},
{
"taskName": "test",
"isTestCommand": true,
"problemMatcher": [
"$tsc"
]
},
{
"taskName": "serve",
"isWatching": true,
"problemMatcher": [
"$tsc"
]
}
]
}

View File

@ -0,0 +1,63 @@
# JQuery, Photopile.JS & Office UI Fabric Client-Side Web Part
## Summary
Sample Web Part illustrating using JQuery and [Photopile.Js](https://github.com/bigbhowell/Photopile-JS)
with the SharePoint Framework.
With it, you can display the photos contained in a SharePoint Pictures Library and it
simulates a pile of photos scattered about on a surface. Thumbnail clicks remove photos from the pile,
(enlarging them as if being picked up by the user), and once in view a secondary click returns the photo to the pile.
![Photpile Web Part displayed in SharePoint Workbench](./assets/photopileoverview.gif)
## 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)
## Solution
Solution|Author(s)
--------|---------
jquery-photopile|Olivier Carpentier (@olivierc)
## Version history
Version|Date|Comments
-------|----|--------
1.0|September 9, 2016|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
- clone this repo
- in the command line run:
- `npm i`
- `tsd install`
- `gulp serve`
## Features
This web part uses React, Office UI Fabric, JQuery, JQuery UI and Photopile.js. This web part is available in English (en-us)
and French (fr-fr).
It is able to:
* List Picture Libs contained in the current SharePoint web site
* List all the pictures in the selected List
* Render the pics as a cool photopile
* Personalize the layout thanks to editable settings
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* Include JQuery and external framework in your solution
* Implement rich web part properties panel with controls like DropDown, Sliders, Toggle, etc.
* Load dynamic data from SharePoint as web part properties
* Load dynamic data from SharePoint REST Services, as lists or items
* Implement mock system to test your solution in the local workbench or on a SharePoint site
* Include Office UI Fabric controls in your project
* Render content with React
* Etc.

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

View File

@ -0,0 +1,35 @@
{
"entries": [
{
"entry": "./lib/webparts/photopileWebPart/PhotopileWebPartWebPart.js",
"manifest": "./src/webparts/photopileWebPart/PhotopileWebPartWebPart.manifest.json",
"outputPath": "./dist/photopile-web-part.bundle.js"
}
],
"externals": {
"@microsoft/sp-client-base": "node_modules/@microsoft/sp-client-base/dist/sp-client-base.js",
"@microsoft/sp-client-preview": "node_modules/@microsoft/sp-client-preview/dist/sp-client-preview.js",
"@microsoft/sp-lodash-subset": "node_modules/@microsoft/sp-lodash-subset/dist/sp-lodash-subset.js",
"office-ui-fabric-react": "node_modules/office-ui-fabric-react/dist/office-ui-fabric-react.js",
"react": "node_modules/react/dist/react.min.js",
"react-dom": "node_modules/react-dom/dist/react-dom.min.js",
"react-dom/server": "node_modules/react-dom/dist/react-dom-server.min.js",
"jquery": {
"path": "node_modules/jquery/dist/jquery.js",
"globalName": "$"
},
"jqueryui": {
"path": "node_modules/jqueryui/jquery-ui.js",
"globalName": "jqueryui",
"globalDependencies": ["jquery"]
},
"photopileModule": {
"path": "src/webparts/photopileWebPart/js/photopile.js",
"globalName": "photopileModule",
"globalDependencies": ["jquery", "jqueryui"]
}
},
"localizedResources": {
"mystrings": "webparts/photopileWebPart/loc/{locale}.js"
}
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "photopilewebpart",
"container": "photopile-web-part",
"accessKey": ""
}

View File

@ -0,0 +1,10 @@
{
"solution": {
"name": "Photopile WebPart",
"id": "22c50f5a-8015-406e-be1f-8cfcffc2ec2b",
"version": "1.0.0.0"
},
"paths": {
"zippedPackage": "photopile-web-part.spapp"
}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,8 @@
{
"port": 4321,
"initialPage": "http://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,3 @@
{
"textMatch": ["src/**/*.template.html"]
}

View File

@ -0,0 +1,26 @@
{
"lintConfig": {
"rules": {
"comment-format": false,
"curly": false,
"indent": false,
"interface-name": false,
"max-line-length": false,
"member-ordering": false,
"no-any": false,
"no-bitwise": false,
"no-consecutive-blank-lines": false,
"no-constant-condition": false,
"no-debugger": false,
"no-empty": false,
"no-null-keyword": false,
"no-string-literal": false,
"no-trailing-whitespace": false,
"no-var-keyword": false,
"one-line": false,
"quotemark": false,
"radix": false,
"triple-equals": false
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "//photopilewebpart.blob.core.windows.net/photopile-web-part/"
}

6
samples/jquery-photopile/gulpfile.js vendored Normal file
View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

View File

@ -0,0 +1,28 @@
{
"name": "photopile-web-part",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"@microsoft/sp-client-base": "~0.1.12",
"@microsoft/sp-client-preview": "~0.1.12",
"jquery": "^3.1.0",
"jqueryui": "^1.11.1",
"office-ui-fabric-react": "0.36.0",
"react": "0.14.8",
"react-dom": "0.14.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "~0.4.32",
"@microsoft/sp-module-interfaces": "~0.1.8",
"@microsoft/sp-webpart-workbench": "~0.1.12",
"gulp": "~3.9.1"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp nuke",
"test": "gulp test"
}
}

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="GulpToVs" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{22c50f5a-8015-406e-be1f-8cfcffc2ec2b}</ProjectGuid>
<ProjectHome />
<ProjectView>ProjectFiles</ProjectView>
<StartupFile>node_modules\gulp\bin\gulp.js</StartupFile>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
<TypeScriptSourceMap>true</TypeScriptSourceMap>
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
<EnableTypeScript>false</EnableTypeScript>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ScriptArguments>serve</ScriptArguments>
<StartWebBrowser>True</StartWebBrowser>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'" />
<PropertyGroup Condition="'$(Configuration)' == 'Release'" />
<Target Name="GulpToVs">
<Message Text="Running gulp2vs.js" Importance="normal" />
<Exec Command="CMD.EXE /c node $(MSBuildThisFileDirectory)\node_modules\@microsoft\npmx\lib\gulp2vs.js" />
</Target>
<ItemGroup>
<Content Include="*.js" />
<Content Include="*.json" />
<Content Include="*.md" />
<Content Include="config\**\*.json" />
<Content Include="docs\*.md" />
<Content Include="sharepoint\feature_xml\**\*.*" />
<Content Include="src\**\*.html" />
<Content Include="src\**\*.js" />
<Content Include="src\**\*.json" />
<Content Include="src\**\*.less" />
<Content Include="src\**\*.resx" />
<Content Include="src\**\*.scss" />
<Content Include="src\**\*.ts" />
<Content Include="src\**\*.tsx" />
<Content Include="typings\**\*.ts" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<!--Do not delete the following Import Project. While this appears to do nothing it is a marker for setting TypeScript properties before our import that depends on them.-->
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="False" />
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsTools.targets" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>0</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:48022/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
<WebProjectProperties>
<StartPageUrl>
</StartPageUrl>
<StartAction>CurrentPage</StartAction>
<AspNetDebugging>True</AspNetDebugging>
<SilverlightDebugging>False</SilverlightDebugging>
<NativeDebugging>False</NativeDebugging>
<SQLDebugging>False</SQLDebugging>
<ExternalProgram>
</ExternalProgram>
<StartExternalURL>
</StartExternalURL>
<StartCmdLineArguments>
</StartCmdLineArguments>
<StartWorkingDirectory>
</StartWorkingDirectory>
<EnableENC>False</EnableENC>
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

5
samples/jquery-photopile/src/tests.js vendored Normal file
View File

@ -0,0 +1,5 @@
var context = require.context('.', true, /.+\.test\.js?$/);
context.keys().forEach(context);
module.exports = context;

View File

@ -0,0 +1,10 @@
/**
* @file
* Photopile Web Part properties interface definition
*
* Author: Olivier Carpentier
*/
import { IPhotopileWebPartWebPartProps } from './IPhotopileWebPartWebPartProps';
export interface IPhotopileWebPartProps extends IPhotopileWebPartWebPartProps {
}

View File

@ -0,0 +1,31 @@
/**
* @file
* Photopile Web Part properties definition
*
* Author: Olivier Carpentier
*/
import { IWebPartContext } from '@microsoft/sp-client-preview';
export interface IPhotopileWebPartWebPartProps {
listName: string;
orderBy: string;
orderByAsc: string;
count: number;
numLayers: number;
thumbOverlap: number;
thumbRotation: number;
thumbBorderWidth: number;
thumbBorderColor: string;
thumbBorderHover: string;
draggable: boolean;
fadeDuration: number;
pickupDuration: number;
photoZIndex: number;
photoBorder: number;
photoBorderColor: string;
showInfo: boolean;
autoplayGallery: boolean;
autoplaySpeed: number;
context: IWebPartContext;
}

View File

@ -0,0 +1,53 @@
/**
* @file
* SharePoint List & ListItems interface definitions
*
* Author: Olivier Carpentier
*/
/**
* @interface
* Defines a collection of SharePoint lists
*/
export interface ISPLists {
value: ISPList[];
}
/**
* @interface
* Defines a SharePoint list
*/
export interface ISPList {
Title: string;
Id: string;
BaseTemplate: string;
}
/**
* @interface
* Defines a SharePoint list's file
*/
export interface ISPFile {
Name: string;
ServerRelativeUrl: string;
ThumbnailServerUrl?: string;
}
/**
* @interface
* Defines a collection of SharePoint list items
*/
export interface ISPListItems {
value: ISPListItem[];
}
/**
* @interface
* Defines a SharePoint list item
*/
export interface ISPListItem {
ID: string;
Title?: string;
Description?: string;
File: ISPFile;
}

View File

@ -0,0 +1,49 @@
/**
* @file
* Implement a http client to request mock data to use the
* web part with the local workbench
*
* Author: Olivier Carpentier
*/
import { ISPList, ISPListItem } from './ISPList';
/**
* @class
* Defines a http client to request mock data to use the web part with the local workbench
*/
export default class MockHttpClient {
/**
* @var
* Mock SharePoint list sample
*/
private static _lists: ISPList[] = [{ Title: 'Mock List', Id: '1', BaseTemplate: '109' }];
/**
* @var
* Mock SharePoint list item sample
*/
private static _items: ISPListItem[] = [
{ "ID": "1", "Title": "Pic 1", "Description": "", "File": { "Name": "1.jpg", "ServerRelativeUrl": "/Images/1.jpg" } }
];
/**
* @function
* Mock get SharePoint list request
*/
public static getLists(restUrl: string, options?: any): Promise<ISPList[]> {
return new Promise<ISPList[]>((resolve) => {
resolve(MockHttpClient._lists);
});
}
/**
* @function
* Mock get SharePoint list items request
*/
public static getListsItems(restUrl: string, options?: any): Promise<ISPListItem[]> {
return new Promise<ISPListItem[]>((resolve) => {
resolve(MockHttpClient._items);
});
}
}

View File

@ -0,0 +1,21 @@
.photopileWebPart {
.workingOnItSpinner {
display: block;
top: 0;
bottom: 0;
margin: auto;
height: 33.33333%;
line-height: 1.5em;
padding: 20px 20px;
}
.loadingLabel {
position: relative;
top: -25px;
left: 35px;
}
}

View File

@ -0,0 +1,37 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "4b1150be-30a9-42a7-9bd4-55d50bbd9473",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,
"preconfiguredEntries": [{
"groupId": "4b1150be-30a9-42a7-9bd4-55d50bbd9473",
"group": { "default": "Default" },
"title": { "default": "Photopile" },
"description": { "default": "PhotopileWebPart description" },
"officeFabricIconFontName": "PhotoCollection",
"properties": {
"listName": "",
"orderBy": "ID",
"orderByAsc": "asc",
"count": 100,
"numLayers": 5,
"thumbOverlap": 50,
"thumbRotation": 45,
"thumbBorderWidth": 2,
"thumbBorderColor": "white",
"thumbBorderHover": "#EAEAEA",
"draggable": true,
"fadeDuration": 200,
"pickupDuration": 500,
"photoZIndex": 100,
"photoBorder": 10,
"photoBorderColor": "white",
"showInfo": true,
"autoplayGallery": false,
"autoplaySpeed": 5000
}
}]
}

View File

@ -0,0 +1,259 @@
/**
* @file
* Defines the Photopile client side web part
*
* Author: Olivier Carpentier
*/
import * as React from 'react';
import * as ReactDom from 'react-dom';
import {
BaseClientSideWebPart,
IPropertyPaneSettings,
IWebPartContext,
PropertyPaneTextField,
PropertyPaneToggle,
PropertyPaneDropdown,
IPropertyPaneDropdownOption,
PropertyPaneSlider
} from '@microsoft/sp-client-preview';
import * as strings from 'mystrings';
import { IPhotopileWebPartProps } from './IPhotopileWebPartProps';
import PhotopileWebPart from './components/PhotopileWebPart';
import { IPhotopileWebPartWebPartProps } from './IPhotopileWebPartWebPartProps';
import { SPPicturesListService } from './SPPicturesListService';
import { ISPList } from './ISPList';
/**
* @class
* Defines the Photopile client side web part
*/
export default class PhotopileWebPartWebPart extends BaseClientSideWebPart<IPhotopileWebPartWebPartProps> {
/**
* @var
* Stores the list of SharePoint Pictures library found in the current SP web
*/
private listsDropdownOptions: IPropertyPaneDropdownOption[] = [];
/**
* @function
* Web Part constructor
*/
public constructor(context: IWebPartContext) {
super(context);
}
/**
* @function
* Function called when the web part is inialized
*/
public onInit<T>(): Promise<T> {
//Init the PicturesListService to get the picture libs
const picturesListService: SPPicturesListService = new SPPicturesListService(this.properties, this.context);
//Request the libs
picturesListService.getPictureLibs()
.then((response) => {
//Store the result as list of dropdown options
this.listsDropdownOptions = response.value.map((list: ISPList) => {
return {
key: list.Id,
text: list.Title
};
});
});
return Promise.resolve();
}
/**
* @function
* Renders the web part
*/
public render(): void {
//Constructs the react element code to JSX
const element: React.ReactElement<IPhotopileWebPartProps> = React.createElement(PhotopileWebPart, {
listName: this.properties.listName,
orderBy: this.properties.orderBy,
orderByAsc: this.properties.orderByAsc,
count: this.properties.count,
numLayers: this.properties.numLayers,
thumbOverlap: this.properties.thumbOverlap,
thumbRotation: this.properties.thumbRotation,
thumbBorderWidth: this.properties.thumbBorderWidth,
thumbBorderColor: this.properties.thumbBorderColor,
thumbBorderHover: this.properties.thumbBorderHover,
draggable: this.properties.draggable,
fadeDuration: this.properties.fadeDuration,
pickupDuration: this.properties.pickupDuration,
photoZIndex: this.properties.photoZIndex,
photoBorder: this.properties.photoBorder,
photoBorderColor: this.properties.photoBorderColor,
showInfo: this.properties.showInfo,
autoplayGallery: this.properties.autoplayGallery,
autoplaySpeed: this.properties.autoplaySpeed,
context: this.context
});
//Render the dom
ReactDom.render(element, this.domElement);
}
/**
* @function
* Prevent from changing the pane properties on typing
*/
protected get disableReactivePropertyChanges(): boolean {
return false;
}
/**
* @function
* Gets the web part properties panel settings
*/
protected get propertyPaneSettings(): IPropertyPaneSettings {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
//Display the web part properties as accordion
displayGroupsAsAccordion: true,
groups: [
{
groupName: strings.PictureLibraryGroupName,
groupFields: [
PropertyPaneDropdown('listName', {
label: strings.PictureLibraryFieldLabel,
options: this.listsDropdownOptions
}),
PropertyPaneDropdown('orderBy', {
label: strings.OrderByFieldLabel,
options: [
{ key: 'ID', text: strings.OrderByChoiceLabelId },
{ key: 'Title', text: strings.OrderByChoiceLabelTitle },
{ key: 'Created', text: strings.OrderByChoiceLabelCreated },
{ key: 'Modified', text: strings.OrderByChoiceLabelModified },
{ key: 'ImageWidth', text: strings.OrderByChoiceLabelImageWidth },
{ key: 'ImageHeight', text: strings.OrderByChoiceLabelImageHeight }
]
}),
PropertyPaneDropdown('orderByAsc', {
label: strings.OrderByAscFieldLabel,
options: [
{ key: 'asc', text: strings.OrderByAscChoiceLabel },
{ key: 'desc', text: strings.OrderByDescChoiceLabel }
]
}),
PropertyPaneSlider('count', {
label: strings.PictureLibraryCountLabel,
min: 1,
max: 100,
step: 1,
showValue: true
})
]
},
{
groupName: strings.ThumbnailsGroupName,
groupFields: [
PropertyPaneSlider('numLayers', {
label: strings.NumLayersFieldLabel,
min: 1,
max: 20,
step: 1,
showValue: true
}),
PropertyPaneSlider('thumbOverlap', {
label: strings.ThumbOverlabFieldLabel,
min: 1,
max: 130,
step: 1,
showValue: true
}),
PropertyPaneSlider('thumbRotation', {
label: strings.ThumbRotationFieldLabel,
min: 0,
max: 360,
step: 1,
showValue: true
}),
PropertyPaneSlider('thumbBorderWidth', {
label: strings.ThumbBorderWidthFieldLabel,
min: 0,
max: 50,
step: 1,
showValue: true
}),
PropertyPaneTextField('thumbBorderColor', {
label: strings.ThumbBorderColorFieldLabel
}),
PropertyPaneTextField('thumbBorderHover', {
label: strings.ThumbBorderHoverFieldLabel
}),
PropertyPaneToggle('draggable', {
label: strings.DraggableFieldLabel
})
]
},
{
groupName: strings.PhotoContainerGroupName,
groupFields: [
PropertyPaneSlider('fadeDuration', {
label: strings.FadeDurationFieldLabel,
min: 0,
max: 5000,
step: 100,
showValue: true
}),
PropertyPaneSlider('pickupDuration', {
label: strings.PickupDurationFieldLabel,
min: 0,
max: 5000,
step: 100,
showValue: true
}),
PropertyPaneSlider('photoZIndex', {
label: strings.PhotoZIndexFieldLabel,
min: 1,
max: 1000,
step: 1,
showValue: true
}),
PropertyPaneSlider('photoBorder', {
label: strings.PhotoBorderFieldLabel,
min: 0,
max: 50,
step: 1,
showValue: true
}),
PropertyPaneTextField('photoBorderColor', {
label: strings.PhotoBorderColorFieldLabel
}),
PropertyPaneToggle('showInfo', {
label: strings.ShowInfoFieldLabel
})
]
},
{
groupName: strings.AutoplayGroupName,
groupFields: [
PropertyPaneToggle('autoplayGallery', {
label: strings.AutoplayGalleryFieldLabel
}),
PropertyPaneSlider('autoplaySpeed', {
label: strings.AutoplaySpeedFieldLabel,
min: 0,
max: 5000,
step: 100,
showValue: true
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,360 @@
/**
* @file
* Service to get list & list items from current SharePoint site
*
* Author: Olivier Carpentier
*/
import { ISPLists, ISPListItems, ISPListItem } from './ISPList';
import { IWebPartContext } from '@microsoft/sp-client-preview';
import { IPhotopileWebPartProps } from './IPhotopileWebPartProps';
import { EnvironmentType } from '@microsoft/sp-client-base';
import MockHttpClient from './MockHttpClient';
/**
* @interface
* Service interface definition
*/
export interface ISPPicturesListService {
/**
* @function
* Gets the list of picture libs in the current SharePoint site
*/
getPictureLibs(): Promise<ISPLists>;
/**
* @function
* Gets the pictures from a SharePoint list
*/
getPictures(libId: string): Promise<ISPListItems>;
}
/**
* @class
* Service implementation to get list & list items from current SharePoint site
*/
export class SPPicturesListService implements ISPPicturesListService {
private context: IWebPartContext;
private props: IPhotopileWebPartProps;
/**
* @function
* Service constructor
*/
constructor(_props: IPhotopileWebPartProps, pageContext: IWebPartContext){
this.props = _props;
this.context = pageContext;
}
/**
* @function
* Gets the list of picture libs in the current SharePoint site
*/
public getPictureLibs(): Promise<ISPLists> {
if (this.context.environment.type === EnvironmentType.Local) {
//If the running environment is local, load the data from the mock
return this.getPictureLibsFromMock();
}
else {
//If the running environment is SharePoint, request the lists REST service
//Gets only the list with BaseTemplate = 109 (picture libs)
return this.context.httpClient.get(
`${this.context.pageContext.web.absoluteUrl}/_api/lists?$select=Title,id,BaseTemplate&$filter=BaseTemplate%20eq%20109`)
.then((response: Response) => {
return response.json();
});
}
}
/**
* @function
* Returns 3 fake SharePoint lists for the Mock mode
*/
private getPictureLibsFromMock(): Promise<ISPLists> {
return MockHttpClient.getLists(this.context.pageContext.web.absoluteUrl).then(() => {
const listData: ISPLists = {
value:
[
{ Title: 'Mock List One', Id: '1', BaseTemplate: '109' },
{ Title: 'Mock List Two', Id: '2', BaseTemplate: '109' },
{ Title: 'Mock List Three', Id: '3', BaseTemplate: '109' }
]
};
return listData;
}) as Promise<ISPLists>;
}
/**
* @function
* Gets the pictures from a SharePoint list
*/
public getPictures(libId: string): Promise<ISPListItems> {
if (this.context.environment.type === EnvironmentType.Local) {
//If the running environment is local, load the data from the mock
return this.getPicturesFromMock(libId);
}
else {
//If the running environment is SharePoint, request the items REST service
//Builds the request to get only some fields, order the items & limit the number of items
//TODO: optimize the request to not include folders and get only items
var restUrl: string = this.context.pageContext.web.absoluteUrl;
restUrl += "/_api/Web/Lists(guid'";
restUrl += this.props.listName;
restUrl += "')/items?$expand=File&$select=Title,Description,id,File,FileSystemObjectType&$orderby=";
restUrl += this.props.orderBy;
restUrl += "%20";
restUrl += this.props.orderByAsc;
restUrl += "&$top=";
restUrl += this.props.count;
//Request the SharePoint web service
return this.context.httpClient.get(restUrl).then((response: Response) => {
return response.json().then((responseFormated: any) => {
var formatedResponse: ISPListItems = { value: []};
//Fetchs the Json response to construct the final items list
responseFormated.value.map((object: any, i: number) => {
//Tests if the result is a file and not a folder
if (object['FileSystemObjectType'] == '0') {
var spListItem: ISPListItem = {
'ID': object["ID"],
'Title': object['Title'],
'Description': object['Description'],
'File': {
'Name': object['File']['Name'],
'ServerRelativeUrl': object['File']['ServerRelativeUrl']
}
};
//Creates the thumbnail item url from the Picture path
spListItem.File.ThumbnailServerUrl = this.getThumbnailUrl(spListItem.File.ServerRelativeUrl, spListItem.File.Name);
formatedResponse.value.push(spListItem);
}
});
return formatedResponse;
});
}) as Promise<ISPListItems>;
}
}
/**
* @function
* Gets the thumbnail picture url from the Picture name.
* In SharePoint pictures libs, the thumbnail url is formated as for example '/_t/10_jpg.jpg'
*/
private getThumbnailUrl(pictureUrl: string, pictureName: string): string {
if (pictureUrl == null || pictureUrl == '')
return '';
var thumbUrl: string = '';
thumbUrl = pictureUrl.replace(pictureName, '');
thumbUrl += "_t/";
thumbUrl += pictureName.replace(".", "_");
thumbUrl += ".jpg";
return thumbUrl;
}
/**
* @function
* Gets the pictures list from the mock. This function will return a
* different list of pics for the lib 1 & 2, and an empty list for the third.
*/
private getPicturesFromMock(libId: string): Promise<ISPListItems> {
return MockHttpClient.getListsItems(this.context.pageContext.web.absoluteUrl).then(() => {
var listData: ISPListItems = { value: []};
if (libId == '1') {
listData = {
value:
[
{
"ID": "1", "Title": "Barton Dam, Ann Arbor, Michigan", "Description": "",
"File":
{
"Name": "01.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/01.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/01.jpg"
}
},
{
"ID": "2", "Title": "Building Atlanta, Georgia", "Description": "",
"File":
{
"Name": "02.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/02.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/02.jpg"
}
},
{
"ID": "3", "Title": "Nice day for a swim", "Description": "",
"File":
{
"Name": "03.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/03.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/03.jpg"
}
},
{
"ID": "4", "Title": "The plants that never die", "Description": "",
"File":
{
"Name": "04.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/04.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/04.jpg"
}
},
{
"ID": "5", "Title": "Downtown Atlanta, Georgia", "Description": "",
"File":
{
"Name": "05.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/05.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/05.jpg"
}
},
{
"ID": "6", "Title": "Atlanta traffic", "Description": "",
"File":
{
"Name": "06.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/06.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/06.jpg"
}
},
{
"ID": "7", "Title": "A pathetic dog", "Description": "",
"File":
{
"Name": "07.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/07.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/07.jpg"
}
},
{
"ID": "8", "Title": "Two happy dogs", "Description": "",
"File":
{
"Name": "08.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/08.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/08.jpg"
}
},
{
"ID": "9", "Title": "Antigua, Guatemala", "Description": "",
"File":
{
"Name": "09.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/09.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/09.jpg"
}
},
{
"ID": "10", "Title": "Iximche, Guatemala", "Description": "",
"File":
{
"Name": "10.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/10.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/10.jpg"
}
}
]
};
}
else if (libId == '2') {
listData = {
value:
[
{
"ID": "11", "Title": "Barton Dam, Ann Arbor, Michigan", "Description": "",
"File":
{
"Name": "11.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/11.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/11.jpg"
}
},
{
"ID": "12", "Title": "Building Atlanta, Georgia", "Description": "",
"File":
{
"Name": "12.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/12.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/12.jpg"
}
},
{
"ID": "13", "Title": "Nice day for a swim", "Description": "",
"File":
{
"Name": "13.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/13.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/13.jpg"
}
},
{
"ID": "14", "Title": "The plants that never die", "Description": "",
"File":
{
"Name": "14.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/14.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/14.jpg"
}
},
{
"ID": "15", "Title": "Downtown Atlanta, Georgia", "Description": "",
"File":
{
"Name": "15.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/15.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/15.jpg"
}
},
{
"ID": "16", "Title": "Atlanta traffic", "Description": "",
"File":
{
"Name": "16.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/16.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/16.jpg"
}
},
{
"ID": "17", "Title": "A pathetic dog", "Description": "",
"File":
{
"Name": "17.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/17.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/17.jpg"
}
},
{
"ID": "18", "Title": "Two happy dogs", "Description": "",
"File":
{
"Name": "18.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/18.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/18.jpg"
}
},
{
"ID": "19", "Title": "Antigua, Guatemala", "Description": "",
"File":
{
"Name": "19.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/19.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/19.jpg"
}
},
{
"ID": "20", "Title": "Iximche, Guatemala", "Description": "",
"File":
{
"Name": "20.jpg",
"ServerRelativeUrl": "../src/webparts/photopileWebPart/images/fullsize/20.jpg",
"ThumbnailServerUrl": "../src/webparts/photopileWebPart/images/thumbs/20.jpg"
}
}
]
};
}
return listData;
}) as Promise<ISPListItems>;
}
}

View File

@ -0,0 +1,201 @@
/**
* @file
* Photopile Web Part React JSX component.
*
* Contains JSX code to render the web part with HTML templates.
*
* Author: Olivier Carpentier
*/
import * as React from 'react';
import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner';
import { IPhotopileWebPartProps } from '../IPhotopileWebPartProps';
import { IWebPartContext } from '@microsoft/sp-client-preview';
import * as strings from 'mystrings';
import styles from '../PhotopileWebPart.module.scss';
import { SPPicturesListService } from '../SPPicturesListService';
import { ISPListItem } from '../ISPList';
import * as photopile from 'photopileModule';
require('jquery');
require('jqueryui');
require('../css/photopile.scss');
require('photopileModule');
/**
* @interface
* Defines Photopile web part state.
*/
export interface IPhotopileState {
results?: ISPListItem[];
loaded: boolean;
}
/**
* @class
* Defines Photopile web part class.
*/
export default class PhotopileWebPart extends React.Component<IPhotopileWebPartProps, IPhotopileState> {
//page context
private myPageContext: IWebPartContext;
/**
* @function
* Photopile web part contructor.
*/
constructor(props: IPhotopileWebPartProps, context: IWebPartContext) {
super(props, context);
//Save the context
this.myPageContext = props.context;
//Init the component state
this.state = {
results: [],
loaded: false
};
};
/**
* @function
* JSX Element render method
*/
public render(): JSX.Element {
if (this.props.listName == null || this.props.listName == '') {
//Display select a list message
return (
<div className="ms-MessageBar">
<div className="ms-MessageBar-content">
<div className="ms-MessageBar-icon">
<i className="ms-Icon ms-Icon--infoCircle"></i>
</div>
<div className="ms-MessageBar-text">
{strings.ErrorSelectList}
</div>
</div>
</div>
);
}
else {
if (this.state.loaded == false) {
//Display the loading spinner with the Office UI Fabric Spinner control
return (
<div className={ styles.photopileWebPart }>
<div className={ styles.workingOnItSpinner }>
<Spinner type={ SpinnerType.normal } />
<div className={ styles.loadingLabel }>
<label className="ms-Label"> {strings.Loading}</label>
</div>
</div>
</div>
);
}
else if (this.state.results.length == 0) {
//Display message no items
return (
<div className="ms-MessageBar ms-MessageBar--error">
<div className="ms-MessageBar-content">
<div className="ms-MessageBar-icon">
<i className="ms-Icon ms-Icon--xCircle"></i>
</div>
<div className="ms-MessageBar-text">
{strings.ErrorNoItems}
</div>
</div>
</div>
);
}
else {
//Display the items list
return (
<div className='photopile-wrapper'>
<ul className='photopile'>
{this.state.results.map((object:ISPListItem, i:number) => {
//Select the best Alt text with title, description or file's name
var altText: string = object.Title;
if (altText == null || altText == '')
altText = object.Description;
if (altText == null || altText == '')
altText = object.File.Name;
//Render the item
return (
<li>
<a href={object.File.ServerRelativeUrl}>
<img src={object.File.ThumbnailServerUrl} alt={altText} width="133" height="100"/>
</a>
</li>
);
})}
</ul>
</div>
);
}
}
}
/**
* @function
* Function called when the component did mount
*/
public componentDidMount(): void {
if (this.props.listName != null && this.props.listName != '') {
//Init the Picture list service
const picturesListService: SPPicturesListService = new SPPicturesListService(this.props, this.myPageContext);
//Load the list of pictures from the current lib
picturesListService.getPictures(this.props.listName)
.then((response) => {
//Modify the component state with the json result
this.setState({ results: response.value, loaded: true});
});
}
}
/**
* @function
* Function called when the web part properties has changed
*/
public componentWillReceiveProps(nextProps: IPhotopileWebPartProps): void {
//Define the state with empty results
this.setState({ results: [], loaded: false});
if (nextProps.listName != null && nextProps.listName != '') {
//Init the Picture list service
const picturesListService: SPPicturesListService = new SPPicturesListService(nextProps, this.myPageContext);
//Load the list of pictures from the current lib
picturesListService.getPictures(nextProps.listName)
.then((response) => {
//Modify the component state with the json result
this.setState({ results: response.value, loaded: true});
});
}
}
/**
* @function
* Function called when the component has been rendered (ie HTML code is ready)
*/
public componentDidUpdate(prevProps: IPhotopileWebPartProps, prevState: IPhotopileState): void {
if (this.state.loaded) {
//Init photopile options
photopile.setNumLayers(this.props.numLayers);
photopile.setThumbOverlap(this.props.thumbOverlap);
photopile.setThumbRotation(this.props.thumbRotation);
photopile.setThumbBorderWidth(this.props.thumbBorderWidth);
photopile.setThumbBorderColor(this.props.thumbBorderColor);
photopile.setThumbBorderHover(this.props.thumbBorderHover);
photopile.setDraggable(this.props.draggable);
photopile.setFadeDuration(this.props.fadeDuration);
photopile.setPickupDuration(this.props.pickupDuration);
photopile.setPhotoZIndex(this.props.photoZIndex);
photopile.setPhotoBorder(this.props.photoBorder);
photopile.setPhotoBorderColor(this.props.photoBorderColor);
photopile.setShowInfo(this.props.showInfo);
photopile.setAutoplayGallery(this.props.autoplayGallery);
photopile.setAutoplaySpeed(this.props.autoplaySpeed);
//Init photopile
photopile.scatter();
}
}
}

View File

@ -0,0 +1,143 @@
/*
* Photopile image gallery base styles
*
* Auth: Brian W. Howell
* Date: 25 April 2014
*
*/
/*-----------------------------------------------------------------------------
* THUMBNAIL SCALING
* As window size gets smaller, reduce the maximum thumbnail width.
* By doing so we can maintain the photopile effect for all screen resolutions.
* It is highly likely that you'll want to customize these values based on
* your personal preferences and the size of your thumbnails.
*----------------------------------------------------------------------------*/
@media (max-width: 320px) { ul.photopile li a { max-width: 85px; }}
@media (min-width: 321px) and (max-width: 568px) { ul.photopile li a { max-width: 100px; }}
@media (min-width: 569px) and (max-width: 768px) { ul.photopile li a { max-width: 115px; }}
@media (min-width: 769px) and (max-width: 1024px) { ul.photopile li a { max-width: 125px; }}
@media (min-width: 1025px) { ul.photopile li a { max-width: 150px; }}
/*-----------------------------------------------------------------------------
* Minimum height of the photopile's container div.
* This is a placeholder for the gallery while it loads which reduces
* shuffling around of elements before the gallery has rendered.
*----------------------------------------------------------------------------*/
div.photopile-wrapper {
/*min-height : 500px;*/
}
/*-----------------------------------------------------------------------------
* Make sure path to navigation sprite is correct
*----------------------------------------------------------------------------*/
div#photopile-nav-next,
div#photopile-nav-next:hover,
div#photopile-nav-prev,
div#photopile-nav-prev:hover {
background-image: url('//photopilewebpart.blob.core.windows.net/photopile-web-part/nav-sprites.png');
}
/*----- end customization -----*/
/* Prevent FOUC */
ul.photopile {
display: none;
}
/* Thumbnails */
ul.photopile {
position: relative;
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
list-style: none;
}
ul.photopile li {
display: inline-block;
position: relative;
margin: 2px;
padding: 0;
-webkit-backface-visibility: hidden;
}
ul.photopile li a {
display: block;
padding: 2px;
outline: none;
text-decoration: none;
border: 1px solid #6F6F6F;
box-shadow: 0 0 20px #3D3D3D;
}
ul.photopile li.photopile-active-thumbnail:hover,
ul.photopile li.photopile-active-thumbnail a:hover {
cursor: default;
}
ul.photopile li a img {
display: block;
margin: 0;
padding: 0;
border: 1px solid #6F6F6F;
width: 100%;
height: auto;
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
box-sizing:border-box;
}
/* Photo container */
div#photopile-active-image-container {
border: 1px solid #6F6F6F;
box-shadow: 0 20px 80px black;
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
box-sizing:border-box;
}
div#photopile-active-image-container img {
margin: 0 auto;
height: auto;
}
div#photopile-active-image-info {
position: relative;
width: 100%;
background: rgba(0,0,0,0.3);
}
div#photopile-active-image-info p {
color: white;
font-size: 12px;
margin: 0;
padding: 3px 8px;
}
/* Navigator */
div#photopile-nav-next,
div#photopile-nav-prev {
opacity: 0;
position: absolute;
top: 50%;
width: 30px;
height: 40px;
margin-top: -20px;
cursor: pointer;
}
div#photopile-nav-next {
right: 0;
margin-right: -35px;
background-position: -50px 0;
}
div#photopile-nav-next:hover {
background-position: -50px -50px;
}
div#photopile-nav-prev {
left: 0;
right: 0;
margin-left: -35px;
background-position: 0 0;
}
div#photopile-nav-prev:hover {
background-position: 0 -50px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Some files were not shown because too many files have changed in this diff Show More