Add react-file-upload sample (#415)

* Adds SPFx react slide swiper sample (#412)

* Add files via upload

Add initial files to the repository

* Add package-lock.json to react-file-upload

it will fix the this error when you run Gulp serve:
SPFileUpload-master/node_modules/react-dropzone-component/typescript/types"' has no default export.
This commit is contained in:
Ramin Ahmadi 2018-02-26 16:30:21 +03:30 committed by Vesa Juvonen
parent c177a89297
commit a342425d05
40 changed files with 17411 additions and 0 deletions

View File

@ -0,0 +1,61 @@
# React File Upload WebPart
## Summary
The file upload web part allowing users to upload multiple files to a document library or as item attachments.
![File upload web part built on the SharePoint Framework using React](./assets/SPFileUploadPreview.gif)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-GA-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
> Update accordingly as needed.
## Prerequisites
> Site Collection created under the **/sites/** Managed Path
> Existing document library or a list
## Solution
Solution|Author(s)
--------|---------
react-file-upload|Ramin Ahmadi
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|February 14, 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.**
---
## Minimal Path to Awesome
- Clone this repository
- in the command line run:
- `npm install`
- `gulp serve`
## Features
This project contains sample client-side web parts built on the SharePoint Framework using React illustrating working with file upload web part.
This sample illustrates the following concepts on top of the SharePoint Framework:
- using React for building SharePoint Framework client-side web parts
- using React components for building file upload web part
- using [DropzoneJs](http://www.dropzonejs.com/) for uploading files
- uploading files to a document library
- uploading files as item attachments by getting the item ID from the query string parameter
- uploading files using RestAPI
- drag and drop feature for uploading files
- ability to remove uploaded files
- ability limit users to upload accepted file types
- using sp-pnp-js to delete uploaded files
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

View File

@ -0,0 +1,18 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"file-upload-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/fileUpload/FileUploadWebPart.js",
"manifest": "./src/webparts/fileUpload/FileUploadWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"FileUploadWebPartStrings": "lib/webparts/fileUpload/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": "dixons-carphone",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "SPFileUpload-client-side-solution",
"id": "297016d1-8144-431f-87e5-fe5272d0a226",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true
},
"paths": {
"zippedPackage": "solution/SPFileUpload.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 -->"
}

7
samples/react-file-upload/gulpfile.js vendored Normal file
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);

15371
samples/react-file-upload/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
{
"name": "dixons-carphone",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "~1.4.0",
"@microsoft/sp-lodash-subset": "~1.4.0",
"@microsoft/sp-office-ui-fabric-core": "~1.4.0",
"@microsoft/sp-webpart-base": "~1.4.0",
"@types/react": "15.6.6",
"@types/react-dom": "15.5.6",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"react": "15.6.2",
"react-dom": "15.6.2",
"react-dropzone-component": "^3.0.0",
"sp-pnp-js": "^3.0.4"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.0",
"@microsoft/sp-module-interfaces": "~1.4.0",
"@microsoft/sp-webpart-workbench": "~1.4.0",
"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 @@
export * from './common/propertyFieldHeader/index';

View File

@ -0,0 +1 @@
export * from './propertyFields/listPicker/index';

View File

@ -0,0 +1,51 @@
import * as React from 'react';
/**
* Enum to describe possible events to show callout
*/
export enum CalloutTriggers {
Click = 1,
Hover
}
/**
* Interface that discibes available settings of Header callout
*/
export interface IPropertyFieldHeaderCalloutProps {
/**
* Callout content - any HTML
*/
calloutContent?: React.ReactNode;
/**
* Custom width for callout including borders. If value is 0, no width is applied.
*/
calloutWidth?: number;
/**
* Event to show the callout
*/
calloutTrigger?: CalloutTriggers;
/**
* The gap between the Callout and the target
*/
gapSpace?: number;
}
/**
* PropertyFieldHeader component props
*/
export interface IPropertyFieldHeaderProps extends IPropertyFieldHeaderCalloutProps {
/**
* The label to be shown in the header
*/
label?: string;
}
/**
* PropertyFieldHeader component state
*/
export interface IPropertyFieldHeaderState {
/**
* Flag if the callout is currently visible
*/
isCalloutVisible?: boolean;
}

View File

@ -0,0 +1,20 @@
.headerBar {
position: relative;
margin-bottom: 5px;
.header {
margin-right: 24px;
}
.info {
position: absolute;
font-size: 14px;
background-color: transparent;
top: 3px;
right: 0px;
cursor: pointer;
}
}
.headerCallout {
padding: 10px;
}

View File

@ -0,0 +1,11 @@
/* tslint:disable */
require('./PropertyFieldHeader.module.css');
const styles = {
headerBar: 'headerBar_de667ef4',
header: 'header_de667ef4',
info: 'info_de667ef4',
headerCallout: 'headerCallout_de667ef4',
};
export default styles;
/* tslint:enable */

View File

@ -0,0 +1,101 @@
import * as React from 'react';
import { IconButton, Callout, DirectionalHint } from 'office-ui-fabric-react';
import { IPropertyFieldHeaderProps, IPropertyFieldHeaderState, CalloutTriggers } from './IPropertyFieldHeader';
import styles from './PropertyFieldHeader.module.scss';
/**
* PropertyFieldHeader component.
* Displays a label and a callout
*/
export default class PropertyFieldHeader extends React.Component<IPropertyFieldHeaderProps, IPropertyFieldHeaderState> {
private _infoIcon: HTMLElement;
public constructor(props: IPropertyFieldHeaderProps, state: IPropertyFieldHeaderState) {
super(props, state);
this._onCalloutDismiss = this._onCalloutDismiss.bind(this);
this.state = {
isCalloutVisible: false
};
}
public render(): JSX.Element {
return (
<div className={styles.headerBar}>
<div className={styles.header}>
{this.props.label}
</div>
<div className={styles.info}>
<i className={'ms-Icon ms-Icon--Info'} ref={(infoIcon) => { this._infoIcon = infoIcon; }}
onMouseOver={this.props.calloutTrigger === CalloutTriggers.Hover ? this._onInfoIconMouseOver.bind(this) : null}
onMouseOut={this.props.calloutTrigger === CalloutTriggers.Hover ? this._onInfoIconMouseOut.bind(this) : null}
onClick={this.props.calloutTrigger === CalloutTriggers.Click ? this._onInfoIconClick.bind(this) : null}></i>
</div>
{this.state.isCalloutVisible && (
<Callout
className={styles.headerCallout}
target={this._infoIcon}
isBeakVisible={true}
directionalHint={DirectionalHint.leftCenter}
directionalHintForRTL={DirectionalHint.rightCenter}
onDismiss={this._onCalloutDismiss}
gapSpace={this.props.gapSpace !== undefined ? this.props.gapSpace : 5}
calloutWidth={this.props.calloutWidth}>
{this.props.calloutContent}
</Callout>
)
}
</div>);
}
private _onCalloutDismiss() {
if (this.state.isCalloutVisible) {
this.setState({
isCalloutVisible: false
});
}
}
private _onInfoIconMouseOver(): void {
if (this.props.calloutTrigger !== CalloutTriggers.Hover) {
return;
}
if (!this.state.isCalloutVisible) {
this.setState({
isCalloutVisible: true
});
}
}
private _onInfoIconMouseOut(e: MouseEvent): void {
if (this.props.calloutTrigger !== CalloutTriggers.Hover) {
return;
}
if (e.relatedTarget) {
let relatedTarget: HTMLElement = (e.relatedTarget as HTMLElement);
if (relatedTarget && relatedTarget.closest('.ms-Callout-container')) {
return;
}
}
this.setState({
isCalloutVisible: false
});
}
private _onInfoIconClick(): void {
if (this.props.calloutTrigger !== CalloutTriggers.Click) {
return;
}
this.setState({
isCalloutVisible: !this.state.isCalloutVisible
});
}
}

View File

@ -0,0 +1,2 @@
export * from './IPropertyFieldHeader';
export * from './PropertyFieldHeader';

View File

@ -0,0 +1,26 @@
import * as React from 'react';
export interface IFieldErrorMessageProps {
errorMessage: string;
}
/**
* Component that shows an error message when something went wront with the property control
*/
export default class FieldErrorMessage extends React.Component<IFieldErrorMessageProps> {
public render(): JSX.Element {
if (this.props.errorMessage !== 'undefined' && this.props.errorMessage !== null && this.props.errorMessage !== '') {
return (
<div style={{ paddingBottom: '8px' }}><div aria-live='assertive' className='ms-u-screenReaderOnly' data-automation-id='error-message'>{this.props.errorMessage}</div>
<span>
<p className='ms-TextField-errorMessage ms-u-slideDownIn20'>{this.props.errorMessage}</p>
</span>
</div>
);
} else {
return <div />;
}
}
}

View File

@ -0,0 +1,21 @@
import { IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { IPropertyFieldListPickerPropsInternal } from './IPropertyFieldListPicker';
/**
* PropertyFieldListPickerHost properties interface
*/
export interface IPropertyFieldListMultiPickerHostProps extends IPropertyFieldListPickerPropsInternal {
onChange: (targetProperty?: string, newValue?: any) => void;
}
/**
* PropertyFieldSPListMultiplePickerHost state interface
*/
export interface IPropertyFieldListMultiPickerHostState {
results: IChoiceGroupOption[];
selectedKeys: string[];
loaded: boolean;
errorMessage?: string;
}

View File

@ -0,0 +1,106 @@
import { IWebPartContext, IPropertyPaneCustomFieldProps } from '@microsoft/sp-webpart-base';
/**
* Enum for specifying how the lists should be sorted
*/
export enum PropertyFieldListPickerOrderBy {
Id = 1,
Title
}
/**
* Public properties of the PropertyFieldListPicker custom field
*/
export interface IPropertyFieldListPickerProps {
/**
* Property field label displayed on top
*/
label: string;
/**
* Context of the current web part
*/
context: IWebPartContext;
/**
* Initial selected list set of the control
*/
selectedList?: string | string[];
/**
* BaseTemplate ID of the lists or libaries you want to return.
*/
baseTemplate?: number;
/**
* Specify if you want to include or exclude hidden lists. By default this is true.
*/
includeHidden?: boolean;
/**
* Specify the property on which you want to order the retrieve set of lists.
*/
orderBy?: PropertyFieldListPickerOrderBy;
/**
* Specify if you want to have a single or mult list selector.
*/
multiSelect?: boolean;
/**
* Defines a onPropertyChange function to raise when the selected value changed.
* Normally this function must be always defined with the 'this.onPropertyChange'
* method of the web part object.
*/
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
/**
* Parent Web Part properties
*/
properties: any;
/**
* An UNIQUE key indicates the identity of this control
*/
key?: string;
/**
* Whether the property pane field is enabled or not.
*/
disabled?: boolean;
/**
* The method is used to get the validation error message and determine whether the input value is valid or not.
*
* When it returns string:
* - If valid, it returns empty string.
* - If invalid, it returns the error message string and the text field will
* show a red border and show an error message below the text field.
*
* When it returns Promise<string>:
* - The resolved value is display as error message.
* - The rejected, the value is thrown away.
*
*/
onGetErrorMessage?: (value: string) => string | Promise<string>;
/**
* Custom Field will start to validate after users stop typing for `deferredValidationTime` milliseconds.
* Default value is 200.
*/
deferredValidationTime?: number;
}
/**
* Private properties of the PropertyFieldListPicker custom field.
* We separate public & private properties to include onRender & onDispose method waited
* by the PropertyFieldCustom, witout asking to the developer to add it when he's using
* the PropertyFieldListPicker.
*
*/
export interface IPropertyFieldListPickerPropsInternal extends IPropertyFieldListPickerProps, IPropertyPaneCustomFieldProps {
label: string;
targetProperty: string;
context: IWebPartContext;
selectedList?: string;
selectedLists?: string[];
baseTemplate?: number;
orderBy?: PropertyFieldListPickerOrderBy;
includeHidden?: boolean;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
properties: any;
key: string;
disabled?: boolean;
onGetErrorMessage?: (value: string | string[]) => string | Promise<string>;
deferredValidationTime?: number;
}

View File

@ -0,0 +1,38 @@
import { IPropertyFieldListPickerPropsInternal } from './IPropertyFieldListPicker';
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
/**
* PropertyFieldListPickerHost properties interface
*/
export interface IPropertyFieldListPickerHostProps extends IPropertyFieldListPickerPropsInternal {
onChange: (targetProperty?: string, newValue?: any) => void;
}
/**
* PropertyFieldListPickerHost state interface
*/
export interface IPropertyFieldListPickerHostState {
results: IDropdownOption[];
selectedKey?: string;
errorMessage?: string;
}
/**
* Defines a collection of SharePoint lists
*/
export interface ISPLists {
value: ISPList[];
}
/**
* Defines a SharePoint list
*/
export interface ISPList {
Title: string;
Id: string;
BaseTemplate: string;
}

View File

@ -0,0 +1,197 @@
import * as React from 'react';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner';
import { Async } from 'office-ui-fabric-react/lib/Utilities';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { IPropertyFieldListMultiPickerHostProps, IPropertyFieldListMultiPickerHostState } from './IPropertyFieldListMultiPickerHost';
import { ISPLists, ISPList } from './IPropertyFieldListPickerHost';
import SPListPickerService from '../../services/SPListPickerService';
import FieldErrorMessage from '../errorMessage/FieldErrorMessage';
/**
* Renders the controls for PropertyFieldSPListMultiplePicker component
*/
export default class PropertyFieldListMultiPickerHost extends React.Component<IPropertyFieldListMultiPickerHostProps, IPropertyFieldListMultiPickerHostState> {
private options: IChoiceGroupOption[] = [];
private loaded: boolean = false;
private async: Async;
private delayedValidate: (value: string[]) => void;
/**
* Constructor
*/
constructor(props: IPropertyFieldListMultiPickerHostProps) {
super(props);
this.onChanged = this.onChanged.bind(this);
this.state = {
results: this.options,
selectedKeys: [],
loaded: this.loaded,
errorMessage: ''
};
this.async = new Async(this);
this.validate = this.validate.bind(this);
this.notifyAfterValidate = this.notifyAfterValidate.bind(this);
this.delayedValidate = this.async.debounce(this.validate, this.props.deferredValidationTime);
this.loadLists();
}
/**
* Loads the list from SharePoint current web site
*/
private loadLists(): void {
// Builds the SharePoint List service
const listService: SPListPickerService = new SPListPickerService(this.props, this.props.context);
// Gets the libs
listService.getLibs().then((response: ISPLists) => {
response.value.map((list: ISPList) => {
let isSelected: boolean = false;
let indexInExisting: number = -1;
// Defines if the current list must be selected by default
if (this.props.selectedLists) {
indexInExisting = this.props.selectedLists.indexOf(list.Id);
}
if (indexInExisting > -1) {
isSelected = true;
this.state.selectedKeys.push(list.Id);
}
// Add the option to the list
this.options.push({
key: list.Id,
text: list.Title,
checked: isSelected
});
});
this.loaded = true;
this.setState({ results: this.options, selectedKeys: this.state.selectedKeys, loaded: true });
});
}
/**
* Raises when a list has been selected
*/
private onChanged(element: React.FormEvent<HTMLElement>, isChecked: boolean): void {
if (element) {
const value: string = (element.currentTarget as any).value;
let selectedKeys = this.state.selectedKeys;
// Check if the element is selected
if (isChecked === false) {
// Remove the unselected item
selectedKeys = selectedKeys.filter(s => s !== value);
} else {
// Add the selected item and filter out the doubles
selectedKeys.push(value);
selectedKeys = selectedKeys.filter((item, pos, self) => {
return self.indexOf(item) == pos;
});
}
// Update the state and validate
this.setState({
selectedKeys: selectedKeys
});
this.delayedValidate(selectedKeys);
}
}
/**
* Validates the new custom field value
*/
private validate(value: string[]): void {
if (this.props.onGetErrorMessage === null || typeof this.props.onGetErrorMessage === 'undefined') {
this.notifyAfterValidate(this.props.selectedLists, value);
return;
}
const result: string | PromiseLike<string> = this.props.onGetErrorMessage(value || []);
if (typeof result !== 'undefined') {
if (typeof result === 'string') {
if (result === '') {
this.notifyAfterValidate(this.props.selectedLists, value);
}
this.setState({
errorMessage: result
});
} else {
result.then((errorMessage: string) => {
if (typeof errorMessage === 'undefined' || errorMessage === '') {
this.notifyAfterValidate(this.props.selectedLists, value);
}
this.setState({
errorMessage: errorMessage
});
});
}
} else {
this.notifyAfterValidate(this.props.selectedLists, value);
}
}
/**
* Notifies the parent Web Part of a property value change
*/
private notifyAfterValidate(oldValue: string[], newValue: string[]) {
if (this.props.onPropertyChange && newValue !== null) {
this.props.properties[this.props.targetProperty] = newValue;
this.props.onPropertyChange(this.props.targetProperty, oldValue, newValue);
// Trigger the apply button
if (typeof this.props.onChange !== 'undefined' && this.props.onChange !== null) {
this.props.onChange(this.props.targetProperty, newValue);
}
}
}
/**
* Called when the component will unmount
*/
public componentWillUnmount() {
this.async.dispose();
}
/**
* Renders the SPListMultiplePicker controls with Office UI Fabric
*/
public render(): JSX.Element {
if (this.loaded === false) {
return (
<div>
<Label>{this.props.label}</Label>
<Spinner type={SpinnerType.normal} />
</div>
);
} else {
const styleOfLabel: any = {
color: this.props.disabled === true ? '#A6A6A6' : 'auto'
};
// Renders content
return (
<div>
<Label>{this.props.label}</Label>
{
this.options.map((item: IChoiceGroupOption, index: number) => {
const uniqueKey = this.props.targetProperty + '-' + item.key;
return (
<div style={{ marginBottom: '5px' }} className='ms-ChoiceField' key={`${this.props.key}-multiplelistpicker-${index}`}>
<Checkbox
defaultChecked={item.checked}
disabled={this.props.disabled}
label={item.text}
onChange={this.onChanged}
inputProps={{ value: item.key }}
/>
</div>
);
})
}
<FieldErrorMessage errorMessage={this.state.errorMessage} />
</div>
);
}
}
}

View File

@ -0,0 +1,151 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import {
IPropertyPaneField,
PropertyPaneFieldType,
IWebPartContext
} from '@microsoft/sp-webpart-base';
import PropertyFieldListPickerHost from './PropertyFieldListPickerHost';
import PropertyFieldListMultiPickerHost from './PropertyFieldListMultiPickerHost';
import { IPropertyFieldListPickerHostProps } from './IPropertyFieldListPickerHost';
import { IPropertyFieldListMultiPickerHostProps } from './IPropertyFieldListMultiPickerHost';
import { PropertyFieldListPickerOrderBy, IPropertyFieldListPickerProps, IPropertyFieldListPickerPropsInternal } from './IPropertyFieldListPicker';
/**
* Represents a PropertyFieldListPicker object
*/
class PropertyFieldListPickerBuilder implements IPropertyPaneField<IPropertyFieldListPickerPropsInternal> {
//Properties defined by IPropertyPaneField
public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
public targetProperty: string;
public properties: IPropertyFieldListPickerPropsInternal;
//Custom properties label: string;
private label: string;
private context: IWebPartContext;
private selectedList: string;
private selectedLists: string[];
private baseTemplate: number;
private orderBy: PropertyFieldListPickerOrderBy;
private multiSelect: boolean;
private includeHidden: boolean;
public onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void { }
private customProperties: any;
private key: string;
private disabled: boolean = false;
private onGetErrorMessage: (value: string) => string | Promise<string>;
private deferredValidationTime: number = 200;
private renderWebPart: () => void;
private disableReactivePropertyChanges: boolean = false;
/**
* Constructor method
*/
public constructor(_targetProperty: string, _properties: IPropertyFieldListPickerPropsInternal) {
this.render = this.render.bind(this);
this.targetProperty = _targetProperty;
this.properties = _properties;
this.properties.onDispose = this.dispose;
this.properties.onRender = this.render;
this.label = _properties.label;
this.context = _properties.context;
this.selectedList = _properties.selectedList;
this.selectedLists = _properties.selectedLists;
this.baseTemplate = _properties.baseTemplate;
this.orderBy = _properties.orderBy;
this.multiSelect = _properties.multiSelect;
this.includeHidden = _properties.includeHidden;
this.onPropertyChange = _properties.onPropertyChange;
this.customProperties = _properties.properties;
this.key = _properties.key;
this.onGetErrorMessage = _properties.onGetErrorMessage;
if (_properties.disabled === true) {
this.disabled = _properties.disabled;
}
if (_properties.deferredValidationTime) {
this.deferredValidationTime = _properties.deferredValidationTime;
}
}
/**
* Renders the SPListPicker field content
*/
private render(elem: HTMLElement, ctx?: any, changeCallback?: (targetProperty?: string, newValue?: any) => void): void {
const componentProps = {
label: this.label,
targetProperty: this.targetProperty,
context: this.context,
baseTemplate: this.baseTemplate,
orderBy: this.orderBy,
multiSelect: this.multiSelect,
includeHidden: this.includeHidden,
onDispose: this.dispose,
onRender: this.render,
onChange: changeCallback,
onPropertyChange: this.onPropertyChange,
properties: this.customProperties,
key: this.key,
disabled: this.disabled,
onGetErrorMessage: this.onGetErrorMessage,
deferredValidationTime: this.deferredValidationTime
};
// Check if the multi or single select component has to get loaded
if (this.multiSelect) {
// Multi selector
componentProps['selectedLists'] = this.selectedLists;
const element: React.ReactElement<IPropertyFieldListMultiPickerHostProps> = React.createElement(PropertyFieldListMultiPickerHost, componentProps);
// Calls the REACT content generator
ReactDom.render(element, elem);
} else {
// Single selector
componentProps['selectedList'] = this.selectedList;
const element: React.ReactElement<IPropertyFieldListPickerHostProps> = React.createElement(PropertyFieldListPickerHost, componentProps);
// Calls the REACT content generator
ReactDom.render(element, elem);
}
}
/**
* Disposes the current object
*/
private dispose(elem: HTMLElement): void {
}
}
/**
* Helper method to create a SPList Picker on the PropertyPane.
* @param targetProperty - Target property the SharePoint list picker is associated to.
* @param properties - Strongly typed SPList Picker properties.
*/
export function PropertyFieldListPicker(targetProperty: string, properties: IPropertyFieldListPickerProps): IPropertyPaneField<IPropertyFieldListPickerPropsInternal> {
//Create an internal properties object from the given properties
const newProperties: IPropertyFieldListPickerPropsInternal = {
label: properties.label,
targetProperty: targetProperty,
context: properties.context,
selectedList: typeof properties.selectedList === 'string' ? properties.selectedList : null,
selectedLists: typeof properties.selectedList !== 'string' ? properties.selectedList : null,
baseTemplate: properties.baseTemplate,
orderBy: properties.orderBy,
multiSelect: properties.multiSelect || false,
includeHidden: properties.includeHidden,
onPropertyChange: properties.onPropertyChange,
properties: properties.properties,
onDispose: null,
onRender: null,
key: properties.key,
disabled: properties.disabled,
onGetErrorMessage: properties.onGetErrorMessage,
deferredValidationTime: properties.deferredValidationTime
};
//Calls the PropertyFieldListPicker builder object
//This object will simulate a PropertyFieldCustom to manage his rendering process
return new PropertyFieldListPickerBuilder(targetProperty, newProperties);
}

View File

@ -0,0 +1,187 @@
import * as React from 'react';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { Async } from 'office-ui-fabric-react/lib/Utilities';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { IPropertyFieldListPickerHostProps, IPropertyFieldListPickerHostState, ISPList, ISPLists } from './IPropertyFieldListPickerHost';
import SPListPickerService from '../../services/SPListPickerService';
import FieldErrorMessage from '../errorMessage/FieldErrorMessage';
// Empty list value, to be checked for single list selection
const EMPTY_LIST_KEY = 'NO_LIST_SELECTED';
/**
* Renders the controls for PropertyFieldListPicker component
*/
export default class PropertyFieldListPickerHost extends React.Component<IPropertyFieldListPickerHostProps, IPropertyFieldListPickerHostState> {
private options: IDropdownOption[] = [];
private selectedKey: string;
private latestValidateValue: string;
private async: Async;
private delayedValidate: (value: string) => void;
/**
* Constructor method
*/
constructor(props: IPropertyFieldListPickerHostProps) {
super(props);
this.state = {
results: this.options,
errorMessage: ''
};
this.async = new Async(this);
this.validate = this.validate.bind(this);
this.onChanged = this.onChanged.bind(this);
this.notifyAfterValidate = this.notifyAfterValidate.bind(this);
this.delayedValidate = this.async.debounce(this.validate, this.props.deferredValidationTime);
}
public componentDidMount(): void {
// Start retrieving the SharePoint lists
this.loadLists();
}
/**
* Loads the list from SharePoint current web site
*/
private loadLists(): void {
const listService: SPListPickerService = new SPListPickerService(this.props, this.props.context);
listService.getLibs().then((response: ISPLists) => {
// Start mapping the list that are selected
response.value.map((list: ISPList) => {
if (this.props.selectedList === list.Id) {
this.selectedKey = list.Id;
}
this.options.push({
key: list.Id,
text: list.Title
});
});
// Option to unselect the list
this.options.unshift({
key: EMPTY_LIST_KEY,
text: ''
});
// Update the current component state
this.setState({
results: this.options,
selectedKey: this.selectedKey
});
});
}
/**
* Raises when a list has been selected
*/
private onChanged(option: IDropdownOption, index?: number): void {
const newValue: string = option.key as string;
this.delayedValidate(newValue);
}
/**
* Validates the new custom field value
*/
private validate(value: string): void {
if (this.props.onGetErrorMessage === null || this.props.onGetErrorMessage === undefined) {
this.notifyAfterValidate(this.props.selectedList, value);
return;
}
if (this.latestValidateValue === value) {
return;
}
this.latestValidateValue = value;
const result: string | PromiseLike<string> = this.props.onGetErrorMessage(value || '');
if (typeof result !== 'undefined') {
if (typeof result === 'string') {
if (result === '') {
this.notifyAfterValidate(this.props.selectedList, value);
}
this.setState({
errorMessage: result
});
} else {
result.then((errorMessage: string) => {
if (typeof errorMessage === 'undefined' || errorMessage === '') {
this.notifyAfterValidate(this.props.selectedList, value);
}
this.setState({
errorMessage: errorMessage
});
});
}
} else {
this.notifyAfterValidate(this.props.selectedList, value);
}
}
/**
* Notifies the parent Web Part of a property value change
*/
private notifyAfterValidate(oldValue: string, newValue: string) {
// Check if the user wanted to unselect the list
const propValue = newValue === EMPTY_LIST_KEY ? '' : newValue;
// Deselect all options
this.options = this.state.results.map(option => {
if (option.selected) {
option.selected = false;
}
return option;
});
// Set the current selected key
this.selectedKey = newValue;
// Update the state
this.setState({
selectedKey: this.selectedKey,
results: this.options
});
if (this.props.onPropertyChange && propValue !== null) {
// Store the new property value
this.props.properties[this.props.targetProperty] = propValue;
// Trigger the default onPrpertyChange event
this.props.onPropertyChange(this.props.targetProperty, oldValue, propValue);
// Trigger the apply button
if (typeof this.props.onChange !== 'undefined' && this.props.onChange !== null) {
this.props.onChange(this.props.targetProperty, propValue);
}
}
}
/**
* Called when the component will unmount
*/
public componentWillUnmount() {
if (typeof this.async !== 'undefined') {
this.async.dispose();
}
}
/**
* Renders the SPListpicker controls with Office UI Fabric
*/
public render(): JSX.Element {
// Renders content
return (
<div>
<Label>{this.props.label}</Label>
<Dropdown
disabled={this.props.disabled}
label=''
onChanged={this.onChanged}
options={this.state.results}
selectedKey={this.state.selectedKey}
/>
<FieldErrorMessage errorMessage={this.state.errorMessage} />
</div>
);
}
}

View File

@ -0,0 +1,4 @@
export * from './PropertyFieldListPicker';
export * from './IPropertyFieldListPicker';
export * from './PropertyFieldListPickerHost';
export * from './IPropertyFieldListPickerHost';

View File

@ -0,0 +1,20 @@
import { ISPLists } from '../propertyFields/listPicker/IPropertyFieldListPickerHost';
/**
* Defines a http client to request mock data to use the web part with the local workbench
*/
export default class SPListPickerMockHttpClient {
/**
* Mock SharePoint result sample
*/
private static _results: ISPLists = { value: [] };
/**
* Mock search People method
*/
public static getLists(restUrl: string, options?: any): Promise<ISPLists> {
return new Promise<ISPLists>((resolve) => {
resolve(SPListPickerMockHttpClient._results);
});
}
}

View File

@ -0,0 +1,83 @@
import { SPHttpClientResponse } from '@microsoft/sp-http';
import { SPHttpClient } from '@microsoft/sp-http';
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { IWebPartContext } from '@microsoft/sp-webpart-base';
import { ISPLists, IPropertyFieldListPickerHostProps } from '../propertyFields/listPicker/IPropertyFieldListPickerHost';
import { PropertyFieldListPickerOrderBy } from '../propertyFields/listPicker/IPropertyFieldListPicker';
import SPListPickerMockHttpClient from './SPListPickerMockService';
/**
* Service implementation to get list & list items from current SharePoint site
*/
export default class SPListPickerService {
private context: IWebPartContext;
private props: IPropertyFieldListPickerHostProps;
/**
* Service constructor
*/
constructor(_props: IPropertyFieldListPickerHostProps, pageContext: IWebPartContext) {
this.props = _props;
this.context = pageContext;
}
/**
* Gets the collection of libs in the current SharePoint site
*/
public getLibs(): Promise<ISPLists> {
if (Environment.type === EnvironmentType.Local) {
// If the running environment is local, load the data from the mock
return this.getLibsFromMock();
}
else {
// If the running environment is SharePoint, request the lists REST service
let queryUrl: string = `${this.context.pageContext.web.absoluteUrl}/_api/lists?$select=Title,id,BaseTemplate`;
// Check if the orderBy property is provided
if (this.props.orderBy !== null) {
queryUrl += '&$orderby=';
switch (this.props.orderBy) {
case PropertyFieldListPickerOrderBy.Id:
queryUrl += 'Id';
break;
case PropertyFieldListPickerOrderBy.Title:
queryUrl += 'Title';
break;
}
}
// Check if the list have get filtered based on the list base template type
if (this.props.baseTemplate !== null && this.props.baseTemplate) {
queryUrl += '&$filter=BaseTemplate%20eq%20';
queryUrl += this.props.baseTemplate;
// Check if you also want to exclude hidden list in the list
if (this.props.includeHidden === false) {
queryUrl += '%20and%20Hidden%20eq%20false';
}
} else {
if (this.props.includeHidden === false) {
queryUrl += '&$filter=Hidden%20eq%20false';
}
}
return this.context.spHttpClient.get(queryUrl, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
return response.json();
}) as Promise<ISPLists>;
}
}
/**
* Returns 3 fake SharePoint lists for the Mock mode
*/
private getLibsFromMock(): Promise<ISPLists> {
return SPListPickerMockHttpClient.getLists(this.context.pageContext.web.absoluteUrl).then(() => {
const listData: ISPLists = {
value:
[
{ Title: 'Mock List One', Id: '6770c83b-29e8-494b-87b6-468a2066bcc6', BaseTemplate: '109' },
{ Title: 'Mock List Two', Id: '2ece98f2-cc5e-48ff-8145-badf5009754c', BaseTemplate: '109' },
{ Title: 'Mock List Three', Id: 'bd5dbd33-0e8d-4e12-b289-b276e5ef79c2', BaseTemplate: '109' }
]
};
return listData;
}) as Promise<ISPLists>;
}
}

View File

@ -0,0 +1,21 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "786e7cdf-d1c8-4412-8abb-1aa18ea555e0",
"alias": "FileUploadWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198322f80",
"group": { "default": "SuperWebParts" },
"title": { "default": "SPFileUpload" },
"description": { "default": "Use this webpart to upload files to a library or as list attachments" },
"officeFabricIconFontName": "OpenFile",
"properties": {
"listName": "Documents",
"fileTypes":"jpg,png,gif,avi,mp4",
"uploadFilesTo":"DocumentLibrary"
}
}]
}

