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:
parent
c177a89297
commit
a342425d05
|
@ -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 |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -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 -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './common/propertyFieldHeader/index';
|
|
@ -0,0 +1 @@
|
|||
export * from './propertyFields/listPicker/index';
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './IPropertyFieldHeader';
|
||||
export * from './PropertyFieldHeader';
|
|
@ -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 />;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './PropertyFieldListPicker';
|
||||
export * from './IPropertyFieldListPicker';
|
||||
export * from './PropertyFieldListPickerHost';
|
||||
export * from './IPropertyFieldListPickerHost';
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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'
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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; }
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "File Uploader WebPart",
|
||||
"BasicGroupName": "Look and feel"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IFileUploadWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'FileUploadWebPartStrings' {
|
||||
const strings: IFileUploadWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue