chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { NodeModulesExternal } from "@perspective-dev/esbuild-plugin/external.js";
import { WasmPlugin } from "@perspective-dev/esbuild-plugin/wasm.js";
import { WorkerPlugin } from "@perspective-dev/esbuild-plugin/worker.js";
import { ResolvePlugin } from "@perspective-dev/esbuild-plugin/resolve.js";
import { build } from "@perspective-dev/esbuild-plugin/build.js";
import {
bundle as bundleCss,
bundleAsync as bundleCssAsync,
} from "lightningcss";
import * as fs from "node:fs";
// import { createRequire } from "node:module";
import {
inlineUrlVisitor,
resolveNPM,
} from "@perspective-dev/viewer/tools.mjs";
import "zx/globals";
// const _require = createRequire(import.meta.url);
const BUILD = [
{
entryPoints: ["src/ts/perspective-workspace.ts"],
define: {
global: "window",
},
format: "esm",
plugins: [
// Inlining `lumino` and importing the `.ts` source saves _50kb_
NodeModulesExternal("@lumino"),
],
loader: {
".html": "text",
".css": "text",
},
external: ["*.wasm"],
outfile: "dist/esm/perspective-workspace.js",
},
{
entryPoints: ["src/ts/perspective-workspace.ts"],
define: {
global: "window",
},
plugins: [
ResolvePlugin({
"@perspective-dev/client":
"@perspective-dev/client/dist/esm/perspective.js",
"@perspective-dev/viewer":
"@perspective-dev/viewer/dist/esm/perspective-viewer.js",
}),
WasmPlugin(false),
WorkerPlugin({ inline: false }),
],
format: "esm",
splitting: true,
loader: {
".css": "text",
".html": "text",
},
outdir: "dist/cdn",
},
];
async function build_all() {
fs.mkdirSync("dist/css", { recursive: true });
const { code: wsCode } = await bundleCssAsync({
filename: "./src/css/workspace.css",
resolver: resolveNPM(import.meta.url),
minify: true,
errorRecovery: true,
visitor: inlineUrlVisitor("./src/css/workspace.css"),
});
fs.writeFileSync("dist/css/workspace.css", wsCode);
const { code: injCode } = await bundleCssAsync({
filename: "./src/css/injected.css",
resolver: resolveNPM(import.meta.url),
minify: true,
errorRecovery: true,
visitor: inlineUrlVisitor("./src/css/workspace.css"),
});
// Workspace themes — bundle with lightningcss (resolves @imports)
fs.writeFileSync("dist/css/injected.css", injCode);
const { code: proCode } = bundleCss({
filename: "./src/themes/pro.css",
minify: true,
visitor: inlineUrlVisitor("./src/themes/pro.css"),
});
fs.writeFileSync("dist/css/pro.css", proCode);
const { code: proDarkCode } = bundleCss({
filename: "./src/themes/pro-dark.css",
minify: true,
visitor: inlineUrlVisitor("./src/themes/pro-dark.css"),
});
fs.writeFileSync("dist/css/pro-dark.css", proDarkCode);
await Promise.all(BUILD.map(build)).catch(() => process.exit(1));
try {
await $`tsc --project ./tsconfig.json`.stdio(
"inherit",
"inherit",
"inherit",
);
} catch (e) {
console.error(e.stdout);
process.exit(1);
}
}
build_all();
+15
View File
@@ -0,0 +1,15 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as fs from "node:fs";
fs.rmSync("dist", { recursive: true, force: true });
+56
View File
@@ -0,0 +1,56 @@
{
"name": "@perspective-dev/workspace",
"version": "4.5.2",
"description": "Perspective Workspace",
"files": [
"dist/**/*",
"src/**/*",
"index.d.ts"
],
"type": "module",
"exports": {
".": "./dist/esm/perspective-workspace.js",
"./esm/": "./dist/esm/",
"./src/*": "./src/*",
"./dist/*": "./dist/*",
"./dist/themes/": "./src/themes/",
"./package.json": "./package.json"
},
"unpkg": "./dist/cdn/perspective-workspace.js",
"jsdelivr": "./dist/cdn/perspective-workspace.js",
"scripts": {
"build": "node ./build.mjs",
"clean": "node ./clean.mjs"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/perspective-dev/perspective"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@perspective-dev/client": "workspace:",
"@lumino/algorithm": ">=2 <3",
"@lumino/commands": ">=2 <3",
"@lumino/domutils": ">=2 <3",
"@lumino/messaging": ">=2 <3",
"@lumino/virtualdom": ">=2 <3",
"@lumino/widgets": ">=2 <3",
"@lumino/coreutils": ">=2 <3",
"@lumino/signaling": ">=2 <3",
"lodash": "catalog:"
},
"devDependencies": {
"@perspective-dev/viewer": "workspace:",
"lightningcss": "catalog:",
"@perspective-dev/esbuild-plugin": "workspace:",
"@perspective-dev/test": "workspace:",
"@types/lodash": "catalog:",
"@types/react": "catalog:",
"typescript": "catalog:",
"zx": "catalog:"
}
}
+110
View File
@@ -0,0 +1,110 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
:host {
.lm-DockPanel {
overflow: var(--dock-panel--overflow, hidden);
position: absolute;
background-color: var(--detail--background-color, transparent);
padding: var(--psp-workspace--spacing);
top: 0;
left: 0;
right: 0;
bottom: 0;
&[data-mode="single-document"] {
padding: 0px;
}
}
.lm-Widget,
.lm-Widget {
cursor: inherit;
}
.lm-DockPanel.ew,
.lm-DockPanel.ew .lm-Widget,
.lm-SplitPanel.ew,
.lm-SplitPanel.ew .lm-Widget {
cursor: ew-resize !important;
}
.lm-DockPanel.ns,
.lm-DockPanel.ns .lm-Widget,
.lm-SplitPanel.ns,
.lm-SplitPanel.ns .lm-Widget {
cursor: ns-resize !important;
}
.workspace-master-widget {
min-width: 100px !important;
}
.lm-DockPanel.resizing ::slotted(perspective-viewer),
.lm-SplitPanel.resizing ::slotted(perspective-viewer) {
pointer-events: none;
}
.widget-blur ::slotted(perspective-viewer) {
opacity: 0.5;
}
.lm-DockPanel-handle.resizing {
background-color: rgba(0, 0, 0, 0.05);
}
.perspective-scroll-panel {
overflow: auto !important;
}
.lm-Panel {
min-height: 100%;
}
.lm-DockPanel-handle {
background-color: none;
}
.lm-SplitPanel-handle {
background-color: var(
--psp-workspace--split-panel-handle--background-color
);
}
.lm-SplitPanel-handle,
.lm-DockPanel-handle {
transition: background-color 0.3s ease-out;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
.lm-DockPanel-handle[data-orientation="horizontal"] {
width: 10px !important;
margin-left: -5px;
}
.lm-DockPanel-handle[data-orientation="vertical"] {
height: 10px !important;
margin-top: -5px;
}
.lm-DockPanel-overlay {
background: rgba(75, 75, 75, 0.2);
border: 1px dashed #666;
border-radius: 6px;
transition-property: top, left, right, bottom;
transition-duration: 50ms;
transition-timing-function: linear;
margin: 0px;
}
}
+15
View File
@@ -0,0 +1,15 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
@import "./viewer.css";
@import "./menu.css";
+130
View File
@@ -0,0 +1,130 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
@import "@lumino/widgets/style/menu.css";
:host {
.lm-Menu {
font-size: 12px;
padding: 8px;
background-color: var(--psp--background-color);
color: var(--psp--color);
border: 1px solid var(--psp-inactive--color);
border-radius: 3px;
max-width: 350px;
top: 0;
left: 0;
}
.lm-Menu.perspective-workspace-menu {
.lm-Menu-item {
padding-left: 37px !important;
}
}
.lm-Menu-item.lm-mod-active {
background-color: var(--psp--color);
color: var(--psp--background-color);
}
.lm-Menu-item.lm-mod-disabled {
opacity: 0.5;
}
.lm-Menu-itemIcon {
width: 24px;
height: 12px;
margin-right: 4px;
}
.lm-Menu-itemLabel {
flex: 1 1;
padding: 4px 2px 4px 2px;
}
.lm-Menu-itemMnemonic {
text-decoration: underline;
}
.lm-Menu-itemShortcut {
padding: 4px 0px;
}
.lm-Menu-itemSubmenuIcon {
width: 24px;
height: 18px;
}
.lm-Menu-item {
display: flex;
align-items: center;
outline: none;
margin: 0 -8px;
padding: 0 8px;
}
.lm-Menu-item[data-type="separator"] > div {
padding: 0;
height: 9px;
}
.lm-Menu-item[data-type="separator"] > div::after {
content: "";
display: block;
position: relative;
top: 4px;
border-top: 1px solid;
}
.lm-MenuBar-menu {
transform: translateY(-1px);
}
.lm-MenuBar-item {
padding: 10px 32px;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
color: #737373 !important;
}
.lm-MenuBar-item.lm-mod-active {
background: rgba(0, 0, 0, 0.2);
}
.lm-MenuBar.lm-mod-active .lm-MenuBar-item.lm-mod-active {
z-index: 10001;
background: rgba(0, 0, 0, 0.2);
/* border-left: 1px solid #c0c0c0; */
/* border-right: 1px solid #c0c0c0; */
}
.lm-Menu-itemIcon:before {
content: attr(content);
font-family:
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
"Liberation Mono", monospace;
}
[data-type="submenu"] .lm-Menu-itemSubmenuIcon:before {
content: ">";
font-family:
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
"Liberation Mono", monospace;
height: 18px;
}
.lm-mod-drag-image.lm-TabBar-tab {
display: none;
}
}
+378
View File
@@ -0,0 +1,378 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
:host {
.lm-TabBar-tabLabel {
background-color: transparent;
border: none;
color: var(--psp--color, #666);
cursor: pointer;
padding: 0px 6px;
margin: 0;
margin-left: 15.5px;
margin-right: auto;
flex: 0 1 auto;
border-radius: 3px;
&:hover {
background-color: var(--psp--color);
color: var(--psp--background-color);
}
}
/* .p-TabBar-tabLabel[value="[untitled]"] { */
/* color: #ddd !important; */
/* } */
.lm-TabBar-tabLabel:focus {
outline: none;
color: var(--workspace-secondary--color, #666) !important;
cursor: text;
}
.lm-TabBar-tab.lm-mod-closable > .lm-TabBar-tabCloseIcon:before {
content: var(--close-button--content, "\2715");
}
.lm-TabBar-tab > .lm-TabBar-tabConfigIcon:before {
content: var(--open-settings-button--content, "\1F527");
font-family: var(--settings--font-family, "Arial");
transition: color 0.5s ease;
}
.lm-TabBar-tab.lm-mod-current.settings_open
> .lm-TabBar-tabConfigIcon:before {
content: var(--close-settings-button--content, "\1F527");
}
.lm-TabBar-tab.lm-mod-current.linked > .lm-TabBar-tabConfigIcon:before {
color: #22a0ce;
}
.lm-TabBar-tab > .lm-TabBar-tabConfigIcon {
opacity: 0;
pointer-events: none;
}
.lm-TabBar-tab.lm-mod-current > .lm-TabBar-tabConfigIcon {
opacity: 1;
pointer-events: all;
}
.lm-mod-current {
.lm-TabBar-tabConfigIcon,
.lm-TabBar-tabCloseIcon {
color: inherit;
transition: color 0.2s ease-out;
}
.lm-TabBar-tabConfigIcon:hover,
.lm-TabBar-tabCloseIcon:hover {
color: #1a7da1;
transition: color 0.2s ease-out;
}
}
.lm-BoxPanel {
overflow: scroll !important;
}
.lm-TabBar,
.lm-TabBar-tab {
position: relative;
overflow: visible !important;
}
.lm-TabBar-tabLabel:empty::after {
content: "untitled";
color: #aaa;
}
.lm-mod-current .lm-TabBar-tabLabel:empty::after {
color: #ddd;
}
.lm-TabBar-tabLabel:empty::after {
content: "untitled";
}
.divider {
left: 32px;
bottom: 0;
position: absolute;
right: 32px;
height: 1px;
background-color: var(--psp-workspace--tabbar--border-color);
margin-bottom: 0px;
}
.lm-TabBar-tab .drag-handle {
pointer-events: all;
height: 12px;
width: 14px;
-webkit-mask-image: var(--psp-icon--column-drag-handle--mask-image);
mask-image: var(--psp-icon--column-drag-handle--mask-image);
padding: 12px 0 12px 12px;
background-repeat: no-repeat;
background-color: var(--psp--color, red);
content: "";
display: inline-block;
-webkit-mask-size: cover;
mask-size: auto;
mask-position: center;
mask-repeat: no-repeat;
}
.lm-TabBar {
/* z-index: -1; */
pointer-events: none;
.lm-TabBar-tabCloseIcon {
pointer-events: all;
}
.bookmarks-button {
pointer-events: all;
}
}
.lm-TabBar-tab.lm-mod-hidden {
display: flex !important;
opacity: 0.5;
}
.lm-TabBar-tab.lm-mod-current .divider {
display: block;
}
.pfm-button-base {
background: white !important;
}
.pfm-button-base:active {
background: #016bc6 !important;
}
.lm-TabBar-content {
padding: 0px;
border-bottom: 0px solid #fff !important;
border-left: #eaeaea;
border-right: #eaeaea;
}
.lm-TabBar-tab.lm-mod-current {
.shadow {
margin: -1px;
position: absolute;
width: calc(100% + 2px);
height: 200%;
pointer-events: none;
left: 0px;
}
&.settings_open .shadow {
box-shadow: none;
}
}
.lm-TabBar-tab {
/* margin: 0px 4px; */
border-color: #eaeaea;
align-items: center;
}
.lm-TabBar-content.inactive-blur > .lm-TabBar-tab {
background-color: var(
--workspace-inactive-blur--background-color,
transparent
);
}
.condensed .lm-TabBar-content {
& > .lm-TabBar-tab.lm-mod-current .lm-TabBar-tabConfigIcon {
display: block;
}
& > .lm-TabBar-tab.lm-mod-current.settings_open {
.lm-TabBar-tabLabel {
margin-left: 0px;
}
}
}
.lm-TabBar-content > .lm-TabBar-tab.lm-mod-current.settings_open {
border-width: 1px 1px 0px 1px !important;
border-color: var(--psp-workspace--tabbar--border-color, #ddd);
.lm-TabBar-tabToolbar {
display: flex;
}
.divider {
height: 1px;
margin-bottom: 0px;
background-color: var(--psp-workspace--tabbar--border-color, #ddd);
}
&:last-child:first-child .divider {
transition: none;
}
}
.lm-TabBar-content > .lm-TabBar-tab.lm-mod-current.perspective_updating {
.lm-TabBar-tabConfigIcon {
display: none;
}
.lm-TabBar-tabLoadingIcon {
display: block;
}
}
.lm-TabBar-content > .lm-TabBar-tab {
max-width: 100000px !important;
flex: 0 1 100000px !important;
background: none;
color: #737373;
height: 40px !important;
max-height: 40px !important;
margin-bottom: -40px;
transform: none !important;
transition: color 0.2s ease-out;
}
.lm-TabBar-content > .lm-TabBar-tab {
color: #ccc;
}
.lm-TabBar-content > .lm-TabBar-tab {
color: #ccc;
}
.lm-TabBar-content > .lm-TabBar-tab:hover {
color: #ccc;
}
.lm-TabBar-content > .lm-TabBar-tab.lm-mod-current {
color: var(--workspace-secondary--color, #666);
}
.lm-TabBar-tab.lm-mod-current .lm-TabBar-tabLabel {
white-space: nowrap !important;
}
.lm-TabBar-tabLabel {
font-family: inherit;
font-size: 12px;
line-height: 22px;
text-align: start;
white-space: normal !important;
word-break: break-all;
}
.lm-TabBar-tabCloseIcon,
.lm-TabBar-tabConfigIcon {
cursor: pointer !important;
line-height: 22px !important;
padding-right: 16.5px !important;
}
.lm-TabBar-tabConfigIcon {
padding-left: 8px;
padding-right: 8px !important;
}
.lm-TabBar-tabLoadingIcon {
padding-left: 11px;
padding-right: 12px;
}
.bottom .lm-TabBar-tab {
flex-basis: 10000px !important;
max-width: 10000px !important;
}
.lm-TabBar-content > .lm-TabBar-tab.lm-mod-current {
color: var(--workspace-tabbar--color, #666) !important;
/* border: var(--psp-workspace--tabbar--border, 1px solid #ddd); */
/* border-width: var(--psp-workspace--tabbar-tab--border-width); */
/* box-shadow: 0 13px 0 -12px var(--psp-inactive--border-color); */
/* border-radius: var(--psp-workspace--tabbar--border-radius, 6px) */
/* var(--psp-workspace--tabbar--border-radius, 6px) 0 0; */
/* background-color: var(--workspace-tabbar--background-color, white); */
background: none;
height: 40px !important;
max-height: 40px !important;
margin-bottom: -40px;
border: 0 solid transparent;
border-width: 1px 1px 0px 1px;
}
.bottom .lm-TabBar-tab.lm-mod-current {
border: none;
}
::-webkit-scrollbar-thumb {
background-color: #ccc;
}
.perspective-workspace.context-menu * .lm-TabBar.context-focus {
opacity: 1;
}
.perspective-workspace.context-menu * .lm-TabBar,
.perspective-workspace.context-menu
.lm-Widget.workspace-widget:not(.context-focus)
.viewer-container {
opacity: 0.2;
}
.bookmarks-button {
margin-left: 11px;
margin-right: -13px;
height: 20px;
border-radius: 3px;
border: 1px solid var(--psp-inactive--color);
background-color: var(--psp--background-color);
&:hover {
background-color: var(--psp--color);
border-color: var(--psp--color);
}
}
.bookmarks {
background-repeat: no-repeat;
background-color: var(--psp--color);
content: "";
display: inline-block;
-webkit-mask-size: cover;
mask-size: cover;
cursor: pointer;
width: 20px;
height: 12px;
-webkit-mask-image: var(--psp-icon--bookmarks--mask-image);
mask-image: var(--psp-icon--bookmarks--mask-image);
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: auto;
mask-size: auto;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
padding: 4px 6px;
&:hover {
background-color: var(--psp--background-color);
}
}
}
+112
View File
@@ -0,0 +1,112 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
::slotted(.workspace-master-widget) {
--psp-status-bar--display: none;
--psp-label--config-button--content: var(
--open-settings-button--content,
"\1F527"
) !important;
}
.workspace-master-widget[settings] {
--psp-label--config-button--content: var(
--close-settings-button--content,
"\1F527"
) !important;
}
:not(.workspace-master-widget).widget-maximize {
--psp-label--config-button--content: var(
--open-settings-button--content,
"\1F527"
) !important;
}
:not(.workspace-master-widget).widget-maximize[settings] {
--psp-label--config-button--content: var(
--close-settings-button--content,
"\1F527"
) !important;
}
.lm-DockPanel-widget {
border: 1px solid #eaeaea;
border-width: 0px !important;
min-width: 300px;
min-height: 200px;
}
:not.workspace-master-widget {
--psp-label--config-button--content: "" !important;
}
.context-menu > perspective-viewer.context-focus {
opacity: 1;
filter: none;
}
.context-menu > perspective-viewer {
filter: saturate(0);
& > * {
pointer-events: none;
}
}
:host {
--psp-icon--bookmarks--mask-image: url("../svg/bookmark-icon.svg");
}
::slotted(perspective-viewer) {
flex: 1;
position: relative;
display: block;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
overflow: visible !important;
--psp-close-button--display: flex;
}
::slotted(perspective-viewer.bookmarks-container:not([settings])) {
--psp-icon--status-ok--mask-image: var(
--psp-icon--bookmarks--mask-image
) !important;
--psp-status-icon--ok--border-color: var(--psp-inactive--color);
--psp-status-icon--ok-hover--border-color: var(--psp--color);
--psp-status-icon--ok-hover--background-color: var(--psp--color);
--psp-status-icon--ok-hover--color: var(--psp--background-color);
--psp-status-icon--ok--cursor: pointer;
--psp-status-icon--pointer-events: all;
}
::slotted(perspective-viewer::part(status-indicator)) {
background-color: red;
border: 1px solid red;
}
::slotted(perspective-viewer:not(.widget-maximize)) {
--psp-status-bar--padding: 0 36px 0 8px;
}
:host-context(.lm-mod-override-cursor) {
cursor: grabbing !important;
}
:host-context(.lm-mod-override-cursor) ::slotted(perspective-viewer),
.context-menu ::slotted(perspective-viewer) {
--override-content-pointer-events: none;
}
+44
View File
@@ -0,0 +1,44 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
.lm-DockPanel:not([data-mode="single-document"]) .viewer-container {
border: var(
--psp-workspace--tabbar--border,
1px solid var(--psp-inactive--color)
);
border-width: var(--psp-workspace--tabbar--border-width);
border-radius: var(--psp-workspace--tabbar--border-radius, 6px)
var(--psp-workspace--tabbar--border-radius, 6px)
var(--psp-workspace--tabbar--border-radius, 6px)
var(--psp-workspace--tabbar--border-radius, 6px);
}
.viewer-container {
flex: 1;
height: 100%;
overflow: hidden;
}
.perspective-scroll-panel {
.workspace-widget {
min-width: 300px;
min-height: 200px;
}
}
.workspace-widget {
display: flex;
flex-direction: column;
border: 1px solid #eaedef;
border-width: 0px !important;
}
+84
View File
@@ -0,0 +1,84 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
@import "@lumino/widgets/style/widget.css";
@import "@lumino/widgets/style/accordionpanel.css";
@import "@lumino/widgets/style/commandpalette.css";
@import "@lumino/widgets/style/dockpanel.css";
@import "@lumino/widgets/style/menu.css";
@import "@lumino/widgets/style/menubar.css";
@import "@lumino/widgets/style/scrollbar.css";
@import "@lumino/widgets/style/splitpanel.css";
@import "@lumino/widgets/style/tabbar.css";
@import "@lumino/widgets/style/tabpanel.css";
/* @import "@lumino/widgets/style/widget.css";
@import "@lumino/widgets/style/accordionpanel.css";
@import "@lumino/widgets/style/commandpalette.css";
@import "@lumino/widgets/style/dockpanel.css";
@import "@lumino/widgets/style/menu.css";
@import "@lumino/widgets/style/menubar.css";
@import "@lumino/widgets/style/scrollbar.css";
@import "@lumino/widgets/style/splitpanel.css";
@import "@lumino/widgets/style/tabbar.css";
@import "@lumino/widgets/style/tabpanel.css"; */
@import "@lumino/widgets/style/index.css";
@import "./injected.css";
@import "./tabbar.css";
@import "./dockpanel.css";
@import "./widget.css";
:host {
--psp-workspace--spacing: 3px;
/* width: 100%; */
/* height: 100%; */
.workspace {
width: 100%;
height: 100%;
}
.lm-SplitPanel {
height: 100%;
width: 100%;
}
div.lm-SplitPanel-handle {
background-color: var(
--psp-workspace--split-panel-handle--background-color
);
}
.master-panel {
background-color: var(--workspace-master--background-color, inherit);
padding: 0px 0px 0px 0px;
.viewer-container {
border-width: 0px;
}
}
.perspective-workspace.context-menu * .lm-SplitPanel-handle {
opacity: 0.2;
}
.perspective-workspace.context-menu > .lm-SplitPanel-handle {
opacity: 0.2;
}
.context-menu .lm-TabBar:not(.context-focus) {
pointer-events: none;
}
}
@@ -0,0 +1,11 @@
<!--
Copyright (c) 2017, the Perspective Authors.
This file is part of the Perspective library, distributed under the terms of
the Apache License 2.0. The full license can be found in the LICENSE file.
-->
<template id="perspective-workspace">
<div id="container" class="workspace"></div>
</template>
@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.66214 6.96476L0.5 9.86338V0.5H7.5V9.86338L4.33786 6.96476L4 6.65505L3.66214 6.96476Z" stroke="#042121"/>
<path d="M15 4L17 6L19 4" stroke="#042121" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 296 B

@@ -0,0 +1,10 @@
<svg width="5" height="12" viewBox="0 0 5 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="3.49996" cy="11.0001" r="0.833333" fill="#6E6E6E"/>
<circle cx="0.833333" cy="11.0001" r="0.833333" fill="#6E6E6E"/>
<circle cx="3.49996" cy="7.66683" r="0.833333" fill="#6E6E6E"/>
<circle cx="0.833333" cy="7.66683" r="0.833333" fill="#6E6E6E"/>
<circle cx="3.49996" cy="4.33333" r="0.833333" fill="#6E6E6E"/>
<circle cx="0.833333" cy="4.33333" r="0.833333" fill="#6E6E6E"/>
<circle cx="3.49996" cy="1.00008" r="0.833333" fill="#6E6E6E"/>
<circle cx="0.833333" cy="1.00008" r="0.833333" fill="#6E6E6E"/>
</svg>

After

Width:  |  Height:  |  Size: 617 B

+125
View File
@@ -0,0 +1,125 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ */
perspective-workspace,
perspective-workspace[theme="Pro Dark"],
perspective-indicator[theme="Pro Dark"] {
--psp-theme-name: "Pro Dark";
}
perspective-workspace perspective-viewer {
--psp-status-bar--height: 39px;
--psp-plugin-selector--height: 47px;
}
perspective-workspace perspective-viewer.widget-maximize {
--modal-panel--margin: -4px 0 -4px 0;
--psp-status-bar--border-radius: 6px 0 0 0;
--psp-main-column--margin: 3px 0 3px 3px;
--psp-main-column--border: 1px solid var(--psp-inactive--color);
--psp-main-column--border-width: 1px 0px 1px 1px;
--psp-main-column--border-radius: 6px 0 0 6px;
--settings-button--margin: 10px 0 0 0;
}
perspective-workspace {
/* perspective-workspace-pro-base */
font-family:
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
"Liberation Mono", monospace;
--open-settings-button--content: "expand_more";
--close-settings-button--content: "expand_less";
--close-button--content: "\2715";
--master-divider--background-color: #243136;
--menu-maximize--content: "fullscreen";
--menu-minimize--content: "fullscreen_exit";
--menu-duplicate--content: "call_split";
--menu-master--content: "cast";
--menu-detail--content: "notes";
--menu-export--content: "file_download";
--menu-copy--content: "file_copy";
--menu-reset--content: "autorenew";
--menu-link--content: "link";
--menu-unlink--content: "link_off";
--menu-newmenu--content: "add";
--menu-close--content: "close";
--menu-new--content: "description";
--menu-newview--content: "file_copy";
--psp-workspace--tabbar--border: 1px solid var(--psp-inactive--color);
--psp-workspace--tabbar--border-width: 0px 1px 1px 1px;
--psp-workspace--tabbar--border-radius: 6px;
--psp-workspace--tabbar--border-color: var(--psp-inactive--color);
--psp-workspace--tabbar-tab--border-width: 1px 1px 0px 1px;
/* Workspace */
--psp-icon--column-drag-handle--mask-image: url("../svg/drag-handle.svg");
--psp-icon--bookmarks--mask-image: url("../svg/bookmark-icon.svg");
/* perspective-viewer-pro-dark--colors */
background-color: #242526;
--psp-workspace--split-panel-handle--background-color: #242526;
color: white;
--psp--color: white;
--psp-active--color: #2770a9;
--psp-error--color: #ff9485;
--psp-inactive--color: #61656e;
--psp-inactive--border-color: #4c505b;
--psp--background-color: #242526;
--psp-active--background: rgba(39, 113, 170, 0.5);
--psp-expression--operator--color: #c5c9d0;
--psp-expression--function--color: #22a0ce;
--psp-expression--error--color: rgb(255, 136, 136);
--psp-calendar--filter: invert(1);
--psp-input--filter: invert(1);
--psp-warning--color: #242526;
--psp-warning--background: var(--psp--color);
--psp-icon--select-arrow--mask-image: var(
--psp-icon--select-arrow-light--mask-image
);
--psp-icon--select-arrow-hover--mask-image: var(
--psp-icon--select-arrow-dark--mask-image
);
/* Syntax */
--psp-code-editor--symbol--color: white;
--psp-code-editor--literal--color: #7dc3f0;
--psp-code-editor--operator--color: rgb(23, 166, 123);
--psp-code-editor--comment--color: rgb(204, 120, 48);
--psp-code-editor--column--color: #e18ee1;
background-color: #000202;
color: white;
--workspace-tabbar--background-color: #242526;
--workspace-secondary--color: #c5c9d0;
--psp-workspace--tabbar--border: 1px solid var(--psp-inactive--color);
--psp-workspace--tabbar--border-width: 1px 1px 1px 1px;
--psp-workspace--tabbar--border-radius: 6px;
--psp-workspace--tabbar--border-color: var(--psp-inactive--color);
--psp-workspace--tabbar-tab--border-width: 1px 1px 0 1px;
}
perspective-viewer[theme="Pro Dark"].workspace-master-widget {
--psp--background-color: #242526;
}
perspective-workspace-menu {
font-family:
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
"Liberation Mono", monospace;
font-weight: 300;
background: #2a2c2f !important;
color: white !important;
border: 1px solid #3b3f46 !important;
}
+121
View File
@@ -0,0 +1,121 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ */
.lm-cursor-backdrop {
display: none;
}
perspective-workspace,
perspective-workspace[theme="Pro Light"],
perspective-indicator[theme="Pro Light"] {
--psp-theme-name: "Pro Light";
}
perspective-workspace {
/* perspective-workspace-pro-base */
font-family:
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
"Liberation Mono", monospace;
--open-settings-button--content: "expand_more";
--close-settings-button--content: "expand_less";
--close-button--content: "\2715";
--master-divider--background-color: #243136;
--menu-maximize--content: "fullscreen";
--menu-minimize--content: "fullscreen_exit";
--menu-duplicate--content: "call_split";
--menu-master--content: "cast";
--menu-detail--content: "notes";
--menu-export--content: "file_download";
--menu-copy--content: "file_copy";
--menu-reset--content: "autorenew";
--menu-link--content: "link";
--menu-unlink--content: "link_off";
--menu-newmenu--content: "add";
--menu-close--content: "close";
--menu-new--content: "description";
--menu-newview--content: "file_copy";
--psp-workspace--split-panel-handle--background-color: #f2f4f6;
--psp-workspace--tabbar--border: 1px solid var(--psp-inactive--color);
--psp-workspace--tabbar--border-width: 1px 1px 1px 1px;
--psp-workspace--tabbar--border-radius: 6px;
--psp-workspace--tabbar--border-color: var(--psp-inactive--color);
--psp-workspace--tabbar-tab--border-width: 1px 1px 0px 1px;
/* Workspace */
--psp-icon--column-drag-handle--mask-image: url("../svg/drag-handle.svg");
--psp-icon--bookmarks--mask-image: url("../svg/bookmark-icon.svg");
/* perspective-viewer-pro--colors */
color: #161616;
background-color: #dadada;
--psp--color: #161616;
--psp-inactive--color: #ababab;
--psp-inactive--border-color: #dadada;
--psp-active--color: #2670a9;
--psp-error--color: #ff471e;
--psp--background-color: #ffffff;
--psp-icon-overflow-hint--color: rgba(0, 0, 0, 0.2);
--psp-select--background-color: none;
--psp-warning--background: #042121;
--psp-warning--color: #fdfffd;
--psp-icon--select-arrow--mask-image: var(
--psp-icon--select-arrow-dark--mask-image
);
--psp-icon--select-arrow-hover--mask-image: var(
--psp-icon--select-arrow-light--mask-image
);
/* TODO deprecate me */
--psp-icon-overflow-hint--color: #fdfffd;
}
perspective-workspace perspective-viewer.widget-maximize {
/* --modal-panel--margin: -4px 0 -4px 0; */
--psp-status-bar--border-radius: 6px 0 0 0;
--psp-main-column--margin: 3px 0 3px 3px;
--psp-main-column--border: 1px solid var(--psp-inactive--color);
--psp-main-column--border-width: 1px 0px 1px 1px;
--psp-main-column--border-radius: 6px 0 0 6px;
/* --settings-button--margin: 10px 0 0 0; */
}
perspective-workspace perspective-viewer {
--psp-status-bar--height: 39px;
--psp-plugin-selector--height: 47px;
}
perspective-viewer[theme="Pro Light"].workspace-master-widget {
background-color: #f2f4f6;
--psp--background-color: #f2f4f6;
regular-table {
td,
th {
border-color: #e0e4e9;
}
}
}
perspective-viewer {
--psp-icon--bookmarks--mask-image: url("../svg/bookmark-icon.svg");
}
perspective-workspace-menu {
font-family:
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
"Liberation Mono", monospace;
font-weight: 300;
color: #161616;
}
+82
View File
@@ -0,0 +1,82 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import type { HTMLPerspectiveWorkspaceElement } from "./perspective-workspace";
import type * as React from "react";
type ReactPerspectiveWorkspaceAttributes<T> = React.HTMLAttributes<T>;
type JsxPerspectiveWorkspaceElement = {
class?: string;
} & React.DetailedHTMLProps<
ReactPerspectiveWorkspaceAttributes<HTMLPerspectiveWorkspaceElement>,
HTMLPerspectiveWorkspaceElement
>;
// React <19
declare global {
namespace JSX {
interface IntrinsicElements {
"perspective-workspace": JsxPerspectiveWorkspaceElement;
}
}
}
// React >=19
// @ts-ignore
declare module "react/jsx-runtime" {
namespace JSX {
interface IntrinsicElements {
"perspective-workspace": JsxPerspectiveWorkspaceElement;
}
}
}
// @ts-ignore
declare module "react/jsx-dev-runtime" {
namespace JSX {
interface IntrinsicElements {
"perspective-workspace": JsxPerspectiveWorkspaceElement;
}
}
}
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"perspective-workspace": JsxPerspectiveWorkspaceElement;
}
}
}
// Custom Elements extensions
declare global {
interface Document {
createElement(
tagName: "perspective-workspace",
options?: ElementCreationOptions,
): HTMLPerspectiveWorkspaceElement;
querySelector<E extends Element = Element>(selectors: string): E | null;
querySelector(
selectors: "perspective-workspace",
): HTMLPerspectiveWorkspaceElement | null;
}
interface CustomElementRegistry {
get(
tagName: "perspective-workspace",
): HTMLPerspectiveWorkspaceElement & typeof HTMLElement;
}
}
+21
View File
@@ -0,0 +1,21 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
declare module "*.css" {
const content: string;
export default content;
}
declare module "*.html" {
const content: string;
export default content;
}
+14
View File
@@ -0,0 +1,14 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// Reminder for future authors as to why this file exists:
// https://github.com/evanw/esbuild/issues/1663
@@ -0,0 +1,266 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { MessageLoop } from "@lumino/messaging";
import { Widget } from "@lumino/widgets";
import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
import type * as psp from "@perspective-dev/client";
import type * as psp_viewer from "@perspective-dev/viewer";
import * as msg from "@lumino/messaging";
export { PerspectiveWorkspace, addViewer, genId } from "./workspace";
export { PerspectiveViewerWidget } from "./workspace/widget";
export * from "./extensions";
import { PerspectiveWorkspace, PerspectiveWorkspaceConfig } from "./workspace";
import { bindTemplate, CustomElementProto } from "./utils/custom_elements";
import style from "../../dist/css/workspace.css";
import template from "../html/workspace.html";
export { PerspectiveWorkspaceConfig };
/**
* A Custom Element for coordinating a set of `<perspective-viewer>` light DOM
* children. `<perspective-workspace>` is built on Lumino.js to allow a more
* app-like experience than `<perspective-viewer>`, providing
* these features additionally:
*
* - Docking, arranging, tabbing and max/min-ing of `<perspective-viewer>`s.
* - Trivial `<perspective-viewer>` duplication.
* - Global Filter sidebar, for using selection state of a pivot table to filter
* siblings.
* - Total persistence/serializable state and child state.
* - Easy sharing/ownership of `Table()` among different `<perspective-viewer>`.
* - A cool DOM-reactive API.
*
* There are a few ways to use this Custom Element. In plain HTML, you can
* express your initial view simply:
*
* ```html
* <perspective-workspace></perspective-workspace>
* ```
*
* You can also use the DOM API in Javascript:
*
* ```javascript
* const workspace = document.createElement("perspective-workspace");
* document.body.appendChild(workspace);
* ```
*
* This will yield a `<perspective-workspace> with the default layout. To load
* a `Table()`, add it to `tables` via the `Map()` API where it will be
* auto-wired into all matching `<perspective-viewer>`s immediately:
*
* ```javascript
* workspace.tables.set("superstore", await worker.table(my_data));
* ```
*
*
*/
export class HTMLPerspectiveWorkspaceElement extends HTMLElement {
private workspace?: PerspectiveWorkspace;
private _resize_observer?: ResizeObserver;
constructor() {
super();
this.setAutoSize(true);
}
/***************************************************************************
*
* Public
*
*/
/**
* Persists this `<perspective-workspace>` to a token `Object`. This object
* is JSON serializable and describes the state of the Workspace and it's
* child `<perspective-viewer>` elements. Some important keys:
*
* - `viewers`: The serialized state of `<perspective-viewer>` children,
* named by `slot`.
* - `detail`: The main layout.
* - `master`: The contents of the Global Filter sidebar.
*
* While the `table` attribute is persisted for each `perspective-viewer`,
* `Table`s themselves must be added to the `tables` property `Map()`
* separately.
*
* @return {Object} A configuration token, compatible with
* `restore(config)`.
* @example
* // Save this layout to local storage
* const workspace = document.querySelector("perspective-workspace");
* localStorage.set("CONFIG", JSON.stringify(workspace.save()));
*/
save(): Promise<PerspectiveWorkspaceConfig> {
return this.workspace!.save();
}
/**
* Restore this `<perspective-workspace>` to a previous state captured by
* `save()`. Calling this method will completely rewrite this element's
* `innerHTML`, but may reuse `<perspective-viewer>` children depending
* on the `slot` attribute names. However, it should always be possible
* to recreate any given state from within the UI itself, as the attributes
* on `<perspective-viewer>` itself create immutable views.
*
* While the `table` attribute is set for each `perspective-viewer`,
* `Table`s themselves must be added to the `tables` property `Map()`
* separately.
* @param {Object} config A configuration token, as returned by `save()`.
* @example
* // Restore this layout from local storage
* const workspace = document.querySelector("perspective-workspace");
* workspace.restore(JSON.parse(localStorage.get("CONFIG"));
*
* // Add `Table` separately.
* workspace.tables.set("superstore", await worker.table(data));
*/
async restore(layout: PerspectiveWorkspaceConfig) {
await this.workspace!.restore(layout);
}
async clear() {
await this.restore({
sizes: [],
master: undefined,
detail: { main: null },
viewers: {},
});
}
/**
* Await all asynchronous tasks for all viewers in this workspace. This is
* useful to make sure asynchonous side effects of synchronous methods calls
* are applied.
*/
async flush() {
if (!this.workspace) {
return;
}
msg.MessageLoop.flush();
await new Promise((x) => requestAnimationFrame(x));
await this.workspace._mutex.lock(async () => {
await Promise.all(
Array.from(this.querySelectorAll("perspective-viewer")).map(
(x) => {
const psp_widget = x as HTMLPerspectiveViewerElement;
return psp_widget
.flush()
.then(() => psp_widget.flush());
},
),
);
});
}
/**
* Add a new viewer to the workspace for a given `ViewerConfigUpdateExt`.
* @param config
*/
async addViewer(config: psp_viewer.ViewerConfigUpdate) {
await this.workspace!.addViewer(config);
await this.flush();
}
async load(client: psp.Client | Promise<psp.Client>) {
if (this.workspace) {
this.workspace.client.push(await client);
} else {
throw new Error("Workspace not mounted");
}
}
/**
* Invalidate this component's dimensions and recalculate.
*/
async resize() {
this.workspace!.update();
await this.flush();
}
/**
* Set whether this workspace element should auto-size itself via a
* `ResizeObserver`.
*/
setAutoSize(is_auto_size: boolean) {
this._resize_observer?.unobserve(this);
this._resize_observer = undefined;
if (is_auto_size) {
this._resize_observer = new ResizeObserver((...args) =>
this.workspace?.update(),
);
this._resize_observer.observe(this);
}
}
connectedCallback() {
if (!this.workspace) {
const container = this.shadowRoot!.querySelector("#container")!;
this.workspace = new PerspectiveWorkspace(this);
this._register_light_dom_listener();
MessageLoop.sendMessage(this.workspace, Widget.Msg.BeforeAttach);
container.insertBefore(this.workspace.node, null);
MessageLoop.sendMessage(this.workspace, Widget.Msg.AfterAttach);
}
}
/***************************************************************************
*
* Private
*
*/
private _light_dom_changed() {
if (!this.workspace) {
return;
}
this.workspace._mutex.lock(async () => {
const viewers = Array.from(
this.childNodes,
) as HTMLPerspectiveViewerElement[];
for (const viewer of viewers) {
if (viewer.nodeType !== Node.ELEMENT_NODE) {
continue;
}
if (viewer.tagName !== "PERSPECTIVE-VIEWER") {
console.warn("Not a <perspective-viewer>");
continue;
}
let _task = this.workspace!.update_widget_for_viewer(
viewer as HTMLPerspectiveViewerElement,
);
}
this.workspace!.remove_unslotted_widgets(viewers);
this.workspace!.update_details_panel(viewers);
});
}
private _register_light_dom_listener() {
let observer = new MutationObserver(this._light_dom_changed.bind(this));
let config = { attributes: false, childList: true, subtree: false };
observer.observe(this, config);
this._light_dom_changed();
}
}
bindTemplate(
template,
style,
)(HTMLPerspectiveWorkspaceElement as unknown as CustomElementProto);
@@ -0,0 +1,95 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
/**
* Instantiate a Template DOM object from an HTML text string.
*
* Params
* ------
* template : An HTML string representing a template.
*
* Returns
* -------
* A Template DOM object.
*/
function importTemplate(template: string): HTMLTemplateElement {
const div = document.createElement("div");
div.innerHTML = template;
return Array.prototype.slice.call(div.children)[0];
}
export interface CustomElementProto extends CustomElementConstructor {
connectedCallback(): void;
}
/**
* A simple tool for creating Web Components v0.
*
* Params
* ------
* template : An HTML string representing a template. Should have an 'id'
* attribute which will become the new Web Component's tag name.
* proto : The new Web Component's prototype object, as per spec.
*/
export function registerElement(
templateString: string,
styleString: string,
proto: CustomElementProto,
) {
const template = importTemplate(templateString);
if (styleString) {
template.innerHTML =
`<style>${styleString.toString()}</style>` + template.innerHTML;
}
const _perspective_element = class extends proto {
private _initialized: boolean;
private _initializing: boolean;
constructor() {
super();
this._initialized = false;
this._initializing = false;
}
connectedCallback() {
if (this._initialized) {
return;
}
this._initializing = true;
const node = document.importNode(template.content, true);
const root = this.attachShadow({ mode: "open" });
root.appendChild(node);
if (proto.prototype.connectedCallback) {
proto.prototype.connectedCallback.call(this);
}
this._initializing = false;
this._initialized = true;
}
static get observedAttributes() {
return Object.getOwnPropertyNames(proto.prototype);
}
};
let name = template.getAttribute("id")!;
window.customElements.define(name, _perspective_element);
}
export function bindTemplate(template: string, ...styleStrings: string[]) {
const style = styleStrings.map((x) => x.toString()).join("\n");
return function (cls: CustomElementProto) {
return registerElement(template, style, cls);
};
}
@@ -0,0 +1,39 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
export class ObservableMap<K, V> extends Map<K, V> {
_set_listener?: (name: K, val: V) => void;
_delete_listener?: (name: K) => void;
set(name: K, item: V) {
this._set_listener?.(name, item);
super.set(name, item);
return this;
}
get(name: K): V {
return super.get(name)!;
}
delete(name: K) {
this._delete_listener?.(name);
return super.delete(name);
}
addSetListener(listener: (name: K, val: V) => void) {
this._set_listener = listener;
}
addDeleteListener(listener: (name: K) => void) {
this._delete_listener = listener;
}
}
@@ -0,0 +1,272 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { CommandRegistry } from "@lumino/commands";
import { Widget } from "@lumino/widgets";
import { Signal } from "@lumino/signaling";
import type {
HTMLPerspectiveViewerCopyMenuElement,
HTMLPerspectiveViewerExportMenuElement,
ViewerConfigUpdate,
} from "@perspective-dev/viewer";
import type { PerspectiveWorkspace } from "./workspace";
import { WorkspaceMenu } from "./menu";
export const createCommands = (
workspace: PerspectiveWorkspace,
indicator: HTMLElement,
) => {
const commands = new CommandRegistry();
commands.addCommand("workspace:export", {
execute: async (args) => {
const menu = document.createElement(
"perspective-export-menu",
) as unknown as HTMLPerspectiveViewerExportMenuElement;
workspace.apply_indicator_theme();
const widget = workspace.getWidgetByName(
args.widget_name as string,
)!;
menu.__set_model(widget.viewer.__get_model());
menu.open(indicator);
workspace.get_context_menu()?.init_overlay?.();
menu.addEventListener("blur", () => {
const context_menu = workspace.get_context_menu()!;
const signal = context_menu.aboutToClose as Signal<
WorkspaceMenu,
any
>;
signal.emit({});
});
},
isEnabled: (args) => {
if (workspace.get_context_menu()?.node.isConnected) {
const box = workspace
.get_context_menu()
?.node.getBoundingClientRect();
if (box) {
indicator.style.top = box.top + "px";
indicator.style.left = box.left + "px";
}
}
return true;
},
// iconClass: "menu-export",
label: "Export",
mnemonic: 0,
});
commands.addCommand("workspace:copy", {
execute: async (args) => {
const menu = document.createElement(
"perspective-copy-menu",
) as HTMLPerspectiveViewerCopyMenuElement;
workspace.apply_indicator_theme();
const widget = workspace.getWidgetByName(
args.widget_name as string,
)!;
menu.__set_model(widget.viewer.__get_model());
menu.open(indicator);
workspace.get_context_menu()?.init_overlay?.();
menu.addEventListener("blur", () => {
(
workspace.get_context_menu()?.aboutToClose as
| Signal<WorkspaceMenu, any>
| undefined
)?.emit({});
});
},
isEnabled: (_) => {
if (workspace.get_context_menu()?.node.isConnected) {
const box = workspace
.get_context_menu()
?.node.getBoundingClientRect();
if (box) {
indicator.style.top = box.top + "px";
indicator.style.left = box.left + "px";
}
}
return true;
},
// iconClass: "menu-copy",
label: "Copy",
mnemonic: 0,
});
commands.addCommand("workspace:new", {
execute: async (args) => {
const widget = await workspace._createWidgetAndNode({
config: {
table: args.table as string,
},
slot: undefined,
});
workspace.get_dock_panel().addWidget(widget, {
mode: "split-right",
ref: workspace.getWidgetByName(args.widget_name as string),
});
},
// iconClass: "menu-new-tables",
label: (args) => {
return args.table as string;
},
});
commands.addCommand("workspace:newview", {
execute: async (args) => {
const widget = workspace.getWidgetByName(
args.widget_name as string,
);
const target_widget = workspace.getWidgetByName(
args.target_widget_name as string,
)!;
const config = (await target_widget.save()) as ViewerConfigUpdate;
const new_widget = await workspace._createWidgetAndNode({
config,
slot: undefined,
});
workspace.get_dock_panel().addWidget(new_widget, {
mode: "split-right",
ref: widget,
});
},
// iconClass: "menu-new-tables",
isVisible: (args) => {
const target_widget = workspace.getWidgetByName(
args.target_widget_name as string,
)!;
return target_widget.title.label !== "";
},
label: (args) => {
const target_widget = workspace.getWidgetByName(
args.target_widget_name as string,
)!;
return target_widget.title.label || "untitled";
},
});
commands.addCommand("workspace:reset", {
execute: (args) => {
workspace
.getWidgetByName(args.widget_name as string)!
.viewer.reset();
},
label: "Reset",
mnemonic: 0,
});
commands.addCommand("workspace:settings", {
execute: async ({ widget_name }) => {
const widget = workspace.getWidgetByName(widget_name as string);
if (!widget) {
throw new Error(`No widget ${widget_name}`);
}
// if (!widget.viewer.hasAttribute("settings")) {
// workspace._maximize(widget);
requestAnimationFrame(() => widget.viewer.toggleConfig());
// } else {
// widget.viewer.toggleConfig();
// }
},
isVisible: (args) => {
const widget = workspace.getWidgetByName(
args.widget_name as string,
);
return widget?.parent! === (workspace.get_dock_panel() as Widget)
? true
: false;
},
label: (args) => {
const widget = workspace.getWidgetByName(
args.widget_name as string,
)!;
if (widget.viewer.hasAttribute("settings")) {
return "Close Settings";
} else {
return "Open Settings";
}
},
mnemonic: 0,
});
commands.addCommand("workspace:duplicate", {
execute: ({ widget_name }) =>
workspace.duplicate(
workspace.getWidgetByName(widget_name as string)!,
),
// iconClass: "menu-duplicate",
isVisible: (args) => {
return workspace.getWidgetByName(args.widget_name as string)
?.parent! === (workspace.get_dock_panel() as Widget)
? true
: false;
},
label: "Duplicate",
mnemonic: 0,
});
commands.addCommand("workspace:master", {
execute: (args) =>
workspace.toggleMasterDetail(
workspace.getWidgetByName(args.widget_name as string)!,
),
isVisible: (args) => {
return !!workspace.getWidgetByName(args.widget_name as string)
?._is_pivoted;
},
label: (args) => {
return workspace.getWidgetByName(args.widget_name as string)!
.parent === workspace.get_dock_panel()
? "Create Global Filter"
: "Remove Global Filter";
},
mnemonic: 0,
});
commands.addCommand("workspace:close", {
execute: (args) => {
workspace.getWidgetByName(args.widget_name as string)!.close();
},
// iconClass: "menu-close",
label: () => "Close",
mnemonic: 0,
});
commands.addCommand("workspace:help", {
// iconClass: "menu-close",
execute: () => {},
label: "Shift+Click for Browser Menu",
isEnabled: () => false,
// mnemonic: 0,
});
return commands;
};
@@ -0,0 +1,156 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { DockLayout, DockPanel, TabBar, Widget } from "@lumino/widgets";
import { PerspectiveTabBar } from "./tabbar";
import { PerspectiveTabBarRenderer } from "./tabbarrenderer";
import { PerspectiveWorkspace } from "./workspace";
import { PerspectiveViewerWidget } from "./widget";
class PerspectiveDockPanelRenderer extends DockPanel.Renderer {
_workspace: PerspectiveWorkspace;
constructor(workspace: PerspectiveWorkspace) {
super();
this._workspace = workspace;
}
createTabBar() {
const tabbar = new PerspectiveTabBar(this._workspace, {
renderer: new PerspectiveTabBarRenderer(false),
});
tabbar.addClass("lm-DockPanel-tabBar");
return tabbar;
}
}
// @ts-ignore: extending a private member `_onTabDetachRequested`
export class PerspectiveDockPanel extends DockPanel {
_workspace: PerspectiveWorkspace;
constructor(workspace: PerspectiveWorkspace) {
super({ renderer: new PerspectiveDockPanelRenderer(workspace) });
// @ts-ignore: accessing a private member `_renderer`
this._renderer.dock = this;
this._workspace = workspace;
}
_onTabDetachRequested(
sender: TabBar<Widget>,
args: TabBar.ITabDetachRequestedArgs<Widget>,
) {
// @ts-ignore: accessing a private member `_onTabDetachRequested`
super._onTabDetachRequested(sender, args);
// blur widget on when it's being moved
const widget = sender.titles[args.index].owner;
const layout = this.layout as DockLayout;
const old = layout.saveLayout();
if (Array.from(layout.widgets()).length > 1) {
layout.removeWidget(widget);
}
widget.addClass("widget-blur");
document.body.classList.add("lm-mod-override-cursor");
// @ts-ignore: accessing a private member `_drag`
const drag = this._drag;
if (drag) {
drag.dragImage?.parentElement?.removeChild?.(drag.dragImage);
drag.dragImage = null;
drag._promise.then(() => {
if (!widget.node.isConnected) {
layout.restoreLayout(old);
}
document.body.classList.remove("lm-mod-override-cursor");
widget.removeClass("widget-blur");
});
}
}
static getWidgets(
layout: DockPanel.ILayoutConfig,
): PerspectiveViewerWidget[] {
if (!!layout.main) {
return PerspectiveDockPanel.getAreaWidgets(layout.main);
} else {
return [];
}
}
static getAreaWidgets(
layout: DockLayout.AreaConfig,
): PerspectiveViewerWidget[] {
if (layout?.hasOwnProperty("children")) {
const split_panel = layout as DockLayout.ISplitAreaConfig;
return split_panel.children.flatMap((widget) =>
PerspectiveDockPanel.getAreaWidgets(widget),
);
} else if (layout?.hasOwnProperty("widgets")) {
const tab_panel = layout as DockLayout.ITabAreaConfig;
return tab_panel.widgets as PerspectiveViewerWidget[];
}
return [];
}
widgets(): IterableIterator<PerspectiveViewerWidget> {
return super.widgets() as IterableIterator<PerspectiveViewerWidget>;
}
static async mapWidgets(
widgetFunc: (widget: any) => Promise<any>,
layout: any,
): Promise<DockPanel.ILayoutConfig> {
if (!!layout.main) {
layout.main = await PerspectiveDockPanel.mapAreaWidgets(
widgetFunc,
layout.main,
);
}
return layout;
}
static async mapAreaWidgets(
widgetFunc: (widget: any) => Promise<any>,
layout: DockLayout.AreaConfig,
): Promise<DockLayout.AreaConfig> {
if (layout.hasOwnProperty("children")) {
const split_panel = layout as DockLayout.ISplitAreaConfig;
split_panel.children = await Promise.all(
split_panel.children.map((widget) =>
PerspectiveDockPanel.mapAreaWidgets(widgetFunc, widget),
),
);
} else if (layout.hasOwnProperty("widgets")) {
const tab_panel = layout as DockLayout.ITabAreaConfig;
tab_panel.widgets = await Promise.all(
tab_panel.widgets.map(widgetFunc),
);
}
return layout;
}
onAfterAttach() {
this.spacing =
parseInt(
window
.getComputedStyle(this._workspace.element)
.getPropertyValue("--psp-workspace--spacing"),
) || 0;
1;
}
}
@@ -0,0 +1,13 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
export * from "./workspace";
+213
View File
@@ -0,0 +1,213 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { ElementExt } from "@lumino/domutils";
import { MessageLoop } from "@lumino/messaging";
import { h } from "@lumino/virtualdom";
import { Menu, Widget } from "@lumino/widgets";
export class WorkspaceMenu extends Menu {
private _host: ShadowRoot;
private _workspace: HTMLElement;
init_overlay?: () => void;
constructor(
host: ShadowRoot,
workspace: HTMLElement,
options: Menu.IOptions,
) {
options.renderer = new MenuRenderer();
super(options);
this._host = host;
this._workspace = workspace;
(this as any)._openChildMenu = this._overrideOpenChildMenu.bind(this);
}
open(x: number, y: number, options?: Menu.IOpenOptions) {
options ||= {};
options.host = this._host as any as HTMLElement;
const box = this._workspace.getBoundingClientRect();
super.open(x, y, options);
const menu_box = this.node.getBoundingClientRect();
if (
menu_box.height + y > box.height &&
menu_box.height + y < document.documentElement.clientHeight
) {
this.node.style.top = `-${menu_box.height}`;
}
if (menu_box.width + x > box.width) {
if (menu_box.width + x < document.documentElement.clientWidth) {
this.node.style.left = `-${menu_box.width + x - box.width}px`;
} else {
this.node.style.left = `-${
document.documentElement.clientWidth - box.width
}px`;
}
}
}
// Override this lumino private method because it will otherwise always
// attach to `document.body`.
private _overrideOpenChildMenu(activateFirst = false) {
const self = this as any;
let item = this.activeItem;
if (!item || item.type !== "submenu" || !item.submenu) {
self._closeChildMenu();
return;
}
let submenu = item.submenu;
if (submenu === self._childMenu) {
return;
}
Menu.saveWindowData();
self._closeChildMenu();
self._childMenu = submenu;
self._childIndex = self._activeIndex;
(submenu as any)._parentMenu = this;
MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
let itemNode = this.contentNode.children[self._activeIndex];
openSubmenu(submenu, itemNode as HTMLElement, self._host);
if (activateFirst) {
submenu.activeIndex = -1;
submenu.activateNextItem();
}
submenu.activate();
}
}
class MenuRenderer extends Menu.Renderer {
formatLabel(data: Menu.IRenderData) {
let { label, mnemonic } = data.item;
if (mnemonic < 0 || mnemonic >= label.length) {
return label;
}
let prefix = label.slice(0, mnemonic);
let suffix = label.slice(mnemonic + 1);
let char = label[mnemonic];
let span = h.span(
{
className: "lm-Menu-itemMnemonic p-Menu-itemMnemonic",
},
char,
);
return [prefix, span, suffix];
}
renderLabel(data: Menu.IRenderData) {
let content = this.formatLabel(data);
return h.div(
{
className: "lm-Menu-itemLabel p-Menu-itemLabel",
},
content,
);
}
renderSubmenu(data: Menu.IRenderData) {
return h.div({
className: "lm-Menu-itemSubmenuIcon" + " p-Menu-itemSubmenuIcon",
});
}
renderItem(data: Menu.IRenderData) {
let className = this.createItemClass(data);
let dataset = this.createItemDataset(data);
let aria = this.createItemARIA(data);
return h.li(
{
className,
dataset,
tabindex: "0",
onfocus: data.onfocus,
...aria,
},
this.renderLabel(data),
this.renderShortcut(data),
this.renderSubmenu(data),
);
}
}
// Prevent submenus from attaching outside the Shadow DOM.
// Forked from [Lumino](https://github.com/jupyterlab/lumino/blob/main/packages/widgets/src/menu.ts).
// [License](https://github.com/jupyterlab/lumino/blob/main/LICENSE)
export function openSubmenu(
submenu: Menu,
itemNode: HTMLElement,
host: HTMLElement,
): void {
const windowData = getWindowData();
let px = windowData.pageXOffset;
let py = windowData.pageYOffset;
let cw = windowData.clientWidth;
let ch = windowData.clientHeight;
const hostData = (host as any).host.getBoundingClientRect();
let hx = hostData.x;
let hy = hostData.y;
MessageLoop.sendMessage(submenu, Widget.Msg.UpdateRequest);
let maxHeight = ch;
let node = submenu.node;
let style = node.style;
style.opacity = "0";
style.maxHeight = `${maxHeight}px`;
Widget.attach(submenu, host);
let { width, height } = node.getBoundingClientRect();
let box = ElementExt.boxSizing(submenu.node);
let itemRect = itemNode.getBoundingClientRect();
let x = itemRect.right - SUBMENU_OVERLAP - hx;
if (x + width > px + cw + hx) {
x = itemRect.left + SUBMENU_OVERLAP - width;
}
let y = itemRect.top - box.borderTop - box.paddingTop - hy;
if (y + height > py + ch + hy) {
y = itemRect.bottom + box.borderBottom + box.paddingBottom - height;
}
style.transform = `translate(${Math.max(0, x)}px, ${Math.max(0, y)}px`;
style.opacity = "1";
}
export const SUBMENU_OVERLAP = 3;
let transientWindowDataCache: IWindowData | null = null;
let transientCacheCounter: number = 0;
function getWindowData(): IWindowData {
if (transientCacheCounter > 0) {
transientCacheCounter--;
return transientWindowDataCache!;
}
return _getWindowData();
}
function _getWindowData(): IWindowData {
return {
pageXOffset: window.pageXOffset,
pageYOffset: window.pageYOffset,
clientWidth: document.documentElement.clientWidth,
clientHeight: document.documentElement.clientHeight,
};
}
interface IWindowData {
pageXOffset: number;
pageYOffset: number;
clientWidth: number;
clientHeight: number;
}
@@ -0,0 +1,243 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { ArrayExt } from "@lumino/algorithm";
import { ElementExt } from "@lumino/domutils";
import { TabBar } from "@lumino/widgets";
import { TabBarItems, PerspectiveTabBarRenderer } from "./tabbarrenderer";
import { VirtualDOM } from "@lumino/virtualdom";
import { CommandRegistry } from "@lumino/commands";
import { Menu } from "@lumino/widgets";
import { PerspectiveWorkspace } from "./workspace";
import { Message } from "@lumino/messaging";
import { Title } from "@lumino/widgets";
import { Signal } from "@lumino/signaling";
import { ReadonlyJSONObject, ReadonlyJSONValue } from "@lumino/coreutils";
import { WorkspaceMenu } from "./menu";
export class PerspectiveTabBar extends TabBar<any> {
_workspace: PerspectiveWorkspace;
__content_node__?: HTMLElement;
_menu?: Menu;
__titles: string[];
constructor(workspace: PerspectiveWorkspace, options = {}) {
super(options);
this._addEventListeners();
this.__content_node__ = undefined;
this._workspace = workspace;
this.__titles = [];
}
get private_titles(): Title<any>[] {
let titles: Title<any>[] = (this as any)._titles;
return titles;
}
onUpdateRequest(msg: Message) {
// NOT INERT! This is a lumino bug fix.
// lumino/virtualdom keeps a weakmap on contentNode which is later
// reset - this causes the diff to double some elements. Memoizing
// prevent collection from the weakmap.
this.__content_node__ = this.contentNode;
this.node.style.contain = "";
// super.onUpdateRequest(msg);
let titles: Title<any>[] = (this as any)._titles;
let renderer = this.renderer;
let currentTitle = this.currentTitle!;
// Another hack. `TabBar` selects by index and I don't want to fork this
// logic, so insert empty divs until the indices match.
let content = new Array();
for (let i = 0, n = titles.length; i < n; ++i) {
let title = titles[i];
title.owner._titlebar = this;
let current = title === currentTitle;
let otherTitles = titles.filter((x) => x !== currentTitle);
let onClick;
if (otherTitles.length > 0) {
onClick = this.onClick.bind(this, otherTitles, i);
}
if (current) {
content[i] = (
renderer as unknown as PerspectiveTabBarRenderer
).renderTab(
{
title,
zIndex: titles.length + 1,
current,
} as TabBar.IRenderData<any>,
onClick,
);
} else {
content[i] = (
renderer as unknown as PerspectiveTabBarRenderer
).renderInert();
}
}
VirtualDOM.render(content, this.contentNode);
this._check_shade();
}
onClick(otherTitles: Title<any>[], index: number, event: MouseEvent) {
const commands = new CommandRegistry();
this._menu = new WorkspaceMenu(
this._workspace.menu_elem.shadowRoot!,
this._workspace.element,
{
commands,
},
);
this._menu.addClass("perspective-workspace-menu");
this._menu.dataset.minwidth = this.__titles[index];
for (const title of otherTitles) {
this._menu.addItem({
command: "tabbar:switch",
args: { title } as unknown as ReadonlyJSONObject,
});
}
commands.addCommand("tabbar:switch", {
execute: async ({ title }) => {
const psp_title = title as any as Title<any>;
const index = this.__titles.findIndex((t) => t === title);
this.currentTitle = psp_title;
(
this.tabActivateRequested as unknown as Signal<
TabBar<any>,
TabBar.ITabActivateRequestedArgs<any>
>
).emit({
index,
title: psp_title,
});
},
label: ({ title }) => {
const psp_title = title as any as Title<any>;
return psp_title.label || "untitled";
},
mnemonic: 0,
});
const box = (
(event.target as HTMLElement).shadowRoot?.querySelector(
"#status_reconnect",
) as HTMLElement
).getBoundingClientRect();
const outer_box = this._workspace.element.getBoundingClientRect();
this._menu.open(box.x - outer_box.x, box.y + box.height - outer_box.y);
this._menu.aboutToClose.connect(() => {
this._menu = undefined;
});
event.preventDefault();
event.stopPropagation();
}
_check_shade() {
if (
Array.from(this.contentNode.children).filter(
(x) =>
x.classList.contains("settings_open") &&
x.classList.contains("lm-mod-current"),
).length > 0
) {
this.contentNode.classList.add("inactive-blur");
} else {
this.contentNode.classList.remove("inactive-blur");
}
}
handleEvent(event: MouseEvent) {
this._menu?.close();
this.retargetEvent(event);
switch (event.type) {
case "contextmenu":
const widget = this.currentTitle?.owner;
let parent = widget.parent;
// TODO There is probably a better way to find the workspace
// relative to a widget command
while (parent && !(parent instanceof PerspectiveWorkspace)) {
parent = parent.parent;
}
(parent as unknown as PerspectiveWorkspace).showContextMenu(
widget,
event,
);
if (!event.shiftKey) {
event.preventDefault();
}
break;
case "mousedown":
if ((event.target as HTMLElement).id === TabBarItems.Label) {
return;
}
break;
case "pointerdown":
if ((event.target as HTMLElement).id === TabBarItems.Label) {
const tabs = this.contentNode.children;
// Find the index of the released tab.
const index = ArrayExt.findFirstIndex(tabs, (tab) => {
return ElementExt.hitTest(
tab,
event.clientX,
event.clientY,
);
});
if (index < 0) {
break;
}
const title = this.titles[index];
this._workspace._maximize(title.owner);
requestAnimationFrame(() =>
title.owner.viewer.toggleConfig(),
);
return;
}
break;
}
super.handleEvent(event);
}
/**
* Shadow dom targets events at the host, not the clicked element, which
* Lumino dislikes. This makes the event look like it is not crossing
* the ShadowDom boundary.
*
*/
retargetEvent(event: MouseEvent) {
Object.defineProperty(event, "target", {
value: event.composedPath()[0],
enumerable: true,
});
return event;
}
_addEventListeners() {
this.node.addEventListener("dblclick", this);
this.node.addEventListener("contextmenu", this);
}
}
@@ -0,0 +1,74 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { h } from "@lumino/virtualdom";
import { TabBar } from "@lumino/widgets";
export const TabBarItems = {
Config: "config",
Label: "label",
};
export const DEFAULT_TITLE = "untitled";
export class PerspectiveTabBarRenderer extends TabBar.Renderer {
maximized: boolean;
constructor(maximized: boolean) {
super();
this.maximized = maximized;
}
renderLabel(data: { title: { label?: string } }) {
return h.span(
{
className: "lm-TabBar-tabLabel",
id: TabBarItems.Label,
},
data.title.label || DEFAULT_TITLE,
);
}
renderInert() {
return h.div();
}
renderTab(
data: TabBar.IRenderData<any>,
onclick?: (this: HTMLElement, event: MouseEvent) => any,
) {
const title = data.title.caption;
const key = this.createTabKey(data);
const style = this.createTabStyle(data);
let className = this.createTabClass(data);
const dataset = this.createTabDataset(data);
const more: h.Child[] = [];
if (onclick) {
data.title.owner.setCallback(onclick);
data.title.owner.addClass("bookmarks-container");
} else {
data.title.owner.setCallback(undefined);
data.title.owner.removeClass("bookmarks-container");
}
return h.li(
{ key, className, title, style, dataset },
// this.renderDragHandle(),
);
}
// renderDragHandle() {
// return h.div({
// className: "drag-handle",
// });
// }
}
@@ -0,0 +1,107 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { Widget } from "@lumino/widgets";
import { Message } from "@lumino/messaging";
import type * as psp_viewer from "@perspective-dev/viewer";
import type * as psp from "@perspective-dev/client";
import { PerspectiveTabBar } from "./tabbar";
interface IPerspectiveViewerWidgetOptions {
node: HTMLElement;
viewer: psp_viewer.HTMLPerspectiveViewerElement;
onAttach?: () => void;
}
export class PerspectiveViewerWidget extends Widget {
viewer: psp_viewer.HTMLPerspectiveViewerElement;
_title: string;
_is_pivoted: boolean;
_restore_config?: () => Promise<void>;
_onAttach?: () => void;
_titlebar?: PerspectiveTabBar;
_deleted: boolean;
_titlebar_callback?: (event: MouseEvent) => {};
constructor({ viewer, node, onAttach }: IPerspectiveViewerWidgetOptions) {
super({ node });
this.viewer = viewer;
this._title = "";
this._is_pivoted = false;
this._onAttach = onAttach;
this._deleted = false;
}
get name(): string {
return this._title;
}
toggleConfig(): Promise<void> {
return this.viewer.toggleConfig();
}
async load(table: psp.Table | Promise<psp.Table>) {
let promises = [this.viewer.load(table)];
if (this._restore_config) {
promises.push(this._restore_config());
}
await Promise.all(promises);
}
restore(config: psp_viewer.ViewerConfigUpdate) {
this._title = config.title as string;
this.title.label = config.title as string;
this._is_pivoted = (config.group_by?.length || 0) > 0;
return this.viewer.restore({ ...config });
}
async save() {
let config = await this.viewer.save();
config["settings"] = false;
return config;
}
addClass(name: string) {
super.addClass(name);
this.viewer?.classList?.add?.(name);
}
removeClass(name: string) {
super.removeClass(name);
this.viewer?.classList?.remove?.(name);
}
setCallback(callback?: (event: MouseEvent) => {}) {
this._titlebar_callback = callback;
}
protected onAfterAttach(msg: Message) {
super.onAfterAttach(msg);
this._onAttach?.();
}
onCloseRequest(msg: Message) {
super.onCloseRequest(msg);
return (async () => {
if (this.viewer.parentElement) {
this.viewer.parentElement.removeChild(this.viewer);
}
if (!this._deleted) {
await this.viewer.delete();
this._deleted = true;
}
})();
}
}
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
Row ID,Order ID,Order Date,Ship Date,Ship Mode,Customer ID,Segment,Country,City,State,Postal Code,Region,Product ID,Category,Sub-Category,Sales,Quantity,Discount,Profit
1,CA-2013-152156,11/9/2013,11/12/2013,Second Class,CG-12520,Consumer,United States,Henderson,Kentucky,42420,South,FUR-BO-10001798,Furniture,Bookcases,261.96,2,0.0,41.9136
2,CA-2013-152156,11/9/2013,11/12/2013,Second Class,CG-12520,Consumer,United States,Henderson,Kentucky,42420,South,FUR-CH-10000454,Furniture,Chairs,731.94,3,0.0,219.582
3,CA-2013-138688,6/13/2013,6/17/2013,Second Class,DV-13045,Corporate,United States,Los Angeles,California,90036,West,OFF-LA-10000240,Office Supplies,Labels,14.62,2,0.0,6.8714
4,US-2012-108966,10/11/2012,10/18/2012,Standard Class,SO-20335,Consumer,United States,Fort Lauderdale,Florida,33311,South,FUR-TA-10000577,Furniture,Tables,957.5775,5,0.45,-383.031
5,US-2012-108966,10/11/2012,10/18/2012,Standard Class,SO-20335,Consumer,United States,Fort Lauderdale,Florida,33311,South,OFF-ST-10000760,Office Supplies,Storage,22.368,2,0.2,2.5164
6,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,FUR-FU-10001487,Furniture,Furnishings,48.86,7,0.0,14.1694
7,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,OFF-AR-10002833,Office Supplies,Art,7.28,4,0.0,1.9656
8,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,TEC-PH-10002275,Technology,Phones,907.152,6,0.2,90.7152
9,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,OFF-BI-10003910,Office Supplies,Binders,18.504,3,0.2,5.7825
10,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,OFF-AP-10002892,Office Supplies,Appliances,114.9,5,0.0,34.47
11,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,FUR-TA-10001539,Furniture,Tables,1706.184,9,0.2,85.3092
12,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,TEC-PH-10002033,Technology,Phones,911.424,4,0.2,68.3568
13,CA-2014-114412,4/16/2014,4/21/2014,Standard Class,AA-10480,Consumer,United States,Concord,North Carolina,28027,South,OFF-PA-10002365,Office Supplies,Paper,15.552,3,0.2,5.4432
14,CA-2013-161389,12/6/2013,12/11/2013,Standard Class,IM-15070,Consumer,United States,Seattle,Washington,98103,West,OFF-BI-10003656,Office Supplies,Binders,407.976,3,0.2,132.5922
15,US-2012-118983,11/22/2012,11/26/2012,Standard Class,HP-14815,Home Office,United States,Fort Worth,Texas,76106,Central,OFF-AP-10002311,Office Supplies,Appliances,68.81,5,0.8,-123.858
16,US-2012-118983,11/22/2012,11/26/2012,Standard Class,HP-14815,Home Office,United States,Fort Worth,Texas,76106,Central,OFF-BI-10000756,Office Supplies,Binders,2.544,3,0.8,-3.816
17,CA-2011-105893,11/11/2011,11/18/2011,Standard Class,PK-19075,Consumer,United States,Madison,Wisconsin,53711,Central,OFF-ST-10004186,Office Supplies,Storage,665.88,6,0.0,13.3176
18,CA-2011-167164,5/13/2011,5/15/2011,Second Class,AG-10270,Consumer,United States,West Jordan,Utah,84084,West,OFF-ST-10000107,Office Supplies,Storage,55.5,2,0.0,9.99
19,CA-2011-143336,8/27/2011,9/1/2011,Second Class,ZD-21925,Consumer,United States,San Francisco,California,94109,West,OFF-AR-10003056,Office Supplies,Art,8.56,2,0.0,2.4824
20,CA-2011-143336,8/27/2011,9/1/2011,Second Class,ZD-21925,Consumer,United States,San Francisco,California,94109,West,TEC-PH-10001949,Technology,Phones,213.48,3,0.2,16.011
21,CA-2011-143336,8/27/2011,9/1/2011,Second Class,ZD-21925,Consumer,United States,San Francisco,California,94109,West,OFF-BI-10002215,Office Supplies,Binders,22.72,4,0.2,7.384
22,CA-2013-137330,12/10/2013,12/14/2013,Standard Class,KB-16585,Corporate,United States,Fremont,Nebraska,68025,Central,OFF-AR-10000246,Office Supplies,Art,19.46,7,0.0,5.0596
23,CA-2013-137330,12/10/2013,12/14/2013,Standard Class,KB-16585,Corporate,United States,Fremont,Nebraska,68025,Central,OFF-AP-10001492,Office Supplies,Appliances,60.34,7,0.0,15.6884
24,US-2014-156909,7/17/2014,7/19/2014,Second Class,SF-20065,Consumer,United States,Philadelphia,Pennsylvania,19140,East,FUR-CH-10002774,Furniture,Chairs,71.372,2,0.3,-1.0196
25,CA-2012-106320,9/25/2012,9/30/2012,Standard Class,EB-13870,Consumer,United States,Orem,Utah,84057,West,FUR-TA-10000577,Furniture,Tables,1044.63,3,0.0,240.2649
26,CA-2013-121755,1/16/2013,1/20/2013,Second Class,EH-13945,Consumer,United States,Los Angeles,California,90049,West,OFF-BI-10001634,Office Supplies,Binders,11.648,2,0.2,4.2224
27,CA-2013-121755,1/16/2013,1/20/2013,Second Class,EH-13945,Consumer,United States,Los Angeles,California,90049,West,TEC-AC-10003027,Technology,Accessories,90.57,3,0.0,11.7741
28,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,FUR-BO-10004834,Furniture,Bookcases,3083.43,7,0.5,-1665.0522
29,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-BI-10000474,Office Supplies,Binders,9.618,2,0.7,-7.0532
30,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,FUR-FU-10004848,Furniture,Furnishings,124.2,3,0.2,15.525
31,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-EN-10001509,Office Supplies,Envelopes,3.264,2,0.2,1.1016
32,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-AR-10004042,Office Supplies,Art,86.304,6,0.2,9.7092
33,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-BI-10001525,Office Supplies,Binders,6.858,6,0.7,-5.715
34,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-AR-10001683,Office Supplies,Art,15.76,2,0.2,3.546
35,CA-2014-107727,10/20/2014,10/24/2014,Second Class,MA-17560,Home Office,United States,Houston,Texas,77095,Central,OFF-PA-10000249,Office Supplies,Paper,29.472,3,0.2,9.9468
36,CA-2013-117590,12/9/2013,12/11/2013,First Class,GH-14485,Corporate,United States,Richardson,Texas,75080,Central,TEC-PH-10004977,Technology,Phones,1097.544,7,0.2,123.4737
37,CA-2013-117590,12/9/2013,12/11/2013,First Class,GH-14485,Corporate,United States,Richardson,Texas,75080,Central,FUR-FU-10003664,Furniture,Furnishings,190.92,5,0.6,-147.963
38,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,OFF-EN-10002986,Office Supplies,Envelopes,113.328,9,0.2,35.415
39,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,FUR-BO-10002545,Furniture,Bookcases,532.3992,3,0.32,-46.9764
40,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,FUR-CH-10004218,Furniture,Chairs,212.058,3,0.3,-15.147
41,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,TEC-PH-10000486,Technology,Phones,371.168,4,0.2,41.7564
42,CA-2014-120999,9/11/2014,9/16/2014,Standard Class,LC-16930,Corporate,United States,Naperville,Illinois,60540,Central,TEC-PH-10004093,Technology,Phones,147.168,4,0.2,16.5564
43,CA-2013-101343,7/18/2013,7/23/2013,Standard Class,RA-19885,Corporate,United States,Los Angeles,California,90049,West,OFF-ST-10003479,Office Supplies,Storage,77.88,2,0.0,3.894
44,CA-2014-139619,9/20/2014,9/24/2014,Standard Class,ES-14080,Corporate,United States,Melbourne,Florida,32935,South,OFF-ST-10003282,Office Supplies,Storage,95.616,2,0.2,9.5616
45,CA-2013-118255,3/12/2013,3/14/2013,First Class,ON-18715,Corporate,United States,Eagan,Minnesota,55122,Central,TEC-AC-10000171,Technology,Accessories,45.98,2,0.0,19.7714
46,CA-2013-118255,3/12/2013,3/14/2013,First Class,ON-18715,Corporate,United States,Eagan,Minnesota,55122,Central,OFF-BI-10003291,Office Supplies,Binders,17.46,2,0.0,8.2062
47,CA-2011-146703,10/20/2011,10/25/2011,Second Class,PO-18865,Consumer,United States,Westland,Michigan,48185,Central,OFF-ST-10001713,Office Supplies,Storage,211.96,4,0.0,8.4784
48,CA-2013-169194,6/21/2013,6/26/2013,Standard Class,LH-16900,Consumer,United States,Dover,Delaware,19901,East,TEC-AC-10002167,Technology,Accessories,45.0,3,0.0,4.95
49,CA-2013-169194,6/21/2013,6/26/2013,Standard Class,LH-16900,Consumer,United States,Dover,Delaware,19901,East,TEC-PH-10003988,Technology,Phones,21.8,2,0.0,6.104
50,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,OFF-BI-10004410,Office Supplies,Binders,38.22,6,0.0,17.9634
51,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,OFF-LA-10002762,Office Supplies,Labels,75.18,6,0.0,35.3346
52,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,FUR-FU-10001706,Furniture,Furnishings,6.16,2,0.0,2.9568
53,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,FUR-CH-10003061,Furniture,Chairs,89.99,1,0.0,17.0981
54,CA-2013-105816,12/12/2013,12/18/2013,Standard Class,JM-15265,Corporate,United States,New York City,New York,10024,East,OFF-FA-10000304,Office Supplies,Fasteners,15.26,7,0.0,6.2566
55,CA-2013-105816,12/12/2013,12/18/2013,Standard Class,JM-15265,Corporate,United States,New York City,New York,10024,East,TEC-PH-10002447,Technology,Phones,1029.95,5,0.0,298.6855
56,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-ST-10000604,Office Supplies,Storage,208.56,6,0.0,52.14
57,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-PA-10001569,Office Supplies,Paper,32.4,5,0.0,15.552
58,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,FUR-CH-10003968,Furniture,Chairs,319.41,5,0.1,7.098
59,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-PA-10000587,Office Supplies,Paper,14.56,2,0.0,6.9888
60,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,TEC-AC-10002167,Technology,Accessories,30.0,2,0.0,3.3
61,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-BI-10001460,Office Supplies,Binders,48.48,4,0.2,16.362
62,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-AR-10001868,Office Supplies,Art,1.68,1,0.0,0.84
63,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,TEC-AC-10004633,Technology,Accessories,13.98,2,0.0,6.1512
64,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,OFF-BI-10001078,Office Supplies,Binders,25.824,6,0.2,9.3612
65,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,OFF-PA-10003892,Office Supplies,Paper,146.73,3,0.0,68.9631
66,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,FUR-FU-10000397,Furniture,Furnishings,79.76,4,0.0,22.3328
67,US-2012-164175,4/30/2012,5/5/2012,Standard Class,PS-18970,Home Office,United States,Chicago,Illinois,60610,Central,FUR-CH-10001146,Furniture,Chairs,213.115,5,0.3,-15.2225
68,CA-2011-106376,12/5/2011,12/10/2011,Standard Class,BS-11590,Corporate,United States,Gilbert,Arizona,85234,West,OFF-AR-10002671,Office Supplies,Art,1113.024,8,0.2,111.3024
69,CA-2011-106376,12/5/2011,12/10/2011,Standard Class,BS-11590,Corporate,United States,Gilbert,Arizona,85234,West,TEC-PH-10002726,Technology,Phones,167.968,4,0.2,62.988
70,CA-2013-119823,6/5/2013,6/7/2013,First Class,KD-16270,Consumer,United States,Springfield,Virginia,22153,South,OFF-PA-10000482,Office Supplies,Paper,75.88,2,0.0,35.6636
71,CA-2013-106075,9/19/2013,9/24/2013,Standard Class,HM-14980,Consumer,United States,New York City,New York,10009,East,OFF-BI-10004654,Office Supplies,Binders,4.616,1,0.2,1.731
72,CA-2014-114440,9/15/2014,9/18/2014,Second Class,TB-21520,Consumer,United States,Jackson,Michigan,49201,Central,OFF-PA-10004675,Office Supplies,Paper,19.05,3,0.0,8.763
73,US-2012-134026,4/26/2012,5/2/2012,Standard Class,JE-15745,Consumer,United States,Memphis,Tennessee,38109,South,FUR-CH-10000513,Furniture,Chairs,831.936,8,0.2,-114.3912
74,US-2012-134026,4/26/2012,5/2/2012,Standard Class,JE-15745,Consumer,United States,Memphis,Tennessee,38109,South,FUR-FU-10003708,Furniture,Furnishings,97.04,2,0.2,1.213
75,US-2012-134026,4/26/2012,5/2/2012,Standard Class,JE-15745,Consumer,United States,Memphis,Tennessee,38109,South,OFF-ST-10004123,Office Supplies,Storage,72.784,1,0.2,-18.196
76,US-2014-118038,12/10/2014,12/12/2014,First Class,KB-16600,Corporate,United States,Houston,Texas,77041,Central,OFF-BI-10004182,Office Supplies,Binders,1.248,3,0.8,-1.9344
77,US-2014-118038,12/10/2014,12/12/2014,First Class,KB-16600,Corporate,United States,Houston,Texas,77041,Central,FUR-FU-10000260,Furniture,Furnishings,9.708,3,0.6,-5.8248
78,US-2014-118038,12/10/2014,12/12/2014,First Class,KB-16600,Corporate,United States,Houston,Texas,77041,Central,OFF-ST-10000615,Office Supplies,Storage,27.24,3,0.2,2.724
79,US-2011-147606,11/26/2011,12/1/2011,Second Class,JE-15745,Consumer,United States,Houston,Texas,77070,Central,FUR-FU-10003194,Furniture,Furnishings,19.3,5,0.6,-14.475
80,CA-2013-127208,6/13/2013,6/16/2013,First Class,SC-20770,Corporate,United States,Decatur,Alabama,35601,South,OFF-AP-10002118,Office Supplies,Appliances,208.16,1,0.0,56.2032
81,CA-2013-127208,6/13/2013,6/16/2013,First Class,SC-20770,Corporate,United States,Decatur,Alabama,35601,South,OFF-BI-10002309,Office Supplies,Binders,16.74,3,0.0,8.0352
82,CA-2011-139451,10/12/2011,10/16/2011,Standard Class,DN-13690,Consumer,United States,San Francisco,California,94122,West,OFF-AR-10002053,Office Supplies,Art,14.9,5,0.0,4.172
83,CA-2011-139451,10/12/2011,10/16/2011,Standard Class,DN-13690,Consumer,United States,San Francisco,California,94122,West,OFF-ST-10002370,Office Supplies,Storage,21.39,1,0.0,6.2031
84,CA-2012-149734,9/3/2012,9/8/2012,Standard Class,JC-16105,Corporate,United States,Durham,North Carolina,27707,South,OFF-EN-10000927,Office Supplies,Envelopes,200.984,7,0.2,62.8075
85,US-2014-119662,11/14/2014,11/17/2014,First Class,CS-12400,Home Office,United States,Chicago,Illinois,60623,Central,OFF-ST-10003656,Office Supplies,Storage,230.376,3,0.2,-48.9549
86,CA-2014-140088,5/29/2014,5/31/2014,Second Class,PO-18865,Consumer,United States,Columbia,South Carolina,29203,South,FUR-CH-10000863,Furniture,Chairs,301.96,2,0.0,33.2156
87,CA-2014-155558,10/27/2014,11/3/2014,Standard Class,PG-18895,Consumer,United States,Rochester,Minnesota,55901,Central,TEC-AC-10001998,Technology,Accessories,19.99,1,0.0,6.7966
88,CA-2014-155558,10/27/2014,11/3/2014,Standard Class,PG-18895,Consumer,United States,Rochester,Minnesota,55901,Central,OFF-LA-10000134,Office Supplies,Labels,6.16,2,0.0,2.9568
89,CA-2013-159695,4/6/2013,4/11/2013,Second Class,GM-14455,Home Office,United States,Houston,Texas,77095,Central,OFF-ST-10003442,Office Supplies,Storage,158.368,7,0.2,13.8572
90,CA-2013-109806,9/18/2013,9/23/2013,Standard Class,JS-15685,Corporate,United States,Los Angeles,California,90036,West,OFF-AR-10004930,Office Supplies,Art,20.1,3,0.0,6.633
91,CA-2013-109806,9/18/2013,9/23/2013,Standard Class,JS-15685,Corporate,United States,Los Angeles,California,90036,West,TEC-PH-10004093,Technology,Phones,73.584,2,0.2,8.2782
92,CA-2013-109806,9/18/2013,9/23/2013,Standard Class,JS-15685,Corporate,United States,Los Angeles,California,90036,West,OFF-PA-10000304,Office Supplies,Paper,6.48,1,0.0,3.1104
93,CA-2012-149587,1/31/2012,2/5/2012,Second Class,KB-16315,Consumer,United States,Minneapolis,Minnesota,55407,Central,OFF-PA-10003177,Office Supplies,Paper,12.96,2,0.0,6.2208
94,CA-2012-149587,1/31/2012,2/5/2012,Second Class,KB-16315,Consumer,United States,Minneapolis,Minnesota,55407,Central,FUR-FU-10003799,Furniture,Furnishings,53.34,3,0.0,16.5354
95,CA-2012-149587,1/31/2012,2/5/2012,Second Class,KB-16315,Consumer,United States,Minneapolis,Minnesota,55407,Central,OFF-BI-10002852,Office Supplies,Binders,32.96,2,0.0,16.1504
96,US-2014-109484,11/7/2014,11/13/2014,Standard Class,RB-19705,Home Office,United States,Portland,Oregon,97206,West,OFF-BI-10004738,Office Supplies,Binders,5.682,1,0.7,-3.788
97,CA-2014-161018,11/10/2014,11/12/2014,Second Class,PN-18775,Home Office,United States,New York City,New York,10009,East,FUR-FU-10000629,Furniture,Furnishings,96.53,7,0.0,40.5426
98,CA-2014-157833,6/18/2014,6/21/2014,First Class,KD-16345,Consumer,United States,San Francisco,California,94122,West,OFF-BI-10001721,Office Supplies,Binders,51.312,3,0.2,17.9592
99,CA-2013-149223,9/7/2013,9/12/2013,Standard Class,ER-13855,Corporate,United States,Saint Paul,Minnesota,55106,Central,OFF-AP-10000358,Office Supplies,Appliances,77.88,6,0.0,22.5852
1 Row ID Order ID Order Date Ship Date Ship Mode Customer ID Segment Country City State Postal Code Region Product ID Category Sub-Category Sales Quantity Discount Profit
2 1 CA-2013-152156 11/9/2013 11/12/2013 Second Class CG-12520 Consumer United States Henderson Kentucky 42420 South FUR-BO-10001798 Furniture Bookcases 261.96 2 0.0 41.9136
3 2 CA-2013-152156 11/9/2013 11/12/2013 Second Class CG-12520 Consumer United States Henderson Kentucky 42420 South FUR-CH-10000454 Furniture Chairs 731.94 3 0.0 219.582
4 3 CA-2013-138688 6/13/2013 6/17/2013 Second Class DV-13045 Corporate United States Los Angeles California 90036 West OFF-LA-10000240 Office Supplies Labels 14.62 2 0.0 6.8714
5 4 US-2012-108966 10/11/2012 10/18/2012 Standard Class SO-20335 Consumer United States Fort Lauderdale Florida 33311 South FUR-TA-10000577 Furniture Tables 957.5775 5 0.45 -383.031
6 5 US-2012-108966 10/11/2012 10/18/2012 Standard Class SO-20335 Consumer United States Fort Lauderdale Florida 33311 South OFF-ST-10000760 Office Supplies Storage 22.368 2 0.2 2.5164
7 6 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West FUR-FU-10001487 Furniture Furnishings 48.86 7 0.0 14.1694
8 7 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West OFF-AR-10002833 Office Supplies Art 7.28 4 0.0 1.9656
9 8 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West TEC-PH-10002275 Technology Phones 907.152 6 0.2 90.7152
10 9 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West OFF-BI-10003910 Office Supplies Binders 18.504 3 0.2 5.7825
11 10 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West OFF-AP-10002892 Office Supplies Appliances 114.9 5 0.0 34.47
12 11 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West FUR-TA-10001539 Furniture Tables 1706.184 9 0.2 85.3092
13 12 CA-2011-115812 6/9/2011 6/14/2011 Standard Class BH-11710 Consumer United States Los Angeles California 90032 West TEC-PH-10002033 Technology Phones 911.424 4 0.2 68.3568
14 13 CA-2014-114412 4/16/2014 4/21/2014 Standard Class AA-10480 Consumer United States Concord North Carolina 28027 South OFF-PA-10002365 Office Supplies Paper 15.552 3 0.2 5.4432
15 14 CA-2013-161389 12/6/2013 12/11/2013 Standard Class IM-15070 Consumer United States Seattle Washington 98103 West OFF-BI-10003656 Office Supplies Binders 407.976 3 0.2 132.5922
16 15 US-2012-118983 11/22/2012 11/26/2012 Standard Class HP-14815 Home Office United States Fort Worth Texas 76106 Central OFF-AP-10002311 Office Supplies Appliances 68.81 5 0.8 -123.858
17 16 US-2012-118983 11/22/2012 11/26/2012 Standard Class HP-14815 Home Office United States Fort Worth Texas 76106 Central OFF-BI-10000756 Office Supplies Binders 2.544 3 0.8 -3.816
18 17 CA-2011-105893 11/11/2011 11/18/2011 Standard Class PK-19075 Consumer United States Madison Wisconsin 53711 Central OFF-ST-10004186 Office Supplies Storage 665.88 6 0.0 13.3176
19 18 CA-2011-167164 5/13/2011 5/15/2011 Second Class AG-10270 Consumer United States West Jordan Utah 84084 West OFF-ST-10000107 Office Supplies Storage 55.5 2 0.0 9.99
20 19 CA-2011-143336 8/27/2011 9/1/2011 Second Class ZD-21925 Consumer United States San Francisco California 94109 West OFF-AR-10003056 Office Supplies Art 8.56 2 0.0 2.4824
21 20 CA-2011-143336 8/27/2011 9/1/2011 Second Class ZD-21925 Consumer United States San Francisco California 94109 West TEC-PH-10001949 Technology Phones 213.48 3 0.2 16.011
22 21 CA-2011-143336 8/27/2011 9/1/2011 Second Class ZD-21925 Consumer United States San Francisco California 94109 West OFF-BI-10002215 Office Supplies Binders 22.72 4 0.2 7.384
23 22 CA-2013-137330 12/10/2013 12/14/2013 Standard Class KB-16585 Corporate United States Fremont Nebraska 68025 Central OFF-AR-10000246 Office Supplies Art 19.46 7 0.0 5.0596
24 23 CA-2013-137330 12/10/2013 12/14/2013 Standard Class KB-16585 Corporate United States Fremont Nebraska 68025 Central OFF-AP-10001492 Office Supplies Appliances 60.34 7 0.0 15.6884
25 24 US-2014-156909 7/17/2014 7/19/2014 Second Class SF-20065 Consumer United States Philadelphia Pennsylvania 19140 East FUR-CH-10002774 Furniture Chairs 71.372 2 0.3 -1.0196
26 25 CA-2012-106320 9/25/2012 9/30/2012 Standard Class EB-13870 Consumer United States Orem Utah 84057 West FUR-TA-10000577 Furniture Tables 1044.63 3 0.0 240.2649
27 26 CA-2013-121755 1/16/2013 1/20/2013 Second Class EH-13945 Consumer United States Los Angeles California 90049 West OFF-BI-10001634 Office Supplies Binders 11.648 2 0.2 4.2224
28 27 CA-2013-121755 1/16/2013 1/20/2013 Second Class EH-13945 Consumer United States Los Angeles California 90049 West TEC-AC-10003027 Technology Accessories 90.57 3 0.0 11.7741
29 28 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East FUR-BO-10004834 Furniture Bookcases 3083.43 7 0.5 -1665.0522
30 29 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East OFF-BI-10000474 Office Supplies Binders 9.618 2 0.7 -7.0532
31 30 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East FUR-FU-10004848 Furniture Furnishings 124.2 3 0.2 15.525
32 31 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East OFF-EN-10001509 Office Supplies Envelopes 3.264 2 0.2 1.1016
33 32 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East OFF-AR-10004042 Office Supplies Art 86.304 6 0.2 9.7092
34 33 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East OFF-BI-10001525 Office Supplies Binders 6.858 6 0.7 -5.715
35 34 US-2012-150630 9/17/2012 9/21/2012 Standard Class TB-21520 Consumer United States Philadelphia Pennsylvania 19140 East OFF-AR-10001683 Office Supplies Art 15.76 2 0.2 3.546
36 35 CA-2014-107727 10/20/2014 10/24/2014 Second Class MA-17560 Home Office United States Houston Texas 77095 Central OFF-PA-10000249 Office Supplies Paper 29.472 3 0.2 9.9468
37 36 CA-2013-117590 12/9/2013 12/11/2013 First Class GH-14485 Corporate United States Richardson Texas 75080 Central TEC-PH-10004977 Technology Phones 1097.544 7 0.2 123.4737
38 37 CA-2013-117590 12/9/2013 12/11/2013 First Class GH-14485 Corporate United States Richardson Texas 75080 Central FUR-FU-10003664 Furniture Furnishings 190.92 5 0.6 -147.963
39 38 CA-2012-117415 12/27/2012 12/31/2012 Standard Class SN-20710 Home Office United States Houston Texas 77041 Central OFF-EN-10002986 Office Supplies Envelopes 113.328 9 0.2 35.415
40 39 CA-2012-117415 12/27/2012 12/31/2012 Standard Class SN-20710 Home Office United States Houston Texas 77041 Central FUR-BO-10002545 Furniture Bookcases 532.3992 3 0.32 -46.9764
41 40 CA-2012-117415 12/27/2012 12/31/2012 Standard Class SN-20710 Home Office United States Houston Texas 77041 Central FUR-CH-10004218 Furniture Chairs 212.058 3 0.3 -15.147
42 41 CA-2012-117415 12/27/2012 12/31/2012 Standard Class SN-20710 Home Office United States Houston Texas 77041 Central TEC-PH-10000486 Technology Phones 371.168 4 0.2 41.7564
43 42 CA-2014-120999 9/11/2014 9/16/2014 Standard Class LC-16930 Corporate United States Naperville Illinois 60540 Central TEC-PH-10004093 Technology Phones 147.168 4 0.2 16.5564
44 43 CA-2013-101343 7/18/2013 7/23/2013 Standard Class RA-19885 Corporate United States Los Angeles California 90049 West OFF-ST-10003479 Office Supplies Storage 77.88 2 0.0 3.894
45 44 CA-2014-139619 9/20/2014 9/24/2014 Standard Class ES-14080 Corporate United States Melbourne Florida 32935 South OFF-ST-10003282 Office Supplies Storage 95.616 2 0.2 9.5616
46 45 CA-2013-118255 3/12/2013 3/14/2013 First Class ON-18715 Corporate United States Eagan Minnesota 55122 Central TEC-AC-10000171 Technology Accessories 45.98 2 0.0 19.7714
47 46 CA-2013-118255 3/12/2013 3/14/2013 First Class ON-18715 Corporate United States Eagan Minnesota 55122 Central OFF-BI-10003291 Office Supplies Binders 17.46 2 0.0 8.2062
48 47 CA-2011-146703 10/20/2011 10/25/2011 Second Class PO-18865 Consumer United States Westland Michigan 48185 Central OFF-ST-10001713 Office Supplies Storage 211.96 4 0.0 8.4784
49 48 CA-2013-169194 6/21/2013 6/26/2013 Standard Class LH-16900 Consumer United States Dover Delaware 19901 East TEC-AC-10002167 Technology Accessories 45.0 3 0.0 4.95
50 49 CA-2013-169194 6/21/2013 6/26/2013 Standard Class LH-16900 Consumer United States Dover Delaware 19901 East TEC-PH-10003988 Technology Phones 21.8 2 0.0 6.104
51 50 CA-2012-115742 4/18/2012 4/22/2012 Standard Class DP-13000 Consumer United States New Albany Indiana 47150 Central OFF-BI-10004410 Office Supplies Binders 38.22 6 0.0 17.9634
52 51 CA-2012-115742 4/18/2012 4/22/2012 Standard Class DP-13000 Consumer United States New Albany Indiana 47150 Central OFF-LA-10002762 Office Supplies Labels 75.18 6 0.0 35.3346
53 52 CA-2012-115742 4/18/2012 4/22/2012 Standard Class DP-13000 Consumer United States New Albany Indiana 47150 Central FUR-FU-10001706 Furniture Furnishings 6.16 2 0.0 2.9568
54 53 CA-2012-115742 4/18/2012 4/22/2012 Standard Class DP-13000 Consumer United States New Albany Indiana 47150 Central FUR-CH-10003061 Furniture Chairs 89.99 1 0.0 17.0981
55 54 CA-2013-105816 12/12/2013 12/18/2013 Standard Class JM-15265 Corporate United States New York City New York 10024 East OFF-FA-10000304 Office Supplies Fasteners 15.26 7 0.0 6.2566
56 55 CA-2013-105816 12/12/2013 12/18/2013 Standard Class JM-15265 Corporate United States New York City New York 10024 East TEC-PH-10002447 Technology Phones 1029.95 5 0.0 298.6855
57 56 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East OFF-ST-10000604 Office Supplies Storage 208.56 6 0.0 52.14
58 57 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East OFF-PA-10001569 Office Supplies Paper 32.4 5 0.0 15.552
59 58 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East FUR-CH-10003968 Furniture Chairs 319.41 5 0.1 7.098
60 59 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East OFF-PA-10000587 Office Supplies Paper 14.56 2 0.0 6.9888
61 60 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East TEC-AC-10002167 Technology Accessories 30.0 2 0.0 3.3
62 61 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East OFF-BI-10001460 Office Supplies Binders 48.48 4 0.2 16.362
63 62 CA-2013-111682 6/18/2013 6/19/2013 First Class TB-21055 Consumer United States Troy New York 12180 East OFF-AR-10001868 Office Supplies Art 1.68 1 0.0 0.84
64 63 CA-2012-135545 11/24/2012 11/30/2012 Standard Class KM-16720 Consumer United States Los Angeles California 90004 West TEC-AC-10004633 Technology Accessories 13.98 2 0.0 6.1512
65 64 CA-2012-135545 11/24/2012 11/30/2012 Standard Class KM-16720 Consumer United States Los Angeles California 90004 West OFF-BI-10001078 Office Supplies Binders 25.824 6 0.2 9.3612
66 65 CA-2012-135545 11/24/2012 11/30/2012 Standard Class KM-16720 Consumer United States Los Angeles California 90004 West OFF-PA-10003892 Office Supplies Paper 146.73 3 0.0 68.9631
67 66 CA-2012-135545 11/24/2012 11/30/2012 Standard Class KM-16720 Consumer United States Los Angeles California 90004 West FUR-FU-10000397 Furniture Furnishings 79.76 4 0.0 22.3328
68 67 US-2012-164175 4/30/2012 5/5/2012 Standard Class PS-18970 Home Office United States Chicago Illinois 60610 Central FUR-CH-10001146 Furniture Chairs 213.115 5 0.3 -15.2225
69 68 CA-2011-106376 12/5/2011 12/10/2011 Standard Class BS-11590 Corporate United States Gilbert Arizona 85234 West OFF-AR-10002671 Office Supplies Art 1113.024 8 0.2 111.3024
70 69 CA-2011-106376 12/5/2011 12/10/2011 Standard Class BS-11590 Corporate United States Gilbert Arizona 85234 West TEC-PH-10002726 Technology Phones 167.968 4 0.2 62.988
71 70 CA-2013-119823 6/5/2013 6/7/2013 First Class KD-16270 Consumer United States Springfield Virginia 22153 South OFF-PA-10000482 Office Supplies Paper 75.88 2 0.0 35.6636
72 71 CA-2013-106075 9/19/2013 9/24/2013 Standard Class HM-14980 Consumer United States New York City New York 10009 East OFF-BI-10004654 Office Supplies Binders 4.616 1 0.2 1.731
73 72 CA-2014-114440 9/15/2014 9/18/2014 Second Class TB-21520 Consumer United States Jackson Michigan 49201 Central OFF-PA-10004675 Office Supplies Paper 19.05 3 0.0 8.763
74 73 US-2012-134026 4/26/2012 5/2/2012 Standard Class JE-15745 Consumer United States Memphis Tennessee 38109 South FUR-CH-10000513 Furniture Chairs 831.936 8 0.2 -114.3912
75 74 US-2012-134026 4/26/2012 5/2/2012 Standard Class JE-15745 Consumer United States Memphis Tennessee 38109 South FUR-FU-10003708 Furniture Furnishings 97.04 2 0.2 1.213
76 75 US-2012-134026 4/26/2012 5/2/2012 Standard Class JE-15745 Consumer United States Memphis Tennessee 38109 South OFF-ST-10004123 Office Supplies Storage 72.784 1 0.2 -18.196
77 76 US-2014-118038 12/10/2014 12/12/2014 First Class KB-16600 Corporate United States Houston Texas 77041 Central OFF-BI-10004182 Office Supplies Binders 1.248 3 0.8 -1.9344
78 77 US-2014-118038 12/10/2014 12/12/2014 First Class KB-16600 Corporate United States Houston Texas 77041 Central FUR-FU-10000260 Furniture Furnishings 9.708 3 0.6 -5.8248
79 78 US-2014-118038 12/10/2014 12/12/2014 First Class KB-16600 Corporate United States Houston Texas 77041 Central OFF-ST-10000615 Office Supplies Storage 27.24 3 0.2 2.724
80 79 US-2011-147606 11/26/2011 12/1/2011 Second Class JE-15745 Consumer United States Houston Texas 77070 Central FUR-FU-10003194 Furniture Furnishings 19.3 5 0.6 -14.475
81 80 CA-2013-127208 6/13/2013 6/16/2013 First Class SC-20770 Corporate United States Decatur Alabama 35601 South OFF-AP-10002118 Office Supplies Appliances 208.16 1 0.0 56.2032
82 81 CA-2013-127208 6/13/2013 6/16/2013 First Class SC-20770 Corporate United States Decatur Alabama 35601 South OFF-BI-10002309 Office Supplies Binders 16.74 3 0.0 8.0352
83 82 CA-2011-139451 10/12/2011 10/16/2011 Standard Class DN-13690 Consumer United States San Francisco California 94122 West OFF-AR-10002053 Office Supplies Art 14.9 5 0.0 4.172
84 83 CA-2011-139451 10/12/2011 10/16/2011 Standard Class DN-13690 Consumer United States San Francisco California 94122 West OFF-ST-10002370 Office Supplies Storage 21.39 1 0.0 6.2031
85 84 CA-2012-149734 9/3/2012 9/8/2012 Standard Class JC-16105 Corporate United States Durham North Carolina 27707 South OFF-EN-10000927 Office Supplies Envelopes 200.984 7 0.2 62.8075
86 85 US-2014-119662 11/14/2014 11/17/2014 First Class CS-12400 Home Office United States Chicago Illinois 60623 Central OFF-ST-10003656 Office Supplies Storage 230.376 3 0.2 -48.9549
87 86 CA-2014-140088 5/29/2014 5/31/2014 Second Class PO-18865 Consumer United States Columbia South Carolina 29203 South FUR-CH-10000863 Furniture Chairs 301.96 2 0.0 33.2156
88 87 CA-2014-155558 10/27/2014 11/3/2014 Standard Class PG-18895 Consumer United States Rochester Minnesota 55901 Central TEC-AC-10001998 Technology Accessories 19.99 1 0.0 6.7966
89 88 CA-2014-155558 10/27/2014 11/3/2014 Standard Class PG-18895 Consumer United States Rochester Minnesota 55901 Central OFF-LA-10000134 Office Supplies Labels 6.16 2 0.0 2.9568
90 89 CA-2013-159695 4/6/2013 4/11/2013 Second Class GM-14455 Home Office United States Houston Texas 77095 Central OFF-ST-10003442 Office Supplies Storage 158.368 7 0.2 13.8572
91 90 CA-2013-109806 9/18/2013 9/23/2013 Standard Class JS-15685 Corporate United States Los Angeles California 90036 West OFF-AR-10004930 Office Supplies Art 20.1 3 0.0 6.633
92 91 CA-2013-109806 9/18/2013 9/23/2013 Standard Class JS-15685 Corporate United States Los Angeles California 90036 West TEC-PH-10004093 Technology Phones 73.584 2 0.2 8.2782
93 92 CA-2013-109806 9/18/2013 9/23/2013 Standard Class JS-15685 Corporate United States Los Angeles California 90036 West OFF-PA-10000304 Office Supplies Paper 6.48 1 0.0 3.1104
94 93 CA-2012-149587 1/31/2012 2/5/2012 Second Class KB-16315 Consumer United States Minneapolis Minnesota 55407 Central OFF-PA-10003177 Office Supplies Paper 12.96 2 0.0 6.2208
95 94 CA-2012-149587 1/31/2012 2/5/2012 Second Class KB-16315 Consumer United States Minneapolis Minnesota 55407 Central FUR-FU-10003799 Furniture Furnishings 53.34 3 0.0 16.5354
96 95 CA-2012-149587 1/31/2012 2/5/2012 Second Class KB-16315 Consumer United States Minneapolis Minnesota 55407 Central OFF-BI-10002852 Office Supplies Binders 32.96 2 0.0 16.1504
97 96 US-2014-109484 11/7/2014 11/13/2014 Standard Class RB-19705 Home Office United States Portland Oregon 97206 West OFF-BI-10004738 Office Supplies Binders 5.682 1 0.7 -3.788
98 97 CA-2014-161018 11/10/2014 11/12/2014 Second Class PN-18775 Home Office United States New York City New York 10009 East FUR-FU-10000629 Furniture Furnishings 96.53 7 0.0 40.5426
99 98 CA-2014-157833 6/18/2014 6/21/2014 First Class KD-16345 Consumer United States San Francisco California 94122 West OFF-BI-10001721 Office Supplies Binders 51.312 3 0.2 17.9592
100 99 CA-2013-149223 9/7/2013 9/12/2013 Standard Class ER-13855 Corporate United States Saint Paul Minnesota 55106 Central OFF-AP-10000358 Office Supplies Appliances 77.88 6 0.0 22.5852
@@ -0,0 +1,136 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
});
test.describe("Context menu", () => {
test("shows tables in the New Table submenu", async ({ page }) => {
await page.evaluate(async () => {
await window.__WORKER__.table("x\n1\n2\n", {
name: "test_table_1",
});
await window.__WORKER__.table("y\n3\n4\n", {
name: "test_table_2",
});
});
// Right-click on the workspace to open context menu
const workspace = page.locator("perspective-workspace");
await workspace.click({
button: "right",
position: { x: 100, y: 100 },
});
// Wait for the context menu to appear in the shadow DOM
const shadowHost = page.locator("perspective-workspace");
const menu = shadowHost.locator(".lm-Menu").first();
await expect(menu).toBeVisible();
// Click on "New Table" to open the submenu
const newTableItem = menu.locator(
".lm-Menu-item:has(.lm-Menu-itemLabel:text('New Table'))",
);
await newTableItem.hover();
// Wait for submenu to appear
const submenu = shadowHost.locator(".lm-Menu").nth(1);
await expect(submenu).toBeVisible();
// Get the submenu content with table entries
const submenuContent = submenu.locator(".lm-Menu-content");
// Wait for the table items to be populated (they are added asynchronously)
// We expect at least our two test tables plus the default "superstore" table
await expect(submenuContent.locator("> .lm-Menu-item")).toHaveCount(3, {
timeout: 5000,
});
const menuItems = submenuContent.locator("> .lm-Menu-item");
const itemCount = await menuItems.count();
// Get the labels of the first items (the tables)
const labels = [];
for (let i = 0; i < itemCount; i++) {
const label = await menuItems
.nth(i)
.locator(".lm-Menu-itemLabel")
.textContent();
labels.push(label);
}
// Verify our test tables appear in the menu
expect(labels).toContain("test_table_1");
expect(labels).toContain("test_table_2");
expect(labels).toContain("superstore");
});
test("context menu table entries have correct structure", async ({
page,
}) => {
// Create two tables and load the workspace
await page.evaluate(async () => {
await window.__WORKER__.table("a\n1\n", { name: "alpha" });
await window.__WORKER__.table("b\n2\n", { name: "beta" });
});
// Right-click on the workspace
const workspace = page.locator("perspective-workspace");
await workspace.click({
button: "right",
position: { x: 100, y: 100 },
});
// Open the "New Table" submenu
const shadowHost = page.locator("perspective-workspace");
const menu = shadowHost.locator(".lm-Menu").first();
await expect(menu).toBeVisible();
const newTableItem = menu.locator(
".lm-Menu-item:has(.lm-Menu-itemLabel:text('New Table'))",
);
await newTableItem.hover();
const submenu = shadowHost.locator(".lm-Menu").nth(1);
await expect(submenu).toBeVisible();
// Verify the DOM structure of table entries
const submenuContent = submenu.locator(".lm-Menu-content");
// Wait for table items to be populated
await expect(submenuContent.locator("> .lm-Menu-item")).toHaveCount(3, {
timeout: 5000,
});
const firstItem = submenuContent.locator("> .lm-Menu-item").first();
// Check that the menu item has the expected Lumino classes
await expect(firstItem).toHaveClass(/lm-Menu-item/);
// Check that it contains a label element
const labelElement = firstItem.locator(".lm-Menu-itemLabel");
await expect(labelElement).toBeVisible();
// Verify the label contains one of our table names
const labelText = await labelElement.textContent();
expect(["alpha", "beta", "superstore"]).toContain(labelText);
});
});
+81
View File
@@ -0,0 +1,81 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test } from "@perspective-dev/test";
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
});
test.describe("Workspace table functions", () => {
test("flush() waits until delete is resolved", async ({ page }) => {
const config = {
viewers: {
One: { table: "superstore", name: "One" },
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["One"],
},
},
};
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
const table = await window.__WORKER__.open_table("superstore");
await workspace.restore(config);
const viewer_removed =
workspace.querySelector("perspective-viewer");
workspace.removeChild(viewer_removed);
await viewer_removed.flush();
await table.delete();
}, config);
});
test("flush() waits until restore is applied and delete is resolved", async ({
page,
}) => {
const config = {
viewers: {
One: { table: "superstore", name: "One" },
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["One"],
},
},
};
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
const table = await window.__WORKER__.open_table("superstore");
workspace.restore(config); // no await
await workspace.flush();
const viewer_removed =
workspace.querySelector("perspective-viewer");
workspace.removeChild(viewer_removed);
await viewer_removed.flush();
await table.delete();
}, config);
});
});
+78
View File
@@ -0,0 +1,78 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test } from "@perspective-dev/test";
import {
compareLightDOMContents,
compareShadowDOMContents,
} from "@perspective-dev/test";
async function setupTestWorkspace(page) {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
await page.evaluate(async () => {
const viewer = document.createElement("perspective-viewer");
viewer.setAttribute("table", "superstore");
viewer.setAttribute("name", "one");
viewer.setAttribute("slot", "one");
const viewer2 = document.createElement("perspective-viewer");
viewer2.setAttribute("table", "superstore");
viewer2.setAttribute("name", "two");
viewer2.setAttribute("slot", "two");
const workspace = document.getElementById("workspace");
workspace.appendChild(viewer);
workspace.appendChild(viewer2);
await workspace.flush();
});
await page.evaluate(async () => {
const viewer = document.body.querySelector(
'perspective-viewer[name="one"]',
);
const workspace = document.getElementById("workspace");
workspace.removeChild(viewer);
await workspace.flush();
});
}
test.describe("Workspace DOM", () => {
test.describe("Light DOM", () => {
test.describe("removeChild", () => {
test("Remove One", async ({ page }) => {
await setupTestWorkspace(page);
await compareLightDOMContents(
page,
"workspace-light-remove-one-child.txt",
);
});
});
});
test.describe("Shadow DOM", () => {
test.describe("removeChild", () => {
test("Remove One", async ({ page }) => {
await setupTestWorkspace(page);
await compareShadowDOMContents(
page,
"workspace-dark-remove-one-child.txt",
);
});
});
});
});
@@ -0,0 +1,472 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@playwright/test";
import {
compareLightDOMContents,
compareShadowDOMContents,
} from "@perspective-dev/test";
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
await page.evaluate(async () => {
const { PerspectiveSelectDetail } = await import(
"/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"
);
window.PerspectiveSelectDetail = PerspectiveSelectDetail;
});
});
function tests(context, compare) {
// TODO: Implement this correctly
test.skip("treemap filters work", async ({ page }) => {
const config = {
viewers: {
One: {
table: "superstore",
name: "Test",
group_by: ["State"],
columns: ["Sales"],
plugin: "Treemap",
},
Two: { table: "superstore", name: "One" },
},
master: {
widgets: ["One"],
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["Two"],
},
},
};
const cfg = await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
const timer = Promise.withResolvers();
workspace.addEventListener("workspace-layout-update", (x) => {
timer.resolve();
});
document
.querySelector("perspective-viewer-charts-treemap")
.shadowRoot.querySelector("g.treemap > g")
.dispatchEvent(new Event("click"));
await timer.promise;
return await workspace.save();
}, config);
expect(cfg.viewers.Two.filter).toEqual([["State", "==", "Alabama"]]);
return compare(page, `${context}-treemap-filters-work.txt`);
});
test("Datagrid filters work", async ({ page }) => {
const config = {
viewers: {
One: {
table: "superstore",
name: "Test",
group_by: ["State"],
columns: ["Sales"],
plugin: "Datagrid",
},
Two: { table: "superstore", name: "One" },
},
master: {
widgets: ["One"],
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["Two"],
},
},
};
async function awaitConfigChange() {
return await page.evaluate(async () => {
let resolve;
const timer = new Promise((x) => {
resolve = x;
});
workspace.addEventListener("workspace-layout-update", resolve);
await timer;
workspace.removeEventListener(
"workspace-layout-update",
resolve,
);
return await workspace.save();
});
}
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
}, config);
let cfgPromise = awaitConfigChange();
await page
.locator(".workspace-master-widget perspective-viewer-datagrid")
.locator("tbody tr:nth-child(6) th:last-of-type")
.click();
let cfg = await cfgPromise;
expect(cfg.viewers.Two.filter).toEqual([["State", "==", "Colorado"]]);
cfgPromise = awaitConfigChange();
await page
.locator(".workspace-master-widget perspective-viewer-datagrid")
.locator("tbody tr:nth-child(6) th:last-of-type")
.click();
cfg = await cfgPromise;
expect(cfg.viewers.Two.filter).toEqual([]);
return compare(page, `${context}-datagrid-filters-work.txt`);
});
test("removeConfigs removes a programmatically applied filter from slave viewers", async ({
page,
}) => {
const config = {
viewers: {
One: {
table: "superstore",
name: "Test",
group_by: ["State"],
columns: ["Sales"],
plugin: "Datagrid",
},
Two: { table: "superstore", name: "One" },
},
master: {
widgets: ["One"],
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["Two"],
},
},
};
async function awaitConfigChange() {
return await page.evaluate(async () => {
let resolve;
const timer = new Promise((x) => {
resolve = x;
});
workspace.addEventListener("workspace-layout-update", resolve);
await timer;
workspace.removeEventListener(
"workspace-layout-update",
resolve,
);
return await workspace.save();
});
}
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
}, config);
// Apply a filter for "Category" via programmatic dispatch.
// "Category" is not in the master's group_by/split_by/filter, so it
// would not be cleared by the candidates mechanism on deselect.
let cfgPromise = awaitConfigChange();
await page.evaluate(async () => {
const masterViewer = document.querySelector(
".workspace-master-widget",
);
masterViewer.dispatchEvent(
new CustomEvent("perspective-global-filter", {
bubbles: true,
composed: true,
detail: new PerspectiveSelectDetail(
true,
{},
["Category"],
[],
[{ filter: [["Category", "==", "Furniture"]] }],
),
}),
);
});
let cfg = await cfgPromise;
expect(cfg.viewers.Two.filter).toEqual([
["Category", "==", "Furniture"],
]);
// Use removeConfigs to explicitly clear the Category filter from
// slave viewers.
cfgPromise = awaitConfigChange();
await page.evaluate(async () => {
const masterViewer = document.querySelector(
".workspace-master-widget",
);
masterViewer.dispatchEvent(
new CustomEvent("perspective-global-filter", {
bubbles: true,
composed: true,
detail: new PerspectiveSelectDetail(
true,
{},
[],
[{ filter: [["Category", "==", "Furniture"]] }],
[],
),
}),
);
});
cfg = await cfgPromise;
expect(cfg.viewers.Two.filter).toEqual([]);
});
test("removeConfigs preserves other slave filters while clearing targeted column", async ({
page,
}) => {
const config = {
viewers: {
One: {
table: "superstore",
name: "Test",
group_by: ["State"],
columns: ["Sales"],
plugin: "Datagrid",
},
Two: { table: "superstore", name: "One" },
},
master: {
widgets: ["One"],
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["Two"],
},
},
};
async function awaitConfigChange() {
return await page.evaluate(async () => {
let resolve;
const timer = new Promise((x) => {
resolve = x;
});
workspace.addEventListener("workspace-layout-update", resolve);
await timer;
workspace.removeEventListener(
"workspace-layout-update",
resolve,
);
return await workspace.save();
});
}
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
}, config);
// Apply filters for both "Category" and "Segment" via programmatic
// dispatch.
let cfgPromise = awaitConfigChange();
await page.evaluate(async () => {
const masterViewer = document.querySelector(
".workspace-master-widget",
);
masterViewer.dispatchEvent(
new CustomEvent("perspective-global-filter", {
bubbles: true,
composed: true,
detail: new PerspectiveSelectDetail(
true,
{},
["Category", "Segment"],
[],
[
{
filter: [
["Category", "==", "Furniture"],
["Segment", "==", "Consumer"],
],
},
],
),
}),
);
});
let cfg = await cfgPromise;
expect(cfg.viewers.Two.filter).toEqual([
["Category", "==", "Furniture"],
["Segment", "==", "Consumer"],
]);
// Remove only "Category"; "Segment" filter should be preserved.
cfgPromise = awaitConfigChange();
await page.evaluate(async () => {
const masterViewer = document.querySelector(
".workspace-master-widget",
);
masterViewer.dispatchEvent(
new CustomEvent("perspective-global-filter", {
bubbles: true,
composed: true,
detail: new PerspectiveSelectDetail(
true,
{},
[],
[{ filter: [["Category", "==", "Furniture"]] }],
[],
),
}),
);
});
cfg = await cfgPromise;
// Category is cleared, Segment is preserved.
expect(cfg.viewers.Two.filter).toEqual([["Segment", "==", "Consumer"]]);
});
test("Child classes of datagrid behave the same way", async ({ page }) => {
const config = {
viewers: {
One: {
table: "superstore",
name: "Test",
group_by: ["State"],
columns: ["Sales"],
plugin: "My Datagrid",
},
Two: { table: "superstore", name: "One" },
},
master: {
widgets: ["One"],
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["Two"],
},
},
};
await page.evaluate(async () => {
class MyGrid extends customElements.get(
"perspective-viewer-datagrid",
) {
get_static_config() {
return {
...super.get_static_config(),
name: "My Datagrid",
};
}
}
customElements.define("my-grid", MyGrid);
customElements.get("perspective-viewer").registerPlugin("my-grid");
});
async function awaitConfigChange() {
return await page.evaluate(async () => {
let resolve;
const timer = new Promise((x) => {
resolve = x;
});
workspace.addEventListener("workspace-layout-update", resolve);
await timer;
workspace.removeEventListener(
"workspace-layout-update",
resolve,
);
return await workspace.save();
});
}
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
}, config);
let cfgPromise = awaitConfigChange();
await page
.locator(".workspace-master-widget my-grid")
.locator("tbody tr:nth-child(6) th:last-of-type")
.click();
let cfg = await cfgPromise;
expect(cfg.viewers.Two.filter).toEqual([["State", "==", "Colorado"]]);
cfgPromise = awaitConfigChange();
await page
.locator(".workspace-master-widget my-grid")
.locator("tbody tr:nth-child(6) th:last-of-type")
.click({
delay: 10,
});
cfg = await cfgPromise;
// console.log("OLD", cfg.viewers.Two.filter);
// await page.evaluate(async () => {
// await new Promise((r) => setTimeout(r, 5000));
// });
// cfg = await page.evaluate(async () => {
// const cfg = await workspace.save();
// console.log("NEW", JSON.stringify(cfg.viewers.Two.filter));
// return cfg;
// });
expect(cfg.viewers.Two.filter).toEqual([]);
return compare(page, `${context}-my-datagrid-filters-work.txt`);
});
}
test.describe("Workspace global filters", () => {
test.describe("Light DOM", () => {
tests("light-dom", compareLightDOMContents);
});
test.describe("Shadow DOM", () => {
tests("shadow-dom", compareShadowDOMContents);
});
});
+117
View File
@@ -0,0 +1,117 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test } from "@perspective-dev/test";
import {
compareLightDOMContents,
compareShadowDOMContents,
} from "@perspective-dev/test";
async function createOneWorkspace(page) {
// await new Promise((x) => setTimeout(x, 2000));
await page.evaluate(async () => {
document.querySelector("perspective-workspace").innerHTML = `
<perspective-viewer table="superstore"></perspective-viewer>
`;
const workspace = document.body.querySelector("perspective-workspace");
await workspace.load(window.__WORKER__);
await workspace.flush();
});
}
async function createMultipleViewers(page) {
await page.evaluate(async () => {
document.querySelector("perspective-workspace").innerHTML = `
<perspective-viewer table="superstore"></perspective-viewer>
<perspective-viewer table="superstore"></perspective-viewer>
`;
const workspace = document.body.querySelector("perspective-workspace");
await workspace.flush();
});
}
async function createMultipleViewersWithNames(page) {
await page.evaluate(async () => {
document.querySelector("perspective-workspace").innerHTML = `
<perspective-viewer name="Table 1" table="superstore"></perspective-viewer>
<perspective-viewer name="Table 2" table="superstore"></perspective-viewer>
`;
const workspace = document.body.querySelector("perspective-workspace");
await workspace.flush();
});
}
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
});
test.describe("Workspace HTML", () => {
test.describe("Light DOM", () => {
test("Create One", async ({ page }) => {
await createOneWorkspace(page);
await compareLightDOMContents(
page,
"workspace-html-light-create-one.txt",
);
});
test("Create Multiple", async ({ page }) => {
await createMultipleViewers(page);
await compareLightDOMContents(
page,
"workspace-html-light-create-multiple.txt",
);
});
test("Create Multiple with names", async ({ page }) => {
await createMultipleViewersWithNames(page);
await compareLightDOMContents(
page,
"workspace-html-light-create-multiple-with-names.txt",
);
});
});
test.describe("Shadow DOM", () => {
test("Create One", async ({ page }) => {
await createOneWorkspace(page);
await compareShadowDOMContents(
page,
"workspace-html-shadow-create-one.txt",
);
});
test("Create Multiple", async ({ page }) => {
await createMultipleViewers(page);
await compareShadowDOMContents(
page,
"workspace-html-shadow-create-multiple.txt",
);
});
test("Create Multiple with names", async ({ page }) => {
await createMultipleViewersWithNames(page);
await compareShadowDOMContents(
page,
"workspace-html-shadow-create-multiple-with-names.txt",
);
});
});
});
+260
View File
@@ -0,0 +1,260 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import {
compareLightDOMContents,
compareShadowDOMContents,
} from "@perspective-dev/test";
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
});
function tests(context, compare) {
test("restore workspace with detail only", async ({ page }) => {
const config = {
viewers: {
One: { table: "superstore", name: "One" },
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["One"],
},
},
};
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
}, config);
await page.evaluate(async () => {
await workspace.flush();
});
return compare(
page,
`${context}-restore-workspace-with-detail-only.txt`,
);
});
test("restore workspace with master and detail", async ({ page }) => {
const config = {
viewers: {
One: {
table: "superstore",
name: "Test",
group_by: ["State"],
columns: ["Sales", "Profit"],
},
Two: { table: "superstore", name: "One" },
},
master: {
widgets: ["One"],
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["Two"],
},
},
};
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
}, config);
await page.evaluate(async () => {
const workspace = document.getElementById("workspace");
await workspace.flush();
});
return compare(
page,
`${context}-restore-workspace-with-master-and-detail.txt`,
);
});
test("restore workspace is symmetric with addViewer", async ({ page }) => {
const initial = await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.addViewer({ table: "superstore" });
await workspace.flush();
const result = await workspace.save();
return result;
});
const second = await page.evaluate(async (initial) => {
const workspace = document.getElementById("workspace");
await workspace.restore(initial);
await workspace.flush();
return await workspace.save();
}, initial);
expect(initial).toEqual(second);
return compare(
page,
`${context}-restore-workspace-is-symmetric-with-addviewer.txt`,
);
});
test.describe("Toggle master/detail", () => {
test("restore a blank view removes master panel", async ({ page }) => {
const config = {
detail: {
main: {
type: "tab-area",
widgets: ["PERSPECTIVE_GENERATED_ID_1"],
currentIndex: 0,
},
},
master: { widgets: ["PERSPECTIVE_GENERATED_ID_0"], sizes: [1] },
viewers: {
PERSPECTIVE_GENERATED_ID_0: {
table: "superstore",
},
PERSPECTIVE_GENERATED_ID_1: {
table: "superstore",
},
},
};
const empty_config = {
detail: {},
viewers: {},
};
// const x = await page.evaluate(async (config) => {
// const workspace = document.getElementById("workspace");
// await workspace.restore(config);
// await workspace.flush();
// return workspace.outerHTML;
// }, config);
// test.expect(x).toEqual(
// `<perspective-workspace id=\"workspace\"><perspective-viewer slot=\"PERSPECTIVE_GENERATED_ID_1\" table=\"superstore\" theme=\"Pro Light\" class=\"lm-DockPanel-widget\"><perspective-viewer-datagrid data-edit-mode=\"READ_ONLY\" style=\"position: absolute; inset: 0px; opacity: 1;\" class=\"edit-mode-allowed\"></perspective-viewer-datagrid><perspective-viewer-datagrid-toolbar slot=\"statusbar-extra\"></perspective-viewer-datagrid-toolbar></perspective-viewer><perspective-viewer slot=\"PERSPECTIVE_GENERATED_ID_0\" table=\"superstore\" theme=\"Pro Light\" class=\"workspace-master-widget lm-SplitPanel-child\" selectable=\"\"><perspective-viewer-datagrid data-edit-mode=\"READ_ONLY\" style=\"position: absolute; inset: 0px; opacity: 1;\"></perspective-viewer-datagrid><perspective-viewer-datagrid-toolbar slot=\"statusbar-extra\"></perspective-viewer-datagrid-toolbar></perspective-viewer></perspective-workspace>`,
// );
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
}, empty_config);
return compare(
page,
`${context}-restore-a-blank-view-removes-master-panel.txt`,
);
});
test("restore a view which reuses a master viewer, removes master panel", async ({
page,
}) => {
const config = {
detail: {
main: {
type: "tab-area",
widgets: ["PERSPECTIVE_GENERATED_ID_1"],
currentIndex: 0,
},
},
master: { widgets: ["PERSPECTIVE_GENERATED_ID_0"], sizes: [1] },
viewers: {
PERSPECTIVE_GENERATED_ID_0: {
table: "superstore",
title: "One",
},
PERSPECTIVE_GENERATED_ID_1: {
table: "superstore",
title: "Two",
},
},
};
const config2 = {
detail: {
main: {
type: "split-area",
orientation: "horizontal",
children: [
{
type: "tab-area",
widgets: ["PERSPECTIVE_GENERATED_ID_0"],
currentIndex: 0,
},
{
type: "tab-area",
widgets: ["PERSPECTIVE_GENERATED_ID_1"],
currentIndex: 0,
},
],
sizes: [0.5, 0.5],
},
},
master: { widgets: [], sizes: [] },
viewers: {
PERSPECTIVE_GENERATED_ID_0: {
table: "superstore",
title: "One",
},
PERSPECTIVE_GENERATED_ID_1: {
table: "superstore",
title: "Two",
},
},
};
// const x = await page.evaluate(async (config) => {
// const workspace = document.getElementById("workspace");
// await workspace.restore(config);
// await workspace.flush();
// return workspace.outerHTML;
// }, config);
// test.expect(x).toEqual(
// `<perspective-workspace id="workspace"><perspective-viewer slot="PERSPECTIVE_GENERATED_ID_1" table="superstore" theme="Pro Light" class="lm-DockPanel-widget"><perspective-viewer-datagrid data-edit-mode="READ_ONLY" style="position: absolute; inset: 0px; opacity: 1;" class="edit-mode-allowed"></perspective-viewer-datagrid><perspective-viewer-datagrid-toolbar slot="statusbar-extra"></perspective-viewer-datagrid-toolbar></perspective-viewer><perspective-viewer slot="PERSPECTIVE_GENERATED_ID_0" table="superstore" theme="Pro Light" class="workspace-master-widget lm-SplitPanel-child" selectable=""><perspective-viewer-datagrid data-edit-mode="READ_ONLY" style="position: absolute; inset: 0px; opacity: 1;"></perspective-viewer-datagrid><perspective-viewer-datagrid-toolbar slot="statusbar-extra"></perspective-viewer-datagrid-toolbar></perspective-viewer></perspective-workspace>`,
// );
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.flush();
}, config2);
return compare(
page,
`${context}-restore-which-reseats-master-viewer.txt`,
);
});
});
}
test.describe("Workspace restore", () => {
test.describe("Light DOM", () => {
tests("light-dom", compareLightDOMContents);
});
test.describe("Shadow DOM", () => {
tests("shadow-dom", compareShadowDOMContents);
});
});
+164
View File
@@ -0,0 +1,164 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import {
compareLightDOMContents,
compareShadowDOMContents,
} from "@perspective-dev/test";
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
});
function tests(context, compare) {
// TODO: 3.x `Client` has no exact `replaceTable`
test.skip("replaceTable() frees the `Table` before resolution", async ({
page,
}) => {
const config = {
viewers: {
One: { table: "superstore", name: "One" },
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["One"],
},
},
};
await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.restore(config);
await workspace.replaceTable(
"superstore",
window.__WORKER__.table("x\n1"),
);
await window.__TABLE__.delete();
}, config);
await page.evaluate(async () => {
await workspace.flush();
});
return compare(page, `${context}-replace-table-frees-table.txt`);
});
test.skip("replaceTable() works when previous table errored", async ({
page,
}) => {
const config = {
viewers: {
One: { table: "errored", name: "One" },
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["One"],
},
},
};
const result = await page.evaluate(async (config) => {
const workspace = document.getElementById("workspace");
await workspace.addTable(
"errored",
new Promise((_, reject) => setTimeout(reject, 50)),
);
try {
await workspace.restore(config);
} catch (e) {
console.error(e);
return e.toString();
}
}, config);
// NOTE This is the error message we expect when `restore()` is called
// without a `Table`, subject to change.
expect(result).toEqual(
"Error: Failed to construct table from JsValue(undefined)",
);
await page.evaluate(async () => {
await workspace.replaceTable(
"errored",
window.__WORKER__.table("x\n1"),
);
});
await page.evaluate(async () => {
await workspace.flush();
});
return compare(
page,
`${context}-replace-table-works-with-errored-table.txt`,
);
});
test("removeTable() smoke test", async ({ page }) => {
await page.evaluate(async () => {
await window.__WORKER__.table("x\n1\n", { name: "temptable" });
const workspace = document.getElementById("workspace");
await workspace.load(window.__WORKER__);
await workspace.restore({
viewers: {
One: { table: "temptable", name: "One" },
},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: ["One"],
},
},
});
await workspace.flush();
});
await page.evaluate(async () => {
await workspace.restore({
viewers: {},
detail: {
main: {
currentIndex: 0,
type: "tab-area",
widgets: [],
},
},
});
await workspace.flush();
});
return compare(page, `${context}-table-delete-works.txt`);
});
}
test.describe("Workspace table functions", () => {
test.describe("Light DOM", () => {
tests("light-dom", compareLightDOMContents);
});
test.describe("Shadow DOM", () => {
tests("shadow-dom", compareShadowDOMContents);
});
});
@@ -0,0 +1,113 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test } from "@perspective-dev/test";
import {
compareLightDOMContents,
compareShadowDOMContents,
} from "@perspective-dev/test";
const BAD_LAYOUT = {
sizes: [1],
detail: {
main: {
type: "tab-area",
widgets: [
"PERSPECTIVE_GENERATED_ID_0",
"PERSPECTIVE_GENERATED_ID_1",
],
currentIndex: 1,
},
},
mode: "globalFilters",
viewers: {
PERSPECTIVE_GENERATED_ID_0: {
plugin: "Sunburst",
plugin_config: {},
settings: false,
theme: null,
group_by: ["State"],
split_by: [],
columns: ["Quantity", null, null],
filter: [],
sort: [],
expressions: {},
aggregates: {},
master: false,
name: "one",
table: "superstore",
linked: false,
},
PERSPECTIVE_GENERATED_ID_1: {
plugin: "Sunburst",
plugin_config: {},
settings: false,
theme: null,
group_by: ["State"],
split_by: [],
columns: ["Sales", null, null],
filter: [],
sort: [],
expressions: {},
aggregates: {},
master: false,
name: "two",
table: "superstore",
linked: false,
},
},
};
function tests(context, compare) {
test.describe("visibility", () => {
test("Sunburst charts do not loop forever when disconnected from DOM", async ({
page,
}) => {
await page.evaluate(async (layout) => {
await window.workspace.restore(layout);
}, BAD_LAYOUT);
await page.evaluate(async () => {
const viewer = document.body.querySelector(
"perspective-viewer[slot=PERSPECTIVE_GENERATED_ID_0]",
);
const workspace = document.getElementById("workspace");
workspace.removeChild(viewer);
await workspace.flush();
});
return compare(
page,
`sunburst-charts-does-not-loop-${context}.txt`,
);
});
});
}
test.beforeEach(async ({ page }) => {
await page.goto("/tools/test/src/html/workspace-test.html");
await page.evaluate(async () => {
while (!window["__TEST_PERSPECTIVE_READY__"]) {
await new Promise((x) => setTimeout(x, 10));
}
});
});
test.describe("Workspace Visibility", () => {
test.describe("Light DOM", () => {
tests("light-dom", compareLightDOMContents);
});
test.describe("Shadow DOM", () => {
tests("shadow-dom", compareShadowDOMContents);
});
});
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"jsx": "preserve",
"strict": true,
"module": "ESNext",
"target": "ESNext",
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist/esm",
"rootDir": "./src/ts",
"moduleResolution": "bundler"
},
"include": ["src"]
}