├── .stylelintignore ├── storybook-vue ├── babel.config.js ├── .storybook │ ├── preview-head.html │ ├── webpack.config.js │ ├── addons.js │ └── config.js ├── package.json └── stories │ ├── index.js │ └── helper.js ├── img └── demo.gif ├── storybook-react ├── babel.config.js ├── .storybook │ ├── preview-head.html │ ├── webpack.config.js │ ├── addons.js │ └── config.js ├── package.json └── stories │ ├── helpers.jsx │ └── index.jsx ├── vue.js ├── react.js ├── storybook-test ├── workflow.png ├── tests │ ├── image │ │ ├── __image_snapshots__ │ │ │ ├── desktop-test-js-desktop-image-tests-file-view-1-snap.png │ │ │ └── mobile-test-js-mobile-image-tests-file-view-1-snap.png │ │ ├── desktop.test.js │ │ ├── mobile.test.js │ │ └── core │ │ │ ├── get_account_response.json │ │ │ ├── folder_content_response.json │ │ │ └── PuppeteerHelper.js │ ├── setupTests.js │ ├── globalTeardown.js │ ├── integration │ │ ├── core │ │ │ └── ApiHelper.js │ │ └── index.test.js │ ├── testEnvironment.js │ └── globalSetup.js ├── babel.config.js ├── .storybook │ ├── preview-head.html │ └── main.js ├── .eslintrc.js ├── stories │ ├── basic.stories.js │ └── core │ │ └── components.js ├── static │ └── style │ │ └── icon.css ├── jest-puppeteer.config.js ├── package.json ├── jest.config.js └── config.js ├── src ├── picker │ ├── css │ │ ├── constants.less │ │ ├── dropdown.less │ │ ├── files.less │ │ ├── mkdir-form.less │ │ ├── box.less │ │ ├── list.less │ │ ├── index.less │ │ ├── selector.less │ │ ├── accounts.less │ │ ├── mixins.less │ │ ├── breadcrumb.less │ │ ├── material-icons-font.less │ │ ├── buttons.less │ │ ├── container.less │ │ ├── global.less │ │ ├── izitoast.less │ │ ├── util.less │ │ ├── filetable.less │ │ └── icons.less │ ├── templates │ │ ├── index.pug │ │ ├── dropzone.pug │ │ ├── footer.pug │ │ ├── picker.pug │ │ ├── mkdir-form.pug │ │ ├── computer.pug │ │ ├── search.pug │ │ ├── files.pug │ │ ├── addconfirm.pug │ │ ├── breadcrumb.pug │ │ ├── selector.pug │ │ └── accounts.pug │ ├── js │ │ ├── config.json │ │ ├── config_prod.json │ │ ├── files.js │ │ ├── dropdown.js │ │ ├── models │ │ │ └── search.js │ │ ├── breadcrumb.js │ │ ├── iexd-transport.js │ │ ├── izitoast-helper.js │ │ ├── router-helper.js │ │ ├── constants.js │ │ ├── accounts.js │ │ └── storage.js │ └── localization │ │ └── messages │ │ ├── zh-CN.json │ │ ├── zh-TW.json │ │ ├── zh.json │ │ ├── ko.json │ │ ├── ja.json │ │ ├── he.json │ │ ├── fi.json │ │ ├── th.json │ │ ├── et.json │ │ ├── cs.json │ │ ├── sv.json │ │ ├── da.json │ │ ├── tr.json │ │ ├── pl.json │ │ ├── pt.json │ │ ├── id.json │ │ ├── nl.json │ │ ├── it.json │ │ ├── ro.json │ │ ├── es.json │ │ ├── ru.json │ │ ├── fr.json │ │ └── de.json ├── loader │ ├── js │ │ ├── vue │ │ │ ├── index.js │ │ │ ├── DefaultButtons.js │ │ │ ├── Dropzone.js │ │ │ └── creators.js │ │ └── react │ │ │ ├── index.js │ │ │ ├── constants.js │ │ │ └── Dropzone.jsx │ └── css │ │ └── modal.less └── constants.js ├── .browserslistrc ├── storybook-common ├── README.footer.md ├── preview-head.html └── webpack-config-generator.js ├── dev-server ├── static │ ├── style.css │ └── translations-suite-sample.json ├── ssl-cert.js ├── picker-template-hot-loader.js ├── dev-server.js ├── cert.pem ├── index.ejs └── key.pem ├── .eslintignore ├── config ├── merge-strategy.js ├── common.js ├── loader-export-helper.js ├── build.js ├── story-dev-server.js ├── webpack.dev.conf.js ├── picker-plugins.js ├── webpack.base.conf.js └── webpack.story.conf.js ├── .bowerrc ├── bower.json ├── test ├── ts │ └── test_global.ts └── dist-test-server.js ├── .eslintrc-ts.js ├── generate_npmignore.sh ├── template └── picker.ejs ├── .gitignore ├── LICENSE.MIT ├── .eslintrc.js ├── CONTRIBUTORS.md ├── babel.config.js └── stylelint.config.js /.stylelintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storybook-vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../babel.config'); 2 | -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kloudless/file-picker/HEAD/img/demo.gif -------------------------------------------------------------------------------- /storybook-react/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../babel.config'); 2 | -------------------------------------------------------------------------------- /storybook-vue/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | ../../storybook-common/preview-head.html -------------------------------------------------------------------------------- /storybook-react/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | ../../storybook-common/preview-head.html -------------------------------------------------------------------------------- /vue.js: -------------------------------------------------------------------------------- 1 | // Re-export Vue bindings 2 | module.exports = require('./dist/commonjs2/vue.min.js'); 3 | -------------------------------------------------------------------------------- /react.js: -------------------------------------------------------------------------------- 1 | // Re-export dist/loader/js/react 2 | module.exports = require('./dist/commonjs2/react.min.js'); 3 | -------------------------------------------------------------------------------- /storybook-test/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kloudless/file-picker/HEAD/storybook-test/workflow.png -------------------------------------------------------------------------------- /src/picker/css/constants.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Breaking Point 3 | */ 4 | @BREAKING_POINT: 480px; 5 | @MODAL_WIDTH: 640px; 6 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | firefox > 56 2 | chrome > 56 3 | ie >= 11 4 | edge >= 12 5 | opera >= 49 6 | safari >= 8 7 | ios_saf >= 8 8 | -------------------------------------------------------------------------------- /src/picker/templates/index.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Kloudless File Picker 5 | body 6 | include picker 7 | -------------------------------------------------------------------------------- /storybook-common/README.footer.md: -------------------------------------------------------------------------------- 1 | - - - 2 | - Github: https://github.com/kloudless/file-picker 3 | - Support: support@kloudless.com 4 | -------------------------------------------------------------------------------- /dev-server/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | a { 6 | color: #00b7ff; 7 | } 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | dist 4 | lib 5 | dev-server/localization 6 | !.eslintrc.js 7 | !.eslintrc-ts.js 8 | storybook-test/static/style/material.min.js 9 | -------------------------------------------------------------------------------- /src/picker/js/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": true, 3 | "logLevel": 1, 4 | "static_path": "https://s3-us-west-2.amazonaws.com/static-assets.kloudless.com" 5 | } 6 | -------------------------------------------------------------------------------- /src/picker/js/config_prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "logLevel": 3, 4 | "static_path": "https://s3-us-west-2.amazonaws.com/static-assets.kloudless.com" 5 | } 6 | -------------------------------------------------------------------------------- /config/merge-strategy.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | 3 | module.exports = merge.strategy( 4 | { 5 | 'module.rules': 'append', 6 | plugins: 'append', 7 | }, 8 | ); 9 | -------------------------------------------------------------------------------- /storybook-react/.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpackConfigGenerator = require( 2 | '../../storybook-common/webpack-config-generator'); 3 | 4 | module.exports = webpackConfigGenerator(__dirname); -------------------------------------------------------------------------------- /storybook-vue/.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpackConfigGenerator = require( 2 | '../../storybook-common/webpack-config-generator'); 3 | 4 | module.exports = webpackConfigGenerator(__dirname); -------------------------------------------------------------------------------- /storybook-vue/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-knobs/register'; 3 | import '@storybook/addon-links/register'; 4 | import 'storybook-readme/register'; -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "scripts": { 4 | "postinstall": "node ./node_modules/cldr-data-downloader/bin/download.js -i bower_components/cldr-data/index.json -o bower_components/cldr-data/" 5 | } 6 | } -------------------------------------------------------------------------------- /src/picker/templates/dropzone.pug: -------------------------------------------------------------------------------- 1 | div#dropzone(style='background-color:#F5F6F7; color:#474747; width:100%; height:100%; text-align:center; cursor: pointer') 2 | span(style='position:relative; top:33%', data-bind='translate: "dropzone/message"') 3 | 4 | -------------------------------------------------------------------------------- /storybook-test/tests/image/__image_snapshots__/desktop-test-js-desktop-image-tests-file-view-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kloudless/file-picker/HEAD/storybook-test/tests/image/__image_snapshots__/desktop-test-js-desktop-image-tests-file-view-1-snap.png -------------------------------------------------------------------------------- /storybook-test/tests/image/__image_snapshots__/mobile-test-js-mobile-image-tests-file-view-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kloudless/file-picker/HEAD/storybook-test/tests/image/__image_snapshots__/mobile-test-js-mobile-image-tests-file-view-1-snap.png -------------------------------------------------------------------------------- /src/picker/css/dropdown.less: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | cursor: default; 3 | position: absolute; 4 | top: 100%; 5 | right: 0; 6 | left: 0; 7 | display: none; 8 | margin-top: 5px; 9 | z-index: 2000; 10 | } 11 | 12 | .dropdown--toggled { 13 | display: block; 14 | } 15 | -------------------------------------------------------------------------------- /storybook-react/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-options/register'; 2 | // Temporarily disable this addon due to the issue: 3 | // https://github.com/storybookjs/storybook/issues/7215 4 | // import '@storybook/addon-actions/register'; 5 | import '@storybook/addon-knobs/register'; 6 | import 'storybook-readme/register'; 7 | -------------------------------------------------------------------------------- /dev-server/ssl-cert.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | let privateKey; 4 | let certificate; 5 | 6 | if (process.env.SSL_CERT) { 7 | privateKey = fs.readFileSync(process.env.SSL_KEY, 'utf8'); 8 | certificate = fs.readFileSync(process.env.SSL_CERT, 'utf8'); 9 | } 10 | 11 | module.exports = { 12 | privateKey, 13 | certificate, 14 | }; 15 | -------------------------------------------------------------------------------- /src/picker/templates/footer.pug: -------------------------------------------------------------------------------- 1 | .container__footer 2 | // ko ifnot: logo_url() 3 | .flex-row.justify-content-center 4 | .icon.icon--xsmall 5 | .icon__brand 6 | |   7 | a.container__footer-link( 8 | href="https://kloudless.com", target="_blank", 9 | data-bind='translate: {html: { message: "global/poweredby" }}') 10 | // /ko 11 | -------------------------------------------------------------------------------- /src/picker/css/files.less: -------------------------------------------------------------------------------- 1 | .search-input { 2 | padding: 0 12px; 3 | width: 100%; 4 | height: 42px; 5 | background-color: @input_color; 6 | border: solid 1px @input_border_color; 7 | border-radius: @border_radius; 8 | } 9 | 10 | .account-name { 11 | .text-ellipsis(); 12 | 13 | margin-left: 8px; 14 | height: 24px; 15 | line-height: 24px; 16 | font-size: @font_size_lg; 17 | } 18 | -------------------------------------------------------------------------------- /src/picker/css/mkdir-form.less: -------------------------------------------------------------------------------- 1 | .mkdir-form { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .mkdir-form__input { 7 | padding: 0 12px; 8 | width: 100%; 9 | height: 32px; 10 | background-color: @input_color; 11 | border: 1px solid @input_border_color; 12 | border-radius: @border_radius; 13 | } 14 | 15 | .mkdir-form__button { 16 | .btn(); 17 | .btn--primary(); 18 | 19 | width: unset; 20 | height: 32px; 21 | } 22 | -------------------------------------------------------------------------------- /storybook-test/tests/setupTests.js: -------------------------------------------------------------------------------- 1 | const { configureToMatchImageSnapshot } = require('jest-image-snapshot'); 2 | 3 | const toMatchImageSnapshot = configureToMatchImageSnapshot({ 4 | // https://github.com/americanexpress/jest-image-snapshot#%EF%B8%8F-api 5 | comparisonMethod: 'pixelmatch', 6 | customDiffConfig: { 7 | threshold: 0.5, 8 | }, 9 | blur: 1, 10 | }); 11 | expect.extend({ toMatchImageSnapshot }); 12 | 13 | jest.setTimeout(5 * 60 * 1000); 14 | -------------------------------------------------------------------------------- /storybook-vue/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addParameters, addDecorator } from '@storybook/vue'; 2 | import { addReadme } from 'storybook-readme/vue'; 3 | 4 | addParameters({ 5 | options: { 6 | name: 'Kloudless File Picker', 7 | addonPanelInRight: false, 8 | showStoriesPanel: true 9 | } 10 | }); 11 | 12 | addDecorator(addReadme); 13 | 14 | function loadStories() { 15 | require('../stories/index.js'); 16 | } 17 | 18 | configure(loadStories, module); 19 | -------------------------------------------------------------------------------- /storybook-react/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addParameters, addDecorator } from '@storybook/react'; 2 | import '@storybook/addon-console'; 3 | import { addReadme } from 'storybook-readme'; 4 | 5 | addParameters({ 6 | options: { 7 | name: 'Kloudless File Picker', 8 | addonPanelInRight: false, 9 | showStoriesPanel: true 10 | } 11 | }); 12 | addDecorator(addReadme); 13 | 14 | function loadStories() { 15 | require('../stories/index.jsx'); 16 | } 17 | 18 | configure(loadStories, module); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kloudless-file-picker", 3 | "version": "1.0.0", 4 | "private": false, 5 | "dependencies": { 6 | "jquery-ui": "1.10.4", 7 | "requirejs": "2.1.14", 8 | "loglevel": "1.4.1", 9 | "momentjs": "2.7.0", 10 | "almond": "0.2.9", 11 | "jquery-dropdown": "1.0.6", 12 | "jquery-scrollstop": "1.1.0", 13 | "cldr-data": "29.0.0", 14 | "globalize": "1.3.0", 15 | "requirejs-plugins": "^1.0.3" 16 | }, 17 | "resolutions": { 18 | "jquery": "2.1.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /storybook-test/tests/globalTeardown.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line max-len 2 | // https://github.com/smooth-code/jest-puppeteer#create-your-own-globalsetup-and-globalteardown 3 | const { teardown: teardownPuppeteer } = require('jest-environment-puppeteer'); 4 | const { teardown: teardownDevServer } = require('jest-dev-server'); 5 | 6 | 7 | module.exports = async function globalTeardown(globalConfig) { 8 | await teardownPuppeteer(globalConfig); 9 | if (process.env.CI) { 10 | await teardownDevServer(); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /storybook-test/tests/image/desktop.test.js: -------------------------------------------------------------------------------- 1 | import PuppeteerHelper from './core/PuppeteerHelper'; 2 | import { STORY_URL } from '../../config'; 3 | 4 | describe('Desktop Image Tests', () => { 5 | const helper = new PuppeteerHelper(); 6 | 7 | beforeEach(async () => { 8 | await helper.init(STORY_URL.chooser); 9 | }); 10 | 11 | afterEach(async () => { 12 | await helper.cleanup(); 13 | }); 14 | 15 | it('file view', async () => { 16 | await helper.launch(); 17 | await helper.assertScreenshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /storybook-test/tests/image/mobile.test.js: -------------------------------------------------------------------------------- 1 | import PuppeteerHelper from './core/PuppeteerHelper'; 2 | import { STORY_URL } from '../../config'; 3 | 4 | describe('Mobile Image Tests', () => { 5 | const helper = new PuppeteerHelper(); 6 | 7 | beforeEach(async () => { 8 | await helper.init(STORY_URL.chooser, { mobile: true }); 9 | }); 10 | 11 | afterEach(async () => { 12 | await helper.cleanup(); 13 | }); 14 | 15 | it('file view', async () => { 16 | await helper.launch(); 17 | await helper.assertScreenshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /storybook-test/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | // We have config.js which is used by jest and storybook. 13 | // Jest globalSetup.js and testEnvironment.js only accepts CommonJS while 14 | // storybook uses ES6 module syntax. 15 | // This plugin helps resolve the problems of mixing CommonJS and ES6 module. 16 | plugins: ['@babel/plugin-transform-modules-commonjs'], 17 | }; 18 | -------------------------------------------------------------------------------- /src/picker/templates/picker.pug: -------------------------------------------------------------------------------- 1 | div#kloudless-file-picker 2 | div.h-100.w-100(data-bind="template: {name: current}") 3 | 4 | // views 5 | script(type='text/html', id='accounts') 6 | include accounts 7 | script(type='text/html', id='files') 8 | include files 9 | script(type='text/html', id='computer') 10 | include computer 11 | script(type='text/html', id='addConfirm') 12 | include addconfirm 13 | script(type='text/html', id='dropzone') 14 | include dropzone 15 | script(type='text/html', id='search') 16 | include search 17 | 18 | 19 | -------------------------------------------------------------------------------- /storybook-test/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /storybook-test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:react/recommended', 5 | ], 6 | env: { 7 | jest: true, 8 | }, 9 | globals: { 10 | // Jest Puppeteer exposes three globals: browser, page, context 11 | page: 'readonly', 12 | browser: 'readonly', 13 | context: 'readonly', 14 | jestPuppeteer: 'readonly', 15 | }, 16 | rules: { 17 | 'no-console': 'off', 18 | 'react/prop-types': 'off', 19 | }, 20 | settings: { 21 | react: { 22 | version: 'detect', 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/picker/templates/mkdir-form.pug: -------------------------------------------------------------------------------- 1 | form.mkdir-form(data-bind="submit: mkdir") 2 | .flex-row.align-items-center 3 | .flex-grow.mr 4 | input.mkdir-form__input(required, data-bind=` 5 | selectInputText: "Untitled Folder", 6 | translate: { 7 | value: { message: "files/untitledFolder" }, 8 | placeholder: { message: "files/folderName" }}`) 9 | .mr 10 | button.mkdir-form__button( 11 | data-bind='translate: "global/save"', type="submit") 12 | .flex-no-shrink 13 | .icon(data-bind='click: rmdir') 14 | .icon__close -------------------------------------------------------------------------------- /src/loader/js/vue/index.js: -------------------------------------------------------------------------------- 1 | import { createChooser, createSaver } from './creators'; 2 | import Dropzone from './Dropzone'; 3 | import filePicker from '../interface'; 4 | 5 | const Chooser = createChooser(); 6 | const Saver = createSaver(); 7 | 8 | // named export 9 | export { 10 | createChooser, createSaver, Chooser, Saver, Dropzone, 11 | }; 12 | 13 | export const { getGlobalOptions, setGlobalOptions } = filePicker; 14 | 15 | // default export 16 | export default { 17 | createChooser, 18 | createSaver, 19 | Chooser, 20 | Saver, 21 | Dropzone, 22 | getGlobalOptions, 23 | setGlobalOptions, 24 | }; 25 | -------------------------------------------------------------------------------- /test/ts/test_global.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, max-len */ 2 | 3 | // eslint-disable-next-line spaced-comment, @typescript-eslint/triple-slash-reference 4 | /// 5 | 6 | const options: Kloudless.filePicker.ChooserOptions = { 7 | app_id: 'APP_ID', 8 | types: ['files'], 9 | }; 10 | const picker = Kloudless.filePicker.picker(options); 11 | picker.choosify(document.getElementById('button')); 12 | picker.on('success', (files: Kloudless.filePicker.FileMetadata[]) => { 13 | files.forEach((file) => { 14 | console.log(file.id, file.name); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/picker/css/box.less: -------------------------------------------------------------------------------- 1 | .box { 2 | width: 100%; 3 | color: @primary_text_color; 4 | background-color: @section_primary_bg_color; 5 | border: solid 1px @section_line_color; 6 | border-radius: @border_radius; 7 | } 8 | 9 | .box--shadow { 10 | box-shadow: 0 1px 2px 1px @shadow_color; 11 | } 12 | 13 | .box__title { 14 | padding: 16px 16px 0 16px; 15 | font-size: @font_size_sm; 16 | color: @secondary_text_color; 17 | text-transform: uppercase; 18 | } 19 | 20 | .box__section { 21 | padding: 8px 12px; 22 | } 23 | 24 | .box__divider { 25 | margin: 0; 26 | border-top: 1px solid @section_line_color; 27 | } 28 | -------------------------------------------------------------------------------- /storybook-test/.storybook/main.js: -------------------------------------------------------------------------------- 1 | const { devServerPorts } = require('../../config/common'); 2 | 3 | // Prefix with `STORYBOOK_` so that they can be accessed in stories. 4 | [ 5 | 'PICKER_URL', 'BASE_URL', 'KLOUDLESS_ACCOUNT_TOKEN', 6 | 'KLOUDLESS_APP_ID', 'LOADER_PATH', 7 | ].forEach(key=>{ 8 | process.env[`STORYBOOK_${key}`] = process.env[key] || ''; 9 | }); 10 | 11 | process.env.STORYBOOK_LOADER_PATH = ( 12 | process.env.LOADER_PATH || `http://localhost:${devServerPorts.loader}/sdk` 13 | ); 14 | 15 | module.exports = { 16 | stories: ['../stories/**/*.stories.js'], 17 | addons: ['@storybook/addon-actions'], 18 | }; 19 | -------------------------------------------------------------------------------- /storybook-test/stories/basic.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStory, createDropzoneStory } from './core'; 3 | 4 | const { filePickerReact } = window.Kloudless; 5 | 6 | export default { 7 | title: 'E2E Test', 8 | }; 9 | 10 | const ChooserStory = createStory(filePickerReact.Chooser); 11 | const SaverStory = createStory(filePickerReact.Saver); 12 | const DropzoneStory = createDropzoneStory(filePickerReact.Dropzone); 13 | 14 | export const Chooser = () => ; 15 | export const Saver = () => ; 16 | export const Dropzone = () => ; 17 | -------------------------------------------------------------------------------- /src/loader/js/react/index.js: -------------------------------------------------------------------------------- 1 | import { createChooser, createSaver } from './creators'; 2 | import Dropzone from './Dropzone'; 3 | import filePicker from '../interface'; 4 | 5 | const Saver = createSaver(); 6 | const Chooser = createChooser(); 7 | 8 | // named exports 9 | export { 10 | createChooser, 11 | createSaver, 12 | Saver, 13 | Chooser, 14 | Dropzone, 15 | }; 16 | 17 | export const { setGlobalOptions, getGlobalOptions } = filePicker; 18 | 19 | // default exports 20 | export default { 21 | createChooser, 22 | createSaver, 23 | Saver, 24 | Chooser, 25 | Dropzone, 26 | setGlobalOptions, 27 | getGlobalOptions, 28 | }; 29 | -------------------------------------------------------------------------------- /storybook-test/tests/integration/core/ApiHelper.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | class ApiHelper { 4 | constructor(baseUrl, token) { 5 | this.axiosInstance = axios.create({ 6 | baseURL: `${baseUrl}/v1/accounts/me/`, 7 | headers: { 8 | Authorization: `Bearer ${token}`, 9 | }, 10 | }); 11 | } 12 | 13 | async listFolderContent(folderId, options = { }) { 14 | const { page = '', pageSize = 1000 } = options; 15 | return this.axiosInstance.get( 16 | `storage/folders/${folderId}/contents?page=${page}&page_size=${pageSize}`, 17 | ); 18 | } 19 | } 20 | 21 | module.exports = ApiHelper; 22 | -------------------------------------------------------------------------------- /src/picker/css/list.less: -------------------------------------------------------------------------------- 1 | .list { 2 | background-color: @section_primary_bg_color; 3 | } 4 | 5 | .list__item { 6 | display: flex; 7 | align-items: center; 8 | cursor: pointer; 9 | padding: 10px 4px; 10 | color: @primary_text_color; 11 | 12 | &:hover { 13 | background-color: @section_primary_hover_bg_color; 14 | } 15 | } 16 | 17 | .list__item--current { 18 | cursor: default; 19 | justify-content: space-between; 20 | pointer-events: none; 21 | 22 | &:hover { 23 | background-color: @section_primary_bg_color; 24 | } 25 | } 26 | 27 | .list__text { 28 | .word-break(); 29 | 30 | margin: 0 10px; 31 | flex: 1; 32 | } 33 | -------------------------------------------------------------------------------- /src/picker/templates/computer.pug: -------------------------------------------------------------------------------- 1 | .container 2 | .container__header 3 | .flex-row.align-items-center 4 | .flex-no-shrink.mr.sm-hidden 5 | //- an empty icon 6 | .icon.icon--large 7 | .flex-grow 8 | include selector 9 | .flex-no-shrink.ml(data-bind="css: {'sm-hidden': attachMode()}") 10 | #plupload_btn_cancel.icon.icon--large( 11 | data-bind="css: {'invisible': attachMode()}") 12 | .icon__close(data-bind='css: $root.e2eSelectors.J_CLOSE_BTN') 13 | .container__body 14 | #computer_uploader.h-100 15 | p(data-bind='translate: "computer/noSupport"') 16 | include footer 17 | -------------------------------------------------------------------------------- /storybook-test/static/style/icon.css: -------------------------------------------------------------------------------- 1 | /* Copy from https://fonts.googleapis.com/icon?family=Material+Icons */ 2 | 3 | @font-face { 4 | font-family: 'Material Icons'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: url(https://fonts.gstatic.com/s/materialicons/v53/flUhRq6tzZclQEJ-Vdg-IuiaDsNZ.ttf) format('truetype'); 8 | } 9 | 10 | .material-icons { 11 | font-family: 'Material Icons'; 12 | font-weight: normal; 13 | font-style: normal; 14 | font-size: 24px; 15 | line-height: 1; 16 | letter-spacing: normal; 17 | text-transform: none; 18 | display: inline-block; 19 | white-space: nowrap; 20 | word-wrap: normal; 21 | direction: ltr; 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc-ts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on the .eslintrc.js. 3 | * All the configurations remain the same besides @typescript-eslint related 4 | * ones. 5 | */ 6 | 7 | const config = require('./.eslintrc.js'); 8 | 9 | config.globals.Kloudless = 'readonly'; 10 | 11 | config.extends = [ 12 | 'airbnb-base', 13 | 'plugin:@typescript-eslint/recommended', 14 | ]; 15 | config.parser = '@typescript-eslint/parser'; 16 | config.plugins = ['@typescript-eslint']; 17 | 18 | config.rules['@typescript-eslint/camelcase'] = 'off'; 19 | // disable import/named to avoid un-expected errors that actually work 20 | // fine with typescript 21 | config.rules['import/named'] = 'off'; 22 | 23 | module.exports = config; 24 | -------------------------------------------------------------------------------- /src/loader/js/vue/DefaultButtons.js: -------------------------------------------------------------------------------- 1 | const BaseButton = { 2 | functional: true, 3 | render(createElement, context) { 4 | return createElement('button', { 5 | ...context.data, // passing attributes and event handlers 6 | domProps: { 7 | textContent: context.props.title, 8 | }, 9 | }); 10 | }, 11 | }; 12 | 13 | export const ChooserButton = { 14 | ...BaseButton, 15 | name: 'Chooser', 16 | props: { 17 | title: { 18 | type: String, 19 | default: 'Choose a file', 20 | }, 21 | }, 22 | }; 23 | 24 | export const SaverButton = { 25 | ...BaseButton, 26 | name: 'Saver', 27 | props: { 28 | title: { 29 | type: String, 30 | default: 'Save a file', 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/picker/css/index.less: -------------------------------------------------------------------------------- 1 | @import './constants.less'; 2 | @import './variables.less'; 3 | 4 | // mixins 5 | @import './global.less'; 6 | @import './mixins.less'; 7 | @import './container.less'; 8 | @import './util.less'; 9 | 10 | // general component 11 | @import './icons.less'; 12 | @import './buttons.less'; 13 | @import './box.less'; 14 | @import './list.less'; 15 | @import './dropdown.less'; 16 | @import './breadcrumb.less'; 17 | @import './selector.less'; 18 | @import './mkdir-form.less'; 19 | @import './filetable.less'; 20 | 21 | // views 22 | @import './accounts.less'; 23 | @import './files.less'; 24 | @import './computer.less'; 25 | 26 | //material-icons-font 27 | @import './material-icons-font.less'; 28 | 29 | // iziToast 30 | @import './izitoast.less'; 31 | -------------------------------------------------------------------------------- /src/loader/js/react/constants.js: -------------------------------------------------------------------------------- 1 | export const EVENT_HANDLER_MAPPING = { 2 | success: 'onSuccess', 3 | cancel: 'onCancel', 4 | error: 'onError', 5 | open: 'onOpen', 6 | load: 'onLoad', 7 | close: 'onClose', 8 | selected: 'onSelected', 9 | addAccount: 'onAddAccount', 10 | deleteAccount: 'onDeleteAccount', 11 | startFileUpload: 'onStartFileUpload', 12 | finishFileUpload: 'onFinishFileUpload', 13 | logout: 'onLogout', 14 | }; 15 | 16 | export const DROPZONE_EVENT_HANDLER_MAPPING = { 17 | ...EVENT_HANDLER_MAPPING, 18 | dropzoneClicked: 'onClick', 19 | drop: 'onDrop', 20 | }; 21 | 22 | export const EVENT_HANDLERS = Object.values(EVENT_HANDLER_MAPPING); 23 | 24 | export const DROPZONE_EVENT_HANDLERS = Object.values( 25 | DROPZONE_EVENT_HANDLER_MAPPING, 26 | ); 27 | -------------------------------------------------------------------------------- /storybook-common/preview-head.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /storybook-test/tests/image/core/get_account_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1234567890, 3 | "account": "this-is-a-very-loooooooooooooooooooong-account@kloudless.com", 4 | "service": "dropbox", 5 | "internal_use": false, 6 | "created": "2020-05-15T03:24:56.887056Z", 7 | "modified": "2020-10-07T10:29:23.282659Z", 8 | "service_name": "Dropbox", 9 | "admin": false, 10 | "apis": [ 11 | "sharing", 12 | "events", 13 | "storage" 14 | ], 15 | "effective_scope": "gdrive:normal.events.default:kloudless gdrive:normal.storage.default:kloudless gdrive:normal.sharing.default:kloudless", 16 | "api": "core", 17 | "type": "account", 18 | "enabled": true, 19 | "object_definitions": {}, 20 | "custom_properties": {}, 21 | "proxy_connection": null, 22 | "active": true 23 | } 24 | -------------------------------------------------------------------------------- /generate_npmignore.sh: -------------------------------------------------------------------------------- 1 | # create .npmignore base on .gitignore 2 | cp .gitignore .npmignore 3 | # whitelist .gitignore and dist folder in .npmignore 4 | # so that npm will pick these two when packing 5 | echo '# generate by generate_npmignore.sh' >> .npmignore 6 | echo 'storybook-common/' >> .npmignore 7 | echo 'storybook-react/' >> .npmignore 8 | echo 'storybook-vue/' >> .npmignore 9 | echo 'storybook-test/' >> .npmignore 10 | echo '.eslintrc.js' >> .npmignore 11 | echo '.eslintignore' >> .npmignore 12 | echo '.bowerrc' >> .npmignore 13 | echo 'bower.json' >> .npmignore 14 | echo 'webpack.loader.config.js' >> .npmignore 15 | echo 'Gruntfile.js' >> .npmignore 16 | echo '!.gitignore' >> .npmignore 17 | echo '!dist/commonjs2' >> .npmignore 18 | echo '!react.js' >> .npmignore 19 | echo '!vue.js' >> .npmignore 20 | -------------------------------------------------------------------------------- /template/picker.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kloudless File Picker 5 | <%# Only change this line if you are moving picker.css to a different 6 | location. %> 7 | 8 | 9 | 10 | 11 | <%# 12 | This line includes html elements for the File Picker. You can move it 13 | around as long as it is inside body tag. 14 | %> 15 | <% include ../dist/template/index.html %> 16 | 17 | <%# Only change this line if you are moving picker.js to a different 18 | location. DO NOT remove or change the "id" attribute %> 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/picker/css/selector.less: -------------------------------------------------------------------------------- 1 | .account-selector .dropdown .box { 2 | overflow-y: auto; 3 | max-height: 50vh; 4 | } 5 | 6 | .account-selector { 7 | cursor: pointer; 8 | position: relative; 9 | width: 100%; 10 | text-align: left; 11 | } 12 | 13 | .account-selector__button { 14 | display: flex; 15 | align-items: center; 16 | padding: 10px 12px; 17 | width: 100%; 18 | color: @primary_text_color; 19 | background-color: @input_color; 20 | border: solid 1px @input_border_color; 21 | border-radius: @border_radius; 22 | } 23 | 24 | .account-selector__name { 25 | .word-break(); 26 | 27 | margin: 0 8px; 28 | flex: 1; 29 | } 30 | 31 | .account-selector__arrow { 32 | width: 0; 33 | height: 0; 34 | border-left: 4px solid transparent; 35 | border-right: 4px solid transparent; 36 | border-top: 4px solid #aaa; 37 | } 38 | -------------------------------------------------------------------------------- /storybook-test/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | const DEBUG = Boolean(JSON.parse(process.env.DEBUG || false)); 2 | 3 | module.exports = { 4 | launch: { 5 | headless: !DEBUG, 6 | devtools: DEBUG, 7 | // Whether to pipe the browser process stdout and stderr into process.stdout 8 | // and process.stderr. 9 | dumpio: false, 10 | args: [ 11 | '--no-sandbox', 12 | '--disable-setuid-sandbox', 13 | '--disable-dev-shm-usage', 14 | '--auto-open-devtools-for-tabs', 15 | // Need this to access iframe when turning off headless. 16 | // REF: https://github.com/puppeteer/puppeteer/issues/4960 17 | '--disable-features=site-per-process', 18 | ], 19 | ignoreDefaultArgs: ['--hide-scrollbars'], 20 | timeout: 60000, // timeout for launching browser 21 | }, 22 | browser: 'chromium', 23 | browserContext: 'default', 24 | }; 25 | -------------------------------------------------------------------------------- /storybook-test/tests/testEnvironment.js: -------------------------------------------------------------------------------- 1 | // https://github.com/smooth-code/jest-puppeteer#extend-puppeteerenvironment 2 | const fs = require('fs'); 3 | const os = require('os'); 4 | const path = require('path'); 5 | const PuppeteerEnvironment = require('jest-environment-puppeteer'); 6 | const { TEST_DATA } = require('../config'); 7 | 8 | class FilePickerTestEnvironment extends PuppeteerEnvironment { 9 | // Execute once in each worker. 10 | async setup() { 11 | await super.setup(); 12 | 13 | // Read test data from file. 14 | const filepath = path.resolve(os.homedir(), TEST_DATA); 15 | let data = fs.readFileSync(filepath, { encoding: 'utf-8' }); 16 | data = JSON.parse(data); 17 | Object.keys(data).forEach((key) => { 18 | this.global[key] = data[key]; 19 | }); 20 | } 21 | 22 | async teardown() { 23 | await super.teardown(); 24 | } 25 | } 26 | 27 | module.exports = FilePickerTestEnvironment; 28 | -------------------------------------------------------------------------------- /storybook-common/webpack-config-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate webpack configuration for Storybook React and Storybook Vue. 3 | */ 4 | 5 | const path = require('path'); 6 | const AutoPrefixer = require('autoprefixer'); 7 | 8 | module.exports = basePath => async ({ config }) => { 9 | config.module.rules.push( 10 | { 11 | test: /\.jsx?$/, 12 | include: [ 13 | path.resolve(basePath, '..', '..', 'src', 'loader'), 14 | path.resolve(basePath, '..', 'stories'), 15 | ], 16 | exclude: /node_modules/, 17 | use: { 18 | loader: 'babel-loader', 19 | }, 20 | }, 21 | { 22 | test: /\.less$/, 23 | use: [ 24 | 'style-loader', 25 | 'css-loader', 26 | { 27 | loader: 'postcss-loader', 28 | options: { plugins: [AutoPrefixer()] }, 29 | }, 30 | 'less-loader', 31 | ], 32 | }, 33 | ); 34 | 35 | return config; 36 | }; 37 | -------------------------------------------------------------------------------- /storybook-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook-react", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "scripts": { 7 | "storybook": "start-storybook -p 9001", 8 | "build": "build-storybook -c .storybook -o ../.demo/react" 9 | }, 10 | "author": "Kloudless (https://kloudless.com)", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@babel/core": "7.3.4", 14 | "@babel/preset-env": "7.4.1", 15 | "@storybook/addon-actions": "5.1.9", 16 | "@storybook/addon-console": "1.1.0", 17 | "@storybook/addon-info": "5.1.9", 18 | "@storybook/addon-knobs": "5.1.9", 19 | "@storybook/addon-options": "5.1.9", 20 | "@storybook/react": "5.1.9", 21 | "babel-loader": "8.0.5", 22 | "babel-plugin-transform-define": "1.3.1", 23 | "core-js": "3.1.4", 24 | "react": "16.8.4", 25 | "react-dom": "16.8.4", 26 | "storybook-readme": "5.0.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /storybook-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "storybook": "start-storybook -p 9001 -s ./static --ci", 7 | "build-storybook": "build-storybook", 8 | "test": "jest", 9 | "dev-server": "npm run dev:story --prefix=../" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": {}, 14 | "devDependencies": { 15 | "@babel/plugin-transform-modules-commonjs": "7.12.1", 16 | "@storybook/addon-actions": "5.3.18", 17 | "@storybook/addons": "5.3.18", 18 | "@storybook/react": "5.3.18", 19 | "axios": "0.20.0", 20 | "babel-jest": "26.5.2", 21 | "eslint": "7.13.0", 22 | "eslint-plugin-react": "7.21.5", 23 | "expect-puppeteer": "4.4.0", 24 | "jest": "26.5.2", 25 | "jest-dev-server": "4.4.0", 26 | "jest-image-snapshot": "4.2.0", 27 | "jest-puppeteer": "4.4.0", 28 | "puppeteer": "5.3.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /storybook-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook-vue", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "Kloudless (https://kloudless.com)", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "@babel/core": "7.4.0", 10 | "@babel/preset-env": "7.4.1", 11 | "@storybook/addon-actions": "5.1.9", 12 | "@storybook/addon-knobs": "5.1.9", 13 | "@storybook/addon-links": "5.1.9", 14 | "@storybook/addons": "5.1.9", 15 | "@storybook/vue": "5.1.9", 16 | "babel-loader": "8.0.5", 17 | "babel-plugin-stylus-compiler": "1.4.0", 18 | "babel-preset-vue": "2.0.2", 19 | "core-js": "3.1.4", 20 | "storybook-readme": "5.0.2", 21 | "vue": "2.6.10", 22 | "vue-loader": "15.7.0", 23 | "vue-template-compiler": "2.6.10" 24 | }, 25 | "scripts": { 26 | "storybook": "start-storybook -p 9001", 27 | "build": "build-storybook -c .storybook -o ../.demo/vue" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /storybook-test/tests/integration/index.test.js: -------------------------------------------------------------------------------- 1 | import PuppeteerHelper from './core/PuppeteerHelper'; 2 | import { STORY_URL } from '../../config'; 3 | 4 | describe('Chooser Tests', () => { 5 | const helper = new PuppeteerHelper(); 6 | 7 | beforeEach(async () => { 8 | await helper.init(STORY_URL.chooser); 9 | }); 10 | 11 | afterEach(async () => { 12 | await helper.cleanup(); 13 | }); 14 | 15 | it('select a file', async () => { 16 | await helper.launch(); 17 | const selectedFile = global.FOLDER_CONTENT.find( 18 | f => f.type === 'file' && f.downloadable !== false, 19 | ); 20 | if (!selectedFile) { 21 | throw new Error('No file to tests.'); 22 | } 23 | await helper.clickFile(selectedFile.id); 24 | const [ 25 | selectedEventData, successEventData, 26 | ] = await helper.clickSelectBtn(); 27 | 28 | expect(selectedEventData).toEqual([selectedFile]); 29 | expect(successEventData).toEqual([selectedFile]); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/picker/css/accounts.less: -------------------------------------------------------------------------------- 1 | .service-list { 2 | display: flex; 3 | flex-wrap: wrap; 4 | padding: 4px 0 4px 4px; 5 | 6 | @media only screen and (max-width: @BREAKING_POINT) { 7 | justify-content: center; 8 | } 9 | } 10 | 11 | .service-list__item { 12 | .shadow-box(); 13 | 14 | flex: 0 0 120px; 15 | cursor: pointer; 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | justify-content: space-evenly; 20 | margin: 10px 16px 10px 0; 21 | padding: 6px; 22 | width: 120px; 23 | height: 120px; 24 | font-size: @font_size_lg; 25 | color: @primary_text_color; 26 | 27 | &:hover { 28 | background-color: @section_primary_hover_bg_color; 29 | } 30 | } 31 | 32 | .service-list__img { 33 | display: flex; 34 | justify-content: center; 35 | } 36 | 37 | .service-list__text { 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | width: 100%; 42 | word-break: break-word; 43 | text-align: center; 44 | } 45 | -------------------------------------------------------------------------------- /src/picker/js/files.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import ko from 'knockout'; 3 | import logger from 'loglevel'; 4 | 5 | 'use strict'; 6 | 7 | // TODO: better handling of file uploads? 8 | var FileManager = function () { 9 | this.files = ko.observableArray([]); 10 | this.current = ko.observable({}); 11 | }; 12 | 13 | // Add a file to upload. 14 | FileManager.prototype.add = function (url, name) { 15 | logger.debug('Adding file: ', url) 16 | this.files.push({ 17 | url: url, 18 | name: name 19 | }); 20 | }; 21 | 22 | // Cancel current upload. 23 | FileManager.prototype.cancel = function () { 24 | this.files.remove(function (file) { 25 | return file.url == this.current().url; 26 | }); 27 | }; 28 | 29 | // Upload current file. Fire callback 30 | FileManager.prototype.upload = function (location_data, callbacks) { 31 | if (this.current()) { 32 | var file = this.current(); 33 | logger.debug('Uploading current file: ', file.url); 34 | } 35 | }; 36 | 37 | export default FileManager; 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | bower_components 27 | 28 | .tmp 29 | dist/* 30 | test/dist 31 | dev/ 32 | 33 | # Mac 34 | .DS_Store 35 | .idea/ 36 | 37 | # test 38 | test/package-lock.json 39 | storybook-test/static/loader/ 40 | storybook-test/static/picker/ 41 | 42 | # tools, IDE 43 | .nvmrc 44 | .vscode 45 | yarn-error.log 46 | 47 | # demo 48 | .demo 49 | 50 | .npmignore 51 | *.tgz 52 | 53 | .env 54 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kloudless 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /config/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Export common variables used by webpack, babel, and jest configs 3 | */ 4 | 5 | module.exports = { 6 | devServerPorts: { 7 | loader: 8081, 8 | picker: 8082, 9 | }, 10 | /** 11 | * A list of paths in regexp format to specify which files / folders 12 | * babel should ignore. 13 | * 14 | * This list is used by 15 | * 1. 'ignore' option in babel.config.js 16 | * 2. 'transformIgnorePatterns' option in jest.conf.js 17 | */ 18 | ignorePaths: [ 19 | new RegExp('(bower_components)'), 20 | new RegExp( 21 | // eslint-disable-next-line max-len 22 | 'node_modules/@kloudless/file-picker-plupload-module/(?!(jquery.ui.plupload))', 23 | ), 24 | new RegExp('node_modules/(?!(@kloudless/file-picker-plupload-module))'), 25 | new RegExp('lib/(?!(jquery.ajax-retry))'), 26 | ], 27 | /** 28 | * A list of paths to resolve module imports 29 | * 30 | * This list is used by 31 | * 1. 'module-resolver' plugin's root option in babel.config.js 32 | * 2. webpack's resolve.modules option 33 | */ 34 | resolvePaths: ['src', 'lib', 'node_modules', 'bower_components'], 35 | }; 36 | -------------------------------------------------------------------------------- /src/picker/css/mixins.less: -------------------------------------------------------------------------------- 1 | /** 2 | * @font-face LESS Mixin 3 | * use: .font-face( 4 | * @font-family, // name 5 | * @file-path, // absolute/relative URL to font files 6 | * @font-format, // font format. ex: woff 7 | * ) 8 | */ 9 | .font-face(@font-family, @file-path, @font-format) { 10 | @font-face { 11 | font-family: @font-family; 12 | src: url('@{file-path}') format('@{font-format}'); 13 | font-weight: 500; 14 | font-style: normal; 15 | } 16 | } 17 | 18 | .shadow-box { 19 | border-radius: @border_radius; 20 | box-shadow: 0 1px 2px 1px @shadow_color; 21 | } 22 | 23 | .text-ellipsis { 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | white-space: nowrap; 27 | } 28 | 29 | .hover-supported(@rules) { 30 | /* 31 | * https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer 32 | * coarse: The primary input mechanism includes a pointing device of limited accuracy. 33 | */ 34 | @media not all and (pointer: coarse) { 35 | &:hover { 36 | @rules(); 37 | } 38 | } 39 | } 40 | 41 | .word-break { 42 | // break-all is applied by IE and Edge 43 | word-break: break-all; 44 | word-break: break-word; 45 | } 46 | -------------------------------------------------------------------------------- /src/picker/templates/search.pug: -------------------------------------------------------------------------------- 1 | .container(data-bind=`css: { 2 | 'container--is-loading': loading() || processingConfirm() }`) 3 | .container__header 4 | .flex-row.align-items-center 5 | .flex-no-shrink.mr 6 | .icon.icon--large( 7 | data-bind='click: files.toggleSearchView.bind($data, false)') 8 | .icon__back 9 | .flex-grow.mr 10 | form(data-bind="submit: files.doSearch") 11 | input.search-input( 12 | type="text", placeholder="Search", 13 | data-bind=` 14 | selectInputText: files.searchQuery, 15 | textInput: files.searchQuery, 16 | translate: {placeholder: { message: "files/search" }}`) 17 | .flex-no-shrink.invisible.sm-hidden 18 | .icon.icon--large 19 | .container__body 20 | .flex-col.flex-grow.h-100 21 | .flex-no-shrink.w-100 22 | .flex-row.mb 23 | .flex-no-shrink 24 | .icon 25 | img.icon__service(data-bind='attr: {src: accounts.active_logo()}') 26 | .flex-grow 27 | .account-name(data-bind='text: accounts.name') 28 | include filetable 29 | include footer -------------------------------------------------------------------------------- /config/loader-export-helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Export filePicker to the following global variables: 3 | * - window.Kloudless.filePicker 4 | * - window.Kloudless.fileExplorer (b/w compatible) 5 | * - window.Kloudless (b/w compatible) 6 | */ 7 | import 'core-js/stable'; 8 | import 'regenerator-runtime/runtime'; 9 | import filePicker from '../src/loader/js/interface'; 10 | 11 | // Determine export target 12 | let currentScript; 13 | if (document.currentScript) { 14 | currentScript = document.currentScript; // eslint-disable-line 15 | } else { 16 | const scripts = document.getElementsByTagName('script'); 17 | currentScript = scripts[scripts.length - 1]; 18 | } 19 | 20 | const customExportTarget = currentScript.getAttribute('data-kloudless-object'); 21 | if (customExportTarget) { 22 | window[customExportTarget] = window[customExportTarget] || {}; 23 | Object.assign(window[customExportTarget], filePicker); 24 | } else { 25 | window.Kloudless = window.Kloudless || {}; 26 | // b/c with <=1.0.0 27 | Object.assign(window.Kloudless, filePicker); 28 | // b/c with ^1.0.1 29 | window.Kloudless.fileExplorer = filePicker; 30 | 31 | window.Kloudless.filePicker = filePicker; 32 | } 33 | -------------------------------------------------------------------------------- /src/picker/css/breadcrumb.less: -------------------------------------------------------------------------------- 1 | .breadcrumb .dropdown .box { 2 | overflow-y: auto; 3 | max-height: 50vh; 4 | } 5 | 6 | .breadcrumb .icon { 7 | flex-shrink: 0; 8 | } 9 | 10 | .breadcrumb { 11 | position: relative; 12 | display: flex; 13 | flex-wrap: nowrap; 14 | align-items: center; 15 | width: 100%; 16 | font-size: @font_size_lg; 17 | } 18 | 19 | .breadcrumb__hidden { 20 | position: absolute; 21 | width: auto; 22 | height: auto; 23 | white-space: nowrap; 24 | visibility: hidden; 25 | } 26 | 27 | .breadcrumb__root-dir-text { 28 | margin-left: 8px; 29 | color: @primary_text_color; 30 | } 31 | 32 | .breadcrumb__toggle-btn { 33 | display: flex; 34 | align-items: center; 35 | flex-shrink: 0; 36 | white-space: nowrap; 37 | } 38 | 39 | .breadcrumb__toggle-btn--hidden { 40 | display: none; 41 | } 42 | 43 | .breadcrumb__text { 44 | white-space: nowrap; 45 | color: @primary_text_color; 46 | } 47 | 48 | .breadcrumb__text--button { 49 | cursor: pointer; 50 | color: @secondary_text_color; 51 | 52 | &:hover { 53 | text-decoration: underline; 54 | color: @main_color; 55 | } 56 | } 57 | 58 | .breadcrumb__text--ellipsis { 59 | .text-ellipsis(); 60 | } 61 | -------------------------------------------------------------------------------- /src/picker/css/material-icons-font.less: -------------------------------------------------------------------------------- 1 | // Copy from node_modules/material-icons-font/material-icons-font.css but remove 2 | // local sources since local font might be out of date. 3 | @font-face { 4 | font-family: "Material Icons"; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: 8 | url(../../../node_modules/material-icons-font/fonts/MaterialIcons-Regular.woff2) format('woff2'), 9 | url(../../../node_modules/material-icons-font/fonts/MaterialIcons-Regular.woff) format('woff'); 10 | } 11 | 12 | .material-icons { 13 | display: inline-block; 14 | font-size: 24px; /* Preferred icon size */ 15 | font-family: "Material Icons"; 16 | font-weight: normal; 17 | font-style: normal; 18 | line-height: 1; 19 | text-transform: none; 20 | letter-spacing: normal; 21 | word-wrap: normal; 22 | white-space: nowrap; 23 | direction: ltr; 24 | 25 | /* Support for all WebKit browsers. */ 26 | -webkit-font-smoothing: antialiased; 27 | 28 | /* Support for Safari and Chrome. */ 29 | text-rendering: optimizeLegibility; 30 | 31 | /* Support for Firefox. */ 32 | -moz-osx-font-smoothing: grayscale; 33 | 34 | /* Support for IE. */ 35 | font-feature-settings: 'liga'; 36 | } 37 | -------------------------------------------------------------------------------- /test/dist-test-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** An express server to serve project root to test dist build 3 | * bind to port 3000 4 | */ 5 | const express = require('express'); 6 | const path = require('path'); 7 | const http = require('http'); 8 | const https = require('https'); 9 | const sslCert = require('../dev-server/ssl-cert'); 10 | 11 | const protocol = sslCert.certificate ? 'https' : 'http'; 12 | 13 | const app = express(); 14 | 15 | app.set('views', path.join(__dirname, './dist/')); 16 | app.set('view engine', 'ejs'); 17 | app.set('port', 3000); 18 | 19 | app.get('/test/dist/', (req, res) => { 20 | res.render('index.ejs', { 21 | appId: process.env.KLOUDLESS_APP_ID || '', 22 | pickerUrl: `${protocol}://localhost:3000/dist/picker/index.html`, 23 | }); 24 | }); 25 | app.use(express.static(path.resolve(__dirname, '../'))); 26 | 27 | let server; 28 | 29 | if (sslCert.certificate) { 30 | server = https.createServer( 31 | { key: sslCert.privateKey, cert: sslCert.certificate }, 32 | app, 33 | ); 34 | } else { 35 | server = http.createServer(app); 36 | } 37 | 38 | server.listen(app.get('port'), () => { 39 | console.log( 40 | `Dist-test server running on ${protocol}://localhost:3000/test/dist/`, 41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /storybook-test/jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | const path = require('path'); 3 | 4 | // For a detailed explanation regarding each configuration property, visit: 5 | // https://jestjs.io/docs/en/configuration.html 6 | module.exports = { 7 | // The root directory that Jest should scan for tests and modules within 8 | rootDir: path.resolve(__dirname), 9 | 10 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 11 | setupFilesAfterEnv: ['/tests/setupTests.js'], 12 | 13 | // The regexp pattern or array of patterns that Jest uses to detect test files 14 | testRegex: './*\\.test\\.js$', 15 | 16 | // A map from regular expressions to paths to transformers 17 | transform: { 18 | '^.+\\.[t|j]sx?$': 'babel-jest', 19 | }, 20 | 21 | // A path to a module which exports an async function that is triggered once before all test suites 22 | globalSetup: '/tests/globalSetup.js', 23 | 24 | // A path to a module which exports an async function that is triggered once after all test suites 25 | globalTeardown: '/tests/globalTeardown.js', 26 | 27 | // The test environment that will be used for testing 28 | testEnvironment: '/tests/testEnvironment.js', 29 | }; 30 | -------------------------------------------------------------------------------- /src/loader/js/vue/Dropzone.js: -------------------------------------------------------------------------------- 1 | import filePicker from '../interface'; 2 | 3 | const Dropzone = { 4 | name: 'dropzone', 5 | props: { 6 | options: { 7 | type: Object, 8 | required: true, 9 | }, 10 | }, 11 | data() { 12 | return { 13 | id: `dz-${Math.floor(Math.random() * (10 ** 12))}`, 14 | dropzone: null, 15 | }; 16 | }, 17 | methods: { 18 | initDropzone() { 19 | // deep clone options 20 | const options = JSON.parse(JSON.stringify(this.options)); 21 | options.elementId = this.id; 22 | this.dropzone = filePicker.dropzone(options); 23 | this.dropzone.on('raw', (...args) => { 24 | if (args[0] === 'dropzoneClicked') { 25 | args[0] = 'click'; 26 | } 27 | this.$emit(...args); 28 | }); 29 | }, 30 | }, 31 | watch: { 32 | options: { 33 | handler() { 34 | this.dropzone.destroy(); 35 | this.initDropzone(); 36 | }, 37 | deep: true, 38 | }, 39 | }, 40 | template: ` 41 |
44 |
`, 45 | mounted() { 46 | this.initDropzone(); 47 | }, 48 | destroyed() { 49 | this.dropzone.destroy(); 50 | }, 51 | }; 52 | 53 | export default Dropzone; 54 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint', 7 | ecmaFeatures: { 8 | jsx: true, 9 | }, 10 | sourceType: 'module', 11 | }, 12 | env: { 13 | browser: true, 14 | commonjs: true, 15 | es6: true, 16 | node: true, 17 | }, 18 | globals: { 19 | $: 'readonly', 20 | }, 21 | extends: [ 22 | 'airbnb-base', 23 | 'plugin:react/recommended', 24 | ], 25 | plugins: [ 26 | 'react', 27 | ], 28 | // add your custom rules here 29 | rules: { 30 | // allow async-await 31 | 'generator-star-spacing': 'off', 32 | // allow debugger during development 33 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 34 | // allow unresolved module and extensions, because webpack will handle 35 | // this part 36 | 'import/no-unresolved': 'off', 37 | 'import/extensions': 'off', 38 | 'import/no-extraneous-dependencies': 'off', 39 | // allow param reassign or the param's properties 40 | 'no-param-reassign': ['error', { props: false }], 41 | 'max-len': ['error', { code: 80 }], 42 | 'operator-linebreak': 'off', 43 | // allow prefix private functions with underscore 44 | 'no-underscore-dangle': 'off', 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /src/picker/js/dropdown.js: -------------------------------------------------------------------------------- 1 | function handleClickAway(e) { 2 | const toggleButtons = document.querySelectorAll('[data-dropdown-id]'); 3 | toggleButtons.forEach((toggleButton) => { 4 | if (toggleButton.contains(e.target)) { 5 | return; 6 | } 7 | const dropdownId = toggleButton.getAttribute('data-dropdown-id'); 8 | const dropdown = document.getElementById(dropdownId); 9 | if (dropdown) { 10 | dropdown.classList.remove('dropdown--toggled'); 11 | } 12 | }); 13 | } 14 | 15 | function toggleDropdown(e) { 16 | const { currentTarget, target } = e; 17 | const dropdownId = currentTarget.getAttribute('data-dropdown-id'); 18 | const dropdown = document.getElementById(dropdownId); 19 | // exclude dropdown itself 20 | if (!dropdown || dropdown.contains(target)) { 21 | return; 22 | } 23 | dropdown.classList.toggle('dropdown--toggled'); 24 | } 25 | 26 | export default function setupDropdown() { 27 | document.body.removeEventListener('click', handleClickAway); 28 | document.body.addEventListener('click', handleClickAway); 29 | 30 | const toggleButtons = document.querySelectorAll('[data-dropdown-id]'); 31 | toggleButtons.forEach((toggleButton) => { 32 | toggleButton.removeEventListener('click', toggleDropdown); 33 | toggleButton.addEventListener('click', toggleDropdown); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/picker/localization/messages/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "zh": { 3 | "global": { 4 | "select": "选择", 5 | "cancel": "取消", 6 | "save": "保存", 7 | "upload": "上传" 8 | }, 9 | "files": { 10 | "name": "名称", 11 | "size": "尺寸", 12 | "updated": "更新", 13 | "noFilesFound": "找不到文件", 14 | "untitledFolder": "无标题文件夹", 15 | "search": "搜索", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "如果您删除此帐户,则无法再浏览该帐户。 您确定吗?(此动作并不会将您登出该帐户)", 25 | "connectedAccounts": "关联帐户", 26 | "logout": "登出", 27 | "manage": "在此管理您的云存储帐户。", 28 | "chooseAccount": "欢迎! 请选择要连接的服务", 29 | "connectMore": "连接更多!", 30 | "upload": "连接更多!", 31 | "uploadFromComputer": "从您的计算机上传" 32 | }, 33 | "selector": { 34 | "myComputer": "我的电脑", 35 | "accounts": "帐号" 36 | }, 37 | "dropzone": { 38 | "message": "在此处拖放文件,或单击以打开文件资源管理器" 39 | }, 40 | "computer": { 41 | "noSupport": "您的浏览器没有Flash,Silverlight或HTML5支持。" 42 | }, 43 | "addConfirm": { 44 | "confirm": "确认帐户连接。", 45 | "clickBelow": "点击下方即可关联您的{serviceName}帐户", 46 | "connectAccount": "连接{serviceName}帐户" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "zh-TW": { 3 | "global": { 4 | "select": "選擇", 5 | "cancel": "取消", 6 | "save": "保存", 7 | "upload": "上傳" 8 | }, 9 | "files": { 10 | "name": "名稱", 11 | "size": "尺寸", 12 | "updated": "更新", 13 | "noFilesFound": "找不到文件", 14 | "untitledFolder": "無標題文件夾", 15 | "search": "搜索", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "如果您刪除此帳戶,則無法再瀏覽該帳戶。 您確定嗎?(此動作並不會將您登出該帳戶)", 25 | "connectedAccounts": "關聯帳戶", 26 | "logout": "登出", 27 | "manage": "在此管理您的雲存儲帳戶。", 28 | "chooseAccount": "歡迎! 請選擇要連接的服務", 29 | "connectMore": "連接更多!", 30 | "upload": "上傳", 31 | "uploadFromComputer": "從您的計算機上傳" 32 | }, 33 | "selector": { 34 | "myComputer": "我的電腦", 35 | "accounts": "帳號" 36 | }, 37 | "dropzone": { 38 | "message": "在此處拖放文件,或單擊以打開文件資源管理器" 39 | }, 40 | "computer": { 41 | "noSupport": "您的瀏覽器沒有Flash,Silverlight或HTML5支持。" 42 | }, 43 | "addConfirm": { 44 | "confirm": "確認帳戶連接。", 45 | "clickBelow": "點擊下方即可關聯您的{serviceName}帳戶", 46 | "connectAccount": "連接{serviceName}帳戶" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "zh": { 3 | "global": { 4 | "select": "选择", 5 | "cancel": "取消", 6 | "save": "保存", 7 | "upload": "上传" 8 | }, 9 | "files": { 10 | "name": "名称", 11 | "size": "尺寸", 12 | "updated": "更新", 13 | "noFilesFound": "找不到文件", 14 | "untitledFolder": "无标题文件夹", 15 | "search": "搜索", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "如果您删除此帐户,则无法再浏览该帐户。 您确定吗?(此动作并不会将您登出该帐户)", 25 | "connectedAccounts": "关联帐户", 26 | "logout": "登出", 27 | "manage": "在此管理您的云存储帐户。", 28 | "chooseAccount": "欢迎! 请选择要连接的服务", 29 | "connectMore": "连接更多!", 30 | "upload": "连接更多!", 31 | "uploadFromComputer": "从您的计算机上传" 32 | }, 33 | "selector": { 34 | "myComputer": "我的电脑", 35 | "accounts": "帐号" 36 | }, 37 | "dropzone": { 38 | "message": "在此处拖放文件,或单击以打开文件资源管理器" 39 | }, 40 | "computer": { 41 | "noSupport": "您的浏览器没有Flash,Silverlight或HTML5支持。" 42 | }, 43 | "addConfirm": { 44 | "confirm": "确认帐户连接。", 45 | "clickBelow": "点击下方即可关联您的{serviceName}帐户", 46 | "connectAccount": "连接{serviceName}帐户" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/templates/files.pug: -------------------------------------------------------------------------------- 1 | .container(data-bind=`css: { 'container--is-loading': ( 2 | loading() && !loadingNextPage()) || processingConfirm() }`) 3 | .container__header 4 | .flex-row.align-items-center 5 | .flex-no-shrink.mr(data-bind="css: {'sm-hidden': $root.flavor() != 'chooser'}") 6 | .icon.icon--large(data-bind=` 7 | click: files.toggleSearchView.bind($data, true), 8 | css: { 'icon--disabled': loading, 'invisible': $root.flavor() != 'chooser'}`) 9 | .icon__search 10 | .flex-grow 11 | include selector 12 | .flex-no-shrink.ml(data-bind="css: {'sm-hidden': attachMode()}") 13 | .icon.icon--large(data-bind=` 14 | click: cancel, 15 | css: { 'invisible': attachMode() }`) 16 | .icon__close(data-bind='css: $root.e2eSelectors.J_CLOSE_BTN') 17 | .container__body 18 | .flex-col.flex-grow.h-100 19 | .flex-no-shrink.w-100 20 | .flex-row.mb 21 | .flex-grow.mr 22 | include breadcrumb 23 | .flex-no-shrink 24 | .icon.icon--button.icon--large(data-bind='click: files.refresh') 25 | .icon__refresh 26 | // ko if: files.allow_newdir 27 | .flex-no-shrink.ml 28 | .icon.icon--button.icon--large(data-bind='click: files.newdir') 29 | .icon__new-folder 30 | // /ko 31 | include filetable 32 | 33 | include footer 34 | -------------------------------------------------------------------------------- /src/picker/css/buttons.less: -------------------------------------------------------------------------------- 1 | .btn { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | padding: 0 15px; 6 | width: 160px; 7 | height: 40px; 8 | text-align: center; 9 | border: 0; 10 | border-radius: @border_radius; 11 | outline: none; 12 | cursor: pointer; 13 | 14 | &:disabled { 15 | cursor: unset; 16 | } 17 | } 18 | 19 | .btn--primary { 20 | color: @primary_btn_text_color; 21 | background-color: @primary_btn_color; 22 | box-shadow: 0 1px 8px 1px @primary_btn_shadow_color; 23 | 24 | &:hover { 25 | background-color: @primary_btn_hover_color; 26 | } 27 | 28 | &:disabled { 29 | cursor: unset; 30 | color: @primary_btn_disable_text_color; 31 | background-color: @primary_btn_disable_color; 32 | } 33 | } 34 | 35 | .btn--secondary { 36 | color: @secondary_btn_text_color; 37 | background-color: @secondary_btn_color; 38 | box-shadow: 0 1px 8px 1px @secondary_btn_shadow_color; 39 | 40 | &:hover { 41 | background-color: @secondary_btn_hover_color; 42 | } 43 | 44 | &:disabled { 45 | cursor: unset; 46 | color: @secondary_btn_disable_text_color; 47 | background-color: @secondary_btn_disable_color; 48 | } 49 | } 50 | 51 | .link-btn { 52 | padding: 5px 0; 53 | font-size: @font_size_lg; 54 | text-align: center; 55 | color: @link_btn_text_color; 56 | cursor: pointer; 57 | 58 | &:hover { 59 | color: @link_btn_text_hover_color; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/picker/css/container.less: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | .container__header { 9 | flex: 0 0 auto; 10 | padding: 12px; 11 | width: 100%; 12 | font-size: @font_size_lg; 13 | text-align: center; 14 | } 15 | 16 | .container__body { 17 | flex: 1 1 auto; 18 | overflow: hidden; 19 | padding: 12px 12px 0 12px; 20 | width: 100%; 21 | height: 100%; 22 | min-height: 0; 23 | } 24 | 25 | .container__body--scroll { 26 | overflow-y: auto; 27 | } 28 | 29 | .container__loading-wrapper { 30 | position: relative; 31 | width: 100%; 32 | height: 100%; 33 | } 34 | 35 | .container__loading { 36 | position: absolute; 37 | display: none; 38 | width: 100%; 39 | height: 100%; 40 | background-color: @loading_overlay_color; 41 | z-index: 1000; 42 | } 43 | 44 | .container--is-loading .container__loading { 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | } 49 | 50 | .container__loading-icon { 51 | width: 21px; 52 | height: 21px; 53 | background: url(@icon_loading) no-repeat center center; 54 | background-size: contain; 55 | } 56 | 57 | .container__footer { 58 | flex: 0 0 auto; 59 | padding: 12px; 60 | width: 100%; 61 | font-size: @font_size_sm; 62 | text-align: center; 63 | color: @secondary_text_color; 64 | } 65 | 66 | .container__footer-link { 67 | text-decoration: none; 68 | color: @secondary_text_color; 69 | } 70 | -------------------------------------------------------------------------------- /src/picker/localization/messages/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "ko": { 3 | "global": { 4 | "select": "고르다", 5 | "cancel": "취소", 6 | "save": "구하다", 7 | "upload": "업로드" 8 | }, 9 | "files": { 10 | "name": "이름", 11 | "size": "크기", 12 | "updated": "업데이트 됨", 13 | "noFilesFound": "파일을 찾을 수 없음", 14 | "untitledFolder": "제목없는 폴더", 15 | "search": "수색", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "이 계정을 볼 수 없습니다. 삭제 하시겠습니까? (이로 인해이 브라우저의 계정에서 로그 아웃되지 않는 점에 유의하십시오)", 25 | "connectedAccounts": "연결된 계정", 26 | "logout": "로그 아웃", 27 | "manage": "여기에 클라우드 스토리지 계정을 관리하십시오.", 28 | "chooseAccount": "환영! 연결할 서비스를 선택하십시오.", 29 | "connectMore": "더 많은 것을 연결하십시오!", 30 | "upload": "업로드", 31 | "uploadFromComputer": "컴퓨터에서 업로드" 32 | }, 33 | "selector": { 34 | "myComputer": "내 컴퓨터", 35 | "accounts": "계정" 36 | }, 37 | "dropzone": { 38 | "message": "여기에 파일을 끌어다 놓거나 클릭하여 파일 탐색기를 엽니 다." 39 | }, 40 | "computer": { 41 | "noSupport": "브라우저에 Flash, Silverlight 또는 HTML5 지원 기능이 없습니다." 42 | }, 43 | "addConfirm": { 44 | "confirm": "계정 연결을 확인하십시오.", 45 | "clickBelow": "{serviceName} 계정을 연결하려면 아래를 클릭하십시오.", 46 | "connectAccount": "{serviceName} 계정 연결" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/css/global.less: -------------------------------------------------------------------------------- 1 | .font-face( 2 | @font_face_name, 3 | @font_face_path, 4 | @font_face_format 5 | ); 6 | 7 | body, 8 | html, 9 | a, 10 | p, 11 | span, 12 | div, 13 | input, 14 | textarea, 15 | button, 16 | h1, 17 | h2, 18 | h3, 19 | h4, 20 | h5, 21 | h6 { 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: auto; 24 | } 25 | 26 | html { 27 | box-sizing: border-box; 28 | font-size: @font_size_lg; 29 | font-family: @font_family; 30 | font-weight: 500; 31 | font-style: normal; 32 | font-stretch: normal; 33 | line-height: normal; 34 | letter-spacing: normal; 35 | color: @primary_text_color; 36 | background-color: @container_color; 37 | } 38 | 39 | body { 40 | width: 100vw; 41 | max-width: 100vw; 42 | height: 100vh; 43 | max-height: 100vh; 44 | 45 | .iexd { 46 | position: absolute; 47 | display: none; 48 | overflow: hidden; 49 | } 50 | 51 | #kloudless-file-picker { 52 | overflow-x: hidden; 53 | width: 100%; 54 | height: 100%; 55 | } 56 | } 57 | 58 | *, 59 | *::before, 60 | *::after { 61 | box-sizing: inherit; 62 | } 63 | 64 | table { 65 | width: 100%; 66 | background-color: @section_primary_bg_color; 67 | border-spacing: 0; 68 | } 69 | 70 | tbody { 71 | width: 100%; 72 | } 73 | 74 | td, 75 | th { 76 | padding: 10px; 77 | border-bottom: 1px solid @section_line_color; 78 | text-align: left; 79 | 80 | &:first-child { 81 | padding-right: 0; 82 | padding-left: 0; 83 | width: 20px; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/picker/localization/messages/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "ja": { 3 | "global": { 4 | "select": "選択する", 5 | "cancel": "キャンセル", 6 | "save": "保存する", 7 | "upload": "アップロードする" 8 | }, 9 | "files": { 10 | "name": "名", 11 | "size": "サイズ", 12 | "updated": "更新しました", 13 | "noFilesFound": "ファイルが見つかりません", 14 | "untitledFolder": "無題のフォルダ", 15 | "search": "サーチ", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "このアカウントを閲覧することはできなくなります。削除してもよろしいですか? (これにより、このブラウザのアカウントからログアウトされないことに注意してください)", 25 | "connectedAccounts": "接続アカウント", 26 | "logout": "ログアウト", 27 | "manage": "ここでクラウドストレージアカウントを管理します。", 28 | "chooseAccount": "ようこそ! 接続するサービスを選択してください", 29 | "connectMore": "もっとつなげよう!", 30 | "upload": "アップロードする", 31 | "uploadFromComputer": "パソコンからアップロードする" 32 | }, 33 | "selector": { 34 | "myComputer": "私のコンピューター", 35 | "accounts": "アカウント" 36 | }, 37 | "dropzone": { 38 | "message": "ここにファイルをドラッグアンドドロップするか、クリックしてファイルエクスプローラを開きます。" 39 | }, 40 | "computer": { 41 | "noSupport": "お使いのブラウザはFlash、Silverlight、またはHTML5をサポートしていません。" 42 | }, 43 | "addConfirm": { 44 | "confirm": "アカウントの接続を確認してください。", 45 | "clickBelow": "下をクリックして{serviceName}アカウントに接続してください", 46 | "connectAccount": "接続{serviceName}アカウント" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/templates/addconfirm.pug: -------------------------------------------------------------------------------- 1 | .container 2 | .container__header 3 | .flex-row.align-items-center 4 | .flex-no-shrink.icon.icon--large( 5 | data-bind='click: $root.setLocation.bind($data, "#/")') 6 | .icon__back 7 | .flex-grow.text-center(data-bind='translate: "addConfirm/confirm"') 8 | .container__body 9 | .flex-col.align-items-center.h-100.w-100 10 | .box.flex-grow 11 | .flex-col.justify-content-center.h-100 12 | .box__section 13 | div 14 | div.flex-row.justify-content-center.mt.mb(data-bind='translate: {html: { message: "addConfirm/clickBelow", variables: { serviceName: addConfirm.serviceName }}}') 15 | div.flex-row.justify-content-center.mb 16 | .flex-no-shrink.icon.icon--xlarge 17 | img.icon__service(data-bind='attr: {src: addConfirm.serviceLogo}') 18 | .flex-no-shrink.icon.icon--xlarge 19 | img.icon__service(data-bind='attr: {src: $root.static("/launch_site/support-plus.png")}') 20 | .flex-no-shrink.icon.icon--xlarge 21 | .icon__brand 22 | div.flex-row.justify-content-center.mb 23 | button.btn.btn--secondary.mr(data-bind=` 24 | click: $root.setLocation.bind($data, "#/"), 25 | translate: "global/cancel"`) 26 | button#confirm-add-button.btn.btn--primary( 27 | data-bind='translate: {html: { message: "addConfirm/connectAccount"}}') 28 | include footer 29 | -------------------------------------------------------------------------------- /src/picker/js/models/search.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import $ from 'jquery'; 3 | import logger from 'loglevel'; 4 | import config from '../config'; 5 | import util from '../util'; 6 | 7 | 'use strict'; 8 | 9 | //Create a search object 10 | var Search = function (account, key, query, rootFolderId = 'root') { 11 | this.account = account; 12 | this.key = key; 13 | this.q = query; 14 | this.results = null; 15 | this.request = null; 16 | this.rootFolderId = rootFolderId; 17 | }; 18 | 19 | Search.prototype.search = function (callback, errback) { 20 | var self = this; 21 | 22 | let searchUrl = `/search/?q=${self.q}`; 23 | if (self.rootFolderId !== 'root') { 24 | searchUrl += `&parents=${self.rootFolderId}`; 25 | } 26 | 27 | self.request = $.ajax({ 28 | url: config.getAccountUrl('storage', searchUrl), 29 | type: 'GET', 30 | headers: { 31 | Authorization: self.key.scheme + ' ' + self.key.key 32 | }, 33 | success: function (data) { 34 | self.results = data; 35 | logger.debug('[Account ' + self.account + '] Search results on ', 36 | self.q, ': ', self.results); 37 | self.results.objects = self.results.objects.map((obj)=>{ 38 | obj.friendlySize = util.getFriendlySize(obj.size); 39 | return obj; 40 | }); 41 | if (callback) callback(); 42 | }, 43 | error: function () { 44 | logger.error('[Account ' + self.account + '] Search request failed.'); 45 | if (errback) errback(); 46 | }, 47 | datatype: 'json' 48 | }) 49 | }; 50 | 51 | export default Search; 52 | -------------------------------------------------------------------------------- /src/picker/localization/messages/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "he": { 3 | "global": { 4 | "select": "בחר", 5 | "cancel": "בטל", 6 | "save": "להציל", 7 | "upload": "העלה" 8 | }, 9 | "files": { 10 | "name": "שם", 11 | "size": "גודל", 12 | "updated": "עודכן", 13 | "noFilesFound": "לא נמצאו קבצים", 14 | "untitledFolder": "תיקיה ללא כותרת", 15 | "search": "לחפש", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "לא תוכל עוד לדפדף בחשבון זה. האם אתה בטוח שברצונך להסיר אותו? (שים לב שזה לא מתנתק מהחשבון בדפדפן זה)", 25 | "connectedAccounts": "חשבונות מחוברים", 26 | "logout": "להתנתק", 27 | "manage": "נהל את חשבונות האחסון שלך בענן כאן..", 28 | "chooseAccount": "ברוך הבא! בחר שירות לחיבור", 29 | "connectMore": "התחבר עוד!", 30 | "upload": "העלה", 31 | "uploadFromComputer": "העלה מהמחשב שלך" 32 | }, 33 | "selector": { 34 | "myComputer": "המחשב שלי", 35 | "accounts": "חשבונות" 36 | }, 37 | "dropzone": { 38 | "message": "גרור ושחרר קבצים כאן, או לחץ כדי לפתוח את סייר הקבצים" 39 | }, 40 | "computer": { 41 | "noSupport": "לדפדפן שלך אין תמיכה ב- Flash, Silverlight או ב- HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "אשר את החיבור לחשבון.", 45 | "clickBelow": "לחץ למטה כדי לחבר את חשבון {serviceName} שלך", 46 | "connectAccount": "התחבר לחשבון {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | We appreciate any and all third-party contributions to help improve our SDKs 2 | and UI Tools. When submitting any changes, please feel free to include your 3 | name and company (if applicable) here for posterity. 4 | 5 | 6 | Contributor offers to license certain software (a "Contribution" or multiple 7 | “Contributions”) to Kloudless, and Kloudless agrees to accept said 8 | Contributions, under the terms of the MIT open source license. 9 | 10 | Contributor understands and agrees that Kloudless shall have the irrevocable 11 | and perpetual right to make and distribute copies of any Contribution, as well 12 | as to create and distribute collective works and derivative works of any 13 | Contribution, under the MIT License. 14 | 15 | # Contributors 16 | 17 | * Leo Zhang [@ilikebits](https://github.com/ilikebits) 18 | * Timothy Liu [@pseudonumos](https://github.com/pseudonumos) 19 | * Vinod Chandru [@vinodc](https://github.com/vinodc) 20 | * Chris Kuehl [@chriskuehl](https://github.com/chriskuehl) 21 | * Edward Look [@edwlook](https://github.com/edwlook) 22 | * Steven Cheng [@chengsteven](https://github.com/chengsteven) 23 | * Alice Cai [@ahcai](https://github.com/ahcai) 24 | * Joeson Chiang [@joesonchiang](https://github.com/joesonchiang) 25 | * Jackson Broussard [@jbrsrd](https://github.com/jbrsrd) 26 | * Katie Low [@ktmellow](https://github.com/ktmellow) 27 | * Artem Pisarev [@artemas](https://github.com/artemas) 28 | * Phil Maclachlan [@flikstrr](https://github.com/flikstrr) 29 | * Anovysh Itechart [@anovysh-itechart](https://github.com/anovysh-itechart) 30 | * Tim Sewell [@timssewell](https://github.com/timssewell) 31 | -------------------------------------------------------------------------------- /config/build.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const argv = process.argv.slice(2); 5 | 6 | if (argv.length === 1) { 7 | // eslint-disable-next-line import/no-dynamic-require, global-require 8 | const config = require(path.resolve(__dirname, '../', argv[0])); 9 | 10 | webpack(config, (err, stats) => { 11 | if (err) { 12 | // eslint-disable-next-line no-console 13 | console.error(err.stack || err); 14 | if (err.details) { 15 | // eslint-disable-next-line no-console 16 | console.error(err.details); 17 | } 18 | // Do not print build msg if there is any error. 19 | return; 20 | } 21 | 22 | const info = stats.toJson(); 23 | 24 | if (stats.hasErrors()) { 25 | // eslint-disable-next-line no-console 26 | console.error(info.errors); 27 | // Do not print build msg if there is any error. 28 | return; 29 | } 30 | 31 | if (stats.hasWarnings()) { 32 | // eslint-disable-next-line no-console 33 | console.warn(info.warnings); 34 | } 35 | 36 | process.stdout.write(`${ 37 | stats.toString({ 38 | colors: true, 39 | })}\n\n${ 40 | process.env.BUILD_LICENSE === 'AGPL' ? 41 | 'This build is under AGPL license.\n' : ( 42 | 'This build is under MIT license.\n' + 43 | 'Run `npm run build:agpl` to include support for ' + 44 | 'local uploads via the Computer option.\n') 45 | }`); 46 | }); 47 | } else { 48 | // eslint-disable-next-line no-console 49 | console.error('Please provide the config filename as the first argument.'); 50 | } 51 | -------------------------------------------------------------------------------- /storybook-test/tests/globalSetup.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line max-len 2 | // https://github.com/smooth-code/jest-puppeteer#create-your-own-globalsetup-and-globalteardown 3 | const fs = require('fs'); 4 | const os = require('os'); 5 | const path = require('path'); 6 | const { setup: setupPuppeteer } = require('jest-environment-puppeteer'); 7 | const { setup: setupDevServer } = require('jest-dev-server'); 8 | const { BASE_URL, KLOUDLESS_ACCOUNT_TOKEN, TEST_DATA } = require('../config'); 9 | const ApiHelper = require('./integration/core/ApiHelper'); 10 | const { devServerPorts } = require('../../config/common'); 11 | 12 | const apiHelper = new ApiHelper(BASE_URL, KLOUDLESS_ACCOUNT_TOKEN); 13 | 14 | // Global setup is executed once before all workers starts. 15 | // In addition, the workers are run in different processes. We have to write 16 | // test data to the file so workers can get data from file. 17 | async function setupTestData() { 18 | const response = await apiHelper.listFolderContent('root'); 19 | const data = { FOLDER_CONTENT: response.data.objects }; 20 | const filepath = path.resolve(os.homedir(), TEST_DATA); 21 | fs.writeFileSync(filepath, JSON.stringify(data), { encoding: 'utf-8' }); 22 | } 23 | 24 | module.exports = async function globalSetup(globalConfig) { 25 | await setupTestData(); 26 | await setupPuppeteer(globalConfig); 27 | if (process.env.CI) { 28 | await setupDevServer([ 29 | { 30 | command: 'npm run storybook', 31 | port: 9001, 32 | launchTimeout: 60000, 33 | }, 34 | { 35 | command: 'npm run dev:story --prefix=../', 36 | port: devServerPorts.picker, 37 | launchTimeout: 60000, 38 | }, 39 | ]); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/picker/localization/messages/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "fi": { 3 | "global": { 4 | "select": "valita", 5 | "cancel": "Peruuttaa", 6 | "save": "Tallentaa", 7 | "upload": "upload" 8 | }, 9 | "files": { 10 | "name": "Nimi", 11 | "size": "Koko", 12 | "updated": "Päivitetty", 13 | "noFilesFound": "Ei tiedostoja", 14 | "untitledFolder": "Nimetön kansio", 15 | "search": "Hae", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Et enää voi selata tämän tilin. Oletko varma, että haluat poistaa sen? (Huomaa, että tämä ei kirjata ulos tilin tässä selaimessa)", 25 | "connectedAccounts": "Yhdistetyt tilit", 26 | "logout": "kirjautua ulos", 27 | "manage": "Hallitse pilvivarastotilejäsi täällä.", 28 | "chooseAccount": "Tervetuloa! Valitse palvelu, johon haluat muodostaa yhteyden", 29 | "connectMore": "Liitä lisää!", 30 | "upload": "upload", 31 | "uploadFromComputer": "Lataa tietokoneesta" 32 | }, 33 | "selector": { 34 | "myComputer": "Tietokoneeni", 35 | "accounts": "tilit" 36 | }, 37 | "dropzone": { 38 | "message": "Vedä ja pudota tiedostot täällä tai avaa File Picker" 39 | }, 40 | "computer": { 41 | "noSupport": "Selaimesi ei sisällä Flash-, Silverlight- tai HTML5-tukea." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Vahvista tilin yhteys.", 45 | "clickBelow": "Yhdistä {serviceName} -tili napsauttamalla alla", 46 | "connectAccount": "Yhdistä {serviceName} -tili" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "th-TH": { 3 | "global": { 4 | "select": "เลือก", 5 | "cancel": "ยกเลิก", 6 | "save": "บันทึก", 7 | "upload": "อัปโหลด" 8 | }, 9 | "files": { 10 | "name": "ชื่อ", 11 | "size": "ขนาด", 12 | "updated": "Updated", 13 | "noFilesFound": "ไม่พบไฟล์", 14 | "untitledFolder": "โฟลเดอร์ไม่มีชื่อ", 15 | "search": "ค้นหา", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "คุณจะไม่สามารถเรียกดูบัญชีนี้ คุณแน่ใจหรือคุณต้องการที่จะลบมันได้หรือไม่ (หมายเหตุว่านี้ไม่ได้เข้าสู่ระบบคุณออกจากบัญชีในเบราว์เซอร์นี้)", 25 | "connectedAccounts": "บัญชีที่เชื่อมต่อ", 26 | "logout": "ออกจากระบบ", 27 | "manage": "จัดการบัญชีที่เก็บข้อมูลบนคลาวด์ของคุณที่นี่", 28 | "chooseAccount": "ยินดีต้อนรับ! โปรดเลือกบริการที่จะเชื่อมต่อ", 29 | "connectMore": "เชื่อมต่อมากขึ้น!", 30 | "upload": "อัปโหลด", 31 | "uploadFromComputer": "อัปโหลดจากคอมพิวเตอร์ของคุณ" 32 | }, 33 | "selector": { 34 | "myComputer": "คอมพิวเตอร์ของฉัน", 35 | "accounts": "บัญชี" 36 | }, 37 | "dropzone": { 38 | "message": "ลากและวางไฟล์ที่นี่หรือคลิกเพื่อเปิด File Picker" 39 | }, 40 | "computer": { 41 | "noSupport": "เบราว์เซอร์ของคุณไม่มีการสนับสนุน Flash, Silverlight หรือ HTML5" 42 | }, 43 | "addConfirm": { 44 | "confirm": "ยืนยันการเชื่อมต่อบัญชี", 45 | "clickBelow": "คลิกด้านล่างเพื่อเชื่อมต่อบัญชี {serviceName} ของคุณ", 46 | "connectAccount": "เชื่อมต่อบัญชี {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "et": { 3 | "global": { 4 | "select": "Valige", 5 | "cancel": "Tühista", 6 | "save": "Salvesta", 7 | "upload": "Laadi üles" 8 | }, 9 | "files": { 10 | "name": "Nimi", 11 | "size": "Suurus", 12 | "updated": "Uuendatud", 13 | "noFilesFound": "Faile ei leitud", 14 | "untitledFolder": "Pealkirjata kaust", 15 | "search": "Otsing", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Te ei saa seda kontot enam sirvida. Kas olete kindel, et soovite selle eemaldada? (Pange tähele, et see ei logi teid selle brauseri kontolt välja)", 25 | "connectedAccounts": "Ühendatud kontod", 26 | "logout": "Logi välja", 27 | "manage": "Hallake oma pilvesalvestuskontot siin.", 28 | "chooseAccount": "Tere tulemast! Palun valige ühendatav teenus", 29 | "connectMore": "Ühendage rohkem!", 30 | "upload": "Laadi üles", 31 | "uploadFromComputer": "Laadige oma arvutisse üles" 32 | }, 33 | "selector": { 34 | "myComputer": "Minu arvuti", 35 | "accounts": "Kontod" 36 | }, 37 | "dropzone": { 38 | "message": "Lohistage failid siia ja klõpsake File Picker avamiseks" 39 | }, 40 | "computer": { 41 | "noSupport": "Teie brauseril puudub Flash, Silverlight või HTML5 tugi." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Kinnitage konto ühendus.", 45 | "clickBelow": "Klõpsake allpool, et ühendada oma {serviceName} konto", 46 | "connectAccount": "Ühenda {serviceName} konto" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "cs": { 3 | "global": { 4 | "select": "Vybrat", 5 | "cancel": "zrušení", 6 | "save": "Uložit", 7 | "upload": "nahrát" 8 | }, 9 | "files": { 10 | "name": "název", 11 | "size": "Velikost", 12 | "updated": "Aktualizováno", 13 | "noFilesFound": "Nebyly nalezeny žádné soubory", 14 | "untitledFolder": "Složka bez názvu", 15 | "search": "Vyhledávání", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Již nebude moci procházet tento účet. Jsou si jisti, že chcete odstranit? (Všimněte si, že to není odhlásit z účtu v tomto prohlížeči)", 25 | "connectedAccounts": "Připojené účty", 26 | "logout": "odhlásit se", 27 | "manage": "Zde můžete spravovat účty cloudového úložiště.", 28 | "chooseAccount": "Vítejte! Vyberte službu, ke které se chcete připojit", 29 | "connectMore": "Připojte více!", 30 | "upload": "nahrát", 31 | "uploadFromComputer": "Nahrát z počítače" 32 | }, 33 | "selector": { 34 | "myComputer": "Můj počítač", 35 | "accounts": "Účty" 36 | }, 37 | "dropzone": { 38 | "message": "Přetáhněte soubory sem nebo kliknutím otevřete Průzkumník souborů" 39 | }, 40 | "computer": { 41 | "noSupport": "Váš prohlížeč nemá podporu Flash, Silverlight nebo HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Potvrďte připojení účtu.", 45 | "clickBelow": "Klikněte níže a připojte svůj účet {serviceName}", 46 | "connectAccount": "Připojit účet {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "sv": { 3 | "global": { 4 | "select": "Välj", 5 | "cancel": "Annullera", 6 | "save": "Spara", 7 | "upload": "Ladda upp" 8 | }, 9 | "files": { 10 | "name": "namn", 11 | "size": "Storlek", 12 | "updated": "Uppdaterad", 13 | "noFilesFound": "Inga filer funna", 14 | "untitledFolder": "Untitled Folder", 15 | "search": "Sök", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Du kommer inte längre att kunna surfa på kontot. Är du säker på att du vill ta bort den? (Observera att detta inte loggar du ut från kontot på denna webbläsare)", 25 | "connectedAccounts": "Anslutna konton", 26 | "logout": "logga ut", 27 | "manage": "Hantera dina Cloud Storage-konton här.", 28 | "chooseAccount": "Välkommen! Välj en tjänst för att ansluta", 29 | "connectMore": "Anslut mer!", 30 | "upload": "Ladda upp", 31 | "uploadFromComputer": "Ladda upp från din dator" 32 | }, 33 | "selector": { 34 | "myComputer": "Min dator", 35 | "accounts": "konton" 36 | }, 37 | "dropzone": { 38 | "message": "Dra och släpp filer här, eller klicka för att öppna File Picker" 39 | }, 40 | "computer": { 41 | "noSupport": "Din webbläsare har inte stöd för Flash, Silverlight eller HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Bekräfta kontoanslutning.", 45 | "clickBelow": "Klicka nedan för att ansluta ditt {serviceName} -konto", 46 | "connectAccount": "Anslut {serviceName} konto" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "da": { 3 | "global": { 4 | "select": "Vælg", 5 | "cancel": "Afbestille", 6 | "save": "Gemme", 7 | "upload": "Upload" 8 | }, 9 | "files": { 10 | "name": "Navn", 11 | "size": "Størrelse", 12 | "updated": "Opdateret", 13 | "noFilesFound": "Ingen filer fundet", 14 | "untitledFolder": "Untitled Folder", 15 | "search": "Søg", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Du vil ikke længere være i stand til at gennemse denne konto. Er du sikker på du ønsker at fjerne det? (Bemærk at dette ikke logge dig ud af kontoen på denne browser)", 25 | "connectedAccounts": "Tilknyttede konti", 26 | "logout": "Log ud", 27 | "manage": "Administrer dine Cloud Storage-konti her.", 28 | "chooseAccount": "Velkommen! Vælg venligst en tjeneste for at oprette forbindelse", 29 | "connectMore": "Tilslut mere!", 30 | "upload": "Upload", 31 | "uploadFromComputer": "Upload fra din computer" 32 | }, 33 | "selector": { 34 | "myComputer": "Min computer", 35 | "accounts": "Konti" 36 | }, 37 | "dropzone": { 38 | "message": "Træk og slip filer her, eller klik for at åbne Filoversigten" 39 | }, 40 | "computer": { 41 | "noSupport": "Din browser har ikke Flash, Silverlight eller HTML5 support." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Bekræft kontoforbindelse.", 45 | "clickBelow": "Klik nedenfor for at forbinde din {serviceName} konto", 46 | "connectAccount": "Tilslut {serviceName} Konto" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "tr": { 3 | "global": { 4 | "select": "seçmek", 5 | "cancel": "İptal etmek", 6 | "save": "Kayıt etmek", 7 | "upload": "Yükleme" 8 | }, 9 | "files": { 10 | "name": "isim", 11 | "size": "Boyut", 12 | "updated": "Güncellenmiş", 13 | "noFilesFound": "Dosya Bulunamadı", 14 | "untitledFolder": "Adsız Klasör", 15 | "search": "Arama", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Artık bu hesaba göz atamayacaksınız. Kaldırmak istediğinizden emin misiniz? (Bunun sizi bu tarayıcıdaki hesaptan çıkmadığını unutmayın)", 25 | "connectedAccounts": "Bağlı hesaplar", 26 | "logout": "çıkış Yap", 27 | "manage": "Bulut depolama hesaplarınızı burada yönetin.", 28 | "chooseAccount": "Hoşgeldiniz! Lütfen bağlanmak için bir servis seçin", 29 | "connectMore": "Daha fazlasını bağla!", 30 | "upload": "Yükleme", 31 | "uploadFromComputer": "Bilgisayarınızdan yükleyin" 32 | }, 33 | "selector": { 34 | "myComputer": "Benim bilgisayarım", 35 | "accounts": "Hesaplar" 36 | }, 37 | "dropzone": { 38 | "message": "Dosyaları buraya sürükleyip bırakın veya Dosya Gezgini'ni açmak için tıklayın." 39 | }, 40 | "computer": { 41 | "noSupport": "Tarayıcınızda Flash, Silverlight veya HTML5 desteği bulunmuyor." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Hesap bağlantısını onayla.", 45 | "clickBelow": "{ServiceName} hesabınızı bağlamak için aşağıyı tıklayın", 46 | "connectAccount": "Connect {serviceName} Hesabı" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "pl": { 3 | "global": { 4 | "select": "Wybierz", 5 | "cancel": "Anuluj", 6 | "save": "Zapisać", 7 | "upload": "Przekazać plik" 8 | }, 9 | "files": { 10 | "name": "Imię", 11 | "size": "Rozmiar", 12 | "updated": "Zaktualizowano", 13 | "noFilesFound": "Nie znaleziono plików", 14 | "untitledFolder": "Folder bez tytułu", 15 | "search": "Szukaj", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Państwo nie będzie już w stanie przeglądać tego konta. Czy na pewno chcesz go usunąć? (Należy pamiętać, że to nie wylogowanie z konta w tej przeglądarce)", 25 | "connectedAccounts": "Połączone konta", 26 | "logout": "Wyloguj", 27 | "manage": "Zarządzaj swoimi kontami przechowywania w chmurze tutaj.", 28 | "chooseAccount": "Witamy! Wybierz usługę do połączenia", 29 | "connectMore": "Połącz więcej!", 30 | "upload": "Przekazać plik", 31 | "uploadFromComputer": "Prześlij z komputera" 32 | }, 33 | "selector": { 34 | "myComputer": "Mój komputer", 35 | "accounts": "Konta" 36 | }, 37 | "dropzone": { 38 | "message": "Przeciągnij i upuść pliki tutaj lub kliknij, aby otworzyć Eksplorator plików" 39 | }, 40 | "computer": { 41 | "noSupport": "Twoja przeglądarka nie obsługuje Flash, Silverlight ani HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Potwierdź połączenie z kontem.", 45 | "clickBelow": "Kliknij poniżej, aby połączyć swoje konto {serviceName}", 46 | "connectAccount": "Połącz konto {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "pt": { 3 | "global": { 4 | "select": "Selecione", 5 | "cancel": "Cancelar", 6 | "save": "Salve", 7 | "upload": "Envio" 8 | }, 9 | "files": { 10 | "name": "Nome", 11 | "size": "Tamanho", 12 | "updated": "Atualizada", 13 | "noFilesFound": "Nenhum arquivo encontrado", 14 | "untitledFolder": "Pasta sem título", 15 | "search": "Procurar", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Você não será mais capaz de navegar esta conta. Tem certeza de que gostaria de removê-lo? (Note que isso não desconectá-lo da conta neste navegador)", 25 | "connectedAccounts": "Contas conectadas", 26 | "logout": "sair", 27 | "manage": "Gerencie suas contas de armazenamento em nuvem aqui.", 28 | "chooseAccount": "Bem vinda! Por favor, escolha um serviço para conectar", 29 | "connectMore": "Ligue mais!", 30 | "upload": "Envio", 31 | "uploadFromComputer": "Upload do seu computador" 32 | }, 33 | "selector": { 34 | "myComputer": "Meu computador", 35 | "accounts": "Contas" 36 | }, 37 | "dropzone": { 38 | "message": "Arraste e solte arquivos aqui ou clique para abrir o Gerenciador de arquivos" 39 | }, 40 | "computer": { 41 | "noSupport": "Seu navegador não tem suporte para Flash, Silverlight ou HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Confirme a conexão da conta.", 45 | "clickBelow": "Clique abaixo para conectar sua conta do {serviceName}", 46 | "connectAccount": "Conecte a conta {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": { 3 | "global": { 4 | "select": "Memilih", 5 | "cancel": "Membatalkan", 6 | "save": "Menyimpan", 7 | "upload": "Unggah" 8 | }, 9 | "files": { 10 | "name": "Nama", 11 | "size": "Ukuran", 12 | "updated": "Diperbarui", 13 | "noFilesFound": "Tidak Ada File", 14 | "untitledFolder": "Folder Tanpa Judul", 15 | "search": "Pencarian", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Anda tidak lagi dapat menelusuri akun ini. Apakah Anda yakin ingin menghapusnya? (Perhatikan bahwa ini tidak membuat Anda keluar dari akun di browser ini)", 25 | "connectedAccounts": "Akun yang terhubung", 26 | "logout": "keluar", 27 | "manage": "Kelola akun penyimpanan cloud Anda di sini.", 28 | "chooseAccount": "Selamat datang! Silakan pilih layanan yang akan dihubungkan", 29 | "connectMore": "Hubungkan lebih banyak!", 30 | "upload": "Unggah", 31 | "uploadFromComputer": "Unggah dari komputer Anda" 32 | }, 33 | "selector": { 34 | "myComputer": "Komputer saya", 35 | "accounts": "Akun" 36 | }, 37 | "dropzone": { 38 | "message": "Seret dan taruh file di sini, atau klik untuk membuka File Picker" 39 | }, 40 | "computer": { 41 | "noSupport": "Browser Anda tidak memiliki dukungan Flash, Silverlight atau HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Konfirmasikan koneksi akun.", 45 | "clickBelow": "Klik di bawah untuk menghubungkan akun {serviceName} Anda", 46 | "connectAccount": "Hubungkan {serviceName} Akun" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "nl": { 3 | "global": { 4 | "select": "kiezen", 5 | "cancel": "annuleren", 6 | "save": "Opslaan", 7 | "upload": "Uploaden" 8 | }, 9 | "files": { 10 | "name": "Naam", 11 | "size": "Grootte", 12 | "updated": "bijgewerkt", 13 | "noFilesFound": "Geen bestanden gevonden", 14 | "untitledFolder": "Map zonder titel", 15 | "search": "Zoeken", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "U zult niet langer in staat zijn om deze account te bladeren. Bent u zeker dat u wilt om het te verwijderen? (Merk op dat dit niet je uitgelogd van het account op deze browser)", 25 | "connectedAccounts": "Verbonden accounts", 26 | "logout": "uitloggen", 27 | "manage": "Beheer hier uw cloudopslag-accounts.", 28 | "chooseAccount": "Welkom! Kies een service om verbinding te maken", 29 | "connectMore": "Verbind meer!", 30 | "upload": "Uploaden", 31 | "uploadFromComputer": "Upload vanaf uw computer" 32 | }, 33 | "selector": { 34 | "myComputer": "Mijn computer", 35 | "accounts": "accounts" 36 | }, 37 | "dropzone": { 38 | "message": "Versleep hier bestanden, of klik om de Verkenner te openen" 39 | }, 40 | "computer": { 41 | "noSupport": "Uw browser ondersteunt geen Flash-, Silverlight- of HTML5-ondersteuning." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Bevestig de accountverbinding.", 45 | "clickBelow": "Klik hieronder om verbinding te maken met uw {serviceName} account", 46 | "connectAccount": "Verbind {serviceName} account" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /dev-server/picker-template-hot-loader.js: -------------------------------------------------------------------------------- 1 | /* global $, ko */ 2 | /** 3 | * Hot reload file picker templates in dev-server. 4 | * Support hot reloading templates without reloading the file picker page. 5 | * 6 | * For now, the hot reload functionality is limited: 7 | * 1. We need to update index.pug once after page load to make hot 8 | * reloading start working. I.E. editing other pug files won't trigger 9 | * hot reload until index.pug is hot reloaded once. 10 | * One workaround is to add an empty div under #kloudless-file-explore, save, 11 | * then revert. 12 | * 13 | * 2. The hot reload function assumes a div with id="kloudless-file-picker" 14 | * exists in index.pug and is served as the main container for 15 | * file picker templates. 16 | */ 17 | 18 | import getTemplateHtml from 'picker/templates/index.pug'; 19 | 20 | function onHotAccept() { 21 | // getTemplateHtml has been reloaded when this callback is executed 22 | 23 | /* eslint-disable no-console */ 24 | console.log('Hot reloading file picker templates...'); 25 | 26 | // remove current ko binding and HTML 27 | let $picker = $('#kloudless-file-picker'); 28 | ko.cleanNode($picker[0]); 29 | $('#kloudless-file-picker').remove(); 30 | 31 | // file picker is exposed in window when in development mode 32 | const { picker } = window; 33 | 34 | // insert updated templates and rebind ko 35 | $('body').prepend(getTemplateHtml()); 36 | // get the newly rendered template DOM reference 37 | $picker = $('#kloudless-file-picker'); 38 | ko.applyBindings(picker.view_model, $picker[0]); 39 | 40 | // force reloading jquery plugins like dropdown and plupload 41 | picker.switchViewTo(picker.view_model.current()); 42 | 43 | console.log('[Done] Hot reloaded file picker templates.'); 44 | } 45 | 46 | module.hot.accept('picker/templates/index.pug', onHotAccept); 47 | -------------------------------------------------------------------------------- /src/picker/localization/messages/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "it": { 3 | "global": { 4 | "select": "Selezionare", 5 | "cancel": "Annulla", 6 | "save": "Salvare", 7 | "upload": "Caricare" 8 | }, 9 | "files": { 10 | "name": "Nome", 11 | "size": "Taglia", 12 | "updated": "aggiornato", 13 | "noFilesFound": "Nessun file trovato", 14 | "untitledFolder": "Cartella senza titolo", 15 | "search": "Ricerca", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Non sarà più in grado di navigare questo account. Sei sicuro di voler rimuovere esso? (Si noti che questo non ti logout dell'account su questo browser)", 25 | "connectedAccounts": "Account collegati", 26 | "logout": "disconnettersi", 27 | "manage": "Gestisci i tuoi account di archiviazione cloud qui.", 28 | "chooseAccount": "Benvenuto! Si prega di scegliere un servizio per la connessione", 29 | "connectMore": "Connetti di più!", 30 | "upload": "Caricare", 31 | "uploadFromComputer": "Carica dal tuo computer" 32 | }, 33 | "selector": { 34 | "myComputer": "Il mio computer", 35 | "accounts": "conti" 36 | }, 37 | "dropzone": { 38 | "message": "Trascina qui i file o fai clic per aprire Esplora file" 39 | }, 40 | "computer": { 41 | "noSupport": "Il tuo browser non ha supporto Flash, Silverlight o HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Conferma la connessione dell'account.", 45 | "clickBelow": "Fai clic di seguito per connettere il tuo account {serviceName}", 46 | "connectAccount": "Connetti l'account {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "ro": { 3 | "global": { 4 | "select": "Selectați", 5 | "cancel": "Anulare", 6 | "save": "Salvați", 7 | "upload": "Încărcați" 8 | }, 9 | "files": { 10 | "name": "Nume", 11 | "size": "mărimea", 12 | "updated": "La curent", 13 | "noFilesFound": "Niciun fișier găsit", 14 | "untitledFolder": "Folder fără titlu", 15 | "search": "Căutare", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Nu veți mai putea naviga în acest cont. Sunteți sigur că doriți să îl eliminați? (Rețineți că acest lucru nu vă deconectează din cont în acest browser)", 25 | "connectedAccounts": "Conturi conectate", 26 | "logout": "logout", 27 | "manage": "Gestionați-vă aici conturile de stocare în cloud.", 28 | "chooseAccount": "Bine ati venit! Alegeți un serviciu pentru conectare", 29 | "connectMore": "Conectează-te mai mult!", 30 | "upload": "Încărcați", 31 | "uploadFromComputer": "Încărcați de pe computer" 32 | }, 33 | "selector": { 34 | "myComputer": "Calculatorul meu", 35 | "accounts": "Conturi" 36 | }, 37 | "dropzone": { 38 | "message": "Glisați și fixați fișierele aici sau faceți clic pentru a deschide fișierul Picker" 39 | }, 40 | "computer": { 41 | "noSupport": "Browserul dvs. nu are suport Flash, Silverlight sau HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Confirmați conexiunea la cont.", 45 | "clickBelow": "Faceți clic mai jos pentru a vă conecta contul {serviceName}", 46 | "connectAccount": "Conectați contul {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "es": { 3 | "global": { 4 | "select": "Seleccionar", 5 | "cancel": "Cancelar", 6 | "save": "Salvar", 7 | "upload": "Subir" 8 | }, 9 | "files": { 10 | "name": "Nombre", 11 | "size": "tamaño", 12 | "updated": "Actualizado", 13 | "noFilesFound": "No se encontraron archivos", 14 | "untitledFolder": "Carpeta sin título", 15 | "search": "Buscar", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Ya no podrás navegar por esta cuenta. ¿Estás seguro de que deseas eliminarlo? (Tenga en cuenta que esto no cierra la sesión de la cuenta en este navegador)", 25 | "connectedAccounts": "Cuentas conectadas", 26 | "logout": "cerrar sesión", 27 | "manage": "Gérez vos comptes de stockage en nuage ici.", 28 | "chooseAccount": "¡Bienvenido! Por favor, elija un servicio para conectarse", 29 | "connectMore": "¡Conecta más!", 30 | "upload": "Subir", 31 | "uploadFromComputer": "Sube desde tu computadora" 32 | }, 33 | "selector": { 34 | "myComputer": "Mi computadora", 35 | "accounts": "Cuentas" 36 | }, 37 | "dropzone": { 38 | "message": "Arrastre y suelte los archivos aquí, o haga clic para abrir el Explorador de archivos" 39 | }, 40 | "computer": { 41 | "noSupport": "Su navegador no tiene Flash, Silverlight o HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Confirmar la conexión de la cuenta.", 45 | "clickBelow": "Haga clic a continuación para conectar su cuenta {serviceName}", 46 | "connectAccount": "Conectar cuenta {serviceName}" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "ru": { 3 | "global": { 4 | "select": "Выбрать", 5 | "cancel": "отменить", 6 | "save": "Сохранить", 7 | "upload": "Загрузить" 8 | }, 9 | "files": { 10 | "name": "название", 11 | "size": "Размер", 12 | "updated": "обновленный", 13 | "noFilesFound": "Файлы не найдены", 14 | "untitledFolder": "Папка без названия", 15 | "search": "Поиск", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Вы больше не сможете просматривать эту учетную запись. Вы уверены, что хотите удалить его? (Обратите внимание, что это не выходит из учетной записи в этом браузере)", 25 | "connectedAccounts": "Подключенные учетные записи", 26 | "logout": "выйти", 27 | "manage": "Управляйте своими учетными записями облачного хранилища здесь.", 28 | "chooseAccount": "Добро пожаловать! Пожалуйста, выберите услугу для подключения", 29 | "connectMore": "Подключи больше!", 30 | "upload": "Загрузить", 31 | "uploadFromComputer": "Загрузить с вашего компьютера" 32 | }, 33 | "selector": { 34 | "myComputer": "Мой компьютер", 35 | "accounts": "Счета" 36 | }, 37 | "dropzone": { 38 | "message": "Перетащите файлы сюда или нажмите, чтобы открыть проводник" 39 | }, 40 | "computer": { 41 | "noSupport": "Ваш браузер не поддерживает Flash, Silverlight или HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Подтвердите подключение аккаунта.", 45 | "clickBelow": "Нажмите ниже, чтобы подключить свою учетную запись {serviceName}", 46 | "connectAccount": "Подключить {serviceName} аккаунт" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/localization/messages/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "fr": { 3 | "global": { 4 | "select": "Sélectionner", 5 | "cancel": "Annuler", 6 | "save": "sauvegarder", 7 | "upload": "Télécharger" 8 | }, 9 | "files": { 10 | "name": "prénom", 11 | "size": "Taille", 12 | "updated": "Mis à jour", 13 | "noFilesFound": "Aucun fichier trouvé", 14 | "untitledFolder": "Dossier sans titre", 15 | "search": "Chercher", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Vous ne serez plus en mesure de parcourir ce compte. Etes-vous sûr que vous souhaitez supprimer? (Notez que cela ne vous déconnecte pas du compte sur ce navigateur)", 25 | "connectedAccounts": "Comptes connectés", 26 | "logout": "Connectez - Out", 27 | "manage": "Gérez vos comptes de stockage en nuage ici.", 28 | "chooseAccount": "Bienvenue! Veuillez choisir un service pour vous connecter", 29 | "connectMore": "Connectez plus!", 30 | "upload": "Télécharger", 31 | "uploadFromComputer": "Télécharger depuis votre ordinateur" 32 | }, 33 | "selector": { 34 | "myComputer": "Mon ordinateur", 35 | "accounts": "Comptes" 36 | }, 37 | "dropzone": { 38 | "message": "Glissez et déposez les fichiers ici, ou cliquez pour ouvrir l'explorateur de fichiers" 39 | }, 40 | "computer": { 41 | "noSupport": "Votre navigateur ne prend pas en charge Flash, Silverlight ou HTML5." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Confirmer la connexion au compte.", 45 | "clickBelow": "Cliquez ci-dessous pour connecter votre compte {serviceName}", 46 | "connectAccount": "Connecter {serviceName} compte" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /dev-server/dev-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | if (!process.env.KLOUDLESS_APP_ID) { 4 | console.log('Environment variable KLOUDLESS_APP_ID not specified.'); 5 | process.exit(1); 6 | } 7 | 8 | const webpackDevMiddleware = require('webpack-dev-middleware'); 9 | const webpackHotMiddleware = require('webpack-hot-middleware'); 10 | const express = require('express'); 11 | const morgan = require('morgan'); 12 | const path = require('path'); 13 | const http = require('http'); 14 | const https = require('https'); 15 | const webpack = require('webpack'); 16 | const sslCert = require('./ssl-cert'); 17 | 18 | const webpackConfigs = require('../config/webpack.dev.conf'); 19 | 20 | const app = express(); 21 | 22 | process.env.PICKER_URL = '/picker/index.html'; 23 | 24 | app.set('port', process.env.PORT || 3000); 25 | app.use(morgan('dev')); 26 | app.use('/static', express.static(path.join(__dirname, './static'))); 27 | 28 | app.use((req, res, next) => { 29 | res.set({ 30 | 'Cache-Control': 'no-cache, no-store, must-revalidate', 31 | Pragma: 'no-cache', 32 | Expires: 0, 33 | }); 34 | next(); 35 | }); 36 | 37 | const compiler = webpack(webpackConfigs); 38 | const middleware = webpackDevMiddleware(compiler, { 39 | logTime: true, 40 | stats: 'minimal', 41 | publicPath: webpackConfigs[0].output.publicPath, 42 | }); 43 | app.use(middleware); 44 | app.use(webpackHotMiddleware(compiler, { 45 | log: false, path: '/__webpack_hmr', heartbeat: 10 * 1000, 46 | })); 47 | 48 | let server; 49 | 50 | if (sslCert.certificate) { 51 | server = https.createServer( 52 | { key: sslCert.privateKey, cert: sslCert.certificate }, 53 | app, 54 | ); 55 | } else { 56 | server = http.createServer(app); 57 | } 58 | 59 | server.listen(app.get('port'), () => { 60 | console.log('Dev server running on http://localhost:3000'); 61 | console.log('Webpack bundles are compiling...'); 62 | }); 63 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const common = require('./config/common'); 3 | const packages = require('./package.json'); 4 | 5 | 6 | /** 7 | * Define build options environment variables here 8 | * format: [var name]: default value 9 | * check README for variable names and purpose 10 | */ 11 | const buildEnvVarDefaults = { 12 | PICKER_URL: 13 | 'https://static-cdn.kloudless.com/p/platform/file-picker/v2/index.html', 14 | // old version that supports custom_css 15 | PICKER_URL_V1: 16 | 'https://static-cdn.kloudless.com/p/platform/explorer/explorer.html', 17 | BASE_URL: 'https://api.kloudless.com', 18 | 19 | // for development only 20 | KLOUDLESS_APP_ID: null, 21 | // 'MIT' or 'AGPL'. 22 | // The MIT build excludes the plupload module. 23 | BUILD_LICENSE: 'MIT', 24 | }; 25 | 26 | const transformDefines = { 27 | VERSION: packages.version, 28 | }; 29 | 30 | Object.keys(buildEnvVarDefaults).forEach((varName) => { 31 | transformDefines[varName] = ( 32 | process.env[varName] || buildEnvVarDefaults[varName]); 33 | }); 34 | 35 | module.exports = { 36 | presets: [ 37 | ['@babel/preset-env', { 38 | useBuiltIns: 'usage', 39 | corejs: 3, 40 | }], 41 | // Used by storybook-react 42 | '@babel/preset-react', 43 | ], 44 | ignore: common.ignorePaths, 45 | plugins: [ 46 | [ 47 | 'module-resolver', { 48 | // avoid node_modules path being relative path 49 | root: common.resolvePaths.filter(p => p !== 'node_modules'), 50 | alias: { 51 | 'picker-config': path.resolve(__dirname, './src/picker/js/', 52 | (process.env.NODE_ENV === 'production' 53 | && !JSON.parse(process.env.DEBUG || false)) ? 54 | './config_prod.json' : './config.json'), 55 | }, 56 | }, 57 | ], 58 | [ 59 | 'transform-define', transformDefines, 60 | ], 61 | ], 62 | }; 63 | -------------------------------------------------------------------------------- /src/picker/js/breadcrumb.js: -------------------------------------------------------------------------------- 1 | const ICON_HTML = '
'; 2 | const ROOT_DIR_HTML = ` 3 |
4 |
5 |
`; 6 | const TOGGLE_BTN_HTML = ` 7 | `; 11 | 12 | /** 13 | * @param {Object[]} breadcrumbs 14 | * @param {string} breadcrumbs[].path 15 | * @param {boolean} breadcrumbs[].visible 16 | * @param {number} maxWidth 17 | */ 18 | function isBreadcrumbOverflow(breadcrumbs, maxWidth) { 19 | const hiddenContainer = $('.breadcrumb__hidden'); 20 | const showToggleButton = breadcrumbs.includes(e => e.visible === false); 21 | const els = [ROOT_DIR_HTML]; 22 | if (showToggleButton) { 23 | els.push(TOGGLE_BTN_HTML); 24 | } 25 | breadcrumbs.filter(e => e.visible).forEach((breadcrumb) => { 26 | els.push( 27 | ICON_HTML, 28 | ``, 29 | ); 30 | }); 31 | const html = ``; 32 | hiddenContainer.html(html); 33 | const width = hiddenContainer.outerWidth(); 34 | return width >= maxWidth; 35 | } 36 | 37 | /** 38 | * @param {Object[]} breadcrumbs 39 | * @param {string} breadcrumbs[].path 40 | * @param {boolean} breadcrumbs[].visible 41 | */ 42 | function adjustBreadcrumbWidth(breadcrumbs) { 43 | if (breadcrumbs === null || breadcrumbs.length <= 1) { 44 | return breadcrumbs; 45 | } 46 | const maxWidth = $('.breadcrumb').outerWidth(); 47 | const { length } = breadcrumbs; 48 | let index = breadcrumbs.findIndex(e => e.visible); 49 | while (index < length - 1 && isBreadcrumbOverflow(breadcrumbs, maxWidth)) { 50 | breadcrumbs[index].visible = false; 51 | index += 1; 52 | } 53 | return breadcrumbs; 54 | } 55 | 56 | export default adjustBreadcrumbWidth; 57 | -------------------------------------------------------------------------------- /src/picker/localization/messages/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "de": { 3 | "global": { 4 | "select": "Wählen", 5 | "cancel": "Stornieren", 6 | "save": "sparen", 7 | "upload": "Hochladen" 8 | }, 9 | "files": { 10 | "name": "Name", 11 | "size": "Größe", 12 | "updated": "Aktualisierte", 13 | "noFilesFound": "Keine Dateien gefunden", 14 | "untitledFolder": "Unbenannter Ordner", 15 | "search": "Suche", 16 | "sizes": { 17 | "b": "{fileSize} B", 18 | "kb": "{fileSize} KB", 19 | "mb": "{fileSize} MB", 20 | "gb": "{fileSize} GB" 21 | } 22 | }, 23 | "accounts": { 24 | "confirmRemove": "Sie können auf dieses Konto nicht mehr sehen. Sind Sie sicher, Sie möchten es entfernen? (Beachten Sie, dass diese Sie nicht melden Sie sich von dem Konto auf diesem Browser)", 25 | "connectedAccounts": "Verbundene Konten", 26 | "logout": "Ausloggen", 27 | "manage": "Verwalten Sie hier Ihre Cloud-Speicherkonten.", 28 | "chooseAccount": "Herzlich willkommen! Bitte wählen Sie einen Dienst aus, um eine Verbindung herzustellen", 29 | "connectMore": "Verbinde mehr!", 30 | "upload": "Hochladen", 31 | "uploadFromComputer": "Hochladen von Ihrem Computer" 32 | }, 33 | "selector": { 34 | "myComputer": "Mein Computer", 35 | "accounts": "Konten" 36 | }, 37 | "dropzone": { 38 | "message": "Ziehen Sie Dateien hierher, und legen Sie sie dort ab, oder klicken Sie darauf, um die Dateiauswahl zu öffnen" 39 | }, 40 | "computer": { 41 | "noSupport": "Ihr Browser unterstützt Flash, Silverlight oder HTML5 nicht." 42 | }, 43 | "addConfirm": { 44 | "confirm": "Bestätigen Sie die Kontoverbindung.", 45 | "clickBelow": "Klicken Sie unten, um eine Verbindung zu Ihrem {serviceName} Konto herzustellen", 46 | "connectAccount": "{ServiceName} Konto verbinden" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/picker/js/iexd-transport.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import $ from 'jquery'; 3 | import config from './config'; 4 | import auth from './auth'; 5 | import util from './util'; 6 | 7 | /** 8 | * Compatability module which proxies requests to the Kloudless API via 9 | * iexd.html (hosted on the API domain) to avoid IE9 restrictions on 10 | * cross-domain requests. 11 | * 12 | * Adds a jQuery AJAX transport which handles all requests to the API server. 13 | * 14 | * In particular, IE9 has some limitations on use of XDomainRequest that 15 | * prevent us from using it to talk to the API server: 16 | * - can't send custom headers like Authorization 17 | * - protocol of the page needs to be https (protocols must match) 18 | * - only GET and POST methods can be used 19 | * - can only use text/plain for the request's Content-Type 20 | */ 21 | 'use strict'; 22 | 23 | // do nothing if proper cross-domain requests are supported 24 | 25 | if (!util.supportsCORS()) { 26 | $.ajaxTransport('+*', function (options, originalOptions, jqXHR) { 27 | if (!canHandleRequest(options)) { 28 | return; 29 | } 30 | 31 | return { 32 | send: function (headers, completeCallback) { 33 | var requestId = util.randomID(); 34 | options.headers = headers; 35 | 36 | var data = { 37 | type: 'proxy', 38 | origin: document.URL, 39 | id: requestId, 40 | options: options 41 | }; 42 | 43 | auth.postMessage(data, requestId, function (response) { 44 | completeCallback( 45 | response.status, response.statusText, response.responses, response.headers); 46 | }); 47 | }, 48 | 49 | /* we don't support aborting requests on IE9 :-) */ 50 | abort: function () { 51 | } 52 | }; 53 | }); 54 | } 55 | 56 | function canHandleRequest(options) { 57 | return options.crossDomain && 58 | options.url.substring(0, config.base_url.length) == config.base_url; 59 | } 60 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'stylelint-config-standard', 4 | ], 5 | plugins: [ 6 | 'stylelint-order', 7 | ], 8 | rules: { 9 | 'order/properties-order': [ 10 | 'position', 11 | 'top', 12 | 'bottom', 13 | 'right', 14 | 'left', 15 | 'display', 16 | 'align-items', 17 | 'justify-content', 18 | 'float', 19 | 'clear', 20 | 'overflow', 21 | 'overflow-x', 22 | 'overflow-y', 23 | 'margin', 24 | 'margin-top', 25 | 'margin-right', 26 | 'margin-bottom', 27 | 'margin-left', 28 | 'padding', 29 | 'padding-top', 30 | 'padding-right', 31 | 'padding-bottom', 32 | 'padding-left', 33 | 'width', 34 | 'min-width', 35 | 'max-width', 36 | 'height', 37 | 'min-height', 38 | 'max-height', 39 | 'font-size', 40 | 'font-family', 41 | 'font-weight', 42 | 'text-align', 43 | 'text-justify', 44 | 'text-indent', 45 | 'text-overflow', 46 | 'text-decoration', 47 | 'white-space', 48 | 'color', 49 | 'background', 50 | 'background-position', 51 | 'background-repeat', 52 | 'background-size', 53 | 'background-color', 54 | 'background-clip', 55 | 'border', 56 | 'border-style', 57 | 'border-width', 58 | 'border-color', 59 | 'border-top-style', 60 | 'border-top-width', 61 | 'border-top-color', 62 | 'border-right-style', 63 | 'border-right-width', 64 | 'border-right-color', 65 | 'border-bottom-style', 66 | 'border-bottom-width', 67 | 'border-bottom-color', 68 | 'border-left-style', 69 | 'border-left-width', 70 | 'border-left-color', 71 | 'border-radius', 72 | 'opacity', 73 | 'filter', 74 | 'list-style', 75 | 'outline', 76 | 'visibility', 77 | 'z-index', 78 | 'box-shadow', 79 | 'text-shadow', 80 | 'resize', 81 | 'transition', 82 | ], 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /src/picker/templates/breadcrumb.pug: -------------------------------------------------------------------------------- 1 | .breadcrumb(data-bind='with: files') 2 | .dropdown(id='breadcrumb-dropdown') 3 | .box.box--shadow 4 | .box__section 5 | .list 6 | .list__item(data-bind='click: up.bind(null, breadcrumbs().length)') 7 | .icon 8 | .icon__folder 9 | .list__text(data-bind='text: root_folder_name') 10 | // ko foreach: breadcrumbs 11 | .list__item(data-bind='click: $parent.up.bind(null, $parent.breadcrumbs().length - parseInt("" + ($index() + 1), 10))') 12 | .icon 13 | .icon__folder 14 | .list__text(data-bind='text: $data.path') 15 | // ko if: $parent.breadcrumbs().length -1 === $index() 16 | .icon.icon--small 17 | .icon__checked 18 | // /ko 19 | // /ko 20 | //- breadcrumb__hidden is a hidden element which is used to measure the 21 | //- breadcrumb width and decide whether to ellipsis breadcrumb 22 | .breadcrumb__hidden 23 | .icon.icon--button.icon--large(data-bind=` 24 | attr: {'title': root_folder_name}, 25 | click: up.bind(null, breadcrumbs().length)`) 26 | .icon__root-dir 27 | .breadcrumb__toggle-btn( 28 | data-bind="css: {'breadcrumb__toggle-btn--hidden': breadcrumbs().filter(function(e) { return !e.visible}).length === 0}") 29 | .icon 30 | .icon__next 31 | .breadcrumb__text.breadcrumb__text--button( 32 | data-dropdown-id='breadcrumb-dropdown') 33 | | ... 34 | // ko foreach: breadcrumbs 35 | // ko if: $data.visible === true 36 | .icon 37 | .icon__next 38 | .breadcrumb__text(data-bind=` 39 | attr: {'title': $data.path}, 40 | css: { 41 | 'breadcrumb__text--button': $parent.breadcrumbs().length -1 !== $index(), 42 | 'breadcrumb__text--ellipsis': $parent.breadcrumbs().filter(function(e) { return e.visible }).length === 1 || $parent.breadcrumbs().length -1 !== $index(), 43 | }, 44 | click: $parent.up.bind(null, $parent.breadcrumbs().length - parseInt("" + ($index() + 1), 10)), 45 | text: $data.path`) 46 | // /ko 47 | // /ko 48 | -------------------------------------------------------------------------------- /storybook-test/stories/core/components.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function TextArea(props) { 4 | const { 5 | value, name, title, onChange, error, 6 | } = props; 7 | const outerClass = [ 8 | 'mdl-textfield mdl-textfield--floating-label', 9 | // Somehow MDL doesn't detect the value while switching between stories. 10 | // So add is-dirty class manually. 11 | value ? 'is-dirty' : '', 12 | error ? 'is-invalid' : '', 13 | ].join(' '); 14 | return ( 15 |
16 |