mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Backports the following commits to 7.x: - [canvas] TS Asset Manager + Stories (#31341)
This commit is contained in:
parent
a304b2918a
commit
c1a11d0bb4
28 changed files with 1253 additions and 362 deletions
|
@ -42,6 +42,7 @@
|
|||
"@storybook/react": "^5.0.5",
|
||||
"@storybook/theming": "^5.0.5",
|
||||
"@types/angular": "1.6.50",
|
||||
"@types/base64-js": "^1.2.5",
|
||||
"@types/cheerio": "^0.22.10",
|
||||
"@types/chroma-js": "^1.4.1",
|
||||
"@types/color": "^3.0.0",
|
||||
|
@ -51,6 +52,7 @@
|
|||
"@types/d3-time-format": "^2.1.0",
|
||||
"@types/d3-time": "^1.0.7",
|
||||
"@types/elasticsearch": "^5.0.30",
|
||||
"@types/file-saver": "^2.0.0",
|
||||
"@types/graphql": "^0.13.1",
|
||||
"@types/history": "^4.6.2",
|
||||
"@types/jest": "^24.0.9",
|
||||
|
@ -58,6 +60,7 @@
|
|||
"@types/json-stable-stringify": "^1.0.32",
|
||||
"@types/jsonwebtoken": "^7.2.7",
|
||||
"@types/lodash": "^3.10.1",
|
||||
"@types/mime": "^2.0.1",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/object-hash": "^1.2.0",
|
||||
"@types/pngjs": "^3.3.1",
|
||||
|
|
88
x-pack/plugins/canvas/common/lib/__tests__/dataurl.test.ts
Normal file
88
x-pack/plugins/canvas/common/lib/__tests__/dataurl.test.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isValidDataUrl, parseDataUrl } from '../dataurl';
|
||||
|
||||
const BASE64_TEXT = 'data:text/plain;charset=utf-8;base64,VGhpcyBpcyBhIHRlc3Q=';
|
||||
const BASE64_SVG =
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=';
|
||||
const BASE64_PIXEL =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk8PxfDwADYgHJvQ16TAAAAABJRU5ErkJggg==';
|
||||
|
||||
const RAW_TEXT = 'data:text/plain;charset=utf-8,This%20is%20a%20test';
|
||||
const RAW_SVG =
|
||||
'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%2F%3E';
|
||||
const RAW_PIXEL =
|
||||
'data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%01%00%00%00%01%08%06%00%00%00%1F%15%C4%89%00%00%00%0DIDATx%DAcd%F0%FC_%0F%00%03b%01%C9%BD%0DzL%00%00%00%00IEND%AEB%60%82';
|
||||
|
||||
describe('dataurl', () => {
|
||||
describe('isValidDataUrl', () => {
|
||||
test('invalid data url', () => {
|
||||
expect(isValidDataUrl('somestring')).toBe(false);
|
||||
});
|
||||
test('valid data urls', () => {
|
||||
expect(isValidDataUrl(BASE64_TEXT)).toBe(true);
|
||||
expect(isValidDataUrl(BASE64_SVG)).toBe(true);
|
||||
expect(isValidDataUrl(BASE64_PIXEL)).toBe(true);
|
||||
expect(isValidDataUrl(RAW_TEXT)).toBe(true);
|
||||
expect(isValidDataUrl(RAW_SVG)).toBe(true);
|
||||
expect(isValidDataUrl(RAW_PIXEL)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataurl.parseDataUrl', () => {
|
||||
test('invalid data url', () => {
|
||||
expect(parseDataUrl('somestring')).toBeNull();
|
||||
});
|
||||
test('text data urls', () => {
|
||||
expect(parseDataUrl(BASE64_TEXT)).toEqual({
|
||||
charset: 'utf-8',
|
||||
data: null,
|
||||
encoding: 'base64',
|
||||
extension: 'txt',
|
||||
isImage: false,
|
||||
mimetype: 'text/plain',
|
||||
});
|
||||
expect(parseDataUrl(RAW_TEXT)).toEqual({
|
||||
charset: 'utf-8',
|
||||
data: null,
|
||||
encoding: undefined,
|
||||
extension: 'txt',
|
||||
isImage: false,
|
||||
mimetype: 'text/plain',
|
||||
});
|
||||
});
|
||||
test('png data urls', () => {
|
||||
expect(parseDataUrl(RAW_PIXEL)).toBeNull();
|
||||
expect(parseDataUrl(BASE64_PIXEL)).toEqual({
|
||||
charset: undefined,
|
||||
data: null,
|
||||
encoding: 'base64',
|
||||
extension: 'png',
|
||||
isImage: true,
|
||||
mimetype: 'image/png',
|
||||
});
|
||||
});
|
||||
test('svg data urls', () => {
|
||||
expect(parseDataUrl(RAW_SVG)).toEqual({
|
||||
charset: undefined,
|
||||
data: null,
|
||||
encoding: undefined,
|
||||
extension: 'svg',
|
||||
isImage: true,
|
||||
mimetype: 'image/svg+xml',
|
||||
});
|
||||
expect(parseDataUrl(BASE64_SVG)).toEqual({
|
||||
charset: undefined,
|
||||
data: null,
|
||||
encoding: 'base64',
|
||||
extension: 'svg',
|
||||
isImage: true,
|
||||
mimetype: 'image/svg+xml',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { hexToRgb } from '../hex_to_rgb';
|
||||
|
||||
describe('hexToRgb', () => {
|
||||
test('invalid hex', () => {
|
||||
expect(hexToRgb('hexadecimal')).toBeNull();
|
||||
expect(hexToRgb('#00')).toBeNull();
|
||||
expect(hexToRgb('#00000')).toBeNull();
|
||||
});
|
||||
test('shorthand', () => {
|
||||
expect(hexToRgb('#000')).toEqual([0, 0, 0]);
|
||||
expect(hexToRgb('#FFF')).toEqual([255, 255, 255]);
|
||||
expect(hexToRgb('#fff')).toEqual([255, 255, 255]);
|
||||
expect(hexToRgb('#fFf')).toEqual([255, 255, 255]);
|
||||
});
|
||||
test('longhand', () => {
|
||||
expect(hexToRgb('#000000')).toEqual([0, 0, 0]);
|
||||
expect(hexToRgb('#ffffff')).toEqual([255, 255, 255]);
|
||||
expect(hexToRgb('#fffFFF')).toEqual([255, 255, 255]);
|
||||
expect(hexToRgb('#FFFFFF')).toEqual([255, 255, 255]);
|
||||
});
|
||||
});
|
|
@ -5,21 +5,23 @@
|
|||
*/
|
||||
|
||||
import { fromByteArray } from 'base64-js';
|
||||
|
||||
// @ts-ignore @types/mime doesn't resolve mime/lite for some reason.
|
||||
import mime from 'mime/lite';
|
||||
|
||||
const dataurlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)(;[a-z-]+=[a-z0-9-]+)?(;([a-z0-9]+))?,/;
|
||||
|
||||
export const imageTypes = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif'];
|
||||
|
||||
export function parseDataUrl(str, withData = false) {
|
||||
export function parseDataUrl(str: string, withData = false) {
|
||||
if (typeof str !== 'string') {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const matches = str.match(dataurlRegex);
|
||||
|
||||
if (!matches) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const [, mimetype, charset, , encoding] = matches;
|
||||
|
@ -27,7 +29,7 @@ export function parseDataUrl(str, withData = false) {
|
|||
// all types except for svg need to be base64 encoded
|
||||
const imageTypeIndex = imageTypes.indexOf(matches[1]);
|
||||
if (imageTypeIndex > 0 && encoding !== 'base64') {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -40,14 +42,14 @@ export function parseDataUrl(str, withData = false) {
|
|||
};
|
||||
}
|
||||
|
||||
export function isValidDataUrl(str) {
|
||||
export function isValidDataUrl(str: string) {
|
||||
return dataurlRegex.test(str);
|
||||
}
|
||||
|
||||
export function encode(data, type = 'text/plain') {
|
||||
export function encode(data: any | null, type = 'text/plain') {
|
||||
// use FileReader if it's available, like in the browser
|
||||
if (FileReader) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string | ArrayBuffer | null>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result);
|
||||
reader.onerror = err => reject(err);
|
|
@ -4,18 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const hexToRgb = hex => {
|
||||
export const hexToRgb = (hex: string) => {
|
||||
const shorthandHexColor = /^#?([a-f\d]{1})([a-f\d]{1})([a-f\d]{1})$/i;
|
||||
const hexColor = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
|
||||
|
||||
const shorthandMatches = shorthandHexColor.exec(hex);
|
||||
if (shorthandMatches) {
|
||||
return shorthandMatches.slice(1, 4).map(hex => parseInt(hex + hex, 16));
|
||||
return shorthandMatches.slice(1, 4).map(mappedHex => parseInt(mappedHex + mappedHex, 16));
|
||||
}
|
||||
|
||||
const hexMatches = hexColor.exec(hex);
|
||||
if (hexMatches) {
|
||||
return hexMatches.slice(1, 4).map(hex => parseInt(hex, 16));
|
||||
return hexMatches.slice(1, 4).map(slicedHex => parseInt(slicedHex, 16));
|
||||
}
|
||||
|
||||
return null;
|
|
@ -0,0 +1,445 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots components/Asset airplane 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "215px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingSmall canvasAsset"
|
||||
>
|
||||
<div
|
||||
className="canvasAsset__thumb canvasCheckered"
|
||||
>
|
||||
<figure
|
||||
className="euiImage canvasAsset__img"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="Asset thumbnail"
|
||||
className="euiImage__img"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4="
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
className="euiText euiText--extraSmall eui-textBreakAll"
|
||||
>
|
||||
<p
|
||||
className="eui-textBreakAll"
|
||||
>
|
||||
<strong>
|
||||
airplane
|
||||
</strong>
|
||||
<br />
|
||||
<span
|
||||
className="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<small>
|
||||
(
|
||||
1
|
||||
kb)
|
||||
</small>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero asset-create-image"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-describedby="generated-id"
|
||||
aria-label="Create image element"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.5 11V5H11V3.5H5V5H3.5v6H5v1.5h6V11h1.5zm1 0H15v4h-4v-1.5H5V15H1v-4h1.5V5H1V1h4v1.5h6V1h4v4h-1.5v6zM4 4V2H2v2h2zm8 0h2V2h-2v2zM2 14h2v-2H2v2zm10 0h2v-2h-2v2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero asset-download"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="canvasDownload"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
aria-label="Download"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 11.692V3.556C7 3.249 7.224 3 7.5 3s.5.249.5.556v8.136l4.096-4.096a.5.5 0 0 1 .707.707l-4.242 4.243a1.494 1.494 0 0 1-.925.433.454.454 0 0 1-.272 0 1.494 1.494 0 0 1-.925-.433L2.197 8.303a.5.5 0 1 1 .707-.707L7 11.692z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="canvasClipboard"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
aria-label="Copy id to clipboard"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 2.729V2a1 1 0 0 1 1-1h2v1H1v12h4v1H1a1 1 0 0 1-1-1V2.729zM12 5V2a1 1 0 0 0-1-1H9v1h2v3h1zm-1 1h2v9H6V6h5V5H6a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2v1z"
|
||||
/>
|
||||
<path
|
||||
d="M7 10h5V9H7zM7 8h5V7H7zM7 12h5v-1H7zM7 14h5v-1H7zM9 2V1a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v1h1V1h4v1h1zM3 3h6V2H3z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-describedby="generated-id"
|
||||
aria-label="Delete"
|
||||
className="euiButtonIcon euiButtonIcon--danger"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M11 3h5v1H0V3h5V1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2zm-7.056 8H7v1H4.1l.392 2.519c.042.269.254.458.493.458h6.03c.239 0 .451-.189.493-.458l1.498-9.576H14l-1.504 9.73c-.116.747-.74 1.304-1.481 1.304h-6.03c-.741 0-1.365-.557-1.481-1.304l-1.511-9.73H9V5.95H3.157L3.476 8H8v1H3.632l.312 2zM6 3h4V1H6v2z"
|
||||
id="trash-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
xlinkHref="#trash-a"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/Asset marker 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "215px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingSmall canvasAsset"
|
||||
>
|
||||
<div
|
||||
className="canvasAsset__thumb canvasCheckered"
|
||||
>
|
||||
<figure
|
||||
className="euiImage canvasAsset__img"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="Asset thumbnail"
|
||||
className="euiImage__img"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4="
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
className="euiText euiText--extraSmall eui-textBreakAll"
|
||||
>
|
||||
<p
|
||||
className="eui-textBreakAll"
|
||||
>
|
||||
<strong>
|
||||
marker
|
||||
</strong>
|
||||
<br />
|
||||
<span
|
||||
className="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<small>
|
||||
(
|
||||
1
|
||||
kb)
|
||||
</small>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero asset-create-image"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-describedby="generated-id"
|
||||
aria-label="Create image element"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.5 11V5H11V3.5H5V5H3.5v6H5v1.5h6V11h1.5zm1 0H15v4h-4v-1.5H5V15H1v-4h1.5V5H1V1h4v1.5h6V1h4v4h-1.5v6zM4 4V2H2v2h2zm8 0h2V2h-2v2zM2 14h2v-2H2v2zm10 0h2v-2h-2v2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero asset-download"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="canvasDownload"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
aria-label="Download"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 11.692V3.556C7 3.249 7.224 3 7.5 3s.5.249.5.556v8.136l4.096-4.096a.5.5 0 0 1 .707.707l-4.242 4.243a1.494 1.494 0 0 1-.925.433.454.454 0 0 1-.272 0 1.494 1.494 0 0 1-.925-.433L2.197 8.303a.5.5 0 1 1 .707-.707L7 11.692z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="canvasClipboard"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
aria-label="Copy id to clipboard"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 2.729V2a1 1 0 0 1 1-1h2v1H1v12h4v1H1a1 1 0 0 1-1-1V2.729zM12 5V2a1 1 0 0 0-1-1H9v1h2v3h1zm-1 1h2v9H6V6h5V5H6a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2v1z"
|
||||
/>
|
||||
<path
|
||||
d="M7 10h5V9H7zM7 8h5V7H7zM7 12h5v-1H7zM7 14h5v-1H7zM9 2V1a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v1h1V1h4v1h1zM3 3h6V2H3z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-describedby="generated-id"
|
||||
aria-label="Delete"
|
||||
className="euiButtonIcon euiButtonIcon--danger"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M11 3h5v1H0V3h5V1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2zm-7.056 8H7v1H4.1l.392 2.519c.042.269.254.458.493.458h6.03c.239 0 .451-.189.493-.458l1.498-9.576H14l-1.504 9.73c-.116.747-.74 1.304-1.481 1.304h-6.03c-.741 0-1.365-.557-1.481-1.304l-1.511-9.73H9V5.95H3.157L3.476 8H8v1H3.632l.312 2zM6 3h4V1H6v2z"
|
||||
id="trash-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
xlinkHref="#trash-a"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,37 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots components/AssetManager no assets 1`] = `
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--primary"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Manage assets
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/AssetManager two assets 1`] = `
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--primary"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Manage assets
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { Asset, AssetType } from '../asset';
|
||||
|
||||
const AIRPLANE: AssetType = {
|
||||
'@created': '2018-10-13T16:44:44.648Z',
|
||||
id: 'airplane',
|
||||
type: 'dataurl',
|
||||
value:
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=',
|
||||
};
|
||||
|
||||
const MARKER: AssetType = {
|
||||
'@created': '2018-10-13T16:44:44.648Z',
|
||||
id: 'marker',
|
||||
type: 'dataurl',
|
||||
value:
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=',
|
||||
};
|
||||
|
||||
storiesOf('components/Asset', module)
|
||||
.addDecorator(story => <div style={{ width: '215px' }}>{story()}</div>)
|
||||
.add('airplane', () => (
|
||||
<Asset
|
||||
asset={AIRPLANE}
|
||||
onCreate={action('onCreate')}
|
||||
onCopy={action('onCopy')}
|
||||
onDelete={action('onDelete')}
|
||||
/>
|
||||
))
|
||||
.add('marker', () => (
|
||||
<Asset
|
||||
asset={MARKER}
|
||||
onCreate={action('onCreate')}
|
||||
onCopy={action('onCopy')}
|
||||
onDelete={action('onDelete')}
|
||||
/>
|
||||
));
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { AssetType } from '../asset';
|
||||
import { AssetManager } from '../asset_manager';
|
||||
|
||||
const AIRPLANE: AssetType = {
|
||||
'@created': '2018-10-13T16:44:44.648Z',
|
||||
id: 'airplane',
|
||||
type: 'dataurl',
|
||||
value:
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=',
|
||||
};
|
||||
|
||||
const MARKER: AssetType = {
|
||||
'@created': '2018-10-13T16:44:44.648Z',
|
||||
id: 'marker',
|
||||
type: 'dataurl',
|
||||
value:
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=',
|
||||
};
|
||||
|
||||
storiesOf('components/AssetManager', module)
|
||||
.add('no assets', () => (
|
||||
// @ts-ignore @types/react has not been updated to support defaultProps yet.
|
||||
<AssetManager
|
||||
onAddImageElement={action('onAddImageElement')}
|
||||
onAssetAdd={action('onAssetAdd')}
|
||||
onAssetCopy={action('onAssetCopy')}
|
||||
onAssetDelete={action('onAssetDelete')}
|
||||
/>
|
||||
))
|
||||
.add('two assets', () => (
|
||||
<AssetManager
|
||||
assetValues={[AIRPLANE, MARKER]}
|
||||
onAddImageElement={action('onAddImageElement')}
|
||||
onAssetAdd={action('onAssetAdd')}
|
||||
onAssetCopy={action('onAssetCopy')}
|
||||
onAssetDelete={action('onAssetDelete')}
|
||||
/>
|
||||
));
|
131
x-pack/plugins/canvas/public/components/asset_manager/asset.tsx
Normal file
131
x-pack/plugins/canvas/public/components/asset_manager/asset.tsx
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
// @ts-ignore (elastic/eui#1262) EuiImage is not exported yet
|
||||
EuiImage,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Clipboard } from '../clipboard';
|
||||
import { Download } from '../download';
|
||||
|
||||
type ValidTypes = 'dataurl';
|
||||
|
||||
export interface AssetType {
|
||||
'@created': string;
|
||||
id: string;
|
||||
type: ValidTypes;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** The asset to be rendered */
|
||||
asset: AssetType;
|
||||
/** The function to execute when the user clicks 'Create' */
|
||||
onCreate: (asset: AssetType) => void;
|
||||
/** The function to execute when the user clicks 'Copy' */
|
||||
onCopy: (asset: AssetType) => void;
|
||||
/** The function to execute when the user clicks 'Delete' */
|
||||
onDelete: (asset: AssetType) => void;
|
||||
}
|
||||
|
||||
export const Asset: FunctionComponent<Props> = props => {
|
||||
const { asset, onCreate, onCopy, onDelete } = props;
|
||||
|
||||
const createImage = (
|
||||
<EuiFlexItem className="asset-create-image" grow={false}>
|
||||
<EuiToolTip content="Create image element">
|
||||
<EuiButtonIcon
|
||||
iconType="vector"
|
||||
aria-label="Create image element"
|
||||
onClick={() => onCreate(asset)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const downloadAsset = (
|
||||
<EuiFlexItem className="asset-download" grow={false}>
|
||||
<EuiToolTip content="Download">
|
||||
<Download fileName={asset.id} content={asset.value}>
|
||||
<EuiButtonIcon iconType="sortDown" aria-label="Download" />
|
||||
</Download>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const copyAsset = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content="Copy id to clipboard">
|
||||
<Clipboard content={asset.id} onCopy={(result: boolean) => result && onCopy(asset)}>
|
||||
<EuiButtonIcon iconType="copyClipboard" aria-label="Copy id to clipboard" />
|
||||
</Clipboard>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const deleteAsset = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content="Delete">
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
aria-label="Delete"
|
||||
onClick={() => onDelete(asset)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const thumbnail = (
|
||||
<div className="canvasAsset__thumb canvasCheckered">
|
||||
<EuiImage
|
||||
className="canvasAsset__img"
|
||||
size="original"
|
||||
url={props.asset.value}
|
||||
fullScreenIconColor="dark"
|
||||
alt="Asset thumbnail"
|
||||
style={{ backgroundImage: `url(${props.asset.value})` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const assetLabel = (
|
||||
<EuiText size="xs" className="eui-textBreakAll">
|
||||
<p className="eui-textBreakAll">
|
||||
<strong>{asset.id}</strong>
|
||||
<br />
|
||||
<EuiTextColor color="subdued">
|
||||
<small>({Math.round(asset.value.length / 1024)} kb)</small>
|
||||
</EuiTextColor>
|
||||
</p>
|
||||
</EuiText>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexItem key={props.asset.id}>
|
||||
<EuiPanel className="canvasAsset" paddingSize="s">
|
||||
{thumbnail}
|
||||
<EuiSpacer size="s" />
|
||||
{assetLabel}
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup alignItems="baseline" justifyContent="center" responsive={false}>
|
||||
{createImage}
|
||||
{downloadAsset}
|
||||
{copyAsset}
|
||||
{deleteAsset}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -1,258 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonIcon,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
EuiOverlayMask,
|
||||
EuiModal,
|
||||
EuiModalHeader,
|
||||
EuiModalBody,
|
||||
EuiText,
|
||||
EuiImage,
|
||||
EuiPanel,
|
||||
EuiModalFooter,
|
||||
EuiModalHeaderTitle,
|
||||
EuiFlexGrid,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiTextColor,
|
||||
EuiToolTip,
|
||||
EuiFilePicker,
|
||||
EuiEmptyPrompt,
|
||||
} from '@elastic/eui';
|
||||
import { ConfirmModal } from '../confirm_modal';
|
||||
import { Clipboard } from '../clipboard';
|
||||
import { Download } from '../download';
|
||||
import { Loading } from '../loading';
|
||||
import { ASSET_MAX_SIZE } from '../../../common/lib/constants';
|
||||
|
||||
export class AssetManager extends React.PureComponent {
|
||||
static propTypes = {
|
||||
assetValues: PropTypes.array,
|
||||
addImageElement: PropTypes.func,
|
||||
removeAsset: PropTypes.func.isRequired,
|
||||
copyAsset: PropTypes.func.isRequired,
|
||||
onAssetAdd: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
deleteId: null,
|
||||
isModalVisible: false,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
_isMounted = true;
|
||||
|
||||
showModal = () => this.setState({ isModalVisible: true });
|
||||
closeModal = () => this.setState({ isModalVisible: false });
|
||||
|
||||
doDelete = () => {
|
||||
this.resetDelete();
|
||||
this.props.removeAsset(this.state.deleteId);
|
||||
};
|
||||
|
||||
handleFileUpload = files => {
|
||||
this.setState({ loading: true });
|
||||
Promise.all(Array.from(files).map(file => this.props.onAssetAdd(file))).finally(() => {
|
||||
this._isMounted && this.setState({ loading: false });
|
||||
});
|
||||
};
|
||||
|
||||
addElement = assetId => {
|
||||
this.props.addImageElement(assetId);
|
||||
};
|
||||
|
||||
resetDelete = () => this.setState({ deleteId: null });
|
||||
|
||||
renderAsset = asset => (
|
||||
<EuiFlexItem key={asset.id}>
|
||||
<EuiPanel className="canvasAssetManager__asset" paddingSize="s">
|
||||
<div className="canvasAssetManager__thumb canvasCheckered">
|
||||
<EuiImage
|
||||
className="canvasAssetManager__img"
|
||||
size="original"
|
||||
url={asset.value}
|
||||
fullScreenIconColor="dark"
|
||||
alt="Asset thumbnail"
|
||||
style={{ backgroundImage: `url(${asset.value})` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiText size="xs" className="eui-textBreakAll">
|
||||
<p className="eui-textBreakAll">
|
||||
<strong>{asset.id}</strong>
|
||||
<br />
|
||||
<EuiTextColor color="subdued">
|
||||
<small>({Math.round(asset.value.length / 1024)} kb)</small>
|
||||
</EuiTextColor>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiFlexGroup alignItems="baseline" justifyContent="center" responsive={false}>
|
||||
<EuiFlexItem className="asset-create-image" grow={false}>
|
||||
<EuiToolTip content="Create image element">
|
||||
<EuiButtonIcon
|
||||
iconType="vector"
|
||||
aria-label="Create image element"
|
||||
onClick={() => {
|
||||
this.addElement(asset.id);
|
||||
this.closeModal();
|
||||
}}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="asset-download" grow={false}>
|
||||
<EuiToolTip content="Download">
|
||||
<Download fileName={asset.id} content={asset.value}>
|
||||
<EuiButtonIcon iconType="sortDown" aria-label="Download" />
|
||||
</Download>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content="Copy id to clipboard">
|
||||
<Clipboard
|
||||
content={asset.id}
|
||||
onCopy={result => result && this.props.copyAsset(asset.id)}
|
||||
>
|
||||
<EuiButtonIcon iconType="copyClipboard" aria-label="Copy id to clipboard" />
|
||||
</Clipboard>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content="Delete">
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
aria-label="Delete"
|
||||
onClick={() => this.setState({ deleteId: asset.id })}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
render() {
|
||||
const { isModalVisible, loading } = this.state;
|
||||
const { assetValues } = this.props;
|
||||
|
||||
const assetsTotal = Math.round(
|
||||
assetValues.reduce((total, { value }) => total + value.length, 0) / 1024
|
||||
);
|
||||
|
||||
const percentageUsed = Math.round((assetsTotal / ASSET_MAX_SIZE) * 100);
|
||||
|
||||
const emptyAssets = (
|
||||
<EuiPanel className="canvasAssetManager__emptyPanel">
|
||||
<EuiEmptyPrompt
|
||||
iconType="importAction"
|
||||
title={<h2>No available assets</h2>}
|
||||
titleSize="s"
|
||||
body={
|
||||
<Fragment>
|
||||
<p>Upload your assets above to get started</p>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
const assetModal = isModalVisible ? (
|
||||
<EuiOverlayMask>
|
||||
<EuiModal
|
||||
onClose={this.closeModal}
|
||||
className="canvasAssetManager canvasModal--fixedSize"
|
||||
maxWidth="1000px"
|
||||
>
|
||||
<EuiModalHeader className="canvasAssetManager__modalHeader">
|
||||
<EuiModalHeaderTitle className="canvasAssetManager__modalHeaderTitle">
|
||||
Manage workpad assets
|
||||
</EuiModalHeaderTitle>
|
||||
<EuiFlexGroup className="canvasAssetManager__fileUploadWrapper">
|
||||
<EuiFlexItem grow={false}>
|
||||
{loading ? (
|
||||
<Loading animated text="Uploading images" />
|
||||
) : (
|
||||
<EuiFilePicker
|
||||
initialPromptText="Select or drag and drop images"
|
||||
compressed
|
||||
multiple
|
||||
onChange={this.handleFileUpload}
|
||||
accept="image/*"
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
Below are the image assets that you added to this workpad. To reclaim space, delete
|
||||
assets that you no longer need. Unfortunately, any assets that are actually in use
|
||||
cannot be determined at this time.
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
{assetValues.length ? (
|
||||
<EuiFlexGrid responsive={false} columns={4}>
|
||||
{assetValues.map(this.renderAsset)}
|
||||
</EuiFlexGrid>
|
||||
) : (
|
||||
emptyAssets
|
||||
)}
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter className="canvasAssetManager__modalFooter">
|
||||
<EuiFlexGroup className="canvasAssetManager__meterWrapper" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiProgress
|
||||
value={assetsTotal}
|
||||
max={ASSET_MAX_SIZE}
|
||||
color={percentageUsed < 90 ? 'secondary' : 'danger'}
|
||||
size="s"
|
||||
aria-labelledby="CanvasAssetManagerLabel"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
||||
<EuiText id="CanvasAssetManagerLabel">{percentageUsed}% space used</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiButton size="s" onClick={this.closeModal}>
|
||||
Close
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
</EuiOverlayMask>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiButtonEmpty onClick={this.showModal}>Manage assets</EuiButtonEmpty>
|
||||
|
||||
{assetModal}
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.deleteId != null}
|
||||
title="Remove Asset"
|
||||
message="Are you sure you want to remove this asset?"
|
||||
confirmButtonText="Remove"
|
||||
onConfirm={this.doDelete}
|
||||
onCancel={this.resetDelete}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -29,25 +29,27 @@
|
|||
padding-right: $euiSize;
|
||||
}
|
||||
|
||||
// ASSETS LIST
|
||||
|
||||
.canvasAssetManager__asset {
|
||||
text-align: center;
|
||||
overflow: hidden; // hides image from outer panel boundaries
|
||||
.canvasAssetManager__modalFooter {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.canvasAssetManager__emptyPanel {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.canvasAssetManager__thumb {
|
||||
.canvasAsset {
|
||||
text-align: center;
|
||||
overflow: hidden; // hides image from outer panel boundaries
|
||||
|
||||
.canvasAsset__thumb {
|
||||
margin: -$euiSizeS;
|
||||
margin-bottom: 0;
|
||||
font-size: 0; // eliminates any extra space around img
|
||||
}
|
||||
|
||||
.canvasAssetManager__img {
|
||||
.canvasAsset__img {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
|
@ -59,8 +61,4 @@
|
|||
opacity: 0; // only show the background image (which will properly keep proportions)
|
||||
}
|
||||
}
|
||||
|
||||
.canvasAssetManager__modalFooter {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
// @ts-ignore (elastic/eui#1557) EuiFilePicker is not exported yet
|
||||
EuiFilePicker,
|
||||
// @ts-ignore (elastic/eui#1557) EuiImage is not exported yet
|
||||
EuiImage,
|
||||
} from '@elastic/eui';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import { ConfirmModal } from '../confirm_modal';
|
||||
import { AssetType } from './asset';
|
||||
import { AssetModal } from './asset_modal';
|
||||
|
||||
interface Props {
|
||||
/** A list of assets, if available */
|
||||
assetValues: AssetType[];
|
||||
/** Function to invoke when an asset is selected to be added as an element to the workpad */
|
||||
onAddImageElement: (id: string) => void;
|
||||
/** Function to invoke when an asset is deleted */
|
||||
onAssetDelete: (id: string | null) => void;
|
||||
/** Function to invoke when an asset is copied */
|
||||
onAssetCopy: () => void;
|
||||
/** Function to invoke when an asset is added */
|
||||
onAssetAdd: (asset: File) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
/** The id of the asset to delete, if applicable. Is set if the viewer clicks the delete icon */
|
||||
deleteId: string | null;
|
||||
/** Determines if the modal is currently visible */
|
||||
isModalVisible: boolean;
|
||||
/** Indicates if the modal is currently loading */
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export class AssetManager extends PureComponent<Props, State> {
|
||||
public static propTypes = {
|
||||
assetValues: PropTypes.array,
|
||||
onAddImageElement: PropTypes.func.isRequired,
|
||||
onAssetAdd: PropTypes.func.isRequired,
|
||||
onAssetCopy: PropTypes.func.isRequired,
|
||||
onAssetDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
public static defaultProps = {
|
||||
assetValues: [],
|
||||
};
|
||||
|
||||
public state = {
|
||||
deleteId: null,
|
||||
isLoading: false,
|
||||
isModalVisible: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { isModalVisible, isLoading } = this.state;
|
||||
const { assetValues, onAssetCopy, onAddImageElement } = this.props;
|
||||
|
||||
const assetModal = (
|
||||
<AssetModal
|
||||
assetValues={assetValues}
|
||||
isLoading={isLoading}
|
||||
onAssetCopy={onAssetCopy}
|
||||
onAssetCreate={(createdAsset: AssetType) => {
|
||||
onAddImageElement(createdAsset.id);
|
||||
this.setState({ isModalVisible: false });
|
||||
}}
|
||||
onAssetDelete={(asset: AssetType) => this.setState({ deleteId: asset.id })}
|
||||
onClose={() => this.setState({ isModalVisible: false })}
|
||||
onFileUpload={this.handleFileUpload}
|
||||
/>
|
||||
);
|
||||
|
||||
const confirmModal = (
|
||||
<ConfirmModal
|
||||
isOpen={this.state.deleteId !== null}
|
||||
title="Remove Asset"
|
||||
message="Are you sure you want to remove this asset?"
|
||||
confirmButtonText="Remove"
|
||||
onConfirm={this.doDelete}
|
||||
onCancel={this.resetDelete}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiButtonEmpty onClick={this.showModal}>Manage assets</EuiButtonEmpty>
|
||||
{isModalVisible ? assetModal : null}
|
||||
{confirmModal}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private showModal = () => this.setState({ isModalVisible: true });
|
||||
private resetDelete = () => this.setState({ deleteId: null });
|
||||
|
||||
private doDelete = () => {
|
||||
this.resetDelete();
|
||||
this.props.onAssetDelete(this.state.deleteId);
|
||||
};
|
||||
|
||||
private handleFileUpload = (files: FileList) => {
|
||||
this.setState({ isLoading: true });
|
||||
Promise.all(Array.from(files).map(file => this.props.onAssetAdd(file))).finally(() => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import {
|
||||
EuiButton,
|
||||
EuiEmptyPrompt,
|
||||
// @ts-ignore (elastic/eui#1557) EuiFilePicker is not exported yet
|
||||
EuiFilePicker,
|
||||
EuiFlexGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiOverlayMask,
|
||||
EuiPanel,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
// @ts-ignore
|
||||
import { ASSET_MAX_SIZE } from '../../../common/lib/constants';
|
||||
import { Loading } from '../loading';
|
||||
import { Asset, AssetType } from './asset';
|
||||
|
||||
interface Props {
|
||||
/** The assets to display within the modal */
|
||||
assetValues: AssetType[];
|
||||
/** Indicates if assets are being loaded */
|
||||
isLoading: boolean;
|
||||
/** Function to invoke when the modal is closed */
|
||||
onClose: () => void;
|
||||
/** Function to invoke when a file is uploaded */
|
||||
onFileUpload: (assets: FileList) => void;
|
||||
/** Function to invoke when an asset is copied */
|
||||
onAssetCopy: (asset: AssetType) => void;
|
||||
/** Function to invoke when an asset is created */
|
||||
onAssetCreate: (asset: AssetType) => void;
|
||||
/** Function to invoke when an asset is deleted */
|
||||
onAssetDelete: (asset: AssetType) => void;
|
||||
}
|
||||
|
||||
export const AssetModal: FunctionComponent<Props> = props => {
|
||||
const {
|
||||
assetValues,
|
||||
isLoading,
|
||||
onAssetCopy,
|
||||
onAssetCreate,
|
||||
onAssetDelete,
|
||||
onClose,
|
||||
onFileUpload,
|
||||
} = props;
|
||||
|
||||
const assetsTotal = Math.round(
|
||||
assetValues.reduce((total, { value }) => total + value.length, 0) / 1024
|
||||
);
|
||||
|
||||
const percentageUsed = Math.round((assetsTotal / ASSET_MAX_SIZE) * 100);
|
||||
|
||||
const emptyAssets = (
|
||||
<EuiPanel className="canvasAssetManager__emptyPanel">
|
||||
<EuiEmptyPrompt
|
||||
iconType="importAction"
|
||||
title={<h2>Import your assets to get started</h2>}
|
||||
titleSize="xs"
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiModal
|
||||
onClose={onClose}
|
||||
className="canvasAssetManager canvasModal--fixedSize"
|
||||
maxWidth="1000px"
|
||||
>
|
||||
<EuiModalHeader className="canvasAssetManager__modalHeader">
|
||||
<EuiModalHeaderTitle className="canvasAssetManager__modalHeaderTitle">
|
||||
Manage workpad assets
|
||||
</EuiModalHeaderTitle>
|
||||
<EuiFlexGroup className="canvasAssetManager__fileUploadWrapper">
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? (
|
||||
<Loading animated text="Uploading images" />
|
||||
) : (
|
||||
<EuiFilePicker
|
||||
initialPromptText="Select or drag and drop images"
|
||||
compressed
|
||||
multiple
|
||||
onChange={onFileUpload}
|
||||
accept="image/*"
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
Below are the image assets in this workpad. Any assets that are currently in use
|
||||
cannot be determined at this time. To reclaim space, delete assets.
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
{assetValues.length ? (
|
||||
<EuiFlexGrid columns={4}>
|
||||
{assetValues.map(asset => (
|
||||
<Asset
|
||||
asset={asset}
|
||||
key={asset.id}
|
||||
onCopy={onAssetCopy}
|
||||
onCreate={onAssetCreate}
|
||||
onDelete={onAssetDelete}
|
||||
/>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
) : (
|
||||
emptyAssets
|
||||
)}
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter className="canvasAssetManager__modalFooter">
|
||||
<EuiFlexGroup className="canvasAssetManager__meterWrapper" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiProgress
|
||||
value={assetsTotal}
|
||||
max={ASSET_MAX_SIZE}
|
||||
color={percentageUsed < 90 ? 'secondary' : 'danger'}
|
||||
size="s"
|
||||
aria-labelledby="CanvasAssetManagerLabel"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
||||
<EuiText id="CanvasAssetManagerLabel">{percentageUsed}% space used</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiButton size="s" onClick={onClose}>
|
||||
Close
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
};
|
||||
|
||||
AssetModal.propTypes = {
|
||||
assetValues: PropTypes.array,
|
||||
isLoading: PropTypes.bool,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onFileUpload: PropTypes.func.isRequired,
|
||||
onAssetCopy: PropTypes.func.isRequired,
|
||||
onAssetCreate: PropTypes.func.isRequired,
|
||||
onAssetDelete: PropTypes.func.isRequired,
|
||||
};
|
|
@ -26,7 +26,7 @@ const mapStateToProps = state => ({
|
|||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
addImageElement: pageId => assetId => {
|
||||
onAddImageElement: pageId => assetId => {
|
||||
const imageElement = elementsRegistry.get('image');
|
||||
const elementAST = fromExpression(imageElement.expression);
|
||||
const selector = ['chain', '0', 'arguments', 'dataurl'];
|
||||
|
@ -56,7 +56,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
// then return the id, so the caller knows the id that will be created
|
||||
return assetId;
|
||||
},
|
||||
removeAsset: assetId => dispatch(removeAsset(assetId)),
|
||||
onAssetDelete: assetId => dispatch(removeAsset(assetId)),
|
||||
});
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
|
@ -67,9 +67,9 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
return {
|
||||
...ownProps,
|
||||
...dispatchProps,
|
||||
onAddImageElement: dispatchProps.onAddImageElement(stateProps.selectedPage),
|
||||
selectedPage,
|
||||
assetValues,
|
||||
addImageElement: dispatchProps.addImageElement(stateProps.selectedPage),
|
||||
onAssetAdd: file => {
|
||||
const [type, subtype] = get(file, 'type', '').split('/');
|
||||
if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) {
|
||||
|
@ -94,5 +94,5 @@ export const AssetManager = compose(
|
|||
mapDispatchToProps,
|
||||
mergeProps
|
||||
),
|
||||
withProps({ copyAsset: assetId => notify.success(`Copied '${assetId}' to clipboard`) })
|
||||
withProps({ onAssetCopy: asset => notify.success(`Copied '${asset.id}' to clipboard`) })
|
||||
)(Component);
|
||||
|
|
|
@ -4,33 +4,40 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { MouseEvent, KeyboardEvent, ReactElement } from 'react';
|
||||
|
||||
export class Clipboard extends React.PureComponent {
|
||||
static propTypes = {
|
||||
interface Props {
|
||||
children: ReactElement<any>;
|
||||
content: string | number;
|
||||
onCopy: (result: boolean) => void;
|
||||
}
|
||||
|
||||
export class Clipboard extends React.PureComponent<Props> {
|
||||
public static propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
onCopy: PropTypes.func,
|
||||
};
|
||||
|
||||
onClick = ev => {
|
||||
const { content, onCopy } = this.props;
|
||||
ev.preventDefault();
|
||||
|
||||
const result = copy(content, { debug: true });
|
||||
|
||||
if (typeof onCopy === 'function') {
|
||||
onCopy(result);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
return (
|
||||
<div className="canvasClipboard" onClick={this.onClick}>
|
||||
<div
|
||||
className="canvasClipboard"
|
||||
onClick={this.onClick}
|
||||
onKeyPress={this.onClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onClick = (ev: MouseEvent<HTMLDivElement> | KeyboardEvent) => {
|
||||
const { content, onCopy } = this.props;
|
||||
ev.preventDefault();
|
||||
onCopy(copy(content.toString(), { debug: true }));
|
||||
};
|
||||
}
|
|
@ -5,11 +5,22 @@
|
|||
*/
|
||||
|
||||
/* eslint-disable react/forbid-elements */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
export const ConfirmModal = props => {
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
title?: string;
|
||||
message: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
cancelButtonText?: string;
|
||||
confirmButtonText?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ConfirmModal: FunctionComponent<Props> = props => {
|
||||
const {
|
||||
isOpen,
|
||||
title,
|
||||
|
@ -22,14 +33,6 @@ export const ConfirmModal = props => {
|
|||
...rest
|
||||
} = props;
|
||||
|
||||
const confirm = ev => {
|
||||
onConfirm && onConfirm(ev);
|
||||
};
|
||||
|
||||
const cancel = ev => {
|
||||
onCancel && onCancel(ev);
|
||||
};
|
||||
|
||||
// render nothing if this component isn't open
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
|
@ -41,8 +44,8 @@ export const ConfirmModal = props => {
|
|||
{...rest}
|
||||
className={`canvasConfirmModal ${className || ''}`}
|
||||
title={title}
|
||||
onCancel={cancel}
|
||||
onConfirm={confirm}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
confirmButtonText={confirmButtonText}
|
||||
cancelButtonText={cancelButtonText}
|
||||
defaultFocusedButton="confirm"
|
|
@ -4,19 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import expect from '@kbn/expect';
|
||||
import { render } from 'enzyme';
|
||||
import { Download } from '../';
|
||||
import React from 'react';
|
||||
import { Download } from '..';
|
||||
|
||||
describe('<Download />', () => {
|
||||
it('has canvasDownload class', () => {
|
||||
test('has canvasDownload class', () => {
|
||||
const wrapper = render(
|
||||
<Download fileName="hello" content="world">
|
||||
<button>Download it</button>
|
||||
</Download>
|
||||
);
|
||||
|
||||
expect(wrapper.hasClass('canvasDownload')).to.be.ok;
|
||||
expect(wrapper.hasClass('canvasDownload')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import fileSaver from 'file-saver';
|
||||
import { toByteArray } from 'base64-js';
|
||||
import { parseDataUrl } from '../../../common/lib/dataurl';
|
||||
|
||||
export class Download extends React.PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
fileName: PropTypes.string,
|
||||
content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
onCopy: PropTypes.func,
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
const { fileName, content } = this.props;
|
||||
const asset = parseDataUrl(content, true);
|
||||
const assetBlob = new Blob([toByteArray(asset.data)], { type: asset.mimetype });
|
||||
const ext = asset.extension ? `.${asset.extension}` : '';
|
||||
fileSaver.saveAs(assetBlob, `canvas-${fileName}${ext}`);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="canvasDownload" onClick={this.onClick}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { toByteArray } from 'base64-js';
|
||||
import fileSaver from 'file-saver';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { parseDataUrl } from '../../../common/lib/dataurl';
|
||||
|
||||
interface Props {
|
||||
children: ReactElement<any>;
|
||||
fileName: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export class Download extends React.PureComponent<Props> {
|
||||
public static propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
fileName: PropTypes.string.isRequired,
|
||||
content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
};
|
||||
|
||||
public onClick = () => {
|
||||
const { fileName, content } = this.props;
|
||||
const asset = parseDataUrl(content, true);
|
||||
|
||||
if (asset && asset.data) {
|
||||
const assetBlob = new Blob([toByteArray(asset.data)], { type: asset.mimetype });
|
||||
const ext = asset.extension ? `.${asset.extension}` : '';
|
||||
fileSaver.saveAs(assetBlob, `canvas-${fileName}${ext}`);
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
className="canvasDownload"
|
||||
onClick={this.onClick}
|
||||
onKeyPress={this.onClick}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
// @ts-ignore (elastic/eui#1262) EuiFilePicker is not exported yet
|
||||
import { EuiFilePicker } from '@elastic/eui';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { SFC } from 'react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
interface Props {
|
||||
/** Optional ID of the component */
|
||||
|
@ -18,7 +18,7 @@ interface Props {
|
|||
onUpload: () => void;
|
||||
}
|
||||
|
||||
export const FileUpload: SFC<Props> = props => (
|
||||
export const FileUpload: FunctionComponent<Props> = props => (
|
||||
<EuiFilePicker compressed id={props.id} className={props.className} onChange={props.onUpload} />
|
||||
);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// @ts-ignore (elastic/eui#1262) EuiSuperSelect is not exported yet
|
||||
import { EuiSuperSelect } from '@elastic/eui';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { SFC } from 'react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { fonts, FontValue } from '../../../common/lib/fonts';
|
||||
|
||||
interface Props {
|
||||
|
@ -15,7 +15,7 @@ interface Props {
|
|||
value?: FontValue;
|
||||
}
|
||||
|
||||
export const FontPicker: SFC<Props> = props => {
|
||||
export const FontPicker: FunctionComponent<Props> = props => {
|
||||
const { value, onSelect } = props;
|
||||
|
||||
// While fonts are strongly-typed, we also support custom fonts someone might type in.
|
||||
|
|
|
@ -4,12 +4,22 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiIcon, EuiLoadingSpinner, isColorDark } from '@elastic/eui';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiLoadingSpinner, EuiIcon, isColorDark } from '@elastic/eui';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { hexToRgb } from '../../../common/lib/hex_to_rgb';
|
||||
|
||||
export const Loading = ({ animated, text, backgroundColor }) => {
|
||||
interface Props {
|
||||
animated?: boolean;
|
||||
backgroundColor?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export const Loading: FunctionComponent<Props> = ({
|
||||
animated = false,
|
||||
text = '',
|
||||
backgroundColor = '#000000',
|
||||
}) => {
|
||||
if (animated) {
|
||||
return (
|
||||
<div className="canvasLoading">
|
||||
|
@ -25,6 +35,11 @@ export const Loading = ({ animated, text, backgroundColor }) => {
|
|||
}
|
||||
|
||||
const rgb = hexToRgb(backgroundColor);
|
||||
let color = 'text';
|
||||
|
||||
if (rgb && isColorDark(rgb[0], rgb[1], rgb[2])) {
|
||||
color = 'ghost';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="canvasLoading">
|
||||
|
@ -34,7 +49,7 @@ export const Loading = ({ animated, text, backgroundColor }) => {
|
|||
|
||||
</span>
|
||||
)}
|
||||
<EuiIcon color={rgb && isColorDark(...rgb) ? 'ghost' : 'text'} type="clock" />
|
||||
<EuiIcon color={color} type="clock" />
|
||||
</div>
|
||||
);
|
||||
};
|
15
yarn.lock
15
yarn.lock
|
@ -2425,6 +2425,11 @@
|
|||
dependencies:
|
||||
"@types/babel-types" "*"
|
||||
|
||||
"@types/base64-js@^1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5"
|
||||
integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U=
|
||||
|
||||
"@types/bluebird@^3.1.1":
|
||||
version "3.5.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.20.tgz#f6363172add6f4eabb8cada53ca9af2781e8d6a1"
|
||||
|
@ -2620,6 +2625,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.2.1.tgz#5630999aa75532e00af42a54cbe05e1651f4a080"
|
||||
integrity sha512-zuLhLEK4gOPxhkiUhqbG4p0lKY2ePEE//5NHTTn/vjYl0XWpfk2x0Fw7EWKtCjlggEsuc1GvpasD46X9PSZFaA==
|
||||
|
||||
"@types/file-saver@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af"
|
||||
integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ==
|
||||
|
||||
"@types/form-data@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e"
|
||||
|
@ -2861,6 +2871,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/mime-db/-/mime-db-1.27.0.tgz#9bc014a1fd1fdf47649c1a54c6dd7966b8284792"
|
||||
integrity sha1-m8AUof0f30dknBpUxt15ZrgoR5I=
|
||||
|
||||
"@types/mime@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
|
||||
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
|
||||
|
||||
"@types/mimos@*":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mimos/-/mimos-3.0.1.tgz#59d96abe1c9e487e7463fe41e8d86d76b57a441a"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue