├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc ├── eslint.config.mjs ├── license.txt ├── locales ├── index.js ├── locales │ ├── cn.js │ └── en.js └── package.json ├── package.json ├── provider ├── .eslintrc.cjs ├── .gitignore ├── package.json ├── src │ ├── RestDataProvider.ts │ └── index.ts ├── test │ └── provider.spec.js ├── tsconfig.json └── vite.config.js ├── readme.md ├── site ├── .gitignore ├── index.html ├── index.js ├── package.json ├── public │ └── placeholder.md ├── src │ ├── CustomFileManager.svelte │ ├── Demo.svelte │ ├── Main.svelte │ ├── ThemeSelect.svelte │ └── data │ │ └── index.js ├── svelte.config.js ├── vite.config.js └── yarn.lock ├── store ├── .eslintrc.cjs ├── .gitignore ├── license.txt ├── package.json ├── src │ ├── DataStore.ts │ ├── FileTree.ts │ ├── helpers.ts │ ├── hotkeys.ts │ ├── index.ts │ ├── menus.ts │ ├── package.ts │ └── types.ts ├── test │ ├── datastore.spec.js │ └── stubs │ │ └── data.js ├── tsconfig.json └── vite.config.js ├── svelte ├── .gitignore ├── cypress.config.js ├── cypress │ ├── e2e │ │ ├── basic │ │ │ ├── basic-ops.cy.js │ │ │ ├── hotkeys.cy.js │ │ │ ├── menus.cy.js │ │ │ ├── navigation.cy.js │ │ │ ├── search.cy.js │ │ │ ├── select.cy.js │ │ │ └── toolbar-buttons.cy.js │ │ └── demos.cy.js │ ├── fixtures │ │ └── example.json │ └── support │ │ ├── commands.js │ │ └── e2e.js ├── demos │ ├── cases │ │ ├── API.svelte │ │ ├── BackendData.svelte │ │ ├── BackendFilter.svelte │ │ ├── BasicInit.svelte │ │ ├── ContextMenu.svelte │ │ ├── CustomStyles.svelte │ │ ├── DataProvider.svelte │ │ ├── ExtraInfo.svelte │ │ ├── Locales.svelte │ │ ├── PathAndSelection.svelte │ │ ├── Readonly.svelte │ │ └── SimpleIcons.svelte │ ├── common │ │ ├── Index.svelte │ │ ├── Link.svelte │ │ ├── Router.svelte │ │ └── helpers.js │ ├── custom │ │ └── placeholder.md │ ├── data.js │ ├── index.html │ ├── index.js │ ├── routes.js │ └── skins.js ├── index.html ├── license.txt ├── package.json ├── postcss.config.js ├── readme.md ├── src │ ├── components │ │ ├── Breadcrumbs.svelte │ │ ├── Cards │ │ │ ├── Item.svelte │ │ │ ├── Panel.svelte │ │ │ └── View.svelte │ │ ├── Drive.svelte │ │ ├── Filemanager.svelte │ │ ├── Info.svelte │ │ ├── Layout.svelte │ │ ├── Modals.svelte │ │ ├── Panels.svelte │ │ ├── SearchView.svelte │ │ ├── Sidebar.svelte │ │ ├── Table │ │ │ ├── NameCell.svelte │ │ │ └── View.svelte │ │ ├── Toolbar.svelte │ │ ├── Tree │ │ │ ├── Folder.svelte │ │ │ └── Tree.svelte │ │ ├── UploadButton.svelte │ │ ├── UploadDropArea.svelte │ │ └── ui │ │ │ ├── Icon.svelte │ │ │ └── Search.svelte │ ├── icons.js │ ├── index.js │ └── themes │ │ ├── Material.svelte │ │ ├── Willow.svelte │ │ └── WillowDark.svelte ├── svelte.config.js ├── tests │ ├── Index.svelte │ ├── cases │ │ ├── CustomMenu.svelte │ │ ├── CustomMenuReadonly.svelte │ │ ├── LocalData.svelte │ │ └── Readonly.svelte │ ├── data.js │ ├── index.html │ ├── index.js │ └── routes.js ├── vite.config.js └── whatsnew.md ├── vitest.workspace.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true, 7 | }, 8 | extends: ["plugin:cypress/recommended", "eslint:recommended", "prettier"], 9 | parserOptions: { 10 | ecmaVersion: 2020, 11 | sourceType: "module", 12 | extraFileExtensions: [".svelte"], 13 | }, 14 | plugins: ["svelte3"], 15 | 16 | overrides: [ 17 | { 18 | files: ["*.svelte"], 19 | processor: "svelte3/svelte3", 20 | }, 21 | ], 22 | settings: { 23 | // [todo] we can add stylelint for this 24 | "svelte3/ignore-styles": () => true, 25 | }, 26 | rules: { 27 | "cypress/no-unnecessary-waiting": 0, 28 | "cypress/no-assigning-return-values": 0, 29 | "no-bitwise": ["error"], 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | 4 | *.zip 5 | .Ds_store 6 | *.tgz 7 | *.log 8 | .vscode 9 | .idea 10 | .env.local -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn run lint-staged 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "semi": true, 4 | "singleQuote": false, 5 | "quoteProps": "as-needed", 6 | "trailingComma": "es5", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "svelteSortOrder": "options-scripts-markup-styles", 10 | "plugins": [ 11 | "prettier-plugin-svelte" 12 | ], 13 | "overrides": [ 14 | { 15 | "files": "*.svelte", 16 | "options": { 17 | "parser": "svelte" 18 | } 19 | }, 20 | { 21 | "files": "*.ts", 22 | "options": { 23 | "parser": "typescript" 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslintConfigPrettier from "eslint-config-prettier"; 2 | import eslintPluginSvelte from 'eslint-plugin-svelte'; 3 | import tsLint from "typescript-eslint"; 4 | import jsLint from "@eslint/js"; 5 | import vitest from "eslint-plugin-vitest"; 6 | import globals from "globals"; 7 | 8 | export default [{ 9 | ignores: ["node_modules/", "dist/", "build/", "coverage/", "public/", "svelte/vite.config.js"], 10 | }, 11 | jsLint.configs.recommended, 12 | ...tsLint.configs.recommended, 13 | ...eslintPluginSvelte.configs['flat/recommended'], 14 | eslintConfigPrettier, 15 | vitest.configs.recommended, 16 | ...eslintPluginSvelte.configs["flat/prettier"], 17 | { 18 | rules: { 19 | "no-bitwise": ["error"], 20 | // there is a misconception between esLint and svelte compiler 21 | // rules that are necessary for compiler, throw errors in esLint 22 | // need to be revised with next version of toolchain 23 | "svelte/no-unused-svelte-ignore": "off", 24 | "svelte/valid-compile": "off", 25 | // Ignore unused vars starting with _ 26 | // "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 27 | // // Turn off the need for explicit function return types 28 | // "@typescript-eslint/explicit-function-return-type": "off", 29 | // // Warn when "any" type is used 30 | "@typescript-eslint/no-explicit-any": "off", 31 | // // Warn on @ts-ignore comments 32 | // "@typescript-eslint/ban-ts-comment": "warn", 33 | // // Public methods should have return types 34 | // "@typescript-eslint/explicit-module-boundary-types": "error", 35 | }, 36 | }, 37 | { 38 | languageOptions: { 39 | globals: { ...globals.browser, ...globals.es2022 }, 40 | ecmaVersion: 2022, 41 | sourceType: "module", 42 | parserOptions: { 43 | extraFileExtensions: [".svelte"], 44 | warnOnUnsupportedTypeScriptVersion: false, 45 | tsconfigRootDir: import.meta.dirname, 46 | }, 47 | }, 48 | 49 | }, 50 | { 51 | 52 | files: ["**/*.svelte"], 53 | rules: { 54 | "@typescript-eslint/no-unused-expressions": "off" 55 | } 56 | } 57 | ]; 58 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 XB Software Sp. z o.o. 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. -------------------------------------------------------------------------------- /locales/index.js: -------------------------------------------------------------------------------- 1 | export { default as en } from "./locales/en.js"; 2 | export { default as cn } from "./locales/cn.js"; 3 | -------------------------------------------------------------------------------- /locales/locales/cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | filemanager: { 3 | "My files": "我的文件", 4 | 5 | "Add New": "添新", 6 | Ok: "好的", 7 | Cancel: "取消", 8 | 9 | "Delete files": "删除文件", 10 | Files: "文件", 11 | Folder: "文件夹", 12 | "Back to parent folder": "返回父文件夹", 13 | 14 | Information: "信息", 15 | Found: "成立", 16 | 17 | "Are you sure you want to delete these items:": "您确定要删除此项目吗:", 18 | "Enter file name": "輸入檔名", 19 | "Enter folder name": "輸入資料夾名稱", 20 | 21 | Result: "结果", 22 | Search: "搜索", 23 | "Search results in": "搜尋結果位於", 24 | "Add new file": "添加新文件", 25 | "Add new folder": "添加新文件夹", 26 | "Upload file": "上传文件", 27 | 28 | Size: "尺寸", 29 | Name: "姓名", 30 | Type: "类型", 31 | Date: "日期", 32 | 33 | Rename: "改名", 34 | Cut: "切", 35 | Delete: "删除", 36 | Copy: "复制", 37 | Paste: "粘贴", 38 | Download: "字典", 39 | Count: "伯爵", 40 | folder: "文件夹", 41 | folders: "文件夹", 42 | file: "文件", 43 | files: "文件", 44 | "Multiple files": "多個文件", 45 | multiple: "多種的", 46 | Preview: "預覽", 47 | of: "的", 48 | used: "過去慣常", 49 | "Select file or folder to view details": 50 | "選擇文件或資料夾以查看詳細信息", 51 | "Looks like nothing is here": "这里好像什么都没有", 52 | "Unknown file": "未知文件", 53 | 54 | "A miniature file preview": "微文件预览", 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /locales/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | filemanager: { 3 | "My files": "My files", 4 | 5 | "Add New": "Add New", 6 | Ok: "Ok", 7 | Cancel: "Cancel", 8 | 9 | "Delete files": "Delete Files", 10 | Files: "Files", 11 | Folder: "Folder", 12 | "Back to parent folder": "Back to parent folder", 13 | 14 | Information: "Information", 15 | Found: "Found", 16 | 17 | "Are you sure you want to delete these items:": 18 | "Are you sure you want to delete these items:", 19 | "Enter file name": "Enter file name", 20 | "Enter folder name": "Enter folder name", 21 | 22 | Result: "Result", 23 | Search: "Search", 24 | "Search results in": "Search results in", 25 | "Add new file": "Add new file", 26 | "Add new folder": "Add new folder", 27 | "Upload file": "Upload file", 28 | 29 | Size: "Size", 30 | Name: "Name", 31 | Type: "Type", 32 | Date: "Date", 33 | 34 | Rename: "Rename", 35 | Cut: "Cut", 36 | Delete: "Delete", 37 | Copy: "Copy", 38 | Paste: "Paste", 39 | Download: "Download", 40 | Count: "Count", 41 | folder: "folder", 42 | folders: "folders", 43 | file: "file", 44 | files: "files", 45 | "Multiple files": "Multiple files", 46 | multiple: "multiple", 47 | Preview: "Preview", 48 | of: "of", 49 | used: "used", 50 | "Select file or folder to view details": 51 | "Select file or folder to view details", 52 | "Looks like nothing is here": "Looks like nothing is here", 53 | "Unknown file": "Unknown file", 54 | 55 | "A miniature file preview": "A miniature file preview", 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /locales/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-filemanager-locales", 3 | "version": "2.1.0", 4 | "description": "Locales for WX Filemanager widget", 5 | "type": "module", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "true", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "wx", 13 | "filemanager", 14 | "locales" 15 | ], 16 | "author": "", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "wx-filemanager", 4 | "workspaces": [ 5 | "svelte", 6 | "store", 7 | "provider", 8 | "locales", 9 | "site" 10 | ], 11 | "scripts": { 12 | "build:deps": "run-s build:store build:provider", 13 | "build:provider": "cd provider && shx rm -f ./dist/index.js && yarn build", 14 | "build:site": "cd site && yarn build", 15 | "build:store": "cd store && shx rm -f ./dist/index.js && yarn build", 16 | "build:tests": "cd svelte && yarn build:tests", 17 | "build": "cd svelte && yarn build", 18 | "lint": "yarn eslint ./svelte/src ./svelte/demos ./store/src ./provider/src", 19 | "prepare": "husky", 20 | "start:demos": "cd svelte && yarn start", 21 | "start:site": "cd site && yarn start", 22 | "start:tests": "cd svelte && yarn start:tests", 23 | "start": "run-s build:deps start:demos", 24 | "test:cypress": "cd svelte && yarn test:cypress", 25 | "test": "vitest --run", 26 | "watch:deps": "run-p watch:store watch:provider", 27 | "watch:provider": "cd provider && shx rm -f ./dist/index.js && yarn watch", 28 | "watch:site": "run-p watch:deps start:site", 29 | "watch:store": "cd store && shx rm -f ./dist/index.js && yarn watch", 30 | "watch:tests": "run-p watch:deps start:tests", 31 | "watch": "run-p watch:deps start:demos" 32 | }, 33 | "devDependencies": { 34 | "@sveltejs/vite-plugin-svelte": "4.0.0", 35 | "@vitest/coverage-v8": "1.6.0", 36 | "wx-vite-tools": "1.0.5", 37 | "autoprefixer": "10.4.20", 38 | "cypress": "13.6.4", 39 | "eslint": "9.14.0", 40 | "eslint-config-prettier": "9.1.0", 41 | "eslint-plugin-cypress": "4.1.0", 42 | "eslint-plugin-svelte": "2.46.0", 43 | "eslint-plugin-vitest": "0.5.4", 44 | "husky": "9.1.6", 45 | "lint-staged": "15.2.10", 46 | "npm-run-all": "4.1.5", 47 | "postcss": "8.4.47", 48 | "prettier": "3.3.3", 49 | "prettier-plugin-svelte": "3.2.7", 50 | "rollup-plugin-visualizer": "5.12.0", 51 | "shx": "0.3.4", 52 | "svelte": "5.1.9", 53 | "svelte-spa-router": "4.0.1", 54 | "typescript-eslint": "8.13.0", 55 | "typescript": "5.6.3", 56 | "vite-plugin-conditional-compile": "1.4.5", 57 | "vite-plugin-dts": "3.7.2", 58 | "vite": "5.4.10", 59 | "vitest": "1.5.0" 60 | }, 61 | "lint-staged": { 62 | "*.{ts,js,svelte}": [ 63 | "eslint --fix --no-warn-ignored", 64 | "prettier --write" 65 | ], 66 | "*.{css,md,json}": [ 67 | "prettier --write" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /provider/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | env: { 5 | browser: true, 6 | node: true, 7 | es6: true, 8 | }, 9 | extends: [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier", 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | sourceType: "module", 17 | 18 | tsconfigRootDir: __dirname, 19 | }, 20 | plugins: ["@typescript-eslint"], 21 | rules: { 22 | "no-bitwise": ["error"], 23 | "@typescript-eslint/explicit-module-boundary-types": "off", 24 | "@typescript-eslint/no-explicit-any": "off", 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /provider/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage -------------------------------------------------------------------------------- /provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-filemanager-data-provider", 3 | "version": "2.1.0", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "module": "dist/index.js", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "yarn tsc", 10 | "watch": "yarn tsc -w --preserveWatchOutput", 11 | "lint": "yarn eslint ./src", 12 | "test": "vitest --run" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "devDependencies": { 18 | "wx-filemanager-store": "2.1.0" 19 | }, 20 | "dependencies": { 21 | "wx-lib-data-provider": "1.5.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /provider/src/RestDataProvider.ts: -------------------------------------------------------------------------------- 1 | import { Rest } from "wx-lib-data-provider"; 2 | import type { ActionMap } from "wx-lib-data-provider"; 3 | import type { 4 | IDrive, 5 | THandlersConfig, 6 | TID, 7 | IEntity, 8 | IFile, 9 | } from "wx-filemanager-store"; 10 | 11 | type TProviderMethodsConfig = THandlersConfig & { 12 | "upload-file": { 13 | file: IFile; 14 | parent: TID; 15 | }; 16 | "file-renamed": { 17 | id: TID; 18 | newId: TID; 19 | }; 20 | }; 21 | 22 | export default class RestDataProvider extends Rest { 23 | constructor(url: string) { 24 | super(url); 25 | } 26 | getHandlers(): ActionMap { 27 | return { 28 | "create-file": { 29 | handler: (ev: THandlersConfig["create-file"]) => { 30 | if (ev.file.file) { 31 | const multipartFormData = new FormData(); 32 | multipartFormData.append("file", ev.file.file); 33 | multipartFormData.append("name", ev.file.name); 34 | 35 | return this.send( 36 | `upload?id=${encodeURIComponent(ev.parent)}`, 37 | "POST", 38 | multipartFormData 39 | ); 40 | } else { 41 | return this.send( 42 | `files/${encodeURIComponent(ev.parent)}`, 43 | "POST", 44 | this.getParams({ 45 | name: `${ev.file.name}`, 46 | type: ev.file.type, 47 | }) 48 | ).then((res: any) => { 49 | this.handleNonUniqueNames( 50 | [res.result.id], 51 | [ev.newId] 52 | ); 53 | return res; 54 | }); 55 | } 56 | }, 57 | }, 58 | "rename-file": { 59 | handler: (ev: THandlersConfig["rename-file"]) => { 60 | return this.send( 61 | `files/${encodeURIComponent(ev.id)}`, 62 | "PUT", 63 | this.getParams({ 64 | operation: "rename", 65 | name: ev.name, 66 | }) 67 | ).then((res: any) => { 68 | this.handleNonUniqueNames([res.result.id], [ev.newId]); 69 | return res; 70 | }); 71 | }, 72 | }, 73 | "move-files": { 74 | handler: async (ev: THandlersConfig["move-files"]) => { 75 | return this.send( 76 | "files", 77 | "PUT", 78 | this.getParams({ 79 | operation: "move", 80 | ids: ev.ids, 81 | target: ev.target, 82 | }) 83 | ).then((res: any) => { 84 | this.handleNonUniqueNames( 85 | res.result.map((i: any) => i.id), 86 | ev.newIds 87 | ); 88 | return res; 89 | }); 90 | }, 91 | }, 92 | "copy-files": { 93 | handler: async (ev: THandlersConfig["copy-files"]) => { 94 | return this.send( 95 | "files", 96 | "PUT", 97 | this.getParams({ 98 | operation: "copy", 99 | ids: ev.ids, 100 | target: ev.target, 101 | }) 102 | ).then((res: any) => { 103 | this.handleNonUniqueNames( 104 | res.result.map((i: any) => i.id), 105 | ev.newIds 106 | ); 107 | return res; 108 | }); 109 | }, 110 | }, 111 | "delete-files": { 112 | handler: async (ev: THandlersConfig["delete-files"]) => { 113 | return this.send( 114 | "files", 115 | "DELETE", 116 | this.getParams({ 117 | ids: ev.ids, 118 | }) 119 | ); 120 | }, 121 | }, 122 | }; 123 | } 124 | 125 | async loadFiles(id: TID): Promise { 126 | const data = await this.send( 127 | id ? `files/${encodeURIComponent(id)}` : "files", 128 | "GET" 129 | ); 130 | return this.parseDates(data); 131 | } 132 | 133 | loadInfo(id: TID): Promise { 134 | return this.send( 135 | id ? `info/${encodeURIComponent(id)}` : "info", 136 | "GET" 137 | ); 138 | } 139 | 140 | parseDates(data: IEntity[]) { 141 | data.forEach(item => { 142 | if (item.date) item.date = new Date(item.date); 143 | }); 144 | return data; 145 | } 146 | 147 | async send( 148 | url: string, 149 | method: string, 150 | data?: any, 151 | customHeaders?: any 152 | ): Promise { 153 | const headers = { 154 | ...customHeaders, 155 | }; 156 | 157 | const req: RequestInit = { 158 | method, 159 | headers, 160 | }; 161 | 162 | if (data) { 163 | req.body = data; 164 | } 165 | 166 | return fetch(`${this._url}/${url}`, req).then(res => { 167 | return res 168 | .json() 169 | .then(response => { 170 | if (!res.ok) 171 | throw new Error(`Network error: ${response.error}`); 172 | return response; 173 | }) 174 | .catch(error => { 175 | console.error(error); 176 | }); 177 | }); 178 | } 179 | 180 | private getParams(obj: Record): string { 181 | return typeof obj === "object" ? JSON.stringify(obj) : obj; 182 | } 183 | 184 | private handleNonUniqueNames(serverIds: TID[], localIds: TID[]) { 185 | localIds.forEach((id: TID, i: number) => { 186 | if (id !== serverIds[i]) 187 | (this as any).exec("file-renamed", { 188 | id, 189 | newId: serverIds[i], 190 | }); 191 | }); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /provider/src/index.ts: -------------------------------------------------------------------------------- 1 | import RestDataProvider from "./RestDataProvider"; 2 | 3 | export { RestDataProvider }; 4 | -------------------------------------------------------------------------------- /provider/test/provider.spec.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { RestDataProvider } from "../src/index"; 3 | 4 | function getDataStore() { 5 | const provider = new RestDataProvider(""); 6 | return { provider }; 7 | } 8 | 9 | describe("data provider", function () { 10 | it("can be initialized", () => { 11 | const t = getDataStore(); 12 | expect(t).not.eq(null); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "esnext", 5 | "lib": ["ESNext", "DOM"], 6 | "target": "ESNext", 7 | 8 | "isolatedModules": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "allowJs": true, 14 | "checkJs": false, 15 | 16 | "noImplicitAny": true, 17 | "sourceMap": true, 18 | 19 | "baseUrl": ".", 20 | "declaration": true, 21 | "outDir": "dist/" 22 | }, 23 | "watchOptions": {}, 24 | "include": ["src/**/*.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /provider/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | // ... 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # SVAR Svelte File Manager | File Explorer 4 | 5 |
6 | 7 |
8 | 9 | :globe_with_meridians: [Website](https://svar.dev/svelte/filemanager/) • :bulb: [Getting Started](https://docs.svar.dev/svelte/filemanager/getting_started/) • :eyes: [Demos](https://docs.svar.dev/svelte/filemanager/samples/#/base/willow) 10 | 11 |
12 | 13 |
14 | 15 | [![npm](https://img.shields.io/npm/v/wx-svelte-filemanager.svg)](https://www.npmjs.com/package/wx-svelte-filemanager) 16 | [![License](https://img.shields.io/github/license/svar-widgets/filemanager)](https://github.com/svar-widgets/filemanager/blob/main/license.txt) 17 | [![npm downloads](https://img.shields.io/npm/dm/wx-svelte-filemanager.svg)](https://www.npmjs.com/package/wx-svelte-filemanager) 18 | 19 |
20 | 21 | **SVAR File Manager** is a flexible file explorer component for Svelte apps. It offers a familiar interface for browsing, organizing, and previewing files. Integrate it with any backend, whether you're using local storage, databases, or cloud services. 22 | 23 |
24 | SVAR File Manager for Svelte - UI 25 |
26 |
27 | 28 | ### :sparkles: Key features: 29 | 30 | - Basic file operations: create, delete, copy, rename, cut, paste 31 | - Download and upload files 32 | - Files tree view 33 | - List and tiles views 34 | - File preview pane with file information (file size, type, modified date, etc) 35 | - Split view to manage files between different locations 36 | - Built-in search box 37 | - Context menu and toolbar 38 | - Keyboard navigation 39 | - Used storage info 40 | 41 | ### :wrench: Svelte 4 and Svelte 5 Versions 42 | 43 | There are two versions of the library: the 1.x version, designed to work with Svelte 4, and the 2.x version, created for Svelte 5. 44 | 45 | To use the SVAR FileManager for Svelte 5, install it as follows: 46 | 47 | ``` 48 | npm install wx-svelte-filemanager 49 | ``` 50 | 51 | To use the SVAR FileManager for Svelte 4: 52 | 53 | ``` 54 | npm install wx-svelte-filemanager@1.3.1 55 | ``` 56 | 57 | ### :hammer_and_pick: How to Use 58 | 59 | To use the widget, simply import the package and include the component in your Svelte file: 60 | 61 | ```svelte 62 | 77 | 78 | 79 | ``` 80 | 81 | ### :computer: How to Modify 82 | 83 | Typically, you don't need to modify the code. However, if you wish to do so, follow these steps: 84 | 85 | 1. Run `yarn` to install dependencies. Note that this project is a monorepo using `yarn` workspaces, so npm will not work 86 | 2. Start the project in development mode with `yarn start` 87 | 88 | ### :white_check_mark: Run Tests 89 | 90 | To run the test: 91 | 92 | 1. Start the test examples with: 93 | ```sh 94 | yarn start:tests 95 | ``` 96 | 2. In a separate console, run the end-to-end tests with: 97 | ```sh 98 | yarn test:cypress 99 | ``` 100 | 101 | ### :speech_balloon: Need Help? 102 | 103 | [Post an Issue](https://github.com/svar-widgets/filemanager/issues/) or use our [community forum](https://forum.svar.dev). 104 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /site/index.js: -------------------------------------------------------------------------------- 1 | import Demo from "./src/Demo.svelte"; 2 | import { mount } from "svelte"; 3 | 4 | mount(Demo, { 5 | target: document.querySelector("#wx_demo_area") || document.body, 6 | props: { 7 | themeSelect: true, 8 | border: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-site-svelte-filemanager", 3 | "version": "2.1.0", 4 | "type": "module", 5 | "scripts": { 6 | "build": "vite build", 7 | "lint": "yarn eslint ./src", 8 | "start": "yarn vite --open" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "wx-svelte-filemanager": "2.1.0", 13 | "wx-svelte-core": "2.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /site/public/placeholder.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svar-widgets/filemanager/4857067ba8b3586373b98e73a9d830f756c16291/site/public/placeholder.md -------------------------------------------------------------------------------- /site/src/CustomFileManager.svelte: -------------------------------------------------------------------------------- 1 | 114 | 115 |
116 |
117 |
118 | Theme 119 | 120 |
121 |
122 |
123 | 132 |
133 |
134 | 135 | 190 | -------------------------------------------------------------------------------- /site/src/Demo.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | {#if themeSelect} 25 |
26 |
27 |
28 | Theme 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 | {:else} 37 |
38 | {/if} 39 | 40 | 41 |
42 | 43 | 76 | -------------------------------------------------------------------------------- /site/src/Main.svelte: -------------------------------------------------------------------------------- 1 | 104 | 105 | 114 | 115 | 122 | -------------------------------------------------------------------------------- /site/src/ThemeSelect.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 | {#snippet children(option)} 26 |
27 |
28 |
29 |
30 |
31 | {option.label} 32 |
33 | 51 | {/snippet} 52 |
53 |
54 | -------------------------------------------------------------------------------- /site/src/data/index.js: -------------------------------------------------------------------------------- 1 | export function getData() { 2 | return { data: [{ label: "A" }, { label: "B" }] }; 3 | } 4 | -------------------------------------------------------------------------------- /site/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /site/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 3 | 4 | export default () => { 5 | let build, 6 | publicDir = resolve(__dirname, "public"), 7 | server = {}, 8 | base = "", 9 | plugins = [svelte({})]; 10 | 11 | build = { 12 | rollupOptions: { 13 | input: { index: resolve(__dirname, "index.html") }, 14 | }, 15 | }; 16 | 17 | return { 18 | base, 19 | build, 20 | publicDir, 21 | resolve: { dedupe: ["svelte"] }, 22 | plugins, 23 | server, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /site/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@wx/core-locales@1.1.3": 6 | version "1.1.3" 7 | resolved "https://npm.svar.dev/@wx%2fcore-locales/-/core-locales-1.1.3.tgz#e3cd9cf38b8297fcc4d2eabcad649f4bb1a4c1f8" 8 | integrity sha512-BjF7p44DNO4gDJeYOX19quggR9Uk4EIB4H3E1D6aSrCrnPPPaLOrv+XEYuzWP73eMeV+PIdKQN+NGbaonEY88w== 9 | 10 | "@wx/lib-dom@^0.5.0": 11 | version "0.5.0" 12 | resolved "https://npm.svar.dev/@wx%2flib-dom/-/lib-dom-0.5.0.tgz#cc10e1e725f733fd50cf44bb375036f5e65fb788" 13 | integrity sha512-aHNr+2/ml++3f5CAA4W8D6/EDz53MDo2FxiPiSMaJ6X5kJFMwF/ZtOISTpes+Rq6qqRM3K9mCXwwPrcaUctF4Q== 14 | 15 | "@wx/svelte-core@*": 16 | version "1.1.3" 17 | resolved "https://npm.svar.dev/@wx%2fsvelte-core/-/svelte-core-1.1.3.tgz#a678f78c19d99552e327159be88e79e2af816117" 18 | integrity sha512-KX4VeHPGpQ34+hNZ68V/irBchgYZxzSf/ccp5nRcuIPbBkP5YwuRle4ju8B0KE3UjJ/Yy3a6j3FcUgy6luqtCA== 19 | dependencies: 20 | "@wx/core-locales" "1.1.3" 21 | "@wx/lib-dom" "^0.5.0" 22 | -------------------------------------------------------------------------------- /store/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | env: { 5 | browser: true, 6 | node: true, 7 | es6: true, 8 | }, 9 | extends: [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier", 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | sourceType: "module", 17 | 18 | tsconfigRootDir: __dirname, 19 | }, 20 | plugins: ["@typescript-eslint"], 21 | rules: { 22 | "no-bitwise": ["error"], 23 | "@typescript-eslint/explicit-module-boundary-types": "off", 24 | "@typescript-eslint/no-explicit-any": "off", 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /store/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | -------------------------------------------------------------------------------- /store/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 XB Software Sp. z o.o. 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. -------------------------------------------------------------------------------- /store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-filemanager-store", 3 | "version": "2.1.0", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "module": "dist/index.js", 7 | "types": "dist/types/index.d.ts", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "vite build", 11 | "watch": "yarn tsc -w --preserveWatchOutput", 12 | "lint": "yarn eslint ./src", 13 | "test": "vitest --run" 14 | }, 15 | "files": [ 16 | "dist", 17 | "license.txt" 18 | ], 19 | "dependencies": { 20 | "wx-lib-state": "1.9.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /store/src/FileTree.ts: -------------------------------------------------------------------------------- 1 | import { DataTree } from "wx-lib-state"; 2 | import type { 3 | IEntity, 4 | IParsedTreeEntity, 5 | IParsedEntity, 6 | TID, 7 | IFile, 8 | } from "./types"; 9 | 10 | export default class FileTree extends DataTree { 11 | constructor(files?: IEntity[]) { 12 | const root = { 13 | id: "/", 14 | name: "My files", 15 | open: true, 16 | $level: 1, 17 | parent: 0 as unknown, 18 | ext: "", 19 | type: "folder", 20 | }; 21 | super([root as IParsedEntity]); 22 | this.parse(files, "/"); 23 | } 24 | 25 | parse(files: IEntity[], parent: TID, force?: boolean) { 26 | if (!files || !files.length) return; 27 | files.forEach(file => 28 | this.parseId(file as IParsedTreeEntity, force ? parent : undefined) 29 | ); 30 | super.parse(files as IParsedEntity[], parent); 31 | } 32 | 33 | parseId(file: IParsedTreeEntity, parent?: TID) { 34 | if (file.parent === (0 as unknown)) return; 35 | const id = file.id; 36 | const slash = id.lastIndexOf("/"); 37 | 38 | file.parent = parent ?? (slash === 0 ? "/" : id.slice(0, slash)); 39 | file.name = id.slice(slash + 1); 40 | file.type = file.type || "file"; 41 | 42 | if (file.type !== "folder") { 43 | const dot = file.name.lastIndexOf("."); 44 | const ext = dot !== -1 ? file.name.slice(dot + 1) : ""; 45 | file.ext = ext !== "new" ? ext?.toLocaleLowerCase() : ""; 46 | } 47 | 48 | } 49 | 50 | getParents(id: TID): any { 51 | const res: IParsedEntity[] = []; 52 | let file = this.byId(id); 53 | if (!file) return res; 54 | 55 | while (file.id !== (0 as unknown)) { 56 | res.push(file); 57 | file = this.byId(file.parent); 58 | } 59 | 60 | return res.reverse(); 61 | } 62 | 63 | findFiles(text: string, parent: TID = "/"): IParsedEntity[] { 64 | const res: IParsedEntity[] = []; 65 | const t = text.toLocaleLowerCase(); 66 | this.eachChild(i => { 67 | if (i.name.toLowerCase().indexOf(t) !== -1) { 68 | res.push(i); 69 | } 70 | }, parent); 71 | return res; 72 | } 73 | add(file: IParsedTreeEntity, index?: number) { 74 | const parent = this.byId(file.parent || "/"); 75 | const data = file.data ? [...file.data] : null; 76 | file.data = null; 77 | super.add( 78 | file as IParsedEntity, 79 | index ?? parent.data ? parent.data.length : 0 80 | ); 81 | if (data) { 82 | data.forEach(i => this.add(i)); 83 | } 84 | } 85 | renameFiles(id: TID, target: TID, newIds: any): IParsedEntity { 86 | const file = this.byId(id); 87 | const copyFile = this.normalizeFile(file, target); 88 | const newData: IParsedEntity[] = []; 89 | copyFile.data?.forEach((item: IParsedEntity) => 90 | newData.push(this.renameFiles(item.id, copyFile.id, newIds)) 91 | ); 92 | 93 | if (newData.length) copyFile.data = newData; 94 | newIds[id] = copyFile.id; 95 | return copyFile as IParsedEntity; 96 | } 97 | copyFiles(ids: TID[], target: TID, remove?: boolean): any { 98 | const newIds: any = {}; 99 | ids.forEach(id => { 100 | const file = this.renameFiles(id, target, newIds); 101 | this.add(file); 102 | if (remove) this.remove(id); 103 | newIds[id] = file.id; 104 | }); 105 | return newIds; 106 | } 107 | moveFiles(ids: TID[], target: TID) { 108 | const parent = this.byId(ids[0]).parent; 109 | if (parent !== target) { 110 | return this.copyFiles(ids, target, true); 111 | } 112 | } 113 | serialize(id: TID = "/"): IEntity[] | null { 114 | const out: IEntity[] = []; 115 | const temp = this.byId(id); 116 | if (!temp) return null; 117 | 118 | temp.data?.forEach(item => { 119 | const { id, date, type, size, lazy } = item; 120 | const serializedItem: IEntity = { 121 | id, 122 | date, 123 | type: type ?? "file", 124 | }; 125 | 126 | if (lazy) serializedItem.lazy = lazy; 127 | if (size) serializedItem.size = size; 128 | 129 | out.push(serializedItem); 130 | 131 | const items = this.serialize(id); 132 | if (items.length) out.push(...items); 133 | }); 134 | 135 | return out; 136 | } 137 | normalizeFile(file: IFile, parent: TID): IParsedTreeEntity { 138 | const name = file.name; 139 | const index = name.lastIndexOf("."); 140 | const ext = index !== -1 ? name.slice(index + 1) : ""; 141 | const endExt = ext ? `.${ext}` : ""; 142 | let newName = index !== -1 ? name.slice(0, index) : name; 143 | let id = parent + (parent === "/" ? "" : "/") + newName; 144 | 145 | while (this.byId(id + endExt)) { 146 | const addNew = ".new"; 147 | id += addNew; 148 | newName += addNew; 149 | } 150 | 151 | return { 152 | ...file, 153 | id: id + endExt, 154 | name: newName + endExt, 155 | parent, 156 | ext: ext.toLocaleLowerCase(), 157 | }; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /store/src/helpers.ts: -------------------------------------------------------------------------------- 1 | import type { IParsedEntity, TSort, TSortValue, TID } from "./types"; 2 | 3 | export function formatSize(size: number): string { 4 | if (size < 1024) { 5 | return `${size} b`; 6 | } else if (size >= 1024 && size < 1000000) { 7 | return `${(size / 1000).toFixed(1)} kb`; 8 | } else if (size >= 1000000 && size < 1000000000) { 9 | return `${(size / 1000000).toFixed(1)} Mb`; 10 | } else if (size >= 1000000000) { 11 | return `${(size / 1000000000).toFixed(1)} Gb`; 12 | } 13 | } 14 | 15 | export function sort(data: IParsedEntity[], conf?: TSort) { 16 | if (!conf) 17 | conf = { 18 | key: "name", 19 | order: "asc", 20 | }; 21 | const folders = data.filter(i => i.type === "folder"); 22 | const files = data.filter(i => i.type !== "folder"); 23 | const sortedFolders = folders.sort(sortBy(conf)); 24 | const sortedFiles = files.sort(sortBy(conf)); 25 | return [...sortedFolders, ...sortedFiles]; 26 | } 27 | 28 | function sortAsc(a: TSortValue, b: TSortValue): number { 29 | if (typeof a === "string") 30 | return a.localeCompare(b as string, undefined, { numeric: true }); 31 | if (typeof a === "object") return a.getTime() - (b as Date).getTime(); 32 | return ((a ?? 0) as number) - ((b ?? 0) as number); 33 | } 34 | 35 | function sortDesc(a: TSortValue, b: TSortValue): number { 36 | if (typeof a === "string") 37 | return -a.localeCompare(b as string, undefined, { numeric: true }); 38 | if (typeof b === "object") return b.getTime() - (a as Date).getTime(); 39 | return ((b ?? 0) as number) - ((a ?? 0) as number); 40 | } 41 | 42 | function sortBy({ key, order }: TSort) { 43 | const sortMethod = order === "asc" ? sortAsc : sortDesc; 44 | return (a: IParsedEntity, b: IParsedEntity) => sortMethod(a[key], b[key]); 45 | } 46 | 47 | export function getSelectionOnNavigation( 48 | id: TID, 49 | crumbs: IParsedEntity[] 50 | ): TID | null { 51 | const length = id === "/" ? 1 : id.split("/").length; 52 | 53 | if (length >= crumbs.length) return null; 54 | return crumbs[length].id; 55 | } 56 | 57 | export function getParentPath(crumbs: IParsedEntity[]): TID | null { 58 | if (crumbs.length > 1) { 59 | return crumbs[crumbs.length - 2].id; 60 | } 61 | 62 | return null; 63 | } 64 | -------------------------------------------------------------------------------- /store/src/hotkeys.ts: -------------------------------------------------------------------------------- 1 | import { locateAttr } from "wx-lib-dom"; 2 | 3 | import type { TID, IParsedEntity } from "./types"; 4 | import { getParentPath, getSelectionOnNavigation } from "./helpers"; 5 | 6 | export function hotkeys(node: HTMLElement, config: any) { 7 | const { api } = config; 8 | 9 | function handleHotkey(hotkey: string, e: KeyboardEvent) { 10 | const panel = config.getPanel(); 11 | const selected = panel.selected; 12 | 13 | const ctrl = e.ctrlKey || e.metaKey; 14 | const shift = e.shiftKey; 15 | 16 | let options; 17 | const item = selected?.[0] ? api.getFile(selected[0]) : null; 18 | if (selected?.length) { 19 | if (selected.length > 1) 20 | options = config.menuOptions("multiselect"); 21 | else options = config.menuOptions(item.type, item); 22 | } else { 23 | options = config.menuOptions("body"); 24 | } 25 | const hotkeys = `${ctrl ? "ctrl+" : ""}${ 26 | shift ? "shift+" : "" 27 | }${hotkey}`; 28 | const option = options 29 | ? options.find((o: any) => o.hotkey?.toLowerCase() === hotkeys) 30 | : false; 31 | const action = config.performAction; 32 | if (option) { 33 | switch (hotkeys) { 34 | case "ctrl+c": 35 | action("copy"); 36 | break; 37 | case "ctrl+x": 38 | action("move"); 39 | break; 40 | case "ctrl+v": 41 | action("paste", item); 42 | break; 43 | case "delete": 44 | action("delete"); 45 | break; 46 | case "ctrl+r": 47 | e.preventDefault(); 48 | action("rename", item); 49 | break; 50 | case "ctrl+d": 51 | e.preventDefault(); 52 | action("download", item); 53 | break; 54 | default: 55 | option.handler?.({ context: item }); 56 | } 57 | } 58 | 59 | switch (hotkey) { 60 | case "enter": { 61 | if (ctrl || shift) break; 62 | const { _crumbs: crumbs, _selectNavigation: selectNavigation } = 63 | panel; 64 | 65 | if (selectNavigation) { 66 | const path = getParentPath(crumbs); 67 | if (path) { 68 | const selection = getSelectionOnNavigation( 69 | path, 70 | crumbs 71 | ); 72 | api.exec("set-path", { 73 | id: path, 74 | selected: [selection], 75 | }); 76 | } 77 | } else if (selected.length === 1) { 78 | const item = api.getFile(selected[0]); 79 | if (item) { 80 | api.exec( 81 | item.type === "folder" ? "set-path" : "open-file", 82 | { id: item.id } 83 | ); 84 | } 85 | } 86 | break; 87 | } 88 | 89 | case "arrowup": 90 | case "arrowleft": { 91 | const { 92 | _files: files, 93 | _selected: selectedFiles, 94 | path, 95 | _selectNavigation: selectNavigation, 96 | } = panel; 97 | 98 | let nextId: TID; 99 | 100 | if (selectedFiles.length) { 101 | const start = selectedFiles.at(0).id; 102 | const currentIndex = files.findIndex( 103 | (file: IParsedEntity) => file.id === start 104 | ); 105 | if (currentIndex !== -1) { 106 | const next = currentIndex - 1; 107 | if (files[next]) nextId = files[next].id; 108 | else if (path !== "/" && !(ctrl || shift)) { 109 | api.exec("select-file", { type: "navigation" }); 110 | } 111 | } 112 | } else { 113 | if (selectNavigation) break; 114 | if (path !== "/") { 115 | api.exec("select-file", { type: "navigation" }); 116 | break; 117 | } 118 | nextId = files.at(0)?.id; 119 | } 120 | 121 | if (nextId) 122 | api.exec("select-file", { 123 | id: nextId, 124 | toggle: ctrl, 125 | range: shift, 126 | }); 127 | break; 128 | } 129 | 130 | case "arrowdown": 131 | case "arrowright": { 132 | const { 133 | _files: files, 134 | _selected: selectedFiles, 135 | path, 136 | _selectNavigation: selectNavigation, 137 | } = panel; 138 | 139 | let nextId: TID; 140 | 141 | if (selectedFiles.length) { 142 | const start = selectedFiles.at(-1).id; 143 | const currentIndex = files.findIndex( 144 | (file: IParsedEntity) => file.id === start 145 | ); 146 | if (currentIndex !== -1) { 147 | const next = currentIndex + 1; 148 | if (files[next]) nextId = files[next].id; 149 | } 150 | } else { 151 | if (path !== "/" && !selectNavigation) { 152 | api.exec("select-file", { type: "navigation" }); 153 | break; 154 | } 155 | nextId = files.at(0)?.id; 156 | } 157 | 158 | if (nextId) { 159 | api.exec("select-file", { 160 | id: nextId, 161 | toggle: ctrl, 162 | range: shift, 163 | }); 164 | } 165 | break; 166 | } 167 | } 168 | } 169 | 170 | function handleKeydown(e: any) { 171 | const code = e.code.replace("Key", "").toLowerCase(); 172 | handleHotkey(code, e); 173 | 174 | //tracking active panel 175 | if (code == "tab") 176 | setTimeout(() => { 177 | const active = document.activeElement; 178 | if (active.className.indexOf("table") != -1) { 179 | api.exec("set-active-panel", { 180 | panel: locateAttr(active, "data-panel"), 181 | }); 182 | } 183 | }); 184 | } 185 | 186 | node.addEventListener("keydown", handleKeydown); 187 | 188 | return { 189 | destroy: () => { 190 | node.removeEventListener("keydown", handleKeydown); 191 | }, 192 | }; 193 | } 194 | -------------------------------------------------------------------------------- /store/src/index.ts: -------------------------------------------------------------------------------- 1 | export { DataStore, type THandlersConfig } from "./DataStore"; 2 | export { formatSize, sort, getSelectionOnNavigation } from "./helpers"; 3 | export { getMenuOptions } from "./menus"; 4 | export { hotkeys } from "./hotkeys"; 5 | 6 | export type { 7 | IDataConfig, 8 | IEntity, 9 | IParsedEntity, 10 | TMode, 11 | IDrive, 12 | IApi, 13 | TID, 14 | IFile, 15 | } from "./types"; 16 | -------------------------------------------------------------------------------- /store/src/menus.ts: -------------------------------------------------------------------------------- 1 | import type { TContextMenuType, TMenuData } from "./types"; 2 | 3 | export function getMenuOptions(mode: TContextMenuType): TMenuData[] { 4 | switch (mode) { 5 | case "body": 6 | return [ 7 | { 8 | icon: "wxi-content-paste", 9 | text: "Paste", 10 | hotkey: "Ctrl+V", 11 | id: "paste", 12 | }, 13 | ]; 14 | case "add": 15 | return [ 16 | { 17 | icon: "mdi mdi-folder-plus-outline", 18 | text: "Add new file", 19 | id: "add-file", 20 | }, 21 | { 22 | icon: "mdi mdi-file-plus-outline", 23 | text: "Add new folder", 24 | id: "add-folder", 25 | }, 26 | { 27 | icon: "mdi mdi-file-upload-outline", 28 | text: "Upload file", 29 | id: "upload", 30 | type: "upload", 31 | }, 32 | ]; 33 | case "multiselect": 34 | return [ 35 | { 36 | icon: "wxi-content-copy", 37 | text: "Copy", 38 | hotkey: "Ctrl+C", 39 | id: "copy", 40 | }, 41 | { 42 | icon: "wxi-content-cut", 43 | text: "Cut", 44 | hotkey: "Ctrl+X", 45 | id: "move", 46 | }, 47 | { 48 | type: "separator", 49 | }, 50 | { 51 | icon: "wxi-close", 52 | text: "Delete", 53 | hotkey: "Delete", 54 | id: "delete", 55 | }, 56 | ]; 57 | default: 58 | return [ 59 | mode === "folder" 60 | ? null 61 | : { 62 | icon: "wxi-download", 63 | text: "Download", 64 | hotkey: "Ctrl+D", 65 | id: "download", 66 | }, 67 | { 68 | icon: "wxi-content-copy", 69 | text: "Copy", 70 | hotkey: "Ctrl+C", 71 | id: "copy", 72 | }, 73 | { 74 | icon: "wxi-content-cut", 75 | text: "Cut", 76 | hotkey: "Ctrl+X", 77 | id: "move", 78 | }, 79 | { 80 | icon: "wxi-content-paste", 81 | text: "Paste", 82 | hotkey: "Ctrl+V", 83 | id: "paste", 84 | }, 85 | { 86 | type: "separator", 87 | }, 88 | { 89 | icon: "wxi-edit", 90 | text: "Rename", 91 | hotkey: "Ctrl+R", 92 | id: "rename", 93 | }, 94 | { 95 | icon: "wxi-close", 96 | text: "Delete", 97 | hotkey: "Delete", 98 | id: "delete", 99 | }, 100 | ].filter(a => a !== null); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /store/src/package.ts: -------------------------------------------------------------------------------- 1 | 2 | export function getStamp() { 3 | return { gen: 1 }; 4 | } 5 | -------------------------------------------------------------------------------- /store/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { DataStore, THandlersConfig } from "./DataStore"; 2 | import type FileTree from "./FileTree"; 3 | import type { IEventBus, IPublicWritable } from "wx-lib-state"; 4 | 5 | export type TID = string; 6 | export type TContextMenuType = 7 | | "folder" 8 | | "file" 9 | | "body" 10 | | "add" 11 | | "multiselect"; 12 | 13 | export interface IActionConfig { 14 | data?: T; 15 | noSave?: boolean; 16 | } 17 | 18 | export interface IEntity { 19 | id: TID; 20 | type?: "file" | "folder"; 21 | size?: number; 22 | lazy?: boolean; 23 | date: Date; 24 | [key: string]: any; 25 | } 26 | 27 | export interface IParsedTreeEntity extends IEntity { 28 | parent: TID; 29 | name: string; 30 | ext: string; 31 | } 32 | 33 | export interface IParsedEntity extends IParsedTreeEntity { 34 | $level: number; 35 | open?: boolean; 36 | data: IParsedEntity[]; 37 | } 38 | 39 | export interface IFile { 40 | name: string; 41 | date: Date; 42 | type?: "file" | "folder"; 43 | size?: number; 44 | file?: File; 45 | } 46 | 47 | export type TMode = "cards" | "table" | "panels" | "search"; 48 | 49 | export type TActivePanel = 0 | 1; 50 | export type TSort = { 51 | key: string; 52 | order: "asc" | "desc"; 53 | }; 54 | export type TSortValue = string | number | Date; 55 | 56 | export interface IPanel { 57 | path: TID; 58 | selected: TID[]; 59 | _selected: IParsedEntity[]; 60 | _files: IParsedEntity[]; 61 | _crumbs: IParsedEntity[]; 62 | _sorts: { [key: TID]: TSort }; 63 | _lastSelected: TID; 64 | _selectNavigation: boolean; 65 | } 66 | 67 | export interface IDataConfig { 68 | tree: IEntity[]; 69 | mode: TMode; 70 | preview: boolean; 71 | search: string; 72 | drive: IDrive; 73 | panels: Partial[]; 74 | activePanel: TActivePanel; 75 | } 76 | 77 | export interface IData { 78 | tree: FileTree; 79 | mode: TMode; 80 | preview: boolean; 81 | search: string; 82 | drive: IDrive; 83 | panels: Partial[]; 84 | activePanel: TActivePanel; 85 | } 86 | 87 | export interface IDrive { 88 | used: number; 89 | total: number; 90 | } 91 | 92 | export type TMenuData = { 93 | id?: string; 94 | text?: string; 95 | hotkey?: string; 96 | icon?: string; 97 | type?: string; 98 | getData?: any; 99 | }; 100 | 101 | export interface IApi { 102 | exec: (action: keyof THandlersConfig, params: any) => Promise; 103 | on: (action: keyof THandlersConfig, callback: (config: any) => any) => void; 104 | intercept: ( 105 | action: keyof THandlersConfig, 106 | callback: (config: any) => any 107 | ) => void; 108 | getState: () => IData; 109 | getReactiveState: () => { 110 | [Key in keyof IData]: IPublicWritable; 111 | }; 112 | setNext: (next: IEventBus) => IEventBus; 113 | getStores: () => { data: DataStore }; 114 | getFile: (id: TID) => IParsedEntity | null; 115 | serialize: (id: TID) => IEntity[] | null; 116 | } 117 | -------------------------------------------------------------------------------- /store/test/stubs/data.js: -------------------------------------------------------------------------------- 1 | export const getData = () => []; 2 | -------------------------------------------------------------------------------- /store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "esnext", 5 | "lib": ["ESNext", "DOM"], 6 | "target": "ESNext", 7 | "types": ["vite/client"], 8 | 9 | "isolatedModules": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "allowJs": true, 15 | "checkJs": false, 16 | 17 | "noImplicitAny": true, 18 | "sourceMap": true, 19 | 20 | "baseUrl": ".", 21 | "declaration": true, 22 | "outDir": "dist/" 23 | }, 24 | "watchOptions": {}, 25 | "include": ["src/**/*.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /store/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { resolve } from "path"; 3 | import { defineConfig, loadEnv } from "vite"; 4 | import dts from "vite-plugin-dts"; 5 | import conditionalCompile from "vite-plugin-conditional-compile"; 6 | import esbuild from "esbuild"; 7 | 8 | const minify = { 9 | name: "minify", 10 | closeBundle: () => { 11 | esbuild.buildSync({ 12 | entryPoints: ["./dist/index.js"], 13 | minify: true, 14 | allowOverwrite: true, 15 | outfile: "./dist/index.js", 16 | }); 17 | }, 18 | }; 19 | 20 | export default function ({ mode }) { 21 | process.env = { ...process.env, ...loadEnv(mode, process.cwd(), "WX") }; 22 | const trial = !!process.env.WX_TRIAL_PACKAGE; 23 | 24 | const config = { 25 | build: { 26 | lib: { 27 | entry: resolve(__dirname, "src/index.ts"), 28 | name: "store", 29 | formats: ["es"], 30 | fileName: () => `index.js`, 31 | }, 32 | sourcemap: !trial, 33 | minify: mode !== "development", 34 | target: "esnext", 35 | }, 36 | test: { 37 | coverage: { 38 | reporter: ["text"], 39 | }, 40 | }, 41 | plugins: [], 42 | }; 43 | 44 | if (mode !== "development") { 45 | config.plugins.push(conditionalCompile({})); 46 | } 47 | 48 | if (!trial) { 49 | config.plugins.push(dts({ outDir: resolve(__dirname, "dist/types") })); 50 | } else { 51 | config.plugins.push(dts({ outDir: resolve(__dirname, "dist/types") })); 52 | } 53 | if (mode !== "development") { 54 | config.plugins.push(minify); 55 | } 56 | 57 | return defineConfig(config); 58 | } 59 | -------------------------------------------------------------------------------- /svelte/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cypress/screenshots 3 | cypress/videos 4 | -------------------------------------------------------------------------------- /svelte/cypress.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | video: false, 5 | e2e: { 6 | setupNodeEvents() {}, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/basic/basic-ops.cy.js: -------------------------------------------------------------------------------- 1 | context("Basic functionality", () => { 2 | it("main views", () => { 3 | cy.visit(`/index.html#/local-data`); 4 | cy.viewport(1300, 900); 5 | cy.shot(`initial`); 6 | 7 | cy.wxF("mode-table").click(); 8 | cy.shot("initial-table"); 9 | 10 | cy.wxF("mode-panels").click(); 11 | cy.shot("initial-panels"); 12 | 13 | cy.wxF("mode-cards").click(); 14 | cy.wxF("panel-cards").wxF("card-item", "/Code").dblclick(); 15 | cy.shot("cards-many-items"); 16 | 17 | cy.wxF("mode-table").click(); 18 | cy.shot("table-many-items"); 19 | 20 | cy.wxF("mode-panels").click(); 21 | cy.shot("panels-many-items"); 22 | }); 23 | 24 | it("renaming works", () => { 25 | cy.visit("/index.html#/local-data"); 26 | cy.viewport(1300, 900); 27 | 28 | cy.wxF("panel-cards").wxF("card-item", "/Code").rightclick(); 29 | cy.wxF("menu-option", "rename").click(); 30 | cy.get(".wx-modal").should("be.visible"); 31 | cy.get(".wx-modal").find("input").type("New name{enter}"); 32 | cy.wxF("panel-cards") 33 | .wxF("card-item", "/New name") 34 | .should("be.visible"); 35 | cy.wxF("tree-item", "/New name").should("be.visible"); 36 | cy.shot("card-renamed"); 37 | 38 | cy.wxF("mode-table").click(); 39 | cy.wxF("panel-table").wxF("grid-item", "/Music").rightclick(); 40 | cy.wxF("menu-option", "rename").click(); 41 | cy.get(".wx-modal").should("be.visible"); 42 | cy.get(".wx-modal").find("input").type("Miracle of sound{enter}"); 43 | cy.wxF("panel-table") 44 | .wxF("grid-item", "/Miracle of sound") 45 | .should("be.visible"); 46 | cy.wxF("tree-item", "/Miracle of sound").should("be.visible"); 47 | cy.shot("grid-item-renamed"); 48 | 49 | cy.wxF("mode-panels").click(); 50 | cy.wxF("fm-right-panel").wxF("grid-item", "/Info.txt").rightclick(); 51 | cy.wxF("menu-option", "rename").click(); 52 | cy.get(".wx-modal").should("be.visible"); 53 | cy.get(".wx-modal").find("input").type("Reamde.xx{enter}"); 54 | cy.wxF("fm-right-panel") 55 | .wxF("grid-item", "/Reamde.xx") 56 | .should("be.visible"); 57 | cy.wxF("fm-left-panel") 58 | .wxF("grid-item", "/Reamde.xx") 59 | .should("be.visible"); 60 | cy.shot("panel-item-renamed"); 61 | 62 | cy.wxF("mode-cards").click(); 63 | cy.wxF("tree-toggle", "/New name").click(); 64 | cy.wxF("tree-item", "/New name").click(); 65 | cy.wxF("tree-item", "/New name/Datepicker").rightclick(); 66 | cy.wxF("menu-option", "rename").click(); 67 | cy.get(".wx-modal").should("be.visible"); 68 | cy.get(".wx-modal").find("input").type("Spacepicker{enter}"); 69 | cy.wxF("tree-item", "/New name/Spacepicker").should("be.visible"); 70 | cy.wxF("panel-cards") 71 | .wxF("card-item", "/New name/Spacepicker") 72 | .should("be.visible"); 73 | cy.shot("tree-item-renamed"); 74 | }); 75 | 76 | it("deleting works", () => { 77 | cy.visit("/index.html#/local-data"); 78 | cy.viewport(1300, 900); 79 | 80 | cy.wxF("panel-cards").wxF("card-item", "/Code").rightclick(); 81 | cy.wxF("menu-option", "delete").click(); 82 | cy.get(".wx-modal").should("be.visible"); 83 | cy.get(".wx-modal").find("button").contains("OK").click(); 84 | cy.wxF("panel-cards").children().should("have.length", 3); 85 | cy.wxF("tree").children().should("have.length", 3); 86 | cy.shot("card-deleted"); 87 | 88 | cy.wxF("mode-table").click(); 89 | cy.wxF("panel-table").wxF("grid-item", "/Music").rightclick(); 90 | cy.wxF("menu-option", "delete").click(); 91 | cy.get(".wx-modal").should("be.visible"); 92 | cy.get(".wx-modal").find("button").contains("Cancel").click(); 93 | cy.wxF("panel-table") 94 | .find(".wx-scroll .wx-body .wx-data") 95 | .children() 96 | .should("have.length", 3); 97 | cy.wxF("tree").children().should("have.length", 3); 98 | cy.shot("item-not-deleted"); 99 | 100 | cy.wxF("tree-item", "/Pictures").rightclick(); 101 | cy.wxF("menu-option", "delete").click(); 102 | cy.get(".wx-modal").should("be.visible"); 103 | cy.get(".wx-modal").find("button").contains("OK").click(); 104 | cy.wxF("panel-table") 105 | .find(".wx-scroll .wx-body .wx-data") 106 | .children() 107 | .should("have.length", 2); 108 | cy.wxF("tree").children().should("have.length", 2); 109 | cy.shot("tree-item-deleted"); 110 | 111 | cy.wxF("mode-panels").click(); 112 | cy.wxF("fm-right-panel").wxF("grid-item", "/Info.txt").click(); 113 | cy.wxF("fm-right-panel") 114 | .wxF("grid-item", "/Music") 115 | .click({ ctrlKey: true }); 116 | cy.wxF("fm-right-panel").wxF("grid-item", "/Info.txt").rightclick(); 117 | cy.wxF("menu-option", "delete").click(); 118 | cy.get(".wx-modal").should("be.visible"); 119 | cy.get(".wx-modal").find("button").contains("OK").click(); 120 | cy.wxF("fm-right-panel") 121 | .find(".wx-scroll .wx-body .wx-data") 122 | .children() 123 | .should("have.length", 0); 124 | cy.wxF("fm-left-panel") 125 | .find(".wx-scroll .wx-body .wx-data") 126 | .children() 127 | .should("have.length", 0); 128 | cy.shot("multiple-deleted"); 129 | cy.wxF("mode-cards").click(); 130 | cy.wxF("tree").children().should("have.length", 1); 131 | cy.shot("multiple-deleted-tree-view"); 132 | }); 133 | 134 | it("sorting works", () => { 135 | cy.visit("/index.html#/local-data"); 136 | cy.viewport(1300, 900); 137 | cy.wxF("mode-table").click(); 138 | cy.wxF("panel-table").find(".wx-header .wx-cell").eq(0).click(); 139 | cy.wxF("panel-table") 140 | .find(".wx-sort .wxi-arrow-down") 141 | .should("be.visible"); 142 | cy.wxF("panel-table") 143 | .find(".wx-row") 144 | .eq(0) 145 | .should("contain", "Pictures"); 146 | cy.shot("table-mode-sort-desc"); 147 | cy.wxF("mode-cards").click(); 148 | cy.wxF("panel-cards") 149 | .find(".wx-item") 150 | .eq(0) 151 | .should("contain", "Pictures"); 152 | cy.shot("cards-mode-sort-desc"); 153 | cy.wxF("mode-panels").click(); 154 | cy.wxF("fm-right-panel").wxF("grid-item", "/Pictures").dblclick(); 155 | cy.wxF("fm-right-panel").find(".wx-header .wx-cell").eq(2).click(); 156 | cy.wxF("fm-right-panel") 157 | .find(".wx-row") 158 | .eq(1) 159 | .should("contain", "dasha-emma-i-kieit.jpeg"); 160 | cy.shot("panels-right-sort-date"); 161 | cy.wxF("mode-cards").click(); 162 | cy.wxF("panel-cards") 163 | .find(".wx-item") 164 | .eq(0) 165 | .should("contain", "dasha-emma-i-kieit.jpeg"); 166 | cy.shot("cards-mode-sort-date"); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/basic/menus.cy.js: -------------------------------------------------------------------------------- 1 | context("Menus", () => { 2 | it("default menus in default fm with local data", () => { 3 | cy.visit(`/index.html#/local-data`); 4 | cy.viewport(1300, 900); 5 | 6 | cy.wxF("card-dots", "/Music").click(); 7 | cy.wxF("menu").should("be.visible"); 8 | cy.wxF("menu").children().should("have.length", 6); 9 | cy.shot(`card-action-menu`); 10 | 11 | cy.wxF("panel-cards").wxF("card-item", "/Code").rightclick(); 12 | cy.wxF("menu").should("be.visible"); 13 | cy.wxF("menu").children().should("have.length", 6); 14 | cy.shot(`card-context-menu`); 15 | cy.wxF("menu-option", "copy").click(); 16 | 17 | cy.wxF("panel-cards").rightclick("bottom"); 18 | cy.wxF("menu").should("be.visible"); 19 | cy.wxF("menu").children().should("have.length", 1); 20 | cy.shot("card-body-menu"); 21 | cy.wxF("menu-option", "paste").click(); 22 | cy.shot("card-copied-by-menu"); 23 | 24 | cy.wxF("mode-table").click(); 25 | cy.wxF("panel-table").wxF("grid-item", "/Code").rightclick(); 26 | cy.wxF("menu").should("be.visible"); 27 | cy.wxF("menu").children().should("have.length", 6); 28 | cy.shot(`table-context-menu`); 29 | 30 | cy.wxF("panel-table").rightclick("bottom"); 31 | cy.shot("table-body-menu"); 32 | 33 | cy.wxF("mode-panels").click(); 34 | cy.wxF("fm-left-panel").wxF("grid-item", "/Pictures").rightclick(); 35 | cy.wxF("menu").should("be.visible"); 36 | cy.wxF("menu").children().should("have.length", 6); 37 | cy.wxF("menu-option", "move").click(); 38 | cy.wxF("fm-right-panel").wxF("grid-item", "/Music").dblclick(); 39 | cy.wxF("fm-right-panel").rightclick("bottom"); 40 | cy.wxF("menu").should("be.visible"); 41 | cy.wxF("menu").children().should("have.length", 1); 42 | cy.wxF("menu-option", "paste").click(); 43 | cy.shot("panels-menus-cut-paste"); 44 | 45 | cy.wxF("fm-left-panel").wxF("grid-item", "/Code").dblclick(); 46 | cy.wxF("fm-left-panel") 47 | .wxF("grid-item", "/Code/ColorPicker.svelte") 48 | .rightclick(); 49 | cy.wxF("menu").should("be.visible"); 50 | cy.wxF("menu").children().should("have.length", 7); 51 | cy.shot("default-file-context-menu"); 52 | 53 | cy.wxF("mode-table").click(); 54 | cy.wxF("tree-item", "/Code").rightclick(); 55 | cy.wxF("menu").should("be.visible"); 56 | cy.wxF("menu").children().should("have.length", 6); 57 | cy.shot("tree-default-menu"); 58 | 59 | cy.wxF("mode-table").click(); 60 | cy.wxF("tree-item", "/").rightclick(); 61 | cy.wxF("menu").should("be.visible"); 62 | cy.wxF("menu").children().should("have.length", 1); 63 | cy.shot("tree-root-default-menu"); 64 | 65 | cy.wxF("tree-item", "/").click(); 66 | cy.wxF("add-button").click(); 67 | cy.wxF("menu").should("be.visible"); 68 | cy.wxF("menu").children().should("have.length", 3); 69 | cy.wxF("menu-option", "add-file").click(); 70 | cy.get(".wx-modal").should("be.visible"); 71 | cy.get(".wx-modal").find("button").contains("OK").click(); 72 | cy.wxF("mode-cards").click(); 73 | cy.wxF("panel-cards").children().should("have.length", 5); 74 | cy.wxF("add-button").click(); 75 | // temp, uncomment when tree add temp ids issue will be fixed 76 | cy.wxF("menu-option", "add-folder").click(); 77 | cy.get(".wx-modal").should("be.visible"); 78 | cy.get(".wx-modal").find("button").contains("OK").click(); 79 | cy.wxF("panel-cards").children().should("have.length", 6); 80 | }); 81 | 82 | it("menu works for multiselection", () => { 83 | cy.visit(`/index.html#/local-data`); 84 | cy.viewport(1300, 900); 85 | 86 | cy.wxF("panel-cards") 87 | .wxF("card-item", "/Code") 88 | .click({ ctrlKey: true }); 89 | cy.wxF("panel-cards") 90 | .wxF("card-item", "/Music") 91 | .click({ ctrlKey: true }); 92 | cy.wxF("panel-cards") 93 | .wxF("card-item", "/Pictures") 94 | .click({ ctrlKey: true }); 95 | cy.wxF("panel-cards").wxF("card-item", "/Pictures").rightclick(); 96 | cy.wxF("menu").should("be.visible"); 97 | cy.wxF("menu").children().should("have.length", 4); 98 | cy.get(".wx-item.wx-selected").should("have.length", 3); 99 | cy.wxF("panel-cards").click("bottom"); 100 | 101 | cy.wxF("mode-table").click(); 102 | cy.wxF("panel-table") 103 | .wxF("grid-item", "/Code") 104 | .click({ ctrlKey: true }); 105 | cy.wxF("panel-table") 106 | .wxF("grid-item", "/Music") 107 | .click({ ctrlKey: true }); 108 | cy.wxF("panel-table") 109 | .wxF("grid-item", "/Pictures") 110 | .click({ ctrlKey: true }); 111 | cy.wait(300); // somehow that last element gets detached 112 | cy.wxF("panel-table").wxF("grid-item", "/Pictures").rightclick(); 113 | cy.wxF("menu").should("be.visible"); 114 | cy.wxF("menu").children().should("have.length", 4); 115 | cy.get(".wx-row.wx-selected").should("have.length", 3); 116 | 117 | cy.wxF("mode-panels").click(); 118 | cy.wxF("fm-left-panel").wxF("grid-item", "/Code").click(); 119 | cy.wxF("fm-right-panel").wxF("grid-item", "/Music").click(); 120 | cy.wxF("fm-left-panel") 121 | .wxF("grid-item", "/Music") 122 | .click({ ctrlKey: true }); 123 | cy.wxF("fm-right-panel") 124 | .wxF("grid-item", "/Pictures") 125 | .click({ ctrlKey: true }); 126 | cy.wxF("fm-right-panel").wxF("grid-item", "/Pictures").rightclick(); 127 | cy.wxF("menu").should("be.visible"); 128 | cy.wxF("menu").children().should("have.length", 4); 129 | 130 | cy.wxF("mode-panels").click(); 131 | cy.wxF("fm-left-panel").wxF("grid-item", "/Music").rightclick(); 132 | cy.wxF("menu").should("be.visible"); 133 | cy.wxF("menu").children().should("have.length", 4); 134 | cy.get(".wx-row.wx-selected").should("have.length", 4); 135 | }); 136 | 137 | it("menu works for new files and folders", () => { 138 | cy.visit(`/index.html#/local-data`); 139 | cy.viewport(1300, 900); 140 | 141 | cy.wxF("add-button").click(); 142 | cy.wxF("menu-option", "add-file").click(); 143 | cy.get(".wx-modal").should("be.visible"); 144 | cy.get(".wx-modal").find("button").contains("OK").click(); 145 | cy.wxF("panel-cards").wxF("card-item", "/New file.txt").rightclick(); 146 | cy.wxF("menu").should("be.visible"); 147 | cy.wxF("menu").children().should("have.length", 7); 148 | 149 | cy.wxF("add-button").click(); 150 | cy.wxF("menu-option", "add-folder").click(); 151 | cy.get(".wx-modal").should("be.visible"); 152 | cy.get(".wx-modal").find("button").contains("OK").click(); 153 | cy.wxF("panel-cards").wxF("card-item", "/New folder").rightclick(); 154 | cy.wxF("menu").should("be.visible"); 155 | cy.wxF("menu").children().should("have.length", 6); 156 | }); 157 | 158 | it("default menus in readonly fm with local data", () => { 159 | cy.visit(`/index.html#/readonly`); 160 | cy.viewport(1300, 900); 161 | 162 | cy.wxF("card-dots", "/Music").click(); 163 | cy.wxF("menu").should("not.exist"); 164 | cy.shot(`card-action-menu-readonly`); 165 | 166 | cy.wxF("panel-cards").wxF("card-item", "/Code").rightclick(); 167 | cy.wxF("menu").should("not.exist"); 168 | cy.shot(`card-context-menu-readonly`); 169 | 170 | cy.wxF("panel-cards").rightclick("bottom"); 171 | cy.shot("card-body-menu-readonly"); 172 | 173 | cy.wxF("mode-table").click(); 174 | cy.wxF("panel-table").wxF("grid-item", "/Code").rightclick(); 175 | cy.wxF("menu").should("not.exist"); 176 | cy.shot(`table-context-menu-readonly`); 177 | 178 | cy.wxF("mode-panels").click(); 179 | cy.wxF("fm-left-panel").wxF("grid-item", "/Pictures").rightclick(); 180 | cy.wxF("menu").should("not.exist"); 181 | 182 | cy.wxF("mode-cards").click(); 183 | cy.wxF("panel-cards").wxF("card-item", "/Code").dblclick(); 184 | 185 | cy.wxF("card-dots", "/Code/Area.svelte").click(); 186 | cy.shot(`card-action-menu-file-readonly`); 187 | 188 | cy.wxF("panel-cards") 189 | .wxF("card-item", "/Code/Area.svelte") 190 | .rightclick(); 191 | cy.wxF("menu").should("be.visible"); 192 | cy.wxF("menu").children().should("have.length", 1); 193 | cy.shot(`card-context-menu-readonly-file`); 194 | 195 | cy.wxF("panel-cards").rightclick(215, 215); 196 | cy.shot("card-body-menu-readonly-2"); 197 | 198 | cy.wxF("mode-table").click(); 199 | cy.wxF("panel-table") 200 | .wxF("grid-item", "/Code/Combo.svelte") 201 | .rightclick(); 202 | cy.wxF("menu").should("be.visible"); 203 | cy.wxF("menu").children().should("have.length", 1); 204 | cy.shot(`table-context-menu-readonly-2`); 205 | 206 | cy.wxF("mode-panels").click(); 207 | cy.wxF("fm-left-panel") 208 | .wxF("grid-item", "/Code/Confirm.svelte") 209 | .rightclick(); 210 | cy.wxF("menu").should("be.visible"); 211 | cy.wxF("menu").children().should("have.length", 1); 212 | 213 | cy.wxF("mode-table").click(); 214 | cy.wxF("tree-item", "/Code").rightclick(); 215 | cy.wxF("menu").should("not.exist"); 216 | cy.shot("tree-readonly-menu"); 217 | 218 | cy.wxF("tree-item", "/").rightclick(); 219 | cy.wxF("menu").should("not.exist"); 220 | cy.shot("tree-root-readonly-menu"); 221 | 222 | cy.wxF("add-button").should("not.exist"); 223 | }); 224 | 225 | it("custom menus in default fm with local data", () => { 226 | cy.visit(`/index.html#/custom-menu`); 227 | cy.viewport(1300, 900); 228 | 229 | cy.wxF("panel-cards").wxF("card-item", "/Code").rightclick(); 230 | cy.wxF("menu").should("not.exist"); 231 | cy.shot(`card-no-custom-context-menu`); 232 | 233 | cy.wxF("card-dots", "/Music").click(); 234 | cy.wxF("menu").should("be.visible"); 235 | cy.wxF("menu").children().should("have.length", 8); 236 | cy.shot(`card-custom-action-menu`); 237 | cy.wxF("menu-option", "clone").click(); 238 | cy.wxF("panel-cards").children().should("have.length", 5); 239 | 240 | cy.wxF("panel-cards").wxF("card-item", "/Pictures").rightclick(); 241 | cy.wxF("menu").should("be.visible"); 242 | cy.wxF("menu").children().should("have.length", 1); 243 | cy.shot(`card-custom-context-menu`); 244 | }); 245 | 246 | it("custom menus in readonly fm with local data", () => { 247 | cy.visit(`/index.html#/custom-menu-readonly`); 248 | cy.viewport(1300, 900); 249 | 250 | cy.wxF("panel-cards").wxF("card-item", "/Code").rightclick(); 251 | cy.wxF("menu").should("not.exist"); 252 | cy.shot(`card-no-custom-context-menu-readonly`); 253 | 254 | cy.wxF("card-dots", "/Music").click(); 255 | cy.wxF("menu").should("not.exist"); 256 | cy.shot(`card-custom-action-menu-readonly`); 257 | 258 | cy.wxF("panel-cards").wxF("card-item", "/Pictures").rightclick(); 259 | cy.wxF("menu").should("not.exist"); 260 | cy.shot(`card-custom-context-menu-readonly`); 261 | 262 | cy.wxF("panel-cards").wxF("card-item", "/Code").dblclick(); 263 | cy.wxF("panel-cards") 264 | .wxF("card-item", "/Code/Button.svelte") 265 | .rightclick(0, 50); // [temp] workaround for svelte-menu fitToBody bug 266 | cy.wxF("menu").should("be.visible"); 267 | cy.wxF("menu").children().should("have.length", 1); 268 | cy.shot(`card-custom-context-menu-readonly-file`); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/basic/navigation.cy.js: -------------------------------------------------------------------------------- 1 | context("Navigation with local data", () => { 2 | beforeEach(() => { 3 | cy.visit("/index.html#/local-data"); 4 | cy.viewport(1300, 900); 5 | }); 6 | 7 | it("by folders, back to parent and crumbs in table", () => { 8 | cy.wxF("mode-table").click(); 9 | cy.wait(100); 10 | cy.wxF("panel-table").should("be.visible"); 11 | cy.wxF("panel-table").get(".wx-row").should("have.length", 4); 12 | cy.wxF("panel-table").wxF("grid-item", "/Code").dblclick(); 13 | cy.wxF("panel-table").wxF("grid-item", "/Code/Datepicker").dblclick(); 14 | cy.wxF("panel-table").get(".wx-row.wx-selected").should("not.exist"); 15 | cy.wxF("panel-crumbs").children().last().contains("Datepicker"); 16 | cy.shot("open-folders-in-table-mode"); 17 | 18 | cy.wxF("panel-crumbs").wxF("crumb", "/Code").click(); 19 | cy.wxF("panel-table") 20 | .wxF("grid-item", "/Code/Datepicker") 21 | .should("have.class", "wx-selected"); 22 | cy.wxF("panel-crumbs").children().last().contains("Code"); 23 | cy.shot("back-by-crumbs-in-table-mode"); 24 | 25 | cy.wxF("panel-table") 26 | .wxF("grid-item", "/wx-filemanager-parent-link") 27 | .dblclick(); 28 | cy.wxF("panel-table") 29 | .wxF("grid-item", "/Code") 30 | .should("have.class", "wx-selected"); 31 | cy.wxF("panel-crumbs").children().last().contains("My files"); 32 | cy.shot("back-by-parent-link-in-table-mode"); 33 | }); 34 | 35 | it("by folders, back to parent and crumbs in cards", () => { 36 | cy.wxF("panel-cards").should("be.visible"); 37 | cy.wxF("panel-cards").wxF("card-item", "/Code").dblclick(); 38 | cy.wxF("panel-cards").wxF("card-item", "/Code/Datepicker").dblclick(); 39 | cy.wxF("panel-cards").get(".wx-item.wx-selected").should("not.exist"); 40 | cy.wxF("panel-crumbs").children().last().contains("Datepicker"); 41 | cy.shot("open-folders-in-cards-mode"); 42 | 43 | cy.wxF("panel-crumbs").wxF("crumb", "/Code").click(); 44 | cy.wxF("panel-cards") 45 | .wxF("card-item", "/Code/Datepicker") 46 | .should("have.class", "wx-selected"); 47 | cy.wxF("panel-crumbs").children().last().contains("Code"); 48 | cy.shot("back-by-crumbs-in-cards-mode"); 49 | 50 | cy.wxF("panel-cards").find(".wx-back").dblclick(); 51 | cy.wxF("panel-cards") 52 | .wxF("card-item", "/Code") 53 | .should("have.class", "wx-selected"); 54 | cy.wxF("panel-crumbs").children().last().contains("My files"); 55 | cy.shot("back-by-parent-link-in-cards-mode"); 56 | }); 57 | 58 | it("by folders, back to parent and crumbs in panels", () => { 59 | cy.wxF("mode-panels").click(); 60 | cy.wxF("fm-left-panel").should("be.visible"); 61 | cy.wxF("fm-right-panel").should("be.visible"); 62 | 63 | cy.wxF("fm-left-panel").wxF("grid-item", "/Code").dblclick(); 64 | cy.wxF("fm-left-panel").wxF("grid-item", "/Code/Datepicker").dblclick(); 65 | cy.wxF("fm-left-panel").get(".wx-row.wx-selected").should("not.exist"); 66 | cy.wxF("fm-left-panel") 67 | .find(".wx-breadcrumbs") 68 | .children() 69 | .last() 70 | .contains("Datepicker"); 71 | cy.shot("open-folders-in-left-panel"); 72 | 73 | cy.wxF("fm-left-panel") 74 | .find(".wx-breadcrumbs") 75 | .wxF("crumb", "/Code") 76 | .click(); 77 | cy.wxF("fm-left-panel") 78 | .wxF("grid-item", "/Code/Datepicker") 79 | .should("have.class", "wx-selected"); 80 | cy.wxF("fm-left-panel") 81 | .find(".wx-breadcrumbs") 82 | .children() 83 | .last() 84 | .contains("Code"); 85 | cy.shot("back-by-crumbs-in-left-panel"); 86 | 87 | cy.wxF("fm-left-panel") 88 | .wxF("grid-item", "/wx-filemanager-parent-link") 89 | .dblclick(); 90 | cy.wxF("fm-left-panel") 91 | .wxF("grid-item", "/Code") 92 | .should("have.class", "wx-selected"); 93 | cy.wxF("panel-crumbs").children().last().contains("My files"); 94 | cy.shot("back-by-parent-link-in-left-panel"); 95 | 96 | cy.wxF("fm-right-panel").wxF("grid-item", "/Code").dblclick(); 97 | cy.wxF("fm-right-panel") 98 | .wxF("grid-item", "/Code/Datepicker") 99 | .dblclick(); 100 | cy.wxF("fm-right-panel") 101 | .find(".wx-row.wx-selected") 102 | .should("have.length", 0); 103 | cy.wxF("fm-right-panel") 104 | .find(".wx-breadcrumbs") 105 | .children() 106 | .last() 107 | .contains("Datepicker"); 108 | cy.shot("open-folders-in-right-panel"); 109 | 110 | cy.wxF("fm-right-panel") 111 | .find(".wx-breadcrumbs") 112 | .wxF("crumb", "/Code") 113 | .click(); 114 | cy.wxF("fm-right-panel") 115 | .find(".wx-breadcrumbs") 116 | .children() 117 | .last() 118 | .contains("Code"); 119 | cy.wxF("fm-right-panel") 120 | .wxF("grid-item", "/Code/Datepicker") 121 | .should("have.class", "wx-selected"); 122 | cy.shot("back-by-crumbs-in-right-panel"); 123 | 124 | cy.wxF("fm-right-panel") 125 | .wxF("grid-item", "/wx-filemanager-parent-link") 126 | .dblclick(); 127 | cy.wxF("fm-right-panel") 128 | .wxF("grid-item", "/Code") 129 | .should("have.class", "wx-selected"); 130 | cy.wxF("panel-crumbs").children().last().contains("My files"); 131 | cy.shot("back-by-parent-link-in-right-panel"); 132 | }); 133 | 134 | it("by tree (open/close branches)", () => { 135 | cy.wxF("tree").should("be.visible"); 136 | cy.wxF("tree") 137 | .wxF("tree-item", "/") 138 | .should("have.class", "wx-selected"); 139 | 140 | cy.wxF("tree").wxF("tree-toggle", "/Code").click(); 141 | cy.wxF("tree") 142 | .wxF("tree-item", "/Code/Datepicker") 143 | .should("be.visible"); 144 | cy.shot("open-tree-branch"); 145 | 146 | cy.wxF("tree").wxF("tree-toggle", "/Code").click(); 147 | cy.wxF("tree").wxF("tree-item", "/Code/Datepicker").should("not.exist"); 148 | cy.wxF("tree") 149 | .wxF("tree-item", "/Code") 150 | .should("not.have.class", "wx-selected"); 151 | cy.shot("close-tree-branch"); 152 | 153 | cy.wxF("tree").wxF("tree-item", "/Code").click(); 154 | cy.wxF("panel-crumbs").children().last().contains("Code"); 155 | cy.wxF("tree") 156 | .wxF("tree-item", "/Code") 157 | .should("have.class", "wx-selected"); 158 | cy.wxF("panel-cards").get(".wx-item.wx-selected").should("not.exist"); 159 | 160 | cy.wxF("tree").wxF("tree-toggle", "/Code").click(); 161 | cy.wxF("tree").wxF("tree-item", "/Code/Datepicker").click(); 162 | cy.wxF("panel-cards").get(".wx-item.wx-selected").should("not.exist"); 163 | cy.wxF("tree") 164 | .wxF("tree-item", "/Code/Datepicker") 165 | .should("have.class", "wx-selected"); 166 | cy.wxF("panel-crumbs").children().last().contains("Datepicker"); 167 | 168 | cy.wxF("tree").wxF("tree-item", "/").click(); 169 | cy.wxF("panel-crumbs").children().last().contains("My file"); 170 | cy.wxF("panel-cards") 171 | .wxF("card-item", "/Code") 172 | .should("have.class", "wx-selected"); 173 | cy.wxF("tree") 174 | .wxF("tree-item", "/") 175 | .should("have.class", "wx-selected"); 176 | cy.shot("open-folders-by-tree"); 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/basic/search.cy.js: -------------------------------------------------------------------------------- 1 | context("Search", () => { 2 | it("Search works with local data", () => { 3 | cy.visit("/index.html#/local-data"); 4 | cy.viewport(1300, 900); 5 | 6 | cy.wxF("toolbar").find(".wxi-search").should("be.visible"); 7 | 8 | cy.wxF("search-input").type("too"); 9 | cy.wxF("toolbar").find(".wxi-close").should("be.visible"); 10 | cy.wxF("sidebar").should("not.exist"); 11 | cy.wxF("panel-search") 12 | .find(".wx-text") 13 | .should("contain", "Search results in My files"); 14 | cy.wxF("mode-panels").parent().should("not.have.class", "wx-selected"); 15 | cy.wxF("mode-cards").parent().should("not.have.class", "wx-selected"); 16 | cy.wxF("mode-table").parent().should("not.have.class", "wx-selected"); 17 | cy.shot("search-mode-from-root"); 18 | 19 | cy.wxF("search-input").clear(); 20 | cy.wxF("toolbar").find(".wxi-search").should("be.visible"); 21 | cy.wxF("sidebar").should("be.visible"); 22 | cy.wxF("panel-crumbs").find(".wx-item").should("contain", "My files"); 23 | cy.wxF("mode-cards").parent().should("have.class", "wx-selected"); 24 | cy.shot("clearing-input-changes-mode-to-cards"); 25 | 26 | cy.wxF("panel-cards").wxF("card-item", "/Code").dblclick(); 27 | cy.wxF("search-input").type("TOO"); 28 | cy.wxF("toolbar").find(".wxi-close").should("be.visible"); 29 | cy.wxF("sidebar").should("not.exist"); 30 | cy.wxF("panel-search") 31 | .find(".wx-text") 32 | .should("contain", "Search results in My files/ Code"); 33 | cy.wxF("mode-panels").parent().should("not.have.class", "wx-selected"); 34 | cy.wxF("mode-cards").parent().should("not.have.class", "wx-selected"); 35 | cy.wxF("mode-table").parent().should("not.have.class", "wx-selected"); 36 | cy.shot("search-mode-from-folder"); 37 | 38 | cy.wxF("panel-search").find(".wxi-angle-left").click(); 39 | cy.wxF("toolbar").find(".wxi-search").should("be.visible"); 40 | cy.wxF("sidebar").should("be.visible"); 41 | cy.wxF("panel-crumbs").children().last().contains("Code"); 42 | cy.wxF("mode-cards").parent().should("have.class", "wx-selected"); 43 | cy.shot("click-left-arrow-icon-changes-mode-to-cards"); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/basic/select.cy.js: -------------------------------------------------------------------------------- 1 | context("Selection", () => { 2 | it("multiple selections with local data", () => { 3 | cy.visit(`/index.html#/local-data`); 4 | cy.viewport(1300, 900); 5 | 6 | cy.wxF("panel-cards").wxF("card-item", "/Code").click(); 7 | cy.wxF("panel-cards") 8 | .wxF("card-item", "/Music") 9 | .click({ ctrlKey: true }); 10 | cy.get(".wx-item.wx-selected").should("have.length", 2); 11 | cy.shot("cars-select-toggle"); 12 | cy.wxF("panel-cards") 13 | .wxF("card-item", "/Info.txt") 14 | .click({ shiftKey: true }); 15 | cy.get(".wx-item.wx-selected").should("have.length", 4); 16 | cy.shot("cards-select-range"); 17 | cy.wxF("panel-cards").click("bottom"); 18 | 19 | cy.wxF("mode-table").click(); 20 | cy.wxF("panel-table").wxF("grid-item", "/Code").click(); 21 | cy.wxF("panel-table") 22 | .wxF("grid-item", "/Music") 23 | .click({ ctrlKey: true }); 24 | cy.get(".wx-row.wx-selected").should("have.length", 2); 25 | cy.shot("grid-select-toggle"); 26 | cy.wxF("panel-table") 27 | .wxF("grid-item", "/Info.txt") 28 | .click({ shiftKey: true }); 29 | cy.get(".wx-row.wx-selected").should("have.length", 4); 30 | cy.shot("grid-select-range"); 31 | 32 | cy.wxF("mode-panels").click(); 33 | cy.wxF("fm-left-panel").wxF("grid-item", "/Code").click(); 34 | cy.wxF("fm-right-panel").wxF("grid-item", "/Code").click(); 35 | cy.wxF("fm-left-panel") 36 | .wxF("grid-item", "/Pictures") 37 | .click({ ctrlKey: true }); 38 | cy.wxF("fm-right-panel") 39 | .wxF("grid-item", "/Info.txt") 40 | .click({ shiftKey: true }); 41 | cy.get(".wx-row.wx-selected").should("have.length", 6); 42 | cy.shot("panel-select-toggle-range"); 43 | }); 44 | 45 | it("back to parent selection with local data", () => { 46 | cy.visit(`/index.html#/local-data`); 47 | cy.viewport(1300, 900); 48 | 49 | cy.wxF("fm").wxF("card-item", "/Code").dblclick(); 50 | cy.wxF("panel-cards").find(".wx-back").should("have.length", 1); 51 | cy.wxF("panel-cards").find(".wx-back").click(); 52 | cy.wxF("panel-cards") 53 | .find(".wx-back") 54 | .should("have.class", "wx-selected"); 55 | cy.shot("cards-select-parent-link"); 56 | 57 | cy.wxF("mode-table").click(); 58 | cy.wxF("panel-table").wxF("grid-item", "/Code/Datepicker").dblclick(); 59 | cy.wxF("panel-table") 60 | .wxF("grid-item", "/wx-filemanager-parent-link") 61 | .click(); 62 | cy.get(".wx-row.wx-selected").should("have.length", 1); 63 | cy.wxF("panel-table") 64 | .wxF("grid-item", "/wx-filemanager-parent-link") 65 | .should("have.class", "wx-selected"); 66 | cy.shot("table-select-parent-link"); 67 | 68 | cy.wxF("mode-panels").click(); 69 | cy.wxF("fm-left-panel") 70 | .wxF("grid-item", "/wx-filemanager-parent-link") 71 | .click(); 72 | cy.wxF("fm-left-panel") 73 | .wxF("grid-item", "/wx-filemanager-parent-link") 74 | .should("have.class", "wx-selected"); 75 | cy.wxF("fm-right-panel").wxF("grid-item", "/Code").dblclick(); 76 | cy.wxF("fm-right-panel") 77 | .wxF("grid-item", "/wx-filemanager-parent-link") 78 | .click(); 79 | cy.wxF("fm-right-panel") 80 | .wxF("grid-item", "/wx-filemanager-parent-link") 81 | .should("have.class", "wx-selected"); 82 | cy.get(".wx-row.wx-selected").should("have.length", 2); 83 | cy.shot("panels-select-parent-link"); 84 | }); 85 | 86 | it("back to parent selection shouldn't work with selected items and ctrl/shift with local data", () => { 87 | cy.visit(`/index.html#/local-data`); 88 | cy.viewport(1300, 900); 89 | 90 | cy.wxF("panel-cards").wxF("card-item", "/Code").dblclick(); 91 | cy.wxF("panel-cards").wxF("card-item", "/Code/Datepicker").click(); 92 | cy.wxF("panel-cards").find(".wx-back").click({ ctrlKey: true }); 93 | cy.wxF("panel-cards") 94 | .get(".wx-item.wx-selected") 95 | .should("have.length", 1); 96 | cy.wxF("panel-cards") 97 | .find(".wx-back") 98 | .should("not.have.class", "wx-selected"); 99 | cy.wxF("panel-cards").find(".wx-back").click({ shiftKey: true }); 100 | cy.wxF("panel-cards") 101 | .get(".wx-item.wx-selected") 102 | .should("have.length", 1); 103 | cy.wxF("panel-cards") 104 | .find(".wx-back") 105 | .should("not.have.class", "wx-selected"); 106 | cy.shot("cards-nonselect-parent-link"); 107 | 108 | cy.wxF("mode-table").click(); 109 | cy.wxF("panel-table") 110 | .wxF("grid-item", "/wx-filemanager-parent-link") 111 | .click({ ctrlKey: true }); 112 | cy.get(".wx-row.wx-selected").should("have.length", 1); 113 | cy.wxF("panel-table") 114 | .wxF("grid-item", "/Code/Datepicker") 115 | .should("have.class", "wx-selected"); 116 | cy.wxF("panel-table") 117 | .wxF("grid-item", "/wx-filemanager-parent-link") 118 | .click({ shiftKey: true }); 119 | cy.get(".wx-row.wx-selected").should("have.length", 1); 120 | cy.wxF("panel-table") 121 | .wxF("grid-item", "/Code/Datepicker") 122 | .should("have.class", "wx-selected"); 123 | cy.shot("table-nonselect-parent-link"); 124 | 125 | cy.wxF("mode-panels").click(); 126 | cy.wxF("fm-left-panel") 127 | .wxF("grid-item", "/wx-filemanager-parent-link") 128 | .click({ ctrlKey: true }); 129 | cy.wxF("fm-left-panel") 130 | .wxF("grid-item", "/wx-filemanager-parent-link") 131 | .click({ shiftKey: true }); 132 | cy.get(".wx-row.wx-selected").should("have.length", 1); 133 | cy.wxF("fm-left-panel") 134 | .wxF("grid-item", "/Code/Datepicker") 135 | .should("have.class", "wx-selected"); 136 | cy.wxF("fm-right-panel").wxF("grid-item", "/Code").dblclick(); 137 | cy.wxF("fm-right-panel").wxF("grid-item", "/Code/Datepicker").click(); 138 | cy.wxF("fm-right-panel") 139 | .wxF("grid-item", "/wx-filemanager-parent-link") 140 | .click({ ctrlKey: true }); 141 | cy.wxF("fm-right-panel") 142 | .wxF("grid-item", "/wx-filemanager-parent-link") 143 | .click({ shiftKey: true }); 144 | cy.get(".wx-row.wx-selected").should("have.length", 2); 145 | cy.wxF("fm-right-panel") 146 | .wxF("grid-item", "/Code/Datepicker") 147 | .should("have.class", "wx-selected"); 148 | cy.shot("panels-nonselect-parent-link"); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/basic/toolbar-buttons.cy.js: -------------------------------------------------------------------------------- 1 | context("Mode change", () => { 2 | it("Mode changes and preview works with local data", () => { 3 | cy.visit("/index.html#/local-data"); 4 | cy.viewport(1300, 900); 5 | 6 | cy.wxF("preview-button").click(); 7 | cy.wxF("panel-preview").should("be.visible"); 8 | cy.shot("open-preview"); 9 | 10 | cy.wxF("preview-button").click(); 11 | cy.wxF("panel-preview").should("not.exist"); 12 | cy.shot("close-preview"); 13 | 14 | cy.wxF("mode-table").click(); 15 | cy.wxF("panel-table").should("be.visible"); 16 | cy.shot("open-panel-table"); 17 | 18 | cy.wxF("mode-cards").click(); 19 | cy.wxF("panel-cards").should("be.visible"); 20 | cy.shot("open-panel-cards"); 21 | 22 | cy.wxF("mode-panels").click(); 23 | cy.wxF("fm-left-panel").should("be.visible"); 24 | cy.wxF("fm-right-panel").should("be.visible"); 25 | cy.shot("open-panels"); 26 | 27 | cy.wxF("mode-panels").click(); 28 | cy.wxF("fm-left-panel").should("be.visible"); 29 | cy.wxF("fm-right-panel").should("be.visible"); 30 | cy.shot("second-click-on-selected-mode"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/demos.cy.js: -------------------------------------------------------------------------------- 1 | const cases = [ 2 | "/base/:skin", 3 | "/selection/:skin", 4 | "/context/:skin", 5 | "/readonly/:skin", 6 | "/custom-styles/:skin", 7 | "/simple-icons/:skin", 8 | "/locales/:skin", 9 | "/api/:skin", 10 | "/extra-info/:skin", 11 | "/serverdata/:skin", 12 | "/data-provider/:skin", 13 | "/serverfilter/:skin", 14 | ]; 15 | 16 | const skins = ["material", "willow", "willow-dark"]; 17 | const links = []; 18 | 19 | cases.forEach(w => { 20 | skins.forEach(s => { 21 | links.push(w.replace(":skin", s)); 22 | }); 23 | }); 24 | 25 | context("Basic functionality", () => { 26 | it("widget", () => { 27 | links.forEach(w => { 28 | cy.visit(`/index.html#${w}`); 29 | cy.shot(w, { area: ".content" }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /svelte/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /svelte/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | Cypress.Commands.add("shot", (...args) => { 12 | // eslint-disable-next-line cypress/no-unnecessary-waiting 13 | cy.wait(100); 14 | 15 | const name = args.filter(a => typeof a !== "object").join("-"); 16 | const conf = 17 | typeof args[args.length - 1] === "object" ? args[args.length - 1] : {}; 18 | const sconf = { ...conf, overwrite: true }; 19 | 20 | if (conf.area) cy.get(conf.area).screenshot(name, sconf); 21 | else cy.screenshot(name, sconf); 22 | }); 23 | 24 | Cypress.Commands.add( 25 | "wxF", 26 | { 27 | prevSubject: "optional", 28 | }, 29 | (subject, type, id) => { 30 | subject = subject ? cy.wrap(subject) : cy; 31 | switch (type) { 32 | case "toolbar": 33 | return subject.get(".wx-toolbar"); 34 | case "search-input": 35 | return subject.get(".wx-toolbar .wx-search-input .wx-text"); 36 | case "preview-button": 37 | return subject.get(".wx-toolbar .wx-preview-icon .wxi-eye"); 38 | case "mode-table": 39 | return subject.get( 40 | ".wx-toolbar .wx-modes .wxi-view-sequential" 41 | ); 42 | case "mode-cards": 43 | return subject.get(".wx-toolbar .wx-modes .wxi-view-grid"); 44 | case "mode-panels": 45 | return subject.get(".wx-toolbar .wx-modes .wxi-view-column"); 46 | 47 | case "fm": 48 | return subject.get(".wx-content-wrapper"); 49 | case "sidebar": 50 | return subject.get(".wx-sidebar"); 51 | case "add-button": 52 | return subject.get(".wx-sidebar button"); 53 | case "tree": 54 | return subject.get(".wx-sidebar .wx-tree ul"); 55 | case "tree-item": 56 | return subject.get(`.wx-tree .wx-folder[data-id="${id}"]`); 57 | case "tree-toggle": 58 | return subject.get( 59 | `.wx-tree .wx-folder[data-id="${id}"] .wx-toggle` 60 | ); 61 | 62 | case "panel-cards": 63 | return subject.get(".wx-cards"); 64 | case "panel-table": 65 | return subject.get(".wx-list .wx-grid"); 66 | case "panel-preview": 67 | return subject.get(".wx-info .wx-wrapper"); 68 | case "panel-crumbs": 69 | return subject.get(".wx-breadcrumbs"); 70 | case "panel-search": 71 | return subject.get(".wx-search"); 72 | 73 | case "grid-item": 74 | return subject.find(`.wx-row[data-id="${id}"]`); 75 | case "card-item": 76 | return subject.find(`.wx-item[data-id="${id}"]`); 77 | 78 | case "crumb": 79 | return subject.find(`.wx-item[data-id="${id}"]`); 80 | 81 | case "fm-left-panel": 82 | return subject.get(".wx-panels .wx-item .wx-wrapper").first(); 83 | case "fm-right-panel": 84 | return subject.get(".wx-panels .wx-item .wx-wrapper").eq(1); 85 | 86 | case "card-dots": 87 | return subject.get( 88 | `.wx-cards .wx-item[data-id="${id}"] .wx-info .wx-more` 89 | ); 90 | case "table-header-cell": 91 | return subject.get(".wx-list .wx-header .wx-cell").eq(id); 92 | 93 | case "menu": 94 | return subject.get(".wx-menu"); 95 | case "menu-option": 96 | return subject.get(`.wx-menu .wx-item[data-id="${id}"]`); 97 | 98 | default: 99 | throw `not supported arguments for wxF: ${type}, ${id}`; 100 | } 101 | } 102 | ); 103 | 104 | // -- This is a parent command -- 105 | // Cypress.Commands.add('login', (email, password) => { ... }) 106 | // 107 | // 108 | // -- This is a child command -- 109 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 110 | // 111 | // 112 | // -- This is a dual command -- 113 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 114 | // 115 | // 116 | // -- This will overwrite an existing command -- 117 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 118 | -------------------------------------------------------------------------------- /svelte/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /svelte/demos/cases/API.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 | 27 | 28 |
29 | 35 | -------------------------------------------------------------------------------- /svelte/demos/cases/BackendData.svelte: -------------------------------------------------------------------------------- 1 | 100 | 101 | 110 | -------------------------------------------------------------------------------- /svelte/demos/cases/BackendFilter.svelte: -------------------------------------------------------------------------------- 1 | 103 | 104 | 112 | -------------------------------------------------------------------------------- /svelte/demos/cases/BasicInit.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /svelte/demos/cases/ContextMenu.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /svelte/demos/cases/CustomStyles.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 |
9 | 10 | 25 | -------------------------------------------------------------------------------- /svelte/demos/cases/DataProvider.svelte: -------------------------------------------------------------------------------- 1 | 91 | 92 | 101 | -------------------------------------------------------------------------------- /svelte/demos/cases/ExtraInfo.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /svelte/demos/cases/Locales.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 |
18 | 19 |
20 | {#if lang === "en"} 21 | 22 | {/if} 23 | {#if lang === "cn"} 24 | 25 | 26 | 27 | {/if} 28 |
29 | 30 | 43 | -------------------------------------------------------------------------------- /svelte/demos/cases/PathAndSelection.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /svelte/demos/cases/Readonly.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /svelte/demos/cases/SimpleIcons.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /svelte/demos/common/Index.svelte: -------------------------------------------------------------------------------- 1 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 | 82 | 83 | 120 | 121 |
(show = false)} 127 | > 128 | 129 | 130 | 131 | 132 | 133 |
134 |
135 | 136 | 323 | -------------------------------------------------------------------------------- /svelte/demos/common/Link.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {data[1]} 10 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /svelte/demos/common/Router.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /svelte/demos/common/helpers.js: -------------------------------------------------------------------------------- 1 | import { push } from "svelte-spa-router"; 2 | import { wrap } from "svelte-spa-router/wrap"; 3 | import { links as raw } from "../routes"; 4 | 5 | const routes = { 6 | "/": wrap({ 7 | component: {}, 8 | conditions: () => { 9 | push("/base/willow"); 10 | return false; 11 | }, 12 | }), 13 | }; 14 | 15 | function getRoutes(skinSettings, cb) { 16 | raw.forEach( 17 | a => 18 | (routes[a[0]] = wrap({ 19 | component: a[2], 20 | userData: a, 21 | props: { ...skinSettings }, 22 | conditions: x => { 23 | cb(x.location); 24 | return true; 25 | }, 26 | })) 27 | ); 28 | 29 | return routes; 30 | } 31 | 32 | function getLinks() { 33 | return raw; 34 | } 35 | 36 | export { push, getRoutes, getLinks }; 37 | -------------------------------------------------------------------------------- /svelte/demos/custom/placeholder.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svar-widgets/filemanager/4857067ba8b3586373b98e73a9d830f756c16291/svelte/demos/custom/placeholder.md -------------------------------------------------------------------------------- /svelte/demos/data.js: -------------------------------------------------------------------------------- 1 | export function getData() { 2 | return [ 3 | { 4 | id: "/Code", 5 | date: new Date(2023, 11, 2, 17, 25), 6 | type: "folder", 7 | }, 8 | { 9 | id: "/Music", 10 | date: new Date(2023, 11, 1, 14, 45), 11 | type: "folder", 12 | }, 13 | { 14 | id: "/Music/Animal_sounds.mp3", 15 | size: 1457296, 16 | date: new Date(2023, 11, 1, 14, 45), 17 | type: "file", 18 | }, 19 | { 20 | id: "/Music/The_sounds_of_silence.mp3", 21 | size: 6825661, 22 | date: new Date(2023, 11, 9, 14, 45), 23 | type: "file", 24 | }, 25 | { 26 | id: "/Music/Dont_worry_Be_happy.mp3", 27 | size: 3522968, 28 | date: new Date(2023, 11, 4, 14, 1), 29 | type: "file", 30 | }, 31 | { 32 | id: "/Music/Mantras.aif", 33 | size: 941256, 34 | date: new Date(2023, 11, 1, 14, 2), 35 | type: "file", 36 | }, 37 | { 38 | id: "/Music/Instrumental.zip", 39 | size: 16234679, 40 | date: new Date(2023, 11, 3, 14, 45), 41 | type: "file", 42 | }, 43 | { 44 | id: "/Music/Best_time.aiff", 45 | size: 3721336, 46 | date: new Date(2023, 11, 1, 17, 45), 47 | type: "file", 48 | }, 49 | { 50 | id: "/Music/More_then_words.2001.mp3", 51 | size: 5622890, 52 | date: new Date(2023, 11, 1, 14, 26), 53 | type: "file", 54 | }, 55 | { 56 | id: "/Music/Jazz.waw", 57 | size: 2056424, 58 | date: new Date(2023, 11, 1, 14, 20), 59 | type: "file", 60 | }, 61 | { 62 | id: "/Music/Calm_wind.mp4", 63 | size: 10226489, 64 | date: new Date(2023, 11, 1, 14, 3), 65 | type: "file", 66 | }, 67 | { 68 | id: "/Music/Best_albums.xls", 69 | size: 4096, 70 | date: new Date(2023, 11, 15, 14, 45), 71 | type: "file", 72 | }, 73 | { 74 | id: "/Pictures", 75 | date: new Date(2023, 10, 30, 6, 13), 76 | type: "folder", 77 | }, 78 | { 79 | id: "/Info.txt", 80 | size: 1000, 81 | date: new Date(2023, 10, 30, 6, 13), 82 | type: "file", 83 | }, 84 | { 85 | id: "/Code/Datepicker", 86 | date: new Date(2023, 11, 7, 15, 23), 87 | type: "folder", 88 | }, 89 | { 90 | id: "/Code/Area.js", 91 | size: 568, 92 | date: new Date(2023, 11, 6, 17, 38), 93 | type: "file", 94 | }, 95 | { 96 | id: "/Code/Button.js", 97 | size: 1177, 98 | date: new Date(2023, 11, 6, 17, 38), 99 | type: "file", 100 | }, 101 | { 102 | id: "/Code/ButtonSelect.js", 103 | size: 842, 104 | date: new Date(2023, 11, 6, 17, 38), 105 | type: "file", 106 | }, 107 | { 108 | id: "/Code/Checkbox.ts", 109 | size: 1231, 110 | date: new Date(2023, 11, 6, 17, 38), 111 | type: "file", 112 | }, 113 | { 114 | id: "/Code/ColorPicker.zip", 115 | size: 1546, 116 | date: new Date(2023, 11, 6, 17, 38), 117 | type: "file", 118 | }, 119 | { 120 | id: "/Code/Combo.zip", 121 | size: 4068, 122 | date: new Date(2023, 11, 6, 17, 38), 123 | type: "file", 124 | }, 125 | { 126 | id: "/Code/Confirm.css", 127 | size: 1120, 128 | date: new Date(2023, 11, 6, 17, 38), 129 | type: "file", 130 | }, 131 | { 132 | id: "/Code/Dates.json", 133 | size: 2981, 134 | date: new Date(2023, 11, 6, 17, 38), 135 | type: "file", 136 | }, 137 | { 138 | id: "/Code/DoubleList.js", 139 | size: 2833, 140 | date: new Date(2023, 11, 6, 17, 38), 141 | type: "file", 142 | }, 143 | { 144 | id: "/Code/Dropdown.js", 145 | size: 2075, 146 | date: new Date(2023, 11, 6, 17, 38), 147 | type: "file", 148 | }, 149 | { 150 | id: "/Code/Editor.css", 151 | size: 839, 152 | date: new Date(2023, 11, 6, 17, 38), 153 | type: "file", 154 | }, 155 | { 156 | id: "/Code/Field.sql", 157 | size: 630, 158 | date: new Date(2023, 11, 6, 17, 38), 159 | type: "file", 160 | }, 161 | { 162 | id: "/Code/Globals.css", 163 | size: 1552, 164 | date: new Date(2023, 11, 6, 17, 38), 165 | type: "file", 166 | }, 167 | { 168 | id: "/Code/IconButton.js", 169 | size: 708, 170 | date: new Date(2023, 11, 6, 17, 38), 171 | type: "file", 172 | }, 173 | { 174 | id: "/Code/InProgress.js", 175 | size: 144, 176 | date: new Date(2023, 11, 6, 17, 38), 177 | type: "file", 178 | }, 179 | { 180 | id: "/Code/List.rar", 181 | size: 627, 182 | date: new Date(2023, 11, 6, 17, 38), 183 | type: "file", 184 | }, 185 | { 186 | id: "/Code/MultiCombo.js", 187 | size: 4210, 188 | date: new Date(2023, 11, 6, 17, 38), 189 | type: "file", 190 | }, 191 | { 192 | id: "/Code/MultiSelect.js", 193 | size: 1958, 194 | date: new Date(2023, 11, 3, 19, 54), 195 | type: "file", 196 | }, 197 | { 198 | id: "/Code/MultiText.doc", 199 | size: 1375, 200 | date: new Date(2023, 11, 6, 17, 38), 201 | type: "file", 202 | }, 203 | { 204 | id: "/Code/Notice.txt", 205 | size: 1092, 206 | date: new Date(2023, 11, 3, 19, 54), 207 | type: "file", 208 | }, 209 | { 210 | id: "/Code/Notices.js", 211 | size: 249, 212 | date: new Date(2023, 11, 3, 19, 54), 213 | type: "file", 214 | }, 215 | { 216 | id: "/Code/Number.js", 217 | size: 436, 218 | date: new Date(2023, 11, 3, 19, 54), 219 | type: "file", 220 | }, 221 | { 222 | id: "/Code/Pager.js", 223 | size: 1714, 224 | date: new Date(2023, 11, 3, 19, 54), 225 | type: "file", 226 | }, 227 | { 228 | id: "/Code/Password.txt", 229 | size: 581, 230 | date: new Date(2023, 11, 3, 19, 54), 231 | type: "file", 232 | }, 233 | { 234 | id: "/Code/Popup.html", 235 | size: 994, 236 | date: new Date(2023, 11, 3, 19, 54), 237 | type: "file", 238 | }, 239 | { 240 | id: "/Code/Prompt.js", 241 | size: 678, 242 | date: new Date(2023, 11, 3, 19, 54), 243 | type: "file", 244 | }, 245 | { 246 | id: "/Code/RadioButton.js", 247 | size: 965, 248 | date: new Date(2023, 11, 3, 19, 54), 249 | type: "file", 250 | }, 251 | { 252 | id: "/Code/RadioButtonGroup.js", 253 | size: 230, 254 | date: new Date(2023, 11, 3, 19, 54), 255 | type: "file", 256 | }, 257 | { 258 | id: "/Code/RichSelect.ts", 259 | size: 2883, 260 | date: new Date(2023, 11, 3, 19, 54), 261 | type: "file", 262 | }, 263 | { 264 | id: "/Code/Select.js", 265 | size: 643, 266 | date: new Date(2023, 11, 3, 19, 54), 267 | type: "file", 268 | }, 269 | { 270 | id: "/Code/SideNav.js", 271 | size: 1477, 272 | date: new Date(2023, 11, 3, 19, 54), 273 | type: "file", 274 | }, 275 | { 276 | id: "/Code/Slider.js", 277 | size: 2539, 278 | date: new Date(2023, 11, 3, 19, 54), 279 | type: "file", 280 | }, 281 | { 282 | id: "/Code/Switch.js", 283 | size: 894, 284 | date: new Date(2023, 11, 3, 19, 54), 285 | type: "file", 286 | }, 287 | { 288 | id: "/Code/Tabs.js", 289 | size: 981, 290 | date: new Date(2023, 11, 3, 19, 54), 291 | type: "file", 292 | }, 293 | { 294 | id: "/Code/Text.txt", 295 | size: 1295, 296 | date: new Date(2023, 11, 3, 19, 54), 297 | type: "file", 298 | }, 299 | { 300 | id: "/Code/Timepicker.css", 301 | size: 1872, 302 | date: new Date(2023, 11, 3, 19, 54), 303 | type: "file", 304 | }, 305 | { 306 | id: "/Code/Toggle.php", 307 | size: 444, 308 | date: new Date(2023, 11, 3, 19, 54), 309 | type: "file", 310 | }, 311 | { 312 | id: "/Code/Toolbar.php", 313 | size: 89, 314 | date: new Date(2023, 11, 3, 19, 54), 315 | type: "file", 316 | }, 317 | { 318 | id: "/Code/Tooltip.html", 319 | size: 1867, 320 | date: new Date(2023, 11, 3, 19, 54), 321 | type: "file", 322 | }, 323 | { 324 | id: "/Code/TwoState.css", 325 | size: 430, 326 | date: new Date(2023, 11, 3, 19, 54), 327 | type: "file", 328 | }, 329 | { 330 | id: "/Code/Datepicker/Calendar.js", 331 | size: 2634, 332 | date: new Date(2023, 11, 7, 15, 23), 333 | type: "file", 334 | }, 335 | { 336 | id: "/Code/Datepicker/DatePicker.js", 337 | size: 862, 338 | date: new Date(2023, 11, 7, 15, 23), 339 | type: "file", 340 | }, 341 | { 342 | id: "/Code/Datepicker/Duodecade.zip", 343 | size: 1627, 344 | date: new Date(2023, 11, 7, 15, 23), 345 | type: "file", 346 | }, 347 | { 348 | id: "/Code/Datepicker/Header.css", 349 | size: 1649, 350 | date: new Date(2023, 11, 7, 15, 23), 351 | type: "file", 352 | }, 353 | { 354 | id: "/Code/Datepicker/Month.js", 355 | size: 3974, 356 | date: new Date(2023, 11, 7, 15, 23), 357 | type: "file", 358 | }, 359 | { 360 | id: "/Code/Datepicker/Year.js", 361 | size: 1595, 362 | date: new Date(2023, 11, 7, 15, 23), 363 | type: "file", 364 | }, 365 | { 366 | id: "/Pictures/Green_hills.png", 367 | size: 510885, 368 | date: new Date(2023, 11, 1, 14, 45), 369 | type: "file", 370 | }, 371 | { 372 | id: "/Pictures/Cold_sea.jpeg", 373 | size: 57199, 374 | date: new Date(2023, 11, 2, 17, 25), 375 | type: "file", 376 | }, 377 | { 378 | id: "/Pictures/Morning_sunrise.jpeg", 379 | size: 448308, 380 | date: new Date(2023, 11, 4, 4, 1), 381 | type: "file", 382 | }, 383 | { 384 | id: "/Pictures/Data.png", 385 | size: 23903, 386 | date: new Date(2023, 11, 4, 4, 1), 387 | type: "file", 388 | }, 389 | { 390 | id: "/Pictures/Forest.jpeg", 391 | size: 907532, 392 | date: new Date(2023, 11, 4, 4, 1), 393 | type: "file", 394 | }, 395 | { 396 | id: "/Pictures/Nigth_sky.jpg", 397 | size: 84506, 398 | date: new Date(2023, 11, 4, 4, 1), 399 | type: "file", 400 | }, 401 | { 402 | id: "/Pictures/Pattern.pdf", 403 | size: 124506, 404 | date: new Date(2023, 11, 4, 4, 5), 405 | type: "file", 406 | }, 407 | ]; 408 | } 409 | 410 | export function getDrive() { 411 | return { 412 | used: 15200000000, 413 | total: 50000000000, 414 | }; 415 | } 416 | -------------------------------------------------------------------------------- /svelte/demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /svelte/demos/index.js: -------------------------------------------------------------------------------- 1 | import { mount } from "svelte"; 2 | import Demos from "./common/Index.svelte"; 3 | 4 | mount(Demos, { 5 | target: document.querySelector("#wx_demo_area") || document.body, 6 | }); 7 | -------------------------------------------------------------------------------- /svelte/demos/routes.js: -------------------------------------------------------------------------------- 1 | import BasicInit from "./cases/BasicInit.svelte"; 2 | import PathAndSelection from "./cases/PathAndSelection.svelte"; 3 | import ContextMenu from "./cases/ContextMenu.svelte"; 4 | import Readonly from "./cases/Readonly.svelte"; 5 | import CustomStyles from "./cases/CustomStyles.svelte"; 6 | import SimpleIcons from "./cases/SimpleIcons.svelte"; 7 | import Locales from "./cases/Locales.svelte"; 8 | import API from "./cases/API.svelte"; 9 | import ExtraInfo from "./cases/ExtraInfo.svelte"; 10 | import BackendData from "./cases/BackendData.svelte"; 11 | import DataProvider from "./cases/DataProvider.svelte"; 12 | import BackendFilter from "./cases/BackendFilter.svelte"; 13 | 14 | export const links = [ 15 | ["/base/:skin", "Basic File Manager", BasicInit], 16 | ["/selection/:skin", "Initial path/selection", PathAndSelection], 17 | ["/context/:skin", "Custom context menu", ContextMenu], 18 | ["/readonly/:skin", "Readonly mode", Readonly], 19 | ["/custom-styles/:skin", "Styling", CustomStyles], 20 | ["/simple-icons/:skin", "Simple icons", SimpleIcons], 21 | ["/locales/:skin", "Locales", Locales], 22 | ["/api/:skin", "API calls", API], 23 | ["/extra-info/:skin", "Extra info", ExtraInfo], 24 | ["/serverdata/:skin", "Backend data", BackendData], 25 | ["/data-provider/:skin", "Saving to backend", DataProvider], 26 | ["/serverfilter/:skin", "Filtering on backend", BackendFilter], 27 | ]; 28 | -------------------------------------------------------------------------------- /svelte/demos/skins.js: -------------------------------------------------------------------------------- 1 | export const skins = [ 2 | { 3 | id: "willow", 4 | name: "Willow", 5 | props: {}, 6 | }, 7 | { 8 | id: "willow-dark", 9 | name: "Dark", 10 | props: {}, 11 | }, 12 | { 13 | id: "material", 14 | name: "Material", 15 | props: {}, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /svelte/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /svelte/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 XB Software Sp. z o.o. 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. -------------------------------------------------------------------------------- /svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-svelte-filemanager", 3 | "version": "2.1.0", 4 | "description": "Easy-to-use Svelte component for adding a file manager (file explorer) UI", 5 | "productTag": "filemanager", 6 | "productTrial": false, 7 | "type": "module", 8 | "scripts": { 9 | "build": "vite build", 10 | "build:dist": "vite build --mode dist", 11 | "build:tests": "vite build --mode test", 12 | "lint": "yarn eslint ./demos ./src", 13 | "start": "vite --open", 14 | "start:tests": "vite --open=/tests/ --host 0.0.0.0 --port 5100 --mode test", 15 | "test": "true", 16 | "test:cypress": "cypress run -P ./ --config \"baseUrl=http://localhost:5100/tests\"" 17 | }, 18 | "svelte": "src/index.js", 19 | "exports": { 20 | ".": { 21 | "svelte": "./src/index.js" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "license": "MIT", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/svar-widgets/filemanager.git" 29 | }, 30 | "bugs": { 31 | "url": "https://forum.svar.dev" 32 | }, 33 | "homepage": "https://svar.dev/svelte/filemanager/", 34 | "dependencies": { 35 | "wx-filemanager-data-provider": "2.1.0", 36 | "wx-filemanager-locales": "2.1.0", 37 | "wx-filemanager-store": "2.1.0", 38 | "wx-lib-dom": "0.8.0", 39 | "wx-lib-svelte": "0.5.1", 40 | "wx-lib-state": "1.9.0", 41 | "wx-svelte-core": "2.1.0", 42 | "wx-svelte-menu": "2.1.0", 43 | "wx-svelte-grid": "2.1.0", 44 | "wx-svelte-uploader": "2.1.0" 45 | }, 46 | "files": [ 47 | "src", 48 | "readme.md", 49 | "whatsnew.md", 50 | "license.txt" 51 | ], 52 | "keywords": [ 53 | "svelte", 54 | "file manager", 55 | "file-manager", 56 | "file-explorer", 57 | "file explorer", 58 | "file browser", 59 | "svelte component", 60 | "ui component", 61 | "svelte file manager", 62 | "svar widgets" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /svelte/postcss.config.js: -------------------------------------------------------------------------------- 1 | import autoprefixer from "autoprefixer"; 2 | 3 | const plugins = [autoprefixer]; 4 | export { plugins }; 5 | -------------------------------------------------------------------------------- /svelte/readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # SVAR Svelte File Manager | File Explorer 4 | 5 |
6 | 7 |
8 | 9 | [Website](https://svar.dev/svelte/filemanager/) • [Getting Started](https://docs.svar.dev/svelte/filemanager/getting_started/) • [Demos](https://docs.svar.dev/svelte/filemanager/samples/#/base/willow) 10 | 11 |
12 | 13 |
14 | 15 | [![npm](https://img.shields.io/npm/v/wx-svelte-filemanager.svg)](https://www.npmjs.com/package/wx-svelte-filemanager) 16 | [![License](https://img.shields.io/github/license/svar-widgets/filemanager)](https://github.com/svar-widgets/filemanager/blob/main/license.txt) 17 | [![npm downloads](https://img.shields.io/npm/dm/wx-svelte-filemanager.svg)](https://www.npmjs.com/package/wx-svelte-filemanager) 18 | [![Last Commit](https://img.shields.io/github/last-commit/svar-widgets/filemanager)](https://github.com/svar-widgets/filemanager) 19 | 20 | 21 |
22 | 23 | [SVAR File Manager](https://svar.dev/svelte/filemanager/$0) is a flexible file explorer component for Svelte apps. It offers a familiar interface for browsing, organizing, and previewing files. Integrate it with any backend, whether you're using local storage, databases, or cloud services. 24 | 25 |
26 | SVAR File Manager for Svelte - UI 27 |
28 | 29 | 30 | # :sparkles: Key features: 31 | - Basic file operations: create, delete, copy, rename, cut, paste 32 | - Download and upload files 33 | - Files tree view 34 | - List and tiles views 35 | - File preview pane with file information (file size, type, modified date, etc) 36 | - Split view to manage files between different locations 37 | - Built-in search box 38 | - Context menu and toolbar 39 | - Keyboard navigation 40 | - Used storage info 41 | 42 | # :hammer_and_pick: How to Use 43 | 44 | To use the widget, simply import the package and include the component in your Svelte file: 45 | 46 | ```svelte 47 | 62 | 63 | 64 | ``` 65 | For further instructions, see our [getting-started guide](https://docs.svar.dev/svelte/filemanager/getting_started/). 66 | 67 | ### :computer: How to Modify 68 | 69 | Typically, you don't need to modify the code. However, if you wish to do so, follow these steps: 70 | 71 | 1. Run `yarn` to install dependencies. Note that this project is a monorepo using `yarn` workspaces, so npm will not work 72 | 2. Start the project in development mode with `yarn start` 73 | 74 | ### :white_check_mark: Run Tests 75 | 76 | To run the test: 77 | 78 | 1. Start the test examples with: 79 | ```sh 80 | yarn start:tests 81 | ``` 82 | 2. In a separate console, run the end-to-end tests with: 83 | ```sh 84 | yarn test:cypress 85 | ``` 86 | 87 | ### :speech_balloon: Need Help? 88 | 89 | Join our [community forum](https://forum.svar.dev) to get help and submit feature requests. 90 | 91 | -------------------------------------------------------------------------------- /svelte/src/components/Breadcrumbs.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 |
48 |
49 | 50 |
51 | {#each crumbs as crumb, i} 52 | {#if i} 53 | 54 | {/if} 55 |
56 | {crumb.id == "/" ? _(crumb.name) : crumb.name} 57 |
58 | {/each} 59 |
60 | 61 | 91 | -------------------------------------------------------------------------------- /svelte/src/components/Cards/Item.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if item.id == "/wx-filemanager-parent-link"} 19 | {#if $mode !== "search"} 20 |
21 |
26 | 27 | {_("Back to parent folder")} 28 |
29 |
30 | {/if} 31 | {:else} 32 |
= 0} 35 | data-id={item.id} 36 | > 37 | {#if preview} 38 |
39 | {_("A 44 |
45 | {:else if icon} 46 |
47 | 48 |
49 | {:else} 50 |
51 | {/if} 52 | {#if item.type === "folder"} 53 |
54 |
55 | {_("Folder")} 56 | {item.name} 57 |
58 |
59 | 60 |
61 |
62 | {:else} 63 |
64 |
65 | {item.name} 66 |
67 |
68 | 69 |
70 |
71 | {/if} 72 |
73 | {/if} 74 | 75 | 192 | -------------------------------------------------------------------------------- /svelte/src/components/Cards/Panel.svelte: -------------------------------------------------------------------------------- 1 | 101 | 102 | {#if $mode == "search" && !renderedFiles.length} 103 |
104 |
{_("Looks like nothing is here")}
105 |
106 | {:else} 107 | 108 |
115 | {#each renderedFiles as child} 116 | 117 | {/each} 118 |
119 | {/if} 120 | 121 | 150 | -------------------------------------------------------------------------------- /svelte/src/components/Cards/View.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 | 44 | -------------------------------------------------------------------------------- /svelte/src/components/Drive.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | {#if used && total} 14 |
15 | 16 |

{formatSize(used)} {_("of")} {formatSize(total)} {_("used")}

17 |
18 | {/if} 19 | 20 | 51 | -------------------------------------------------------------------------------- /svelte/src/components/Filemanager.svelte: -------------------------------------------------------------------------------- 1 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /svelte/src/components/Layout.svelte: -------------------------------------------------------------------------------- 1 | 195 | 196 |
206 | {#if narrowMode && $rPreview} 207 |
208 | 209 |
210 | {:else} 211 | 212 | 219 | 225 | 226 |
227 | {#if $rMode !== "panels" && $rMode !== "search"} 228 |
235 | 236 |
237 |
241 |
247 | {#if $rMode === "table"} 248 | 249 | {:else} 250 | 251 | {/if} 252 |
253 | {#if $rPreview} 254 |
255 | 256 |
257 | {/if} 258 |
259 | {:else} 260 |
263 | {#if $rMode === "panels"} 264 | 265 | {:else} 266 | 267 | {/if} 268 |
269 | {#if $rPreview} 270 |
271 | 272 |
273 | {/if} 274 | {/if} 275 |
276 |
277 |
278 |
279 | {/if} 280 |
281 | 282 | 350 | -------------------------------------------------------------------------------- /svelte/src/components/Modals.svelte: -------------------------------------------------------------------------------- 1 | 67 | 68 | 69 | {@render children?.()} 70 | 71 | 72 | {#if prompt} 73 | 78 | 79 | 80 | 81 | {/if} 82 | 83 | {#if confirm} 84 | (confirm = null)} 88 | > 89 | {#if confirm.selected} 90 |
    91 | {#each confirm.selected as item} 92 |
  • {item}
  • 93 | {/each} 94 |
95 | {/if} 96 |
97 | {/if} 98 | 99 | 111 | -------------------------------------------------------------------------------- /svelte/src/components/Panels.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 | toggleActive(0)} 19 | oncontextmenu={() => toggleActive(0)} 20 | /> 21 |
22 |
23 | toggleActive(1)} 27 | oncontextmenu={() => toggleActive(1)} 28 | /> 29 |
30 |
31 | 32 | 48 | -------------------------------------------------------------------------------- /svelte/src/components/SearchView.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | 35 | 65 | -------------------------------------------------------------------------------- /svelte/src/components/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 |
49 | {#if !readonly} 50 |
51 | 52 | 53 | 54 |
55 | {/if} 56 |
57 | 58 |
59 | 60 |
61 | 62 | 82 | -------------------------------------------------------------------------------- /svelte/src/components/Table/NameCell.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | {#if row.id === "/wx-filemanager-parent-link"} 14 | 15 | {_("Back to parent folder")} 16 | {:else} 17 | {#if icon} 18 | 19 | {:else}{/if} 20 | {row.name} 21 | {/if} 22 |
23 | 24 | 51 | -------------------------------------------------------------------------------- /svelte/src/components/Table/View.svelte: -------------------------------------------------------------------------------- 1 | 172 | 173 | 174 | 175 |
176 | 177 |
183 | 184 | "wx-each-cell"} 190 | sizes={{ rowHeight: 42, headerHeight: 42 }} 191 | /> 192 | 193 |
194 |
195 | 196 | 253 | -------------------------------------------------------------------------------- /svelte/src/components/Toolbar.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 |
34 |
35 | {#if !narrowMode} 36 |
{_("Files")}
37 | {:else if !($mode == "panels" || $mode == "search")} 38 |
39 | onshowtree && onshowtree()} 41 | name="subtask" 42 | /> 43 |
44 | {/if} 45 | 46 |
47 | 48 |
49 | {#if !narrowMode} 50 |
51 | 52 | 53 | 54 |
55 | {/if} 56 |
57 | 58 |
59 |
60 |
61 | 62 | 139 | -------------------------------------------------------------------------------- /svelte/src/components/Tree/Folder.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
  • 26 | {#if hasFolders} 27 | 31 | {:else}{/if} 32 | 33 | {name} 34 |
  • 35 | 36 | {#if open && folder.data && folder.data.length && hasFolders} 37 | {#each folder.data as folder (folder.id)} 38 | {#if folder.type === "folder"} 39 | 40 | {/if} 41 | {/each} 42 | {/if} 43 | 44 | 80 | -------------------------------------------------------------------------------- /svelte/src/components/Tree/Tree.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | {#key $data} 33 |
      34 | {#each $data.byId(0).data as folder (folder.id)} 35 | {#if folder.type === "folder"} 36 | 37 | {/if} 38 | {/each} 39 |
    40 | {/key} 41 | 42 | 51 | -------------------------------------------------------------------------------- /svelte/src/components/UploadButton.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 |
    18 | {_(item.text)} 19 |
    20 | 21 | 26 | -------------------------------------------------------------------------------- /svelte/src/components/UploadDropArea.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |
    35 | {@render children?.()} 36 |
    37 | 38 | 46 | -------------------------------------------------------------------------------- /svelte/src/components/ui/Icon.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | { 13 | onclick && onclick(); 14 | }} 15 | > 16 | 17 | 54 | -------------------------------------------------------------------------------- /svelte/src/components/ui/Search.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
    24 | 25 | 26 | 27 | 35 |
    36 | 37 | 77 | -------------------------------------------------------------------------------- /svelte/src/icons.js: -------------------------------------------------------------------------------- 1 | export let whitelist = { 2 | "7z": true, 3 | rar: true, 4 | zip: true, 5 | css: true, 6 | html: true, 7 | js: true, 8 | php: true, 9 | md: true, 10 | xml: true, 11 | sql: true, 12 | aif: true, 13 | mid: true, 14 | mp3: true, 15 | waw: true, 16 | wma: true, 17 | doc: true, 18 | docx: true, 19 | txt: true, 20 | avi: true, 21 | mov: true, 22 | mp4: true, 23 | mpeg: true, 24 | mpg: true, 25 | pdf: true, 26 | xls: true, 27 | xlsx: true, 28 | gif: true, 29 | jpg: true, 30 | jpeg: true, 31 | png: true, 32 | psd: true, 33 | tiff: true, 34 | svg: true, 35 | folder: true, 36 | file: true, 37 | multiple: true, 38 | search: true, 39 | none: true, 40 | }; 41 | -------------------------------------------------------------------------------- /svelte/src/index.js: -------------------------------------------------------------------------------- 1 | import Filemanager from "./components/Filemanager.svelte"; 2 | 3 | import Material from "./themes/Material.svelte"; 4 | import Willow from "./themes/Willow.svelte"; 5 | import WillowDark from "./themes/WillowDark.svelte"; 6 | 7 | export { Filemanager, Material, Willow, WillowDark }; 8 | 9 | export { getMenuOptions } from "wx-filemanager-store"; 10 | 11 | import { setEnv } from "wx-lib-dom"; 12 | import { env } from "wx-lib-svelte"; 13 | setEnv(env); 14 | -------------------------------------------------------------------------------- /svelte/src/themes/Material.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if children} 7 | {@render children()} 8 | {:else} 9 | 10 | {/if} 11 | 12 | 26 | -------------------------------------------------------------------------------- /svelte/src/themes/Willow.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if children} 7 | {@render children()} 8 | {:else} 9 | 10 | {/if} 11 | 12 | 26 | -------------------------------------------------------------------------------- /svelte/src/themes/WillowDark.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if children} 7 | {@render children()} 8 | {:else} 9 | 10 | {/if} 11 | 12 | 26 | -------------------------------------------------------------------------------- /svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /svelte/tests/Index.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 | 49 | 50 | 51 | 52 |
    53 | 54 | 55 | 56 | 57 | 58 |
    59 | 60 | 98 | -------------------------------------------------------------------------------- /svelte/tests/cases/CustomMenu.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /svelte/tests/cases/CustomMenuReadonly.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /svelte/tests/cases/LocalData.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /svelte/tests/cases/Readonly.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /svelte/tests/data.js: -------------------------------------------------------------------------------- 1 | export function getData() { 2 | return [ 3 | { 4 | id: "/Code", 5 | size: 4096, 6 | date: new Date(2023, 11, 2, 17, 25), 7 | type: "folder", 8 | }, 9 | { 10 | id: "/Music", 11 | size: 4096, 12 | date: new Date(2023, 11, 1, 14, 45), 13 | type: "folder", 14 | }, 15 | { 16 | id: "/Pictures", 17 | size: 4096, 18 | date: new Date(2023, 10, 30, 6, 13), 19 | type: "folder", 20 | }, 21 | { 22 | id: "/Info.txt", 23 | size: 1000, 24 | date: new Date(2023, 10, 30, 6, 13), 25 | type: "file", 26 | }, 27 | { 28 | id: "/Code/Datepicker", 29 | size: 4096, 30 | date: new Date(2023, 11, 7, 15, 23), 31 | type: "folder", 32 | }, 33 | { 34 | id: "/Code/Area.svelte", 35 | size: 568, 36 | date: new Date(2023, 11, 6, 17, 38), 37 | type: "file", 38 | }, 39 | { 40 | id: "/Code/Button.svelte", 41 | size: 1177, 42 | date: new Date(2023, 11, 6, 17, 38), 43 | type: "file", 44 | }, 45 | { 46 | id: "/Code/ButtonSelect.svelte", 47 | size: 842, 48 | date: new Date(2023, 11, 6, 17, 38), 49 | type: "file", 50 | }, 51 | { 52 | id: "/Code/Checkbox.svelte", 53 | size: 1231, 54 | date: new Date(2023, 11, 6, 17, 38), 55 | type: "file", 56 | }, 57 | { 58 | id: "/Code/ColorPicker.svelte", 59 | size: 1546, 60 | date: new Date(2023, 11, 6, 17, 38), 61 | type: "file", 62 | }, 63 | { 64 | id: "/Code/Combo.svelte", 65 | size: 4068, 66 | date: new Date(2023, 11, 6, 17, 38), 67 | type: "file", 68 | }, 69 | { 70 | id: "/Code/Confirm.svelte", 71 | size: 1120, 72 | date: new Date(2023, 11, 6, 17, 38), 73 | type: "file", 74 | }, 75 | { 76 | id: "/Code/DateRangePicker.svelte", 77 | size: 2981, 78 | date: new Date(2023, 11, 6, 17, 38), 79 | type: "file", 80 | }, 81 | { 82 | id: "/Code/DoubleList.svelte", 83 | size: 2833, 84 | date: new Date(2023, 11, 6, 17, 38), 85 | type: "file", 86 | }, 87 | { 88 | id: "/Code/Dropdown.svelte", 89 | size: 2075, 90 | date: new Date(2023, 11, 6, 17, 38), 91 | type: "file", 92 | }, 93 | { 94 | id: "/Code/Editor.svelte", 95 | size: 839, 96 | date: new Date(2023, 11, 6, 17, 38), 97 | type: "file", 98 | }, 99 | { 100 | id: "/Code/Field.svelte", 101 | size: 630, 102 | date: new Date(2023, 11, 6, 17, 38), 103 | type: "file", 104 | }, 105 | { 106 | id: "/Code/Globals.svelte", 107 | size: 1552, 108 | date: new Date(2023, 11, 6, 17, 38), 109 | type: "file", 110 | }, 111 | { 112 | id: "/Code/IconButton.svelte", 113 | size: 708, 114 | date: new Date(2023, 11, 6, 17, 38), 115 | type: "file", 116 | }, 117 | { 118 | id: "/Code/InProgress.svelte", 119 | size: 144, 120 | date: new Date(2023, 11, 6, 17, 38), 121 | type: "file", 122 | }, 123 | { 124 | id: "/Code/List.svelte", 125 | size: 627, 126 | date: new Date(2023, 11, 6, 17, 38), 127 | type: "file", 128 | }, 129 | { 130 | id: "/Code/MultiCombo.svelte", 131 | size: 4210, 132 | date: new Date(2023, 11, 6, 17, 38), 133 | type: "file", 134 | }, 135 | { 136 | id: "/Code/MultiSelect.svelte", 137 | size: 1958, 138 | date: new Date(2023, 11, 3, 19, 54), 139 | type: "file", 140 | }, 141 | { 142 | id: "/Code/MultiText.svelte", 143 | size: 1375, 144 | date: new Date(2023, 11, 6, 17, 38), 145 | type: "file", 146 | }, 147 | { 148 | id: "/Code/Notice.svelte", 149 | size: 1092, 150 | date: new Date(2023, 11, 3, 19, 54), 151 | type: "file", 152 | }, 153 | { 154 | id: "/Code/Notices.svelte", 155 | size: 249, 156 | date: new Date(2023, 11, 3, 19, 54), 157 | type: "file", 158 | }, 159 | { 160 | id: "/Code/Number.svelte", 161 | size: 436, 162 | date: new Date(2023, 11, 3, 19, 54), 163 | type: "file", 164 | }, 165 | { 166 | id: "/Code/Pager.svelte", 167 | size: 1714, 168 | date: new Date(2023, 11, 3, 19, 54), 169 | type: "file", 170 | }, 171 | { 172 | id: "/Code/Password.svelte", 173 | size: 581, 174 | date: new Date(2023, 11, 3, 19, 54), 175 | type: "file", 176 | }, 177 | { 178 | id: "/Code/Popup.svelte", 179 | size: 994, 180 | date: new Date(2023, 11, 3, 19, 54), 181 | type: "file", 182 | }, 183 | { 184 | id: "/Code/Prompt.svelte", 185 | size: 678, 186 | date: new Date(2023, 11, 3, 19, 54), 187 | type: "file", 188 | }, 189 | { 190 | id: "/Code/RadioButton.svelte", 191 | size: 965, 192 | date: new Date(2023, 11, 3, 19, 54), 193 | type: "file", 194 | }, 195 | { 196 | id: "/Code/RadioButtonGroup.svelte", 197 | size: 230, 198 | date: new Date(2023, 11, 3, 19, 54), 199 | type: "file", 200 | }, 201 | { 202 | id: "/Code/RichSelect.svelte", 203 | size: 2883, 204 | date: new Date(2023, 11, 3, 19, 54), 205 | type: "file", 206 | }, 207 | { 208 | id: "/Code/Select.svelte", 209 | size: 643, 210 | date: new Date(2023, 11, 3, 19, 54), 211 | type: "file", 212 | }, 213 | { 214 | id: "/Code/SideNav.svelte", 215 | size: 1477, 216 | date: new Date(2023, 11, 3, 19, 54), 217 | type: "file", 218 | }, 219 | { 220 | id: "/Code/Slider.svelte", 221 | size: 2539, 222 | date: new Date(2023, 11, 3, 19, 54), 223 | type: "file", 224 | }, 225 | { 226 | id: "/Code/Switch.svelte", 227 | size: 894, 228 | date: new Date(2023, 11, 3, 19, 54), 229 | type: "file", 230 | }, 231 | { 232 | id: "/Code/Tabs.svelte", 233 | size: 981, 234 | date: new Date(2023, 11, 3, 19, 54), 235 | type: "file", 236 | }, 237 | { 238 | id: "/Code/Text.svelte", 239 | size: 1295, 240 | date: new Date(2023, 11, 3, 19, 54), 241 | type: "file", 242 | }, 243 | { 244 | id: "/Code/Timepicker.svelte", 245 | size: 1872, 246 | date: new Date(2023, 11, 3, 19, 54), 247 | type: "file", 248 | }, 249 | { 250 | id: "/Code/Toggle.svelte", 251 | size: 444, 252 | date: new Date(2023, 11, 3, 19, 54), 253 | type: "file", 254 | }, 255 | { 256 | id: "/Code/Toolbar.svelte", 257 | size: 89, 258 | date: new Date(2023, 11, 3, 19, 54), 259 | type: "file", 260 | }, 261 | { 262 | id: "/Code/Tooltip.svelte", 263 | size: 1867, 264 | date: new Date(2023, 11, 3, 19, 54), 265 | type: "file", 266 | }, 267 | { 268 | id: "/Code/TwoState.svelte", 269 | size: 430, 270 | date: new Date(2023, 11, 3, 19, 54), 271 | type: "file", 272 | }, 273 | { 274 | id: "/Code/Datepicker/Calendar.svelte", 275 | size: 2634, 276 | date: new Date(2023, 11, 7, 15, 23), 277 | type: "file", 278 | }, 279 | { 280 | id: "/Code/Datepicker/DatePicker.svelte", 281 | size: 862, 282 | date: new Date(2023, 11, 7, 15, 23), 283 | type: "file", 284 | }, 285 | { 286 | id: "/Code/Datepicker/Duodecade.svelte", 287 | size: 1627, 288 | date: new Date(2023, 11, 7, 15, 23), 289 | type: "file", 290 | }, 291 | { 292 | id: "/Code/Datepicker/Header.svelte", 293 | size: 1649, 294 | date: new Date(2023, 11, 7, 15, 23), 295 | type: "file", 296 | }, 297 | { 298 | id: "/Code/Datepicker/Month.svelte", 299 | size: 3974, 300 | date: new Date(2023, 11, 7, 15, 23), 301 | type: "file", 302 | }, 303 | { 304 | id: "/Code/Datepicker/Year.svelte", 305 | size: 1595, 306 | date: new Date(2023, 11, 7, 15, 23), 307 | type: "file", 308 | }, 309 | { 310 | id: "/Pictures/162822515312968813.png", 311 | size: 510885, 312 | date: new Date(2023, 11, 1, 14, 45), 313 | type: "file", 314 | }, 315 | { 316 | id: "/Pictures/18596d96.jpeg", 317 | size: 57199, 318 | date: new Date(2023, 11, 2, 17, 25), 319 | type: "file", 320 | }, 321 | { 322 | id: "/Pictures/dasha-emma-i-kieit.jpeg", 323 | size: 448308, 324 | date: new Date(2023, 11, 4, 4, 1), 325 | type: "file", 326 | }, 327 | { 328 | id: "/Pictures/Data.png", 329 | size: 23903, 330 | date: new Date(2023, 11, 4, 4, 1), 331 | type: "file", 332 | }, 333 | { 334 | id: "/Pictures/emma-i-dasha.jpeg", 335 | size: 907532, 336 | date: new Date(2023, 11, 4, 4, 1), 337 | type: "file", 338 | }, 339 | { 340 | id: "/Pictures/raskraska_dasha_i_druzja8.jpg", 341 | size: 84506, 342 | date: new Date(2023, 11, 4, 4, 1), 343 | type: "file", 344 | }, 345 | ]; 346 | } 347 | 348 | export function getDrive() { 349 | return { 350 | used: 15200000000, 351 | total: 50000000000, 352 | }; 353 | } 354 | -------------------------------------------------------------------------------- /svelte/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /svelte/tests/index.js: -------------------------------------------------------------------------------- 1 | import Demos from "./Index.svelte"; 2 | import { mount } from "svelte"; 3 | 4 | mount(Demos, { 5 | target: document.querySelector("#wx_demo_area") || document.body, 6 | }); 7 | -------------------------------------------------------------------------------- /svelte/tests/routes.js: -------------------------------------------------------------------------------- 1 | import LocalData from "./cases/LocalData.svelte"; 2 | import Readonly from "./cases/Readonly.svelte"; 3 | import CustomMenu from "./cases/CustomMenu.svelte"; 4 | import CustomMenuReadonly from "./cases/CustomMenuReadonly.svelte"; 5 | 6 | export const links = [ 7 | ["/local-data", "", LocalData], 8 | ["/readonly", "", Readonly], 9 | ["/custom-menu", "", CustomMenu], 10 | ["/custom-menu-readonly", "", CustomMenuReadonly], 11 | ]; 12 | -------------------------------------------------------------------------------- /svelte/vite.config.js: -------------------------------------------------------------------------------- 1 | import { loadEnv } from "vite"; 2 | import { resolve } from "path"; 3 | import { existsSync } from "fs"; 4 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 5 | import { visualizer } from "rollup-plugin-visualizer"; 6 | import { waitChanges, waitOn } from "wx-vite-tools"; 7 | import conditionalCompile from "vite-plugin-conditional-compile"; 8 | import pkg from "./package.json" with { type: "json" }; 9 | 10 | export default async ({ mode }) => { 11 | process.env = { ...process.env, ...loadEnv(mode, process.cwd(), "WX") }; 12 | const files = []; 13 | 14 | if (mode !== "production") { 15 | const paths = [ 16 | resolve(__dirname, "../store/dist/index.js"), 17 | resolve(__dirname, "../provider/dist/index.js"), 18 | ]; 19 | 20 | paths.forEach(path => { 21 | if (existsSync(path)) { 22 | files.push(path); 23 | } 24 | }); 25 | } 26 | 27 | const plugins = []; 28 | 29 | if (files.length) plugins.push(waitChanges({ files })); 30 | if (mode !== "development") plugins.push(conditionalCompile()); 31 | plugins.push(svelte({})); 32 | 33 | const name = pkg.productTag; 34 | 35 | let build, 36 | publicDir = resolve(__dirname, "public"), 37 | server = {}, 38 | base = ""; 39 | 40 | if (mode === "test") { 41 | build = { 42 | rollupOptions: { 43 | input: { tests: resolve(__dirname, "tests/index.html") }, 44 | }, 45 | }; 46 | server.port = 5100; 47 | } else { 48 | build = { 49 | rollupOptions: { 50 | input: { index: resolve(__dirname, "index.html") }, 51 | }, 52 | }; 53 | } 54 | 55 | if (process.env.WX_BUILD_STATS) { 56 | build = { 57 | lib: { 58 | entry: resolve(__dirname, "src/index.js"), 59 | name, 60 | formats: ["es"], 61 | fileName: format => `${name}.${format}.js`, 62 | }, 63 | outDir: "./dist", 64 | sourcemap: true, 65 | minify: true, 66 | target: "esnext", 67 | }; 68 | publicDir = false; 69 | plugins.push(visualizer({ filename: "dist/stats.html" })); 70 | } 71 | 72 | if (files.length) await waitOn({ files }); 73 | 74 | return { 75 | base, 76 | build, 77 | publicDir, 78 | resolve: { dedupe: ["svelte"] }, 79 | plugins, 80 | server, 81 | watch: { 82 | persistent: true, 83 | include: ["src/**/*.ts", "src/**/*.js"], 84 | }, 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /svelte/whatsnew.md: -------------------------------------------------------------------------------- 1 | ## 2.1.0 2 | 3 | - Using core@2.1.0 4 | 5 | ## 2.0.1 6 | 7 | ### New features 8 | 9 | - Svelte-5 support 10 | 11 | ## 1.3.1 12 | 13 | ### Fixes 14 | 15 | - Incorrect types 16 | 17 | ## 1.3.0 18 | 19 | - Publish under MIT license 20 | 21 | ## 1.0.0 22 | 23 | - Initial version released 24 | -------------------------------------------------------------------------------- /vitest.workspace.js: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from "vitest/config"; 2 | 3 | export default defineWorkspace([ 4 | "./store/vite.config.js", 5 | "./provider/vite.config.js", 6 | ]); 7 | --------------------------------------------------------------------------------