View File

@ -0,0 +1,106 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
IWebPartContext
} from '@microsoft/sp-webpart-base';
import { IDigestCache, DigestCache } from '@microsoft/sp-http';
import * as strings from 'FileUploadWebPartStrings';
import FileUpload from './components/FileUpload';
import { IFileUploadProps } from './components/IFileUploadProps';
import * as loader from '@microsoft/sp-loader';
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '../../PropertyFieldListPicker';
import { PropertyPaneDropdown } from '@microsoft/sp-webpart-base/lib/propertyPane/propertyPaneFields/propertyPaneDropdown/PropertyPaneDropdown';
export interface IFileUploadWebPartProps {
listName:string;
fileTypes:string;
queryString:string;
uploadFilesTo:string;
}
require("./filepicker.css");
require("./dropzone.css");
export default class FileUploadWebPart extends BaseClientSideWebPart<IFileUploadWebPartProps> {
public digest:string="";
public constructor(context:IWebPartContext){
super();
loader.SPComponentLoader.loadCss('https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css');
}
protected onInit(): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
const digestCache: IDigestCache = this.context.serviceScope.consume(DigestCache.serviceKey);
digestCache.fetchDigest(this.context.pageContext.web.serverRelativeUrl).then((digest: string): void => {
// use the digest here
this.digest=digest;
resolve();
});
});
}
public render(): void {
const element: React.ReactElement<IFileUploadProps > = React.createElement(
FileUpload,
{
digest:this.digest,
context:this.context,
listName:this.properties.listName,
fileTypes:this.properties.fileTypes,
queryString:this.properties.queryString,
uploadFilesTo:this.properties.uploadFilesTo
}
);
ReactDom.render(element, this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('uploadFilesTo',{
label:'Upload files to',
options:[{key:'DocumentLibrary',text:'Document Library'},
{key:'List',text:'As item attachments'} ]
}),
PropertyFieldListPicker('listName', {
label: 'Select a list or library',
selectedList: this.properties.listName,
includeHidden: false,
//baseTemplate: 109,
orderBy: PropertyFieldListPickerOrderBy.Title,
// multiSelect: false,
disabled: false,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
context: this.context,
onGetErrorMessage: null,
deferredValidationTime: 0,
key: 'listPickerFieldId'
}),
PropertyPaneTextField('fileTypes',{
label:'File Types (use , as seperator)',
}),
PropertyPaneTextField('queryString',{
label:'Query String parameter',
description:'If you want to attach files to a list item you need to define the ID of the item in a query string parameter, example: ID=1'
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,74 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.fileUpload {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,17 @@
/* tslint:disable */
require('./FileUpload.module.css');
const styles = {
fileUpload: 'fileUpload_207465a2',
container: 'container_207465a2',
row: 'row_207465a2',
column: 'column_207465a2',
'ms-Grid': 'ms-Grid_207465a2',
title: 'title_207465a2',
subTitle: 'subTitle_207465a2',
description: 'description_207465a2',
button: 'button_207465a2',
label: 'label_207465a2',
};
export default styles;
/* tslint:enable */

View File

@ -0,0 +1,83 @@
import * as React from 'react';
import styles from './FileUpload.module.scss';
import { IFileUploadProps } from './IFileUploadProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { Log,UrlQueryParameterCollection } from '@microsoft/sp-core-library';
import DropzoneComponent from 'react-dropzone-component';
import pnp,{Web} from 'sp-pnp-js';
export default class FileUpload extends React.Component<IFileUploadProps, {}> {
constructor(props: IFileUploadProps){
super(props);
}
public render(): React.ReactElement<IFileUploadProps> {
let _context = this.props.context;
let _listName = this.props.listName;
let _fileUploadTo=this.props.uploadFilesTo;
let _queryStringParam = this.props.queryString;
let queryParameters = new UrlQueryParameterCollection(window.location.href);
let _itemId = queryParameters.getValue(_queryStringParam);
let _parent = this;
let componentConfig = {
iconFiletypes: this.props.fileTypes.split(','),
showFiletypeIcon: true,
postUrl: _context.pageContext.web.absoluteUrl
};
let myDropzone;
let eventHandlers = {
// This one receives the dropzone object as the first parameter
// and can be used to additional work with the dropzone.js
// object
init: function(dz){
myDropzone=dz;
},
removedfile: function(file){
let web:Web=new Web(_context.pageContext.web.absoluteUrl);
if(_fileUploadTo=="DocumentLibrary"){
web.lists.getById(_listName).rootFolder.files.getByName(file.name).delete().then(t=>{
//add your code here if you want to do more after deleting the file
});
}
else{
web.lists.getById(_listName).items.getById(Number(_itemId)).attachmentFiles.deleteMultiple(file.name).then(t=>{
//add your code here if you want to do more after deleting the file
});
}
},
processing: function (file, xhr) {
if(_fileUploadTo=="DocumentLibrary")
myDropzone.options.url = `${_context.pageContext.web.absoluteUrl}/_api/web/Lists/getById('${_listName}')/rootfolder/files/add(overwrite=true,url='${file.name}')`;
else
{
if(_itemId)
myDropzone.options.url = `${_context.pageContext.web.absoluteUrl}/_api/web/lists/getById('${_listName}')/items(${_itemId})/AttachmentFiles/add(FileName='${file.name}')`;
else
alert('Item not found or query string value is null!')
}
},
sending: function (file, xhr) {
let _send = xhr.send;
xhr.send = function () {
_send.call(xhr, file);
};
},
error:function(file,error,xhr){
if(_fileUploadTo!="DocumentLibrary")
alert(`File '${file.name}' is already exists, please rename your file or select another file.`);
//if(myDropzone)
// myDropzone.removeFile(file);
}
};
var djsConfig = {
headers: {
"X-RequestDigest": this.props.digest
},
addRemoveLinks:true
};
return (
<DropzoneComponent eventHandlers={eventHandlers} djsConfig={djsConfig} config={componentConfig}>
<div className="dz-message icon ion-upload">Drop files here or click to upload.</div>
</DropzoneComponent>
);
}
}

View File

@ -0,0 +1,11 @@
import {
IWebPartContext
} from '@microsoft/sp-webpart-base';
export interface IFileUploadProps {
digest:string;
context:IWebPartContext;
listName:string;
fileTypes:string;
queryString:string;
uploadFilesTo:string;
}

View File

@ -0,0 +1,389 @@
/*
* The MIT License
* Copyright (c) 2012 Matias Meno <m@tias.me>
*/
@-webkit-keyframes passing-through {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30%, 70% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); }
100% {
opacity: 0;
-webkit-transform: translateY(-40px);
-moz-transform: translateY(-40px);
-ms-transform: translateY(-40px);
-o-transform: translateY(-40px);
transform: translateY(-40px); } }
@-moz-keyframes passing-through {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30%, 70% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); }
100% {
opacity: 0;
-webkit-transform: translateY(-40px);
-moz-transform: translateY(-40px);
-ms-transform: translateY(-40px);
-o-transform: translateY(-40px);
transform: translateY(-40px); } }
@keyframes passing-through {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30%, 70% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); }
100% {
opacity: 0;
-webkit-transform: translateY(-40px);
-moz-transform: translateY(-40px);
-ms-transform: translateY(-40px);
-o-transform: translateY(-40px);
transform: translateY(-40px); } }
@-webkit-keyframes slide-in {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); } }
@-moz-keyframes slide-in {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); } }
@keyframes slide-in {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); } }
@-webkit-keyframes pulse {
0% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); }
10% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1); }
20% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); } }
@-moz-keyframes pulse {
0% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); }
10% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1); }
20% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); } }
@keyframes pulse {
0% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); }
10% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1); }
20% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); } }
.dropzone, .dropzone * {
box-sizing: border-box; }
.dropzone {
min-height: 150px;
border: 2px solid rgba(0, 0, 0, 0.3);
padding: 20px 20px; }
.dropzone.dz-clickable {
cursor: pointer; }
.dropzone.dz-clickable * {
cursor: default; }
.dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * {
cursor: pointer; }
.dropzone.dz-started .dz-message {
display: none; }
.dropzone.dz-drag-hover {
border-style: solid; }
.dropzone.dz-drag-hover .dz-message {
opacity: 0.5; }
.dropzone .dz-message {
text-align: center;
margin: 2em 0;
font-size: 22px;
}
.dropzone .dz-preview {
position: relative;
display: inline-block;
vertical-align: top;
margin: 16px;
min-height: 100px; }
.dropzone .dz-preview:hover {
z-index: 1000; }
.dropzone .dz-preview:hover .dz-details {
opacity: 1; }
.dropzone .dz-preview.dz-file-preview .dz-image {
border-radius: 20px;
background: #999;
background: linear-gradient(to bottom, #eee, #ddd); }
.dropzone .dz-preview.dz-file-preview .dz-details {
opacity: 1; }
.dropzone .dz-preview.dz-image-preview {
}
.dropzone .dz-preview.dz-image-preview .dz-details {
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-ms-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear; }
.dropzone .dz-preview .dz-remove {
font-size: 14px;
text-align: center;
display: block;
cursor: pointer;
border: none; }
.dropzone .dz-preview .dz-remove:hover {
text-decoration: underline; }
.dropzone .dz-preview:hover .dz-details {
opacity: 1; }
.dropzone .dz-preview .dz-details {
z-index: 20;
position: absolute;
top: 0;
left: 0;
opacity: 0;
font-size: 13px;
min-width: 100%;
max-width: 100%;
padding: 2em 1em;
text-align: center;
color: rgba(0, 0, 0, 0.9);
line-height: 150%; }
.dropzone .dz-preview .dz-details .dz-size {
margin-bottom: 1em;
font-size: 16px; }
.dropzone .dz-preview .dz-details .dz-filename {
white-space: nowrap; }
.dropzone .dz-preview .dz-details .dz-filename:hover span {
border: 1px solid rgba(200, 200, 200, 0.8);
background-color: rgba(255, 255, 255, 0.8); }
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) {
overflow: hidden;
text-overflow: ellipsis; }
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
border: 1px solid transparent; }
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
background-color: rgba(255, 255, 255, 0.4);
padding: 0 0.4em;
border-radius: 3px; }
.dropzone .dz-preview:hover .dz-image img {
-webkit-transform: scale(1.05, 1.05);
-moz-transform: scale(1.05, 1.05);
-ms-transform: scale(1.05, 1.05);
-o-transform: scale(1.05, 1.05);
transform: scale(1.05, 1.05);
-webkit-filter: blur(8px);
filter: blur(8px); }
.dropzone .dz-preview .dz-image {
border-radius: 20px;
overflow: hidden;
width: 120px;
height: 120px;
position: relative;
display: block;
z-index: 10; }
.dropzone .dz-preview .dz-image img {
display: block; }
.dropzone .dz-preview.dz-success .dz-success-mark {
-webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
-moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
-ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
-o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); }
.dropzone .dz-preview.dz-error .dz-error-mark {
opacity: 1;
-webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
-moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
-ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
-o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); }
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
pointer-events: none;
opacity: 0;
z-index: 500;
position: absolute;
display: block;
top: 50%;
left: 50%;
margin-left: -27px;
margin-top: -27px; }
.dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg {
display: block;
width: 54px;
height: 54px; }
.dropzone .dz-preview.dz-processing .dz-progress {
opacity: 1;
-webkit-transition: all 0.2s linear;
-moz-transition: all 0.2s linear;
-ms-transition: all 0.2s linear;
-o-transition: all 0.2s linear;
transition: all 0.2s linear; }
.dropzone .dz-preview.dz-complete .dz-progress {
opacity: 0;
-webkit-transition: opacity 0.4s ease-in;
-moz-transition: opacity 0.4s ease-in;
-ms-transition: opacity 0.4s ease-in;
-o-transition: opacity 0.4s ease-in;
transition: opacity 0.4s ease-in; }
.dropzone .dz-preview:not(.dz-processing) .dz-progress {
-webkit-animation: pulse 6s ease infinite;
-moz-animation: pulse 6s ease infinite;
-ms-animation: pulse 6s ease infinite;
-o-animation: pulse 6s ease infinite;
animation: pulse 6s ease infinite; }
.dropzone .dz-preview .dz-progress {
opacity: 1;
z-index: 1000;
pointer-events: none;
position: absolute;
height: 16px;
left: 50%;
top: 50%;
margin-top: -8px;
width: 80px;
margin-left: -40px;
background: rgba(255, 255, 255, 0.9);
-webkit-transform: scale(1);
border-radius: 8px;
overflow: hidden; }
.dropzone .dz-preview .dz-progress .dz-upload {
background: #333;
background: linear-gradient(to bottom, #666, #444);
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 0;
-webkit-transition: width 300ms ease-in-out;
-moz-transition: width 300ms ease-in-out;
-ms-transition: width 300ms ease-in-out;
-o-transition: width 300ms ease-in-out;
transition: width 300ms ease-in-out; }
.dropzone .dz-preview.dz-error .dz-error-message {
display: block; }
.dropzone .dz-preview.dz-error:hover .dz-error-message {
opacity: 1;
pointer-events: auto; }
.dropzone .dz-preview .dz-error-message {
pointer-events: none;
z-index: 1000;
position: absolute;
display: block;
display: none;
opacity: 0;
-webkit-transition: opacity 0.3s ease;
-moz-transition: opacity 0.3s ease;
-ms-transition: opacity 0.3s ease;
-o-transition: opacity 0.3s ease;
transition: opacity 0.3s ease;
border-radius: 8px;
font-size: 13px;
top: 130px;
left: -10px;
width: 140px;
background: #be2626;
background: linear-gradient(to bottom, #be2626, #a92222);
padding: 0.5em 1.2em;
color: white; }
.dropzone .dz-preview .dz-error-message:after {
content: '';
position: absolute;
top: -6px;
left: 64px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #be2626; }

View File

@ -0,0 +1,74 @@
/* Filepicker CSS */
.filepicker {
font-family: sans-serif;
}
div.filepicker {
text-align: center;
padding: 5px;
background-color: #fff;
border-radius: 5px;
height: 200px;
min-height: 60px;
border: 2px dashed #7092BE;
}
/* Icon */
.filepicker-file-icon
{
position: relative;
display: inline-block;
margin: 1.5em 0 2.5em 0;
padding-left: 45px;
color: black;
}
.filepicker-file-icon::before
{
position: absolute;
top: -7px;
left: 0;
width: 29px;
height: 34px;
content: '';
border: solid 2px #7F7F7F;
border-radius: 2px;
}
.filepicker-file-icon::after
{
font-size: 11px;
line-height: 1.3;
position: absolute;
top: 9px;
left: -4px;
padding: 0 2px;
content: 'file';
content: attr(data-filetype);
text-align: right;
letter-spacing: 1px;
text-transform: uppercase;
color: #fff;
background-color: #000;
}
.filepicker-file-icon .fileCorner
{
position: absolute;
top: -7px;
left: 22px;
width: 0;
height: 0;
border-width: 11px 0 0 11px;
border-style: solid;
border-color: white transparent transparent #920035;
}

View File

@ -0,0 +1,6 @@
define([], function() {
return {
"PropertyPaneDescription": "File Uploader WebPart",
"BasicGroupName": "Look and feel"
}
});

View File

@ -0,0 +1,10 @@
declare interface IFileUploadWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'FileUploadWebPartStrings' {
const strings: IFileUploadWebPartStrings;
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"
]
}
}