examples: remove table-view (#20101)
This is an example that demonstrates the deprecated 'table-document' DataObject. We maintain 'table-document' to support migrating legacy document. The Table-View sample is unused, has no tests, and should be removed.
This commit is contained in:
Родитель
8d526c6f12
Коммит
e4c933f3a3
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,14 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"),
|
||||
"prettier",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
},
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
# Compiled TypeScript and CSS
|
||||
dist
|
||||
lib
|
||||
|
||||
# Babel
|
||||
public/scripts/es5
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
.cache-loader
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
# Typings
|
||||
typings
|
||||
|
||||
# Debug log from npm
|
||||
npm-debug.log
|
||||
|
||||
# Code coverage
|
||||
nyc
|
||||
.nyc_output/
|
||||
|
||||
# Chart dependencies
|
||||
**/charts/*.tgz
|
||||
|
||||
# Generated modules
|
||||
intel_modules/
|
||||
temp_modules/
|
|
@ -1,6 +0,0 @@
|
|||
nyc
|
||||
*.log
|
||||
**/*.tsbuildinfo
|
||||
src/test
|
||||
dist/test
|
||||
**/_api-extractor-temp/**
|
|
@ -1,81 +0,0 @@
|
|||
# @fluid-example/table-view
|
||||
|
||||
## 2.0.0-rc.2.0.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-rc.1.0.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.8.0.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.7.4.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.7.3.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.7.2.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.7.1.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.7.0.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.6.4.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.6.3.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.6.2.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.6.1.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.6.0.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.5.4.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.5.3.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.5.2.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.5.1.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.5.0.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.4.4.0
|
||||
|
||||
Dependency updates only.
|
||||
|
||||
## 2.0.0-internal.4.1.0
|
||||
|
||||
Dependency updates only.
|
|
@ -1,21 +0,0 @@
|
|||
Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,34 +0,0 @@
|
|||
# @fluid-example/table-view
|
||||
|
||||
**Table View** is a basic table/grid view built on top of the `@fluid-example/table-document` data object.
|
||||
Since Table View uses the data model provided by Table Document it only uses it's DDS to store a reference
|
||||
to the created Table Document.
|
||||
|
||||
<!-- AUTO-GENERATED-CONTENT:START (README_EXAMPLE_GETTING_STARTED_SECTION:usesTinylicious=FALSE) -->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- NOTE: This section is automatically generated using @fluid-tools/markdown-magic. Do not update these generated contents directly. -->
|
||||
|
||||
## Getting Started
|
||||
|
||||
You can run this example using the following steps:
|
||||
|
||||
1. Enable [corepack](https://nodejs.org/docs/latest-v16.x/api/corepack.html) by running `corepack enable`.
|
||||
1. Run `pnpm install` and `pnpm run build:fast --nolint` from the `FluidFramework` root directory.
|
||||
- For an even faster build, you can add the package name to the build command, like this:
|
||||
`pnpm run build:fast --nolint @fluid-example/table-view`
|
||||
1. Run `pnpm start` from this directory and open <http://localhost:8080> in a web browser to see the app running.
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- AUTO-GENERATED-CONTENT:END -->
|
||||
|
||||
## Data model
|
||||
|
||||
Table View uses the following distributed data structures:
|
||||
|
||||
- SharedDirectory - root
|
||||
|
||||
Table View creates the following Fluid objects:
|
||||
|
||||
- `@fluid-example/table-document`
|
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
"name": "@fluid-example/table-view",
|
||||
"version": "2.0.0-rc.2.0.0",
|
||||
"private": true,
|
||||
"description": "Chaincode component that provides a view for a table-document.",
|
||||
"homepage": "https://fluidframework.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoft/FluidFramework.git",
|
||||
"directory": "examples/data-objects/table-view"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Microsoft and contributors",
|
||||
"sideEffects": [
|
||||
"./src/publicpath.ts"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "fluid-build . --task build",
|
||||
"build:compile": "fluid-build . --task compile",
|
||||
"build:copy": "copyfiles -u 1 \"src/**/*.css\" lib/",
|
||||
"build:esnext": "tsc",
|
||||
"clean": "rimraf --glob dist lib \"**/*.tsbuildinfo\" \"**/*.build.log\"",
|
||||
"eslint": "eslint --format stylish src",
|
||||
"eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
|
||||
"format": "npm run prettier:fix",
|
||||
"lint": "npm run prettier && npm run eslint",
|
||||
"lint:fix": "npm run prettier:fix && npm run eslint:fix",
|
||||
"prepack": "npm run webpack",
|
||||
"prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
|
||||
"prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore",
|
||||
"start": "webpack serve --config webpack.config.cjs",
|
||||
"start:docker": "webpack serve --config webpack.config.cjs --env mode=docker",
|
||||
"start:r11s": "webpack serve --config webpack.config.cjs --env mode=r11s",
|
||||
"start:spo": "webpack serve --config webpack.config.cjs --env mode=spo",
|
||||
"start:spo-df": "webpack serve --config webpack.config.cjs --env mode=spo-df",
|
||||
"start:tinylicious": "webpack serve --config webpack.config.cjs --env mode=tinylicious",
|
||||
"webpack": "webpack --env production",
|
||||
"webpack:dev": "webpack --env development"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluid-example/example-utils": "workspace:~",
|
||||
"@fluid-example/table-document": "workspace:~",
|
||||
"@fluidframework/aqueduct": "workspace:~",
|
||||
"@fluidframework/container-definitions": "workspace:~",
|
||||
"@fluidframework/core-interfaces": "workspace:~",
|
||||
"@fluidframework/matrix": "workspace:~",
|
||||
"@fluidframework/runtime-utils": "workspace:~",
|
||||
"@fluidframework/sequence": "workspace:~",
|
||||
"@tiny-calc/micro": "0.0.0-alpha.5",
|
||||
"@tiny-calc/nano": "0.0.0-alpha.5",
|
||||
"react": "^17.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluid-example/webpack-fluid-loader": "workspace:~",
|
||||
"@fluidframework/build-common": "^2.0.3",
|
||||
"@fluidframework/build-tools": "^0.34.0",
|
||||
"@fluidframework/eslint-config-fluid": "^5.1.0",
|
||||
"@types/node": "^18.19.0",
|
||||
"@types/react": "^17.0.44",
|
||||
"copyfiles": "^2.4.1",
|
||||
"css-loader": "^1.0.0",
|
||||
"eslint": "~8.55.0",
|
||||
"prettier": "~3.0.3",
|
||||
"rimraf": "^4.4.0",
|
||||
"source-map-loader": "^2.0.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"ts-loader": "^9.3.0",
|
||||
"typescript": "~5.1.6",
|
||||
"url-loader": "^2.1.0",
|
||||
"webpack": "^5.82.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "~4.6.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"fluid": {
|
||||
"browser": {
|
||||
"umd": {
|
||||
"files": [
|
||||
"dist/main.bundle.js"
|
||||
],
|
||||
"library": "main"
|
||||
}
|
||||
}
|
||||
},
|
||||
"typeValidation": {
|
||||
"disabled": true,
|
||||
"broken": {}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
...require("@fluidframework/build-common/prettier.config.cjs"),
|
||||
};
|
|
@ -1,70 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
const enum StyleIndex {
|
||||
Near = 0,
|
||||
Middle = 1,
|
||||
Far = 2,
|
||||
}
|
||||
|
||||
export class BorderRect {
|
||||
public get min() {
|
||||
return [Math.min(this.start[0], this.end[0]), Math.min(this.start[1], this.end[1])];
|
||||
}
|
||||
public get max() {
|
||||
return [Math.max(this.start[0], this.end[0]), Math.max(this.start[1], this.end[1])];
|
||||
}
|
||||
|
||||
public start = [NaN, NaN];
|
||||
public end = [NaN, NaN];
|
||||
constructor(private readonly styles: string[][]) {}
|
||||
|
||||
public reset() {
|
||||
this.start = [NaN, NaN];
|
||||
this.end = [NaN, NaN];
|
||||
}
|
||||
|
||||
public intersect(row: number, col: number) {
|
||||
const min = this.min;
|
||||
const max = this.max;
|
||||
return this.inRange(min[0], row, max[0]) && this.inRange(min[1], col, max[1]);
|
||||
}
|
||||
|
||||
public getStyle(row: number, col: number) {
|
||||
if (!this.intersect(row, col)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const min = this.min;
|
||||
const max = this.max;
|
||||
const vert = this.getStyleIndices(min[0], row, max[0]);
|
||||
const horiz = this.getStyleIndices(min[1], col, max[1]);
|
||||
return vert.reduce((vertAccum, vertIndex) => {
|
||||
const vertStyles = this.styles[vertIndex];
|
||||
return horiz.reduce(
|
||||
(horizAccum, horizIndex) => `${horizAccum} ${vertStyles[horizIndex]}`,
|
||||
vertAccum,
|
||||
);
|
||||
}, "");
|
||||
}
|
||||
|
||||
private inRange(min: number, value: number, max: number) {
|
||||
return min <= value && value <= max;
|
||||
}
|
||||
|
||||
private getStyleIndices(min: number, value: number, max: number) {
|
||||
const styles: number[] = [];
|
||||
if (value === min) {
|
||||
styles.push(StyleIndex.Near);
|
||||
}
|
||||
if (value === max) {
|
||||
styles.push(StyleIndex.Far);
|
||||
}
|
||||
if (styles.length === 0) {
|
||||
styles.push(StyleIndex.Middle);
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
declare let __webpack_public_path__: string;
|
|
@ -1,525 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { colIndexToName } from "@fluid-example/table-document";
|
||||
import { SharedMatrix } from "@fluidframework/matrix";
|
||||
import { ISheetlet, createSheetletProducer } from "@tiny-calc/micro";
|
||||
import type { IMatrixProducer } from "@tiny-calc/nano";
|
||||
import { BorderRect } from "./borderstyle.js";
|
||||
import * as styles from "./index.css";
|
||||
|
||||
// eslint-disable-next-line unicorn/no-unsafe-regex
|
||||
const numberExp = /^[+-]?\d*\.?\d+(?:[Ee][+-]?\d+)?$/;
|
||||
|
||||
const enum KeyCode {
|
||||
tab = "Tab", // 9
|
||||
enter = "Enter", // 13
|
||||
escape = "Escape", // 27
|
||||
arrowLeft = "ArrowLeft", // 37
|
||||
arrowUp = "ArrowUp", // 38
|
||||
arrowRight = "ArrowRight", // 39
|
||||
arrowDown = "ArrowDown", // 40
|
||||
}
|
||||
|
||||
// Extract Value type from createSheetletProducer requirements. (Value is not exported.)
|
||||
type GridContentType = Parameters<typeof createSheetletProducer>[0] extends IMatrixProducer<infer T>
|
||||
? T
|
||||
: never;
|
||||
|
||||
export class GridView {
|
||||
private get numRows() {
|
||||
return this.matrix.rowCount;
|
||||
}
|
||||
private get numCols() {
|
||||
return this.matrix.colCount;
|
||||
}
|
||||
public readonly root;
|
||||
|
||||
private _startRow = 0;
|
||||
public get startRow() {
|
||||
return this._startRow;
|
||||
}
|
||||
public set startRow(value: number) {
|
||||
this._startRow = value;
|
||||
this.cancelInput();
|
||||
this.refreshCells();
|
||||
}
|
||||
|
||||
private readonly cols = document.createElement("tr");
|
||||
private readonly tbody = document.createElement("tbody");
|
||||
private readonly inputBox = document.createElement("input");
|
||||
private tdText?: Node;
|
||||
private readonly selection = new BorderRect([
|
||||
[`${styles.selectedTL}`, `${styles.selectedT}`, `${styles.selectedTR}`],
|
||||
[`${styles.selectedL}`, `${styles.selected}`, `${styles.selectedR}`],
|
||||
[`${styles.selectedBL}`, `${styles.selectedB}`, `${styles.selectedBR}`],
|
||||
]);
|
||||
private readonly maxRows = 10;
|
||||
|
||||
private readonly sheetlet: ISheetlet;
|
||||
|
||||
private generateDom() {
|
||||
const root = document.createElement("table");
|
||||
root.classList.add(styles.view);
|
||||
root.tabIndex = 0;
|
||||
|
||||
const caption = document.createElement("caption");
|
||||
const captionSpan = document.createElement("span");
|
||||
captionSpan.textContent = "Table";
|
||||
caption.append(captionSpan);
|
||||
|
||||
const head = document.createElement("thead");
|
||||
head.append(this.cols);
|
||||
|
||||
root.append(caption, head, this.tbody);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly matrix: SharedMatrix<GridContentType>,
|
||||
private readonly getFormula: () => string,
|
||||
private readonly setFormula: (val: string) => void,
|
||||
private readonly setSelectionSummary: (val: string) => void,
|
||||
) {
|
||||
this.root = this.generateDom();
|
||||
this.root.addEventListener("click", this.onGridClick as EventListener);
|
||||
this.tbody.addEventListener("pointerdown", this.cellPointerDown as EventListener);
|
||||
this.tbody.addEventListener("pointermove", this.cellPointerMove as EventListener);
|
||||
this.inputBox.classList.add(styles.inputBox);
|
||||
this.inputBox.addEventListener("keydown", this.cellKeyDown);
|
||||
this.inputBox.addEventListener("input", this.cellInput);
|
||||
|
||||
const blank = document.createElement("th");
|
||||
this.cols.appendChild(blank);
|
||||
|
||||
this.sheetlet = createSheetletProducer(matrix);
|
||||
|
||||
this.setupMatrixConsumer();
|
||||
this.refreshCells();
|
||||
}
|
||||
|
||||
private setupMatrixConsumer() {
|
||||
let scheduled = false;
|
||||
const scheduleGridRefresh = () => {
|
||||
if (scheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
scheduled = false;
|
||||
this.refreshCells();
|
||||
});
|
||||
|
||||
scheduled = true;
|
||||
};
|
||||
|
||||
const invalidateCells = (
|
||||
rowStart: number,
|
||||
colStart: number,
|
||||
rowCount: number,
|
||||
colCount: number,
|
||||
) => {
|
||||
for (let row = rowStart; row < rowStart + rowCount; row++) {
|
||||
for (let col = colStart; col < colStart + colCount; col++) {
|
||||
this.sheetlet.invalidate(row, col);
|
||||
}
|
||||
}
|
||||
scheduleGridRefresh();
|
||||
};
|
||||
|
||||
const matrixReader = {
|
||||
rowsChanged() {
|
||||
scheduleGridRefresh();
|
||||
},
|
||||
colsChanged() {
|
||||
scheduleGridRefresh();
|
||||
},
|
||||
cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number) {
|
||||
invalidateCells(rowStart, colStart, rowCount, colCount);
|
||||
},
|
||||
};
|
||||
|
||||
this.matrix.openMatrix(matrixReader);
|
||||
}
|
||||
|
||||
private refreshCell(td: HTMLTableCellElement, row: number, col: number) {
|
||||
const className = this.selection.getStyle(row, col);
|
||||
if (td.className !== className) {
|
||||
td.className = className;
|
||||
}
|
||||
|
||||
// While the cell is being edited, we use the <td>'s content to size the table to the
|
||||
// formula. Don't synchronize it now.
|
||||
if (this.inputBox.parentElement !== td) {
|
||||
const value = this.sheetlet.evaluateCell(row, col);
|
||||
|
||||
const text = `\u200B${value ?? ""}`;
|
||||
|
||||
if (td.textContent !== text) {
|
||||
td.textContent = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly refreshCells = () => {
|
||||
let row = this.startRow;
|
||||
const numRows = Math.min(this.numRows, row + this.maxRows);
|
||||
{
|
||||
let tr = this.tbody.firstElementChild;
|
||||
|
||||
while (tr) {
|
||||
const next = tr.nextElementSibling;
|
||||
if (row < numRows) {
|
||||
let col = -1;
|
||||
for (const td of tr.children) {
|
||||
if (col < 0) {
|
||||
td.textContent = `${row + 1}`;
|
||||
} else {
|
||||
this.refreshCell(td as HTMLTableCellElement, row, col);
|
||||
}
|
||||
col++;
|
||||
}
|
||||
|
||||
// Append any missing columns
|
||||
for (; col < this.numCols; col++) {
|
||||
const td = document.createElement("td");
|
||||
this.refreshCell(td, row, col);
|
||||
tr.appendChild(td);
|
||||
}
|
||||
} else {
|
||||
tr.remove();
|
||||
}
|
||||
|
||||
tr = next;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
// Append any missing rows
|
||||
for (; row < numRows; row++) {
|
||||
const tr = document.createElement("tr");
|
||||
const th = document.createElement("th");
|
||||
th.textContent = `${row + 1}`;
|
||||
tr.appendChild(th);
|
||||
|
||||
for (let col = 0; col < this.numCols; col++) {
|
||||
const td = document.createElement("td");
|
||||
this.refreshCell(td, row, col);
|
||||
tr.appendChild(td);
|
||||
}
|
||||
|
||||
this.tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
// Append any missing col headers
|
||||
for (let col = this.cols.childElementCount - 1; col < this.numCols; col++) {
|
||||
const th = document.createElement("th");
|
||||
// Skip placeholder <th> above the row number column.
|
||||
if (col >= 0) {
|
||||
th.textContent = `${colIndexToName(col)}`;
|
||||
}
|
||||
this.cols.append(th);
|
||||
}
|
||||
|
||||
this.refreshFormulaInput();
|
||||
this.refreshNumberSummary();
|
||||
};
|
||||
|
||||
private readonly onGridClick = (e: MouseEvent) => {
|
||||
const maybeTd = this.getCellFromEvent(e);
|
||||
if (maybeTd) {
|
||||
const [row, col] = this.getRowColFromTd(maybeTd);
|
||||
if (row < 0 && col >= 0) {
|
||||
this.selection.start = [0, col];
|
||||
this.selection.end = [this.numRows - 1, col];
|
||||
this.refreshCells();
|
||||
} else if (col < 0 && row >= 0) {
|
||||
this.selection.start = [row, 0];
|
||||
this.selection.end = [row, this.numCols - 1];
|
||||
this.refreshCells();
|
||||
} else if (col >= 0) {
|
||||
this.moveInputToPosition(row, col, e.shiftKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private readonly cellPointerDown = (e: PointerEvent) => {
|
||||
const maybeTd = this.getCellFromEvent(e);
|
||||
if (maybeTd) {
|
||||
this.commitInput();
|
||||
const [row, col] = this.getRowColFromTd(maybeTd);
|
||||
if (col >= 0) {
|
||||
this.selection.start = this.selection.end = [row, col];
|
||||
this.refreshCells();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private readonly cellPointerMove = (e: PointerEvent) => {
|
||||
if (!e.buttons) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maybeTd = this.getCellFromEvent(e);
|
||||
if (maybeTd) {
|
||||
const [row, col] = this.getRowColFromTd(maybeTd);
|
||||
if (col >= 0) {
|
||||
this.commitInput();
|
||||
this.inputBox.remove();
|
||||
this.selection.end = [row, col];
|
||||
this.refreshCells();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private readonly cancelInput = () => {
|
||||
const maybeParent = this.inputBox.parentElement as HTMLTableCellElement | null;
|
||||
if (maybeParent) {
|
||||
this.inputBox.remove();
|
||||
const [row, col] = this.getRowColFromTd(maybeParent);
|
||||
this.refreshCell(maybeParent, row, col);
|
||||
}
|
||||
};
|
||||
|
||||
private parseInput(input: string) {
|
||||
if (numberExp.exec(input)) {
|
||||
const asNumber = Number(input);
|
||||
if (!isNaN(asNumber)) {
|
||||
return asNumber;
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private commitInput() {
|
||||
const maybeParent = this.inputBox.parentElement as HTMLTableCellElement | null;
|
||||
if (maybeParent) {
|
||||
const [row, col] = this.getRowColFromTd(maybeParent);
|
||||
const previous = this.matrix.getCell(row, col);
|
||||
const current = this.parseInput(this.inputBox.value);
|
||||
if (previous !== current) {
|
||||
this.matrix.setCell(row, col, current);
|
||||
this.sheetlet.invalidate(row, col);
|
||||
}
|
||||
this.refreshCell(maybeParent, row, col);
|
||||
}
|
||||
}
|
||||
|
||||
private moveInputToPosition(row: number, col: number, extendSelection: boolean) {
|
||||
const newParent = this.getTdFromRowCol(row, col);
|
||||
if (newParent) {
|
||||
this.commitInput();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.tdText = newParent.firstChild!;
|
||||
console.assert(
|
||||
this.tdText.nodeType === Node.TEXT_NODE,
|
||||
"TableData text has wrong node type!",
|
||||
);
|
||||
|
||||
const value = this.matrix.getCell(row, col);
|
||||
this.inputBox.value = `${value ?? ""}`;
|
||||
newParent.appendChild(this.inputBox);
|
||||
this.cellInput();
|
||||
this.tdText.textContent = `\u200B${this.inputBox.value}`;
|
||||
this.inputBox.focus();
|
||||
|
||||
this.selection.end = [row, col];
|
||||
if (!extendSelection) {
|
||||
this.selection.start = this.selection.end;
|
||||
}
|
||||
|
||||
this.refreshCells();
|
||||
}
|
||||
|
||||
// 'getTdFromRowCol(..)' return false if row/col are outside the sheet range.
|
||||
return !!newParent;
|
||||
}
|
||||
|
||||
private moveInputByOffset(e: KeyboardEvent, rowOffset: number, colOffset: number) {
|
||||
let _colOffset = colOffset;
|
||||
// Allow the left/right arrow keys to move the caret inside the inputBox until the caret
|
||||
// is in the first/last character position. Then move the inputBox.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (e.target === this.inputBox && this.inputBox.selectionStart! >= 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const x = this.inputBox.selectionStart! + _colOffset;
|
||||
if (0 <= x && x <= this.inputBox.value.length) {
|
||||
_colOffset = 0;
|
||||
if (rowOffset === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're moving 'inputBox' prevent the arrow keys from moving the caret. If we don't do this,
|
||||
// our 'setSelectionRange()' below will appear off-by-one, and up/down in the top/bottom cells
|
||||
// will behave like home/end respectively.
|
||||
e.preventDefault();
|
||||
|
||||
const parent = this.inputBox.parentElement as HTMLTableCellElement;
|
||||
const [row, col] = this.getRowColFromTd(parent);
|
||||
if (this.moveInputToPosition(row + rowOffset, col + colOffset, e.shiftKey)) {
|
||||
// If we moved horizontally, move the caret to the beginning/end of the input as appropriate.
|
||||
const caretPosition = colOffset > 0 ? 0 : this.inputBox.value.length;
|
||||
this.inputBox.setSelectionRange(caretPosition, caretPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly cellInput = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.tdText!.textContent = `\u200B${this.inputBox.value}`;
|
||||
this.refreshFormulaInput();
|
||||
};
|
||||
|
||||
private readonly cellKeyDown = (e: KeyboardEvent) => {
|
||||
switch (e.code) {
|
||||
case KeyCode.escape: {
|
||||
this.cancelInput();
|
||||
break;
|
||||
}
|
||||
case KeyCode.arrowUp: {
|
||||
this.moveInputByOffset(e, /* rowOffset: */ -1, /* colOffset */ 0);
|
||||
break;
|
||||
}
|
||||
case KeyCode.enter: {
|
||||
this.commitInput(); /* fall-through */
|
||||
}
|
||||
case KeyCode.arrowDown: {
|
||||
this.moveInputByOffset(e, /* rowOffset: */ 1, /* colOffset */ 0);
|
||||
break;
|
||||
}
|
||||
case KeyCode.arrowLeft: {
|
||||
this.moveInputByOffset(e, /* rowOffset: */ 0, /* colOffset */ -1);
|
||||
break;
|
||||
}
|
||||
case KeyCode.tab: {
|
||||
e.preventDefault(); /* fall-through */
|
||||
}
|
||||
case KeyCode.arrowRight: {
|
||||
this.moveInputByOffset(e, /* rowOffset: */ 0, /* colOffset */ 1);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
public readonly formulaKeypress = (e: KeyboardEvent) => {
|
||||
if (e.code === KeyCode.enter) {
|
||||
this.updateSelectionFromFormulaInput();
|
||||
}
|
||||
};
|
||||
|
||||
public readonly formulaFocusOut = () => {
|
||||
this.updateSelectionFromFormulaInput();
|
||||
};
|
||||
|
||||
private getCellFromEvent(e: Event) {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
return target.nodeName === "TD" || target.nodeName === "TH"
|
||||
? (target as HTMLTableCellElement)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// Map the given 'id' string in the from 'row,col' to an array of 2 integers [row, col].
|
||||
private getRowColFromTd(td: HTMLTableDataCellElement) {
|
||||
const colOffset = td.cellIndex;
|
||||
const rowOffset = (td.parentElement as HTMLTableRowElement).rowIndex;
|
||||
|
||||
// The '-1' are to account for Row/Columns headings. Note that even though the column
|
||||
// headings our outside the body, it still impacts their cellIndex.
|
||||
return [this.startRow + rowOffset - 1, colOffset - 1];
|
||||
}
|
||||
|
||||
private getTdFromRowCol(row: number, col: number) {
|
||||
let _row = row;
|
||||
_row -= this.startRow;
|
||||
|
||||
// Column heading are outside the <tbody> in <thead>, and therefore we do not need
|
||||
// to make adjustments when indexing into children.
|
||||
const rows = this.tbody.children;
|
||||
if (_row < 0 || _row >= rows.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const cols = rows.item(_row)!.children;
|
||||
|
||||
// Row headings are inside the <tbody>, therefore we need to adjust our column
|
||||
// index by +/-1 to skip them.
|
||||
return 0 <= col && col < cols.length - 1 && cols.item(col + 1);
|
||||
}
|
||||
|
||||
private refreshFormulaInput() {
|
||||
if (this.selection.start === this.selection.end) {
|
||||
const [row, col] = this.selection.start;
|
||||
// The formula bar should always show raw values, but when a cell is
|
||||
// selected for edit it will be showing the raw value
|
||||
const cellValue = this.matrix.getCell(row, col);
|
||||
this.setFormula(`${cellValue ?? ""}`);
|
||||
} else {
|
||||
this.setFormula("<multiple selection>");
|
||||
}
|
||||
}
|
||||
|
||||
private updateSelectionFromFormulaInput() {
|
||||
// Don't handle multiple selection yet
|
||||
if (this.selection.start === this.selection.end) {
|
||||
const [row, col] = this.selection.start;
|
||||
const selectedCell = this.getTdFromRowCol(row, col) as HTMLTableDataCellElement;
|
||||
if (selectedCell) {
|
||||
const previous = this.matrix.getCell(row, col);
|
||||
const current = this.parseInput(this.getFormula());
|
||||
if (previous !== current) {
|
||||
selectedCell.textContent = `\u200B${current}`;
|
||||
this.matrix.setCell(row, col, current);
|
||||
this.sheetlet.invalidate(row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private refreshNumberSummary() {
|
||||
const [rowStart, colStart] = this.selection.start;
|
||||
const [rowEnd, colEnd] = this.selection.end;
|
||||
|
||||
const colStartLetter = this.numberToColumnLetter(colStart);
|
||||
const colEndLetter = this.numberToColumnLetter(colEnd);
|
||||
|
||||
const averageFormula = `=AVERAGE(${colStartLetter}${rowStart + 1}:${colEndLetter}${
|
||||
rowEnd + 1
|
||||
})`;
|
||||
const countFormula = `=COUNT(${colStartLetter}${rowStart + 1}:${colEndLetter}${
|
||||
rowEnd + 1
|
||||
})`;
|
||||
const sumFormula = `=SUM(${colStartLetter}${rowStart + 1}:${colEndLetter}${rowEnd + 1})`;
|
||||
|
||||
const avg = this.sheetlet.evaluateFormula(averageFormula);
|
||||
const count = this.sheetlet.evaluateFormula(countFormula);
|
||||
const sum = this.sheetlet.evaluateFormula(sumFormula);
|
||||
|
||||
if ((count as number) > 1) {
|
||||
this.setSelectionSummary(`Average:${avg} Count:${count} Sum:${sum}`);
|
||||
} else {
|
||||
this.setSelectionSummary("\u200B");
|
||||
}
|
||||
}
|
||||
|
||||
private numberToColumnLetter(index: number): string {
|
||||
let _index = index;
|
||||
let colString = String.fromCharCode((_index % 26) + 65);
|
||||
_index = _index / 26;
|
||||
|
||||
while (_index >= 1) {
|
||||
colString = String.fromCharCode((_index % 26) + 64) + colString;
|
||||
_index = _index / 26;
|
||||
}
|
||||
|
||||
return colString;
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.view {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
table.view {
|
||||
font: normal 11pt sanserif;
|
||||
margin: auto;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px;
|
||||
--select-border: ridge thin #88f;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
table.view tbody tr:nth-child(odd) td {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
table.view:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
table.view:focus-within tfoot tr {
|
||||
visibility: visible;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
table.view th {
|
||||
font: normal 8pt sanserif;
|
||||
border: solid transparent thin;
|
||||
margin: 0px;
|
||||
padding: 0px 4px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
table.view:focus-within th {
|
||||
opacity: 1;
|
||||
border: solid lightgray thin;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
table.view:focus-within table {
|
||||
background-color: white;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
table.view td {
|
||||
position: relative;
|
||||
padding: 4px;
|
||||
border: solid lightgray thin;
|
||||
white-space: pre;
|
||||
min-width: 63px;
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
position: absolute;
|
||||
padding: 4px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font: normal 11pt sanserif;
|
||||
width: 100%;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
table.view caption {
|
||||
text-align: center;
|
||||
padding: 4px 0px 0px 0px;
|
||||
}
|
||||
|
||||
/* Note: Must use 'table.view td.' in selector to override default style (more specific styles win). */
|
||||
/* Note: Border must be at least 2px to avoid gaps. */
|
||||
table.view:focus-within td.selectedTL,
|
||||
table.view:focus-within td.selectedT,
|
||||
table.view:focus-within td.selectedTR {
|
||||
border-top: var(--select-border);
|
||||
}
|
||||
|
||||
table.view:focus-within td.selectedTL,
|
||||
table.view:focus-within td.selectedL,
|
||||
table.view:focus-within td.selectedBL {
|
||||
border-left: var(--select-border);
|
||||
}
|
||||
|
||||
table.view:focus-within td.selectedTR,
|
||||
table.view:focus-within td.selectedR,
|
||||
table.view:focus-within td.selectedBR {
|
||||
border-right: var(--select-border);
|
||||
}
|
||||
|
||||
table.view:focus-within td.selectedBL,
|
||||
table.view:focus-within td.selectedB,
|
||||
table.view:focus-within td.selectedBR {
|
||||
border-bottom: var(--select-border);
|
||||
}
|
||||
|
||||
table.view:focus-within td.selectedTL,
|
||||
table.view:focus-within td.selectedT,
|
||||
table.view:focus-within td.selectedTR,
|
||||
table.view:focus-within td.selectedL,
|
||||
table.view:focus-within td.selected,
|
||||
table.view:focus-within td.selectedR,
|
||||
table.view:focus-within td.selectedBL,
|
||||
table.view:focus-within td.selectedB,
|
||||
table.view:focus-within td.selectedBR {
|
||||
background-color: rgba(17, 102, 238, 0.1);
|
||||
}
|
||||
|
||||
.grid {
|
||||
max-width: 100%;
|
||||
max-height: 50%;
|
||||
padding: 0px 0px 8px 0px;
|
||||
overflow: auto;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
export const grid: string;
|
||||
export const view: string;
|
||||
export const inputBox: string;
|
||||
export const selectedTL: string;
|
||||
export const selectedT: string;
|
||||
export const selectedTR: string;
|
||||
export const selectedL: string;
|
||||
export const selectedBL: string;
|
||||
export const selectedR: string;
|
||||
export const selectedBR: string;
|
||||
export const selectedB: string;
|
||||
export const selected: string;
|
|
@ -1,11 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
// set the base path for all dynamic imports first
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "./publicpath.js";
|
||||
|
||||
export { fluidExport } from "./runtime.js";
|
||||
export { TableModel, tableModelType } from "./tableModel.js";
|
|
@ -1,11 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
// We assume the current script runs at the base path. Simply extract out its filename and then use that path
|
||||
// as the base
|
||||
const base = (document.currentScript as HTMLScriptElement).src;
|
||||
__webpack_public_path__ = base.substr(0, base.lastIndexOf("/") + 1);
|
||||
|
||||
export {};
|
|
@ -1,27 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { ContainerViewRuntimeFactory } from "@fluid-example/example-utils";
|
||||
import { createDataStoreFactory } from "@fluidframework/runtime-utils";
|
||||
import React from "react";
|
||||
import { TableModel, tableModelType } from "./tableModel.js";
|
||||
import { TableView } from "./tableView.js";
|
||||
|
||||
const tableModelFactory = createDataStoreFactory(
|
||||
tableModelType,
|
||||
import(/* webpackChunkName: "table-view", webpackPreload: true */ "./tableModel").then((m) =>
|
||||
m.TableModel.getFactory(),
|
||||
),
|
||||
);
|
||||
|
||||
const tableViewCallback = (model: TableModel) => React.createElement(TableView, { model });
|
||||
|
||||
/**
|
||||
* This does setup for the Container. The ContainerViewRuntimeFactory will instantiate a single Fluid object to use
|
||||
* as our model (using the factory we provide), and the view callback we provide will pair that model with an
|
||||
* appropriate view.
|
||||
* @internal
|
||||
*/
|
||||
export const fluidExport = new ContainerViewRuntimeFactory(tableModelFactory, tableViewCallback);
|
|
@ -1,45 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { SharedMatrix } from "@fluidframework/matrix";
|
||||
import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
|
||||
import { IFluidHandle } from "@fluidframework/core-interfaces";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const tableModelType = "@fluid-example/table-view";
|
||||
|
||||
const matrixKey = "matrixKey";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class TableModel extends DataObject {
|
||||
public static getFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
private _tableMatrix: SharedMatrix | undefined;
|
||||
public get tableMatrix() {
|
||||
if (this._tableMatrix === undefined) {
|
||||
throw new Error("Table matrix not fully initialized");
|
||||
}
|
||||
return this._tableMatrix;
|
||||
}
|
||||
|
||||
protected async initializingFirstTime() {
|
||||
const matrix = SharedMatrix.create(this.runtime);
|
||||
this.root.set(matrixKey, matrix.handle);
|
||||
matrix.insertRows(0, 5);
|
||||
matrix.insertCols(0, 8);
|
||||
}
|
||||
|
||||
protected async hasInitialized(): Promise<void> {
|
||||
this._tableMatrix = await this.root.get<IFluidHandle<SharedMatrix>>(matrixKey)?.get();
|
||||
}
|
||||
}
|
||||
|
||||
const factory = new DataObjectFactory(tableModelType, TableModel, [SharedMatrix.getFactory()], {});
|
|
@ -1,89 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { GridView } from "./grid.js";
|
||||
import * as styles from "./index.css";
|
||||
import { TableModel } from "./tableModel.js";
|
||||
|
||||
interface ITableViewProps {
|
||||
model: TableModel;
|
||||
}
|
||||
|
||||
export const TableView: React.FC<ITableViewProps> = (props: ITableViewProps) => {
|
||||
const { model } = props;
|
||||
|
||||
const formulaInputRef = useRef<HTMLInputElement>(null);
|
||||
const selectionSummarySpanRef = useRef<HTMLSpanElement>(null);
|
||||
const goToInputRef = useRef<HTMLInputElement>(null);
|
||||
const gridRootRef = useRef<HTMLDivElement>(null);
|
||||
const gridView = useRef<GridView>();
|
||||
|
||||
const getFormula = () => formulaInputRef.current?.value ?? "";
|
||||
const setFormula = (val: string) => {
|
||||
if (formulaInputRef.current !== null) {
|
||||
formulaInputRef.current.value = val;
|
||||
}
|
||||
};
|
||||
const setSelectionSummary = (val: string) => {
|
||||
if (selectionSummarySpanRef.current !== null) {
|
||||
selectionSummarySpanRef.current.textContent = val;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
gridView.current = new GridView(
|
||||
model.tableMatrix,
|
||||
getFormula,
|
||||
setFormula,
|
||||
setSelectionSummary,
|
||||
);
|
||||
if (gridRootRef.current !== null) {
|
||||
while (gridRootRef.current.firstChild !== null) {
|
||||
gridRootRef.current.removeChild(gridRootRef.current.firstChild);
|
||||
}
|
||||
gridRootRef.current.append(gridView.current.root);
|
||||
}
|
||||
}, [model]);
|
||||
|
||||
const executeGoTo = () => {
|
||||
if (gridView.current !== undefined && goToInputRef.current !== null) {
|
||||
gridView.current.startRow = parseInt(goToInputRef.current.value, 10) - 1;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => model.tableMatrix.insertRows(model.tableMatrix.rowCount, 1)}>
|
||||
R+
|
||||
</button>
|
||||
<button onClick={() => model.tableMatrix.insertCols(model.tableMatrix.colCount, 1)}>
|
||||
C+
|
||||
</button>
|
||||
<button onClick={() => model.tableMatrix.insertRows(model.tableMatrix.rowCount, 10)}>
|
||||
R++
|
||||
</button>
|
||||
<button onClick={() => model.tableMatrix.insertCols(model.tableMatrix.colCount, 10)}>
|
||||
C++
|
||||
</button>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
ref={formulaInputRef}
|
||||
onKeyPress={(e) => {
|
||||
gridView.current?.formulaKeypress(e.nativeEvent);
|
||||
}}
|
||||
onBlur={() => {
|
||||
gridView.current?.formulaFocusOut();
|
||||
}}
|
||||
placeholder="Formula input"
|
||||
/>
|
||||
<div ref={gridRootRef} className={styles.grid}></div>
|
||||
<span ref={selectionSummarySpanRef}></span>
|
||||
</div>
|
||||
<input type="text" ref={goToInputRef} onChange={executeGoTo} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": "@fluidframework/build-common/ts-common-config.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"jsx": "react",
|
||||
"rootDir": "./src",
|
||||
"outDir": "lib",
|
||||
"types": ["react"],
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
const fluidRoute = require("@fluid-example/webpack-fluid-loader");
|
||||
const path = require("path");
|
||||
const { merge } = require("webpack-merge");
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = (env) => {
|
||||
const isProduction = env?.production;
|
||||
const styleLocalIdentName = isProduction ? "[hash:base64:5]" : "[local]-[hash:base64:5]";
|
||||
|
||||
return merge(
|
||||
{
|
||||
entry: "./src/index.ts",
|
||||
resolve: {
|
||||
extensionAlias: {
|
||||
".js": [".ts", ".tsx", ".js", ".cjs", ".mjs"],
|
||||
},
|
||||
extensions: [".mjs", ".ts", ".tsx", ".js"],
|
||||
fallback: {
|
||||
dgram: false,
|
||||
fs: false,
|
||||
net: false,
|
||||
tls: false,
|
||||
child_process: false,
|
||||
},
|
||||
},
|
||||
devtool: "source-map",
|
||||
mode: "production",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
compilerOptions: {
|
||||
module: "esnext",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
use: [require.resolve("source-map-loader")],
|
||||
enforce: "pre",
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
modules: true,
|
||||
localIdentName: styleLocalIdentName,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
|
||||
loader: "url-loader",
|
||||
options: {
|
||||
limit: 10000,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: "html-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].bundle.js",
|
||||
chunkFilename: "[name].async.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
publicPath: "/dist/",
|
||||
library: "[name]",
|
||||
libraryTarget: "umd",
|
||||
globalObject: "self",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
process: "process/browser",
|
||||
}),
|
||||
],
|
||||
// This impacts which files are watched by the dev server (and likely by webpack if watch is true).
|
||||
// This should be configurable under devServer.static.watch
|
||||
// (see https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md) but that does not seem to work.
|
||||
// The CLI options for disabling watching don't seem to work either, so this may be a symptom of using webpack4 with the newer webpack-cli and webpack-dev-server.
|
||||
watchOptions: {
|
||||
ignored: "**/node_modules/**",
|
||||
},
|
||||
},
|
||||
isProduction ? require("./webpack.prod.cjs") : require("./webpack.dev.cjs"),
|
||||
fluidRoute.devServerConfig(__dirname, env),
|
||||
);
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
// const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = {
|
||||
mode: "production",
|
||||
devtool: "source-map",
|
||||
// plugins: [
|
||||
// new BundleAnalyzerPlugin()
|
||||
// ]
|
||||
};
|
|
@ -2637,73 +2637,6 @@ importers:
|
|||
rimraf: 4.4.1
|
||||
typescript: 5.1.6
|
||||
|
||||
examples/data-objects/table-view:
|
||||
specifiers:
|
||||
'@fluid-example/example-utils': workspace:~
|
||||
'@fluid-example/table-document': workspace:~
|
||||
'@fluid-example/webpack-fluid-loader': workspace:~
|
||||
'@fluidframework/aqueduct': workspace:~
|
||||
'@fluidframework/build-common': ^2.0.3
|
||||
'@fluidframework/build-tools': ^0.34.0
|
||||
'@fluidframework/container-definitions': workspace:~
|
||||
'@fluidframework/core-interfaces': workspace:~
|
||||
'@fluidframework/eslint-config-fluid': ^5.1.0
|
||||
'@fluidframework/matrix': workspace:~
|
||||
'@fluidframework/runtime-utils': workspace:~
|
||||
'@fluidframework/sequence': workspace:~
|
||||
'@tiny-calc/micro': 0.0.0-alpha.5
|
||||
'@tiny-calc/nano': 0.0.0-alpha.5
|
||||
'@types/node': ^18.19.0
|
||||
'@types/react': ^17.0.44
|
||||
copyfiles: ^2.4.1
|
||||
css-loader: ^1.0.0
|
||||
eslint: ~8.55.0
|
||||
prettier: ~3.0.3
|
||||
react: ^17.0.1
|
||||
rimraf: ^4.4.0
|
||||
source-map-loader: ^2.0.0
|
||||
style-loader: ^1.0.0
|
||||
ts-loader: ^9.3.0
|
||||
typescript: ~5.1.6
|
||||
url-loader: ^2.1.0
|
||||
webpack: ^5.82.0
|
||||
webpack-cli: ^4.9.2
|
||||
webpack-dev-server: ~4.6.0
|
||||
webpack-merge: ^5.8.0
|
||||
dependencies:
|
||||
'@fluid-example/example-utils': link:../../utils/example-utils
|
||||
'@fluid-example/table-document': link:../table-document
|
||||
'@fluidframework/aqueduct': link:../../../packages/framework/aqueduct
|
||||
'@fluidframework/container-definitions': link:../../../packages/common/container-definitions
|
||||
'@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces
|
||||
'@fluidframework/matrix': link:../../../packages/dds/matrix
|
||||
'@fluidframework/runtime-utils': link:../../../packages/runtime/runtime-utils
|
||||
'@fluidframework/sequence': link:../../../packages/dds/sequence
|
||||
'@tiny-calc/micro': 0.0.0-alpha.5
|
||||
'@tiny-calc/nano': 0.0.0-alpha.5
|
||||
react: 17.0.2
|
||||
devDependencies:
|
||||
'@fluid-example/webpack-fluid-loader': link:../../utils/webpack-fluid-loader
|
||||
'@fluidframework/build-common': 2.0.3
|
||||
'@fluidframework/build-tools': 0.34.0_h467wi3sy6j67ifywrn7x7qf4m
|
||||
'@fluidframework/eslint-config-fluid': 5.1.0_bpztyfltmpuv6lhsgzfwtmxhte
|
||||
'@types/node': 18.19.1
|
||||
'@types/react': 17.0.71
|
||||
copyfiles: 2.4.1
|
||||
css-loader: 1.0.1_webpack@5.89.0
|
||||
eslint: 8.55.0
|
||||
prettier: 3.0.3
|
||||
rimraf: 4.4.1
|
||||
source-map-loader: 2.0.2_webpack@5.89.0
|
||||
style-loader: 1.3.0_webpack@5.89.0
|
||||
ts-loader: 9.5.1_535b6rdv6xzubhvm4whegmtnju
|
||||
typescript: 5.1.6
|
||||
url-loader: 2.3.0_webpack@5.89.0
|
||||
webpack: 5.89.0_webpack-cli@4.10.0
|
||||
webpack-cli: 4.10.0_o3vqr5s7sd4b3hfy35jxofofzu
|
||||
webpack-dev-server: 4.6.0_xufw7vsm4hgw2efzchq5tzqpge
|
||||
webpack-merge: 5.10.0
|
||||
|
||||
examples/data-objects/todo:
|
||||
specifiers:
|
||||
'@fluid-example/example-utils': workspace:~
|
||||
|
@ -21510,6 +21443,7 @@ packages:
|
|||
resolution: {integrity: sha512-ldmINj6PDn5SaeIT9STO/P1O2rhPZjSkIoWUaXjfuBAEcE5bflwh5+WTFShofJcpnaCVpGannXuDS0+qn6Ctfg==}
|
||||
dependencies:
|
||||
'@tiny-calc/nano': 0.0.0-alpha.5
|
||||
dev: true
|
||||
|
||||
/@tiny-calc/nano/0.0.0-alpha.5:
|
||||
resolution: {integrity: sha512-Hs37tz9ZtvK21/5s4tjt5RBa/PFHKYS0AzvdxiXuSd3+AKQN2ygxw7uwD9j0DIG9qONddg1vIASO77JIGyZzyw==}
|
||||
|
|
Загрузка…
Ссылка в новой задаче