mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-13 15:09:05 +00:00
M13: web app (WASM via trunk), async file open, ROADMAP with parity estimates, parity report, xtask alias
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
beb353943e
commit
b6ae96633d
10 files changed
+762
-1
No files matched your search
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
Generated
+17
@@ -2928,6 +2928,23 @@ dependencies = [
|
||||
"sheetcraft-engine",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sheetcraft-web"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
"js-sys",
|
||||
"log",
|
||||
"rfd",
|
||||
"serde_json",
|
||||
"sheetcraft-engine",
|
||||
"sheetcraft-ui-egui",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sheetcraft-xlsx"
|
||||
version = "0.1.0"
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# SheetCraft roadmap
|
||||
|
||||
SheetCraft aims at full Microsoft Excel parity — and to be better: faster, open (XLSX is the
|
||||
native format), fully scriptable by agents (MCP + control channel + CLI), and available on the
|
||||
web.
|
||||
|
||||
## Status (2026-10-07)
|
||||
|
||||
**Parity estimate**
|
||||
|
||||
| Measure | Value | Notes |
|
||||
|---|---|---|
|
||||
| Ribbon/menu command catalog | **60%** (169 / 281) | `cargo xtask parity` → [`docs/parity.md`](docs/parity.md). Home is 97%. |
|
||||
| Worksheet functions | **~93%** (478 + 32 evaluator built-ins of ~545) | Missing: ODDF*/ODDL*, BAHTTEXT, IMAGE, TRANSLATE, COPILOT/PY, STOCKHISTORY, RTD |
|
||||
| Feature depth (weighted) | **~38%** | Pivot tables, Power Query, print/PDF, page layout view, chart formatting depth, ink, macros/scripts, threaded collaboration and solver/analysis tools are the big missing areas |
|
||||
|
||||
**Estimated remaining effort to full parity: ~220 wall-clock hours of Claude Opus 5.5 agent
|
||||
work** (≈ 9–10 days running continuously with 3–4 parallel agents), broken down below.
|
||||
|
||||
**Working today**
|
||||
- Engine: A1/R1C1 formula parser with reference adjustment, dependency-graph recalculation with
|
||||
dynamic arrays (spill + `#SPILL!`), cycle detection, LET/LAMBDA/MAP/REDUCE/SCAN/BYROW/BYCOL/
|
||||
MAKEARRAY, structured table references, defined names, INDIRECT/OFFSET/INDEX references,
|
||||
478 worksheet functions (math, statistics and distributions, text and regex, lookup incl.
|
||||
XLOOKUP/FILTER/SORT/UNIQUE/VSTACK, dates, financial incl. bonds, engineering incl. complex
|
||||
numbers and Bessel, database, information).
|
||||
- Number formats: Excel's full format-code language (sections, conditions, colours, dates and
|
||||
elapsed time, fractions, scientific, accounting fill), General narrowing to column width.
|
||||
- Files: XLSX read/write (styles, themes, merges, CF, validation, tables, comments, hyperlinks,
|
||||
charts, pictures, sparklines, print settings, defined names, shared/array/dynamic formulas),
|
||||
CSV/TSV with encoding and delimiter detection, HTML export, JSON.
|
||||
- Editing: cell entry with type detection and auto-formats, in-cell and formula-bar editing,
|
||||
point mode (click/drag/arrow references), reference colouring, F4 anchor cycling, function
|
||||
autocomplete and argument hints, copy/cut/paste and Paste Special (values, formats, transpose,
|
||||
operations, link, skip blanks), AutoFill series (numbers, dates, months, weekdays, custom lists,
|
||||
text+number), Fill Series, Flash Fill, find/replace with wildcards, Go To / Go To Special,
|
||||
undo/redo with history, format painter.
|
||||
- Formatting: fonts, fills, all border styles, alignment (wrap, indent, rotation, merge), number
|
||||
formats, 47 cell styles, 60 table styles, conditional formatting (cell rules, text, dates,
|
||||
duplicates, top/bottom, averages, data bars, colour scales, icon sets, formulas), automatic
|
||||
row heights, autofit, hide/unhide, freeze panes.
|
||||
- Data: sort (multi-level, by colour, custom lists), AutoFilter (values, custom, top 10,
|
||||
average, colour), tables with totals rows, remove duplicates, text to columns, data validation
|
||||
with error alerts, grouping/outline, subtotals.
|
||||
- Charts: column, bar, line, area, pie, doughnut, scatter, bubble, radar, histogram, waterfall,
|
||||
funnel, treemap, sunburst, box & whisker, stock and combo charts; sparklines.
|
||||
- UI (egui): Excel-style title bar with Quick Access Toolbar and AutoSave, the full ribbon with
|
||||
galleries (cell styles, table styles, colour palettes, conditional formatting menus),
|
||||
contextual Table/Chart Design tabs, formula bar with Name Box, virtualised grid (smooth
|
||||
scrolling, zoom, frozen panes, text overflow), sheet tabs (rename, reorder, colour, hide),
|
||||
status bar with Sum/Count/Average and zoom, dialogs (Format Cells, Formula Builder, Find and
|
||||
Replace, Sort, Name Manager, Data Validation, Paste Special…), command palette, dark mode.
|
||||
- Agents: every action is an engine command; JSON control channel in the desktop app (pointer,
|
||||
keyboard, dialogs, screenshots); MCP server (headless or bridged); CLI (`info`, `convert`,
|
||||
`eval`, `cat`, `run`, `commands`, `functions`, `mcp`, `send`).
|
||||
- Platforms: macOS, Windows, Linux, FreeBSD; web (WASM via trunk). Release workflows for
|
||||
signed/notarized macOS universal DMG, signed Windows x64/x86 MSI + zip, Linux AppImage/deb/rpm/
|
||||
tar.gz + Flatpak manifest, FreeBSD tarball and a web zip.
|
||||
|
||||
## Milestones
|
||||
|
||||
| # | Milestone | Status | Est. hours left |
|
||||
|---|---|---|---|
|
||||
| M0 | Foundation: core, number formats, formula language, functions, model, calc, XLSX/CSV, engine | ✅ done | — |
|
||||
| M1 | Excel look: chrome, ribbon, formula bar, grid, tabs, status bar, control channel | ✅ done (polish continues) | 8 |
|
||||
| M2 | Formatting completeness (Format Cells parity, borders drawing, themes fonts/effects, styles authoring) | 🟡 most | 10 |
|
||||
| M3 | Editing power (drag-move/copy with Alt, insert copied cells, AutoComplete in column, AutoCorrect, spelling) | 🟡 most | 12 |
|
||||
| M4 | Data (advanced filter, consolidate, what-if: goal seek, scenarios, data tables; Get & Transform lite) | 🟡 partial | 24 |
|
||||
| M5 | Formulas tab (trace arrows on the grid, watch window, evaluate stepper UI, error-check UI, remaining functions) | 🟡 partial | 12 |
|
||||
| M6 | Conditional formatting manager parity (rule editor dialog, all icon sets drawn, stop-if-true UI) | 🟡 most | 6 |
|
||||
| M7 | Charts parity (element selection & Format pane, axes options, trendlines, error bars, chart sheets, styles/colours galleries, PivotCharts) | 🟡 partial | 28 |
|
||||
| M8 | Insert & Review (icons library of our own, SmartArt-lite, equations, threaded comments pane, show changes, accessibility pane) | 🟡 partial | 18 |
|
||||
| M9 | Page layout & print (Page Layout and Page Break Preview views, header/footer editor, print to PDF, print preview) | ⬜ | 26 |
|
||||
| M10 | PivotTables (field list, layouts, grouping, calculated fields, slicers, timelines), Solver, Analysis ToolPak | ⬜ | 40 |
|
||||
| M11 | Automation (record actions → replayable scripts, Rhai script editor, form controls) | ⬜ | 14 |
|
||||
| M12 | Performance (1M-row files, parallel recalc, incremental geometry caches), accessibility, i18n | 🟡 partial | 16 |
|
||||
| M13 | Release & polish (signed builds green on every platform, side-by-side pixel tuning, README screenshots) | 🟡 tooling ready | 6 |
|
||||
| | **Total** | | **≈ 220** |
|
||||
|
||||
## How to measure
|
||||
|
||||
- `cargo xtask parity` — catalog coverage (feature names from Excel's ribbon and menus).
|
||||
- `cargo test --workspace` — engine, formula, function, XLSX and UI behaviour.
|
||||
- `cargo run --release -p sheetcraft-ui-egui --example snapshot -- --sample sales out.png` — look at the UI offscreen.
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "sheetcraft-web"
|
||||
description = "SheetCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "sheetcraft-web"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
sheetcraft-engine = { workspace = true }
|
||||
sheetcraft-ui-egui = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] }
|
||||
rfd = { workspace = true }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration"] }
|
||||
@@ -0,0 +1,10 @@
|
||||
# Build with: trunk build --release (from apps/sheetcraft-web)
|
||||
[build]
|
||||
target = "index.html"
|
||||
dist = "../../dist/web"
|
||||
public_url = "./"
|
||||
filehash = true
|
||||
|
||||
[serve]
|
||||
addresses = ["127.0.0.1"]
|
||||
port = 8771
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<title>SheetCraft</title>
|
||||
<link data-trunk rel="rust" data-bin="sheetcraft-web" data-wasm-opt="z" data-weak-refs />
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #f3f3f3; }
|
||||
#sheetcraft_canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#sheetcraft_loading { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
color: #555; font: 14px system-ui, sans-serif; pointer-events: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="sheetcraft_canvas"></canvas>
|
||||
<div id="sheetcraft_loading">Loading SheetCraft…</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
//! SheetCraft in the browser.
|
||||
//!
|
||||
//! Runs the same [`sheetcraft_ui_egui::SheetApp`] as the desktop app through eframe's web runner
|
||||
//! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from
|
||||
//! this directory (output in `dist/web`).
|
||||
//!
|
||||
//! Differences from the desktop app: no TCP control channel; Open uses the browser file picker
|
||||
//! (bytes arrive asynchronously through `Services::inbox`); Save downloads the file; dropped
|
||||
//! files are read asynchronously.
|
||||
//!
|
||||
//! URL query flags: `?webgl` forces WebGL2; `?sample=sales|budget|grades` opens a sample.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
web::start();
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
eprintln!("sheetcraft-web only runs in the browser: build it with `trunk build --release` in apps/sheetcraft-web");
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//! The browser shell: web `Services`, drag-and-drop, and the eframe web runner.
|
||||
|
||||
use sheetcraft_engine::Session;
|
||||
use sheetcraft_ui_egui::{Inbox, Services, SheetApp};
|
||||
use wasm_bindgen::JsCast as _;
|
||||
|
||||
const CANVAS_ID: &str = "sheetcraft_canvas";
|
||||
const LOADING_ID: &str = "sheetcraft_loading";
|
||||
|
||||
pub fn start() {
|
||||
eframe::WebLogger::init(log::LevelFilter::Info).ok();
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
let Some(document) = web_sys::window().and_then(|w| w.document()) else {
|
||||
log::error!("no document");
|
||||
return;
|
||||
};
|
||||
let Some(canvas) = document.get_element_by_id(CANVAS_ID).and_then(|e| e.dyn_into::<web_sys::HtmlCanvasElement>().ok()) else {
|
||||
log::error!("missing <canvas id=\"{CANVAS_ID}\">");
|
||||
return;
|
||||
};
|
||||
let mut options = eframe::WebOptions::default();
|
||||
if query().contains("webgl")
|
||||
&& let eframe::egui_wgpu::WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup
|
||||
{
|
||||
create.instance_descriptor.backends = eframe::wgpu::Backends::GL;
|
||||
}
|
||||
let result = eframe::WebRunner::new()
|
||||
.start(
|
||||
canvas,
|
||||
options,
|
||||
Box::new(move |cc| {
|
||||
let inbox: Inbox = Inbox::default();
|
||||
let mut session = Session::new();
|
||||
let q = query();
|
||||
if let Some(name) = q.split(['?', '&']).find_map(|kv| kv.strip_prefix("sample=")) {
|
||||
let _ = session.execute("file.new", serde_json::json!({"sample": name}));
|
||||
} else if q.contains("sample") {
|
||||
let _ = session.execute("file.new", serde_json::json!({"sample": "sales"}));
|
||||
}
|
||||
let app = SheetApp::new(session, services(inbox.clone(), cc.egui_ctx.clone()));
|
||||
SheetApp::setup_context(&cc.egui_ctx, false);
|
||||
Ok(Box::new(WebShell { app, inbox }))
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
if let Some(el) = document.get_element_by_id(LOADING_ID) {
|
||||
match result {
|
||||
Ok(()) => el.remove(),
|
||||
Err(e) => el.set_inner_html(&format!("<p>SheetCraft failed to start: {e:?}</p><p>A browser with WebGPU or WebGL2 is required.</p>")),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn query() -> String {
|
||||
web_sys::window().and_then(|w| w.location().search().ok()).unwrap_or_default()
|
||||
}
|
||||
|
||||
struct WebShell {
|
||||
app: SheetApp,
|
||||
inbox: Inbox,
|
||||
}
|
||||
|
||||
impl eframe::App for WebShell {
|
||||
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
let dropped = ctx.input_mut(|i| std::mem::take(&mut i.raw.dropped_files));
|
||||
for f in dropped {
|
||||
let inbox = self.inbox.clone();
|
||||
let ctx = ctx.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let name = f.path().file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| "dropped.xlsx".into());
|
||||
match f.bytes_async().await {
|
||||
Ok(bytes) => {
|
||||
inbox.lock().unwrap_or_else(|e| e.into_inner()).push((name, bytes));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
Err(e) => log::error!("couldn't read dropped file {name}: {e}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
self.app.logic(ctx);
|
||||
}
|
||||
|
||||
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
self.app.raw_input_hook(raw);
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
self.app.ui(ui);
|
||||
}
|
||||
}
|
||||
|
||||
fn services(inbox: Inbox, ctx: egui::Context) -> Services {
|
||||
let open_inbox = inbox.clone();
|
||||
Services {
|
||||
open_async: Some(Box::new(move || {
|
||||
let inbox = open_inbox.clone();
|
||||
let ctx = ctx.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let Some(file) = rfd::AsyncFileDialog::new().add_filter("Spreadsheets", &["xlsx", "xlsm", "csv", "tsv", "txt", "json"]).pick_file().await else {
|
||||
return;
|
||||
};
|
||||
let bytes = file.read().await;
|
||||
inbox.lock().unwrap_or_else(|e| e.into_inner()).push((file.file_name(), bytes));
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})),
|
||||
download: Some(Box::new(|name: &str, bytes: &[u8]| {
|
||||
if let Err(e) = download(name, bytes) {
|
||||
log::error!("download of {name} failed: {e}");
|
||||
}
|
||||
})),
|
||||
open_url: Some(Box::new(|url: &str| {
|
||||
if let Some(w) = web_sys::window() {
|
||||
let _ = w.open_with_url_and_target(url, "_blank");
|
||||
}
|
||||
})),
|
||||
inbox: Some(inbox),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers a browser download of `bytes`.
|
||||
fn download(name: &str, bytes: &[u8]) -> Result<(), String> {
|
||||
let js = |e: wasm_bindgen::JsValue| format!("{e:?}");
|
||||
let window = web_sys::window().ok_or("no window")?;
|
||||
let document = window.document().ok_or("no document")?;
|
||||
let parts = js_sys::Array::of1(&js_sys::Uint8Array::from(bytes));
|
||||
let opts = web_sys::BlobPropertyBag::new();
|
||||
opts.set_type(if name.ends_with(".csv") { "text/csv" } else { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
|
||||
let blob = web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, &opts).map_err(js)?;
|
||||
let url = web_sys::Url::create_object_url_with_blob(&blob).map_err(js)?;
|
||||
let a: web_sys::HtmlAnchorElement = document.create_element("a").map_err(js)?.dyn_into().map_err(|_| "not an anchor")?;
|
||||
a.set_href(&url);
|
||||
a.set_download(name);
|
||||
a.style().set_property("display", "none").map_err(js)?;
|
||||
let body = document.body().ok_or("no body")?;
|
||||
body.append_child(&a).map_err(js)?;
|
||||
a.click();
|
||||
a.remove();
|
||||
let revoke = wasm_bindgen::closure::Closure::once_into_js(move || {
|
||||
web_sys::Url::revoke_object_url(&url).ok();
|
||||
});
|
||||
window.set_timeout_with_callback_and_timeout_and_arguments_0(revoke.unchecked_ref(), 10_000).map_err(js)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -68,8 +68,15 @@ pub struct Services {
|
||||
pub open_url: Option<Box<dyn Fn(&str)>>,
|
||||
/// Web: deliver a file to the user (name, bytes).
|
||||
pub download: Option<Box<dyn Fn(&str, &[u8])>>,
|
||||
/// Web: start an asynchronous file pick; the bytes arrive later in `inbox`.
|
||||
pub open_async: Option<Box<dyn Fn()>>,
|
||||
/// Files delivered asynchronously (name, bytes), opened on the next frame.
|
||||
pub inbox: Option<Inbox>,
|
||||
}
|
||||
|
||||
/// Shared queue of files read asynchronously (browser file picker, drag and drop).
|
||||
pub type Inbox = std::sync::Arc<std::sync::Mutex<Vec<(String, Vec<u8>)>>>;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Perf {
|
||||
pub frame_ms: f64,
|
||||
@@ -210,7 +217,9 @@ impl SheetApp {
|
||||
pub fn open_dialog(&mut self, name: &str, params: Json) {
|
||||
match name {
|
||||
"open" => {
|
||||
if let Some(pick) = &self.services.pick_open
|
||||
if let Some(start) = &self.services.open_async {
|
||||
start();
|
||||
} else if let Some(pick) = &self.services.pick_open
|
||||
&& let Some(path) = pick()
|
||||
{
|
||||
self.open_path(&path);
|
||||
@@ -347,6 +356,15 @@ impl SheetApp {
|
||||
theme::apply(ctx, self.ui.dark);
|
||||
}
|
||||
control::poll(self, ctx);
|
||||
// Files read asynchronously (web file picker, dropped files).
|
||||
let arrived: Vec<(String, Vec<u8>)> = self.services.inbox.as_ref().map(|i| std::mem::take(&mut *i.lock().unwrap_or_else(std::sync::PoisonError::into_inner))).unwrap_or_default();
|
||||
for (name, bytes) in arrived {
|
||||
let b64 = sheetcraft_engine::io::base64_encode(&bytes);
|
||||
if let Err(e) = self.session.run("file.open", json!({"name": name, "base64": b64})) {
|
||||
self.message = Some(("SheetCraft".into(), clean_error(&e)));
|
||||
}
|
||||
self.after_engine();
|
||||
}
|
||||
if self.session.active().is_none() {
|
||||
self.session.new_workbook();
|
||||
}
|
||||
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
# Excel parity
|
||||
|
||||
<!-- Generated by `cargo xtask parity` from crates/engine/src/catalog.rs. Don't edit by hand. -->
|
||||
|
||||
Each entry is an Excel ribbon or menu command (feature names only). It counts as implemented when SheetCraft registers a command with the same id.
|
||||
|
||||
**Overall: 169 / 281 commands implemented (60.1%).**
|
||||
|
||||
| Tab | Implemented | Total | Parity |
|
||||
|---|---|---|---|
|
||||
| File | 8 | 11 | 73% |
|
||||
| Home | 91 | 94 | 97% |
|
||||
| Insert | 11 | 25 | 44% |
|
||||
| Draw | 0 | 4 | 0% |
|
||||
| Page Layout | 12 | 23 | 52% |
|
||||
| Formulas | 13 | 23 | 57% |
|
||||
| Data | 10 | 28 | 36% |
|
||||
| Review | 7 | 18 | 39% |
|
||||
| View | 6 | 20 | 30% |
|
||||
| Automate | 0 | 3 | 0% |
|
||||
| Table Design | 9 | 13 | 69% |
|
||||
| Chart Design | 2 | 8 | 25% |
|
||||
| Format | 0 | 3 | 0% |
|
||||
| PivotTable Analyze | 0 | 4 | 0% |
|
||||
| PivotTable Design | 0 | 4 | 0% |
|
||||
|
||||
## File (8 / 11, 73%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `file.new` New (File)
|
||||
- `file.open` Open (File)
|
||||
- `file.save` Save (File)
|
||||
- `file.saveAs` Save As (File)
|
||||
- `file.close` Close (File)
|
||||
- `file.exportCsv` CSV (Export)
|
||||
- `file.exportHtml` Web Page (Export)
|
||||
- `file.properties` Properties (Info)
|
||||
|
||||
Missing:
|
||||
|
||||
- `file.print` Print (File)
|
||||
- `file.exportPdf` Export to PDF (Export)
|
||||
- `file.options` Options (Options)
|
||||
|
||||
## Home (91 / 94, 97%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `edit.paste` Paste (Clipboard)
|
||||
- `edit.pasteSpecial` Paste Special (Clipboard)
|
||||
- `edit.cut` Cut (Clipboard)
|
||||
- `edit.copy` Copy (Clipboard)
|
||||
- `edit.formatPainter` Format Painter (Clipboard)
|
||||
- `home.fontName` Font (Font)
|
||||
- `home.fontSize` Font Size (Font)
|
||||
- `home.increaseFontSize` Increase Font Size (Font)
|
||||
- `home.decreaseFontSize` Decrease Font Size (Font)
|
||||
- `home.bold` Bold (Font)
|
||||
- `home.italic` Italic (Font)
|
||||
- `home.underline` Underline (Font)
|
||||
- `home.doubleUnderline` Double Underline (Font)
|
||||
- `home.strikethrough` Strikethrough (Font)
|
||||
- `home.superscript` Superscript (Font)
|
||||
- `home.subscript` Subscript (Font)
|
||||
- `home.borders` Borders (Font)
|
||||
- `home.fillColor` Fill Color (Font)
|
||||
- `home.fontColor` Font Color (Font)
|
||||
- `home.alignTop` Top Align (Alignment)
|
||||
- `home.alignMiddle` Middle Align (Alignment)
|
||||
- `home.alignBottom` Bottom Align (Alignment)
|
||||
- `home.orientation` Orientation (Alignment)
|
||||
- `home.alignLeft` Align Left (Alignment)
|
||||
- `home.alignCenter` Center (Alignment)
|
||||
- `home.alignRight` Align Right (Alignment)
|
||||
- `home.decreaseIndent` Decrease Indent (Alignment)
|
||||
- `home.increaseIndent` Increase Indent (Alignment)
|
||||
- `home.wrapText` Wrap Text (Alignment)
|
||||
- `home.mergeCenter` Merge & Center (Alignment)
|
||||
- `home.mergeAcross` Merge Across (Alignment)
|
||||
- `home.mergeCells` Merge Cells (Alignment)
|
||||
- `home.unmergeCells` Unmerge Cells (Alignment)
|
||||
- `home.numberFormat` Number Format (Number)
|
||||
- `home.accounting` Accounting Number Format (Number)
|
||||
- `home.percent` Percent Style (Number)
|
||||
- `home.comma` Comma Style (Number)
|
||||
- `home.increaseDecimal` Increase Decimal (Number)
|
||||
- `home.decreaseDecimal` Decrease Decimal (Number)
|
||||
- `home.conditionalFormat` Conditional Formatting (Styles)
|
||||
- `home.clearRules` Clear Rules (Styles)
|
||||
- `home.manageRules` Manage Rules (Styles)
|
||||
- `home.formatAsTable` Format as Table (Styles)
|
||||
- `home.cellStyle` Cell Styles (Styles)
|
||||
- `home.insertCells` Insert Cells (Cells)
|
||||
- `home.insertRows` Insert Sheet Rows (Cells)
|
||||
- `home.insertColumns` Insert Sheet Columns (Cells)
|
||||
- `home.insertSheet` Insert Sheet (Cells)
|
||||
- `home.deleteCells` Delete Cells (Cells)
|
||||
- `home.deleteRows` Delete Sheet Rows (Cells)
|
||||
- `home.deleteColumns` Delete Sheet Columns (Cells)
|
||||
- `home.deleteSheet` Delete Sheet (Cells)
|
||||
- `home.rowHeight` Row Height (Cells)
|
||||
- `home.autofitRowHeight` AutoFit Row Height (Cells)
|
||||
- `home.columnWidth` Column Width (Cells)
|
||||
- `home.autofitColumnWidth` AutoFit Column Width (Cells)
|
||||
- `home.defaultWidth` Default Width (Cells)
|
||||
- `home.hideRows` Hide Rows (Cells)
|
||||
- `home.hideColumns` Hide Columns (Cells)
|
||||
- `home.unhideRows` Unhide Rows (Cells)
|
||||
- `home.unhideColumns` Unhide Columns (Cells)
|
||||
- `sheet.hide` Hide Sheet (Cells)
|
||||
- `sheet.unhide` Unhide Sheet (Cells)
|
||||
- `sheet.rename` Rename Sheet (Cells)
|
||||
- `sheet.move` Move or Copy Sheet (Cells)
|
||||
- `sheet.tabColor` Tab Color (Cells)
|
||||
- `review.protectSheet` Protect Sheet (Cells)
|
||||
- `home.lockCell` Lock Cell (Cells)
|
||||
- `home.formatCells` Format Cells (Cells)
|
||||
- `formulas.autoSum` AutoSum (Editing)
|
||||
- `edit.fillDown` Fill Down (Editing)
|
||||
- `edit.fillRight` Fill Right (Editing)
|
||||
- `edit.fillUp` Fill Up (Editing)
|
||||
- `edit.fillLeft` Fill Left (Editing)
|
||||
- `edit.fillSeries` Series (Editing)
|
||||
- `edit.flashFill` Flash Fill (Editing)
|
||||
- `edit.clearAll` Clear All (Editing)
|
||||
- `edit.clearFormats` Clear Formats (Editing)
|
||||
- `edit.clearContents` Clear Contents (Editing)
|
||||
- `edit.clearComments` Clear Comments (Editing)
|
||||
- `edit.clearHyperlinks` Clear Hyperlinks (Editing)
|
||||
- `data.sortAscending` Sort A to Z (Editing)
|
||||
- `data.sortDescending` Sort Z to A (Editing)
|
||||
- `data.sort` Custom Sort (Editing)
|
||||
- `data.filter` Filter (Editing)
|
||||
- `data.clearFilter` Clear Filter (Editing)
|
||||
- `data.reapply` Reapply (Editing)
|
||||
- `edit.find` Find (Editing)
|
||||
- `edit.replace` Replace (Editing)
|
||||
- `edit.goTo` Go To (Editing)
|
||||
- `edit.goToSpecial` Go To Special (Editing)
|
||||
|
||||
Missing:
|
||||
|
||||
- `home.drawBorder` Draw Border (Font)
|
||||
- `home.newCellStyle` New Cell Style (Styles)
|
||||
- `home.addIns` Add-ins (Add-ins)
|
||||
|
||||
## Insert (11 / 25, 44%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `insert.table` Table (Tables)
|
||||
- `insert.picture` Pictures (Illustrations)
|
||||
- `insert.shape` Shapes (Illustrations)
|
||||
- `insert.recommendedCharts` Recommended Charts (Charts)
|
||||
- `insert.chart` Charts (Charts)
|
||||
- `insert.sparkline` Sparklines (Sparklines)
|
||||
- `insert.link` Link (Links)
|
||||
- `review.newComment` Comment (Comments)
|
||||
- `insert.textBox` Text Box (Text)
|
||||
- `pageLayout.headerFooter` Header & Footer (Text)
|
||||
- `insert.symbol` Symbol (Symbols)
|
||||
|
||||
Missing:
|
||||
|
||||
- `insert.pivotTable` PivotTable (Tables)
|
||||
- `insert.recommendedPivotTables` Recommended PivotTables (Tables)
|
||||
- `insert.icons` Icons (Illustrations)
|
||||
- `insert.smartArt` SmartArt (Illustrations)
|
||||
- `insert.screenshot` Screenshot (Illustrations)
|
||||
- `insert.pivotChart` PivotChart (Charts)
|
||||
- `insert.map` Maps (Charts)
|
||||
- `insert.slicer` Slicer (Filters)
|
||||
- `insert.timeline` Timeline (Filters)
|
||||
- `insert.wordArt` WordArt (Text)
|
||||
- `insert.signatureLine` Signature Line (Text)
|
||||
- `insert.object` Object (Text)
|
||||
- `insert.equation` Equation (Symbols)
|
||||
- `insert.checkbox` Checkbox (Cell Controls)
|
||||
|
||||
## Draw (0 / 4, 0%)
|
||||
|
||||
Missing:
|
||||
|
||||
- `draw.pen` Pen (Tools)
|
||||
- `draw.eraser` Eraser (Tools)
|
||||
- `draw.lasso` Lasso Select (Tools)
|
||||
- `draw.inkToShape` Ink to Shape (Convert)
|
||||
|
||||
## Page Layout (12 / 23, 52%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `pageLayout.theme` Themes (Themes)
|
||||
- `pageLayout.margins` Margins (Page Setup)
|
||||
- `pageLayout.orientation` Orientation (Page Setup)
|
||||
- `pageLayout.size` Size (Page Setup)
|
||||
- `pageLayout.printArea` Print Area (Page Setup)
|
||||
- `pageLayout.breaks` Breaks (Page Setup)
|
||||
- `pageLayout.printTitles` Print Titles (Page Setup)
|
||||
- `pageLayout.scaleToFit` Scale to Fit (Scale to Fit)
|
||||
- `view.gridlines` View Gridlines (Sheet Options)
|
||||
- `pageLayout.printGridlines` Print Gridlines (Sheet Options)
|
||||
- `view.headings` View Headings (Sheet Options)
|
||||
- `pageLayout.printHeadings` Print Headings (Sheet Options)
|
||||
|
||||
Missing:
|
||||
|
||||
- `pageLayout.themeColors` Colors (Themes)
|
||||
- `pageLayout.themeFonts` Fonts (Themes)
|
||||
- `pageLayout.themeEffects` Effects (Themes)
|
||||
- `pageLayout.background` Background (Page Setup)
|
||||
- `pageLayout.pageSetup` Page Setup (Page Setup)
|
||||
- `arrange.bringForward` Bring Forward (Arrange)
|
||||
- `arrange.sendBackward` Send Backward (Arrange)
|
||||
- `arrange.selectionPane` Selection Pane (Arrange)
|
||||
- `arrange.align` Align (Arrange)
|
||||
- `arrange.group` Group (Arrange)
|
||||
- `arrange.rotate` Rotate (Arrange)
|
||||
|
||||
## Formulas (13 / 23, 57%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `formulas.insertFunction` Insert Function (Function Library)
|
||||
- `formulas.nameManager` Name Manager (Defined Names)
|
||||
- `formulas.defineName` Define Name (Defined Names)
|
||||
- `formulas.createFromSelection` Create from Selection (Defined Names)
|
||||
- `formulas.tracePrecedents` Trace Precedents (Formula Auditing)
|
||||
- `formulas.traceDependents` Trace Dependents (Formula Auditing)
|
||||
- `formulas.removeArrows` Remove Arrows (Formula Auditing)
|
||||
- `formulas.showFormulas` Show Formulas (Formula Auditing)
|
||||
- `formulas.errorChecking` Error Checking (Formula Auditing)
|
||||
- `formulas.evaluateFormula` Evaluate Formula (Formula Auditing)
|
||||
- `formulas.calculationOptions` Calculation Options (Calculation)
|
||||
- `formulas.calculateNow` Calculate Now (Calculation)
|
||||
- `formulas.calculateSheet` Calculate Sheet (Calculation)
|
||||
|
||||
Missing:
|
||||
|
||||
- `formulas.recentlyUsed` Recently Used (Function Library)
|
||||
- `formulas.financial` Financial (Function Library)
|
||||
- `formulas.logical` Logical (Function Library)
|
||||
- `formulas.text` Text (Function Library)
|
||||
- `formulas.dateTime` Date & Time (Function Library)
|
||||
- `formulas.lookupReference` Lookup & Reference (Function Library)
|
||||
- `formulas.mathTrig` Math & Trig (Function Library)
|
||||
- `formulas.moreFunctions` More Functions (Function Library)
|
||||
- `formulas.useInFormula` Use in Formula (Defined Names)
|
||||
- `formulas.watchWindow` Watch Window (Formula Auditing)
|
||||
|
||||
## Data (10 / 28, 36%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `data.textToColumns` Text to Columns (Data Tools)
|
||||
- `data.flashFill` Flash Fill (Data Tools)
|
||||
- `data.removeDuplicates` Remove Duplicates (Data Tools)
|
||||
- `data.validation` Data Validation (Data Tools)
|
||||
- `data.circleInvalid` Circle Invalid Data (Data Tools)
|
||||
- `data.group` Group (Outline)
|
||||
- `data.ungroup` Ungroup (Outline)
|
||||
- `data.subtotal` Subtotal (Outline)
|
||||
- `data.showDetail` Show Detail (Outline)
|
||||
- `data.hideDetail` Hide Detail (Outline)
|
||||
|
||||
Missing:
|
||||
|
||||
- `data.getData` Get Data (Get & Transform Data)
|
||||
- `data.fromTextCsv` From Text/CSV (Get & Transform Data)
|
||||
- `data.fromWeb` From Web (Get & Transform Data)
|
||||
- `data.fromTableRange` From Table/Range (Get & Transform Data)
|
||||
- `data.refreshAll` Refresh All (Queries & Connections)
|
||||
- `data.queriesConnections` Queries & Connections (Queries & Connections)
|
||||
- `data.stocks` Stocks (Data Types)
|
||||
- `data.geography` Geography (Data Types)
|
||||
- `data.advancedFilter` Advanced (Sort & Filter)
|
||||
- `data.consolidate` Consolidate (Data Tools)
|
||||
- `data.relationships` Relationships (Data Tools)
|
||||
- `data.scenarioManager` Scenario Manager (Forecast)
|
||||
- `data.goalSeek` Goal Seek (Forecast)
|
||||
- `data.dataTable` Data Table (Forecast)
|
||||
- `data.forecastSheet` Forecast Sheet (Forecast)
|
||||
- `data.analyzeData` Analyze Data (Analysis)
|
||||
- `data.solver` Solver (Analysis)
|
||||
- `data.analysisToolPak` Data Analysis (Analysis)
|
||||
|
||||
## Review (7 / 18, 39%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `review.workbookStatistics` Workbook Statistics (Proofing)
|
||||
- `review.checkAccessibility` Check Accessibility (Accessibility)
|
||||
- `review.deleteComment` Delete Comment (Comments)
|
||||
- `review.newNote` New Note (Notes)
|
||||
- `review.showNote` Show/Hide Note (Notes)
|
||||
- `review.unprotectSheet` Unprotect Sheet (Protect)
|
||||
- `review.protectWorkbook` Protect Workbook (Protect)
|
||||
|
||||
Missing:
|
||||
|
||||
- `review.spelling` Spelling (Proofing)
|
||||
- `review.thesaurus` Thesaurus (Proofing)
|
||||
- `review.smartLookup` Smart Lookup (Insights)
|
||||
- `review.translate` Translate (Language)
|
||||
- `review.previousComment` Previous Comment (Comments)
|
||||
- `review.nextComment` Next Comment (Comments)
|
||||
- `review.showComments` Show Comments (Comments)
|
||||
- `review.showAllNotes` Show All Notes (Notes)
|
||||
- `review.convertToComments` Convert to Comments (Notes)
|
||||
- `review.allowEditRanges` Allow Edit Ranges (Protect)
|
||||
- `review.showChanges` Show Changes (Changes)
|
||||
|
||||
## View (6 / 20, 30%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `view.zoom` Zoom (Zoom)
|
||||
- `view.zoomToSelection` Zoom to Selection (Zoom)
|
||||
- `view.freezePanes` Freeze Panes (Window)
|
||||
- `view.freezeTopRow` Freeze Top Row (Window)
|
||||
- `view.freezeFirstColumn` Freeze First Column (Window)
|
||||
- `view.unfreezePanes` Unfreeze Panes (Window)
|
||||
|
||||
Missing:
|
||||
|
||||
- `view.normal` Normal (Workbook Views)
|
||||
- `view.pageBreakPreview` Page Break Preview (Workbook Views)
|
||||
- `view.pageLayout` Page Layout (Workbook Views)
|
||||
- `view.customViews` Custom Views (Workbook Views)
|
||||
- `view.ruler` Ruler (Show)
|
||||
- `view.formulaBar` Formula Bar (Show)
|
||||
- `view.zoom100` 100% (Zoom)
|
||||
- `view.newWindow` New Window (Window)
|
||||
- `view.arrangeAll` Arrange All (Window)
|
||||
- `view.split` Split (Window)
|
||||
- `view.hideWindow` Hide (Window)
|
||||
- `view.unhideWindow` Unhide (Window)
|
||||
- `view.switchWindows` Switch Windows (Window)
|
||||
- `view.macros` Macros (Macros)
|
||||
|
||||
## Automate (0 / 3, 0%)
|
||||
|
||||
Missing:
|
||||
|
||||
- `automate.newScript` New Script (Scripting Tools)
|
||||
- `automate.allScripts` All Scripts (Scripting Tools)
|
||||
- `automate.recordActions` Record Actions (Scripting Tools)
|
||||
|
||||
## Table Design (9 / 13, 69%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `table.rename` Table Name (Properties)
|
||||
- `table.convertToRange` Convert to Range (Tools)
|
||||
- `table.headerRow` Header Row (Table Style Options)
|
||||
- `table.totalRow` Total Row (Table Style Options)
|
||||
- `table.bandedRows` Banded Rows (Table Style Options)
|
||||
- `table.firstColumn` First Column (Table Style Options)
|
||||
- `table.lastColumn` Last Column (Table Style Options)
|
||||
- `table.bandedColumns` Banded Columns (Table Style Options)
|
||||
- `table.style` Table Styles (Table Styles)
|
||||
|
||||
Missing:
|
||||
|
||||
- `table.resize` Resize Table (Properties)
|
||||
- `table.summarizePivot` Summarize with PivotTable (Tools)
|
||||
- `table.removeDuplicates` Remove Duplicates (Tools)
|
||||
- `table.insertSlicer` Insert Slicer (Tools)
|
||||
|
||||
## Chart Design (2 / 8, 25%)
|
||||
|
||||
Implemented:
|
||||
|
||||
- `chart.set` Chart Styles (Chart Styles)
|
||||
- `chart.switchRowColumn` Switch Row/Column (Data)
|
||||
|
||||
Missing:
|
||||
|
||||
- `chart.addElement` Add Chart Element (Chart Layouts)
|
||||
- `chart.quickLayout` Quick Layout (Chart Layouts)
|
||||
- `chart.changeColors` Change Colors (Chart Styles)
|
||||
- `chart.selectData` Select Data (Data)
|
||||
- `chart.changeType` Change Chart Type (Type)
|
||||
- `chart.move` Move Chart (Location)
|
||||
|
||||
## Format (0 / 3, 0%)
|
||||
|
||||
Missing:
|
||||
|
||||
- `chart.formatSelection` Format Selection (Current Selection)
|
||||
- `chart.shapeFill` Shape Fill (Shape Styles)
|
||||
- `chart.shapeOutline` Shape Outline (Shape Styles)
|
||||
|
||||
## PivotTable Analyze (0 / 4, 0%)
|
||||
|
||||
Missing:
|
||||
|
||||
- `pivot.fieldList` Field List (Show)
|
||||
- `pivot.refresh` Refresh (Data)
|
||||
- `pivot.changeSource` Change Data Source (Data)
|
||||
- `pivot.calculatedField` Fields, Items & Sets (Calculations)
|
||||
|
||||
## PivotTable Design (0 / 4, 0%)
|
||||
|
||||
Missing:
|
||||
|
||||
- `pivot.grandTotals` Grand Totals (Layout)
|
||||
- `pivot.subtotals` Subtotals (Layout)
|
||||
- `pivot.reportLayout` Report Layout (Layout)
|
||||
- `pivot.style` PivotTable Styles (PivotTable Styles)
|
||||
Reference in new issue
Block a user