Compare commits
10 Commits
feature/ni
...
feature/ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379c84f64a | ||
|
|
49c3b14bf0 | ||
|
|
d2cba48f8e | ||
|
|
f752c90058 | ||
|
|
a78d5593f2 | ||
|
|
079208a1bd | ||
|
|
4721582dfe | ||
|
|
1746eec5da | ||
|
|
f9cf86a17c | ||
|
|
49ef846ebd |
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
target/*
|
target/*
|
||||||
|
.DS_Store
|
||||||
12
Cargo.lock
generated
@@ -370,7 +370,6 @@ dependencies = [
|
|||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -679,17 +678,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_repr"
|
|
||||||
version = "0.1.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
|||||||
12
Makefile
@@ -1,13 +1,6 @@
|
|||||||
all:
|
|
||||||
cargo build --release
|
|
||||||
cd bindings/wasm/ && make
|
|
||||||
cd webapp && npm install && npm run build
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
cargo fmt -- --check
|
cargo fmt -- --check
|
||||||
# TODO: See issue #33
|
cargo clippy --all-targets --all-features
|
||||||
# cargo clippy --all-targets --all-features -- -D warnings -D clippy::expect_used -D clippy::unwrap_used -D clippy::panic
|
|
||||||
cargo clippy --all-targets --all-features -- -D warnings
|
|
||||||
|
|
||||||
format:
|
format:
|
||||||
cargo fmt
|
cargo fmt
|
||||||
@@ -17,7 +10,7 @@ tests: lint
|
|||||||
./target/debug/documentation
|
./target/debug/documentation
|
||||||
cmp functions.md wiki/functions.md || exit 1
|
cmp functions.md wiki/functions.md || exit 1
|
||||||
make remove-artifacts
|
make remove-artifacts
|
||||||
cd bindings/wasm/ && make tests
|
cd bindings/wasm/ && wasm-pack build --target nodejs && node tests/test.mjs
|
||||||
|
|
||||||
remove-artifacts:
|
remove-artifacts:
|
||||||
rm -f xlsx/hello-calc.xlsx
|
rm -f xlsx/hello-calc.xlsx
|
||||||
@@ -32,7 +25,6 @@ clean: remove-artifacts
|
|||||||
rm -f cargo-test-*
|
rm -f cargo-test-*
|
||||||
rm -f base/cargo-test-*
|
rm -f base/cargo-test-*
|
||||||
rm -f xlsx/cargo-test-*
|
rm -f xlsx/cargo-test-*
|
||||||
rm -r -f webapp/node_modules
|
|
||||||
|
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
|
|||||||
BIN
assets/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 441 B |
BIN
assets/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 15 KiB |
BIN
assets/icon/ironcalc_icon.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
8
assets/icon/ironcalc_icon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="600" height="600" rx="20" fill="#F2994A"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 100C348.98 166.034 322.748 229.362 276.055 276.055C268.163 283.947 259.796 291.255 251.021 297.95L251.021 500L348.98 500H251.021C251.021 433.966 277.252 370.637 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05L348.98 100Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M251.021 100.068C251.003 140.096 235.094 178.481 206.788 206.787C178.466 235.109 140.053 251.02 100 251.02V348.979C154.873 348.979 207.877 330.866 251.021 297.95V100.068Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 499.882C349.011 459.872 364.918 421.507 393.213 393.213C421.534 364.891 459.947 348.98 500 348.98V251.02C445.128 251.02 392.123 269.134 348.98 302.05V499.882Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.055 276.055C322.748 229.362 348.98 166.034 348.98 100H251.021V297.95C259.796 291.255 268.163 283.947 276.055 276.055Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M348.98 302.05V499.895C348.98 499.93 348.98 499.965 348.98 500L251.021 500C251.021 499.946 251.02 499.891 251.021 499.837C251.064 433.862 277.291 370.599 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/logo.png
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 33 KiB |
BIN
assets/logo/png/black.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/logo/png/orange+black.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/logo/png/orange+white.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
assets/logo/png/white.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
8
assets/logo/svg/black.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
8
assets/logo/svg/orange+black.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
8
assets/logo/svg/orange+white.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
8
assets/logo/svg/white.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
@@ -12,8 +12,6 @@ readme = "README.md"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
|
||||||
serde_repr = "0.1"
|
|
||||||
ryu = "1.0"
|
ryu = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
chrono-tz = "0.9"
|
chrono-tz = "0.9"
|
||||||
@@ -21,6 +19,9 @@ regex = "1.0"
|
|||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
bitcode = "0.6.0"
|
bitcode = "0.6.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
js-sys = { version = "0.3.69" }
|
js-sys = { version = "0.3.69" }
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
|
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
/// A CellValue is the representation of the cell content.
|
/// A CellValue is the representation of the cell content.
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum CellValue {
|
pub enum CellValue {
|
||||||
None,
|
None,
|
||||||
String(String),
|
String(String),
|
||||||
@@ -14,17 +11,6 @@ pub enum CellValue {
|
|||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellValue {
|
|
||||||
pub fn to_json_str(&self) -> String {
|
|
||||||
match &self {
|
|
||||||
CellValue::None => "null".to_string(),
|
|
||||||
CellValue::String(s) => json!(s).to_string(),
|
|
||||||
CellValue::Number(f) => json!(f).to_string(),
|
|
||||||
CellValue::Boolean(b) => json!(b).to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for CellValue {
|
impl From<f64> for CellValue {
|
||||||
fn from(value: f64) -> Self {
|
fn from(value: f64) -> Self {
|
||||||
Self::Number(value)
|
Self::Number(value)
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ impl Parser {
|
|||||||
|
|
||||||
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
||||||
self.lexer.set_formula(formula);
|
self.lexer.set_formula(formula);
|
||||||
self.context = context.clone();
|
self.context.clone_from(context);
|
||||||
self.parse_expr()
|
self.parse_expr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::fmt;
|
|||||||
|
|
||||||
use bitcode::{Decode, Encode};
|
use bitcode::{Decode, Encode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
|
||||||
|
|
||||||
use crate::language::Language;
|
use crate::language::Language;
|
||||||
|
|
||||||
@@ -81,8 +80,7 @@ impl fmt::Display for OpProduct {
|
|||||||
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
|
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
|
||||||
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
|
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
|
||||||
/// Note that they are serialized/deserialized by index
|
/// Note that they are serialized/deserialized by index
|
||||||
#[derive(Serialize_repr, Deserialize_repr, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
REF,
|
REF,
|
||||||
NAME,
|
NAME,
|
||||||
|
|||||||
1
base/src/language/language.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PfrendeesD<>VRAITRUEWAHRVERDADEROTVFAUXFALSEFALSCHFALSOUw#REF!#REF!#BEZUG!#¡REF!e<>#NOM?#NAME?#NAME?#¿NOMBRE?x<>#VALEUR!#VALUE!#WERT!#¡VALOR!w<>#DIV/0!#DIV/0!#DIV/0!#¡DIV/0!<04>#N/A#N/A#NV#N/AXv#NOMBRE!#NUM!#ZAHL!#¡NUM!<02><>#N/IMPL!#N/IMPL!#N/IMPL!#N/IMPL!w{#SPILL!#SPILL!#ÜBERLAUF!#SPILL!ff#CALC!#CALC!#CALC!#CALC!ff#CIRC!#CIRC!#CIRC!#CIRC!ww#ERROR!#ERROR!#ERROR!#ERROR!ff#NULL!#NULL!#NULL!#NULL!
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
use once_cell::sync::Lazy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
use bitcode::{Decode, Encode};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Booleans {
|
pub struct Booleans {
|
||||||
pub r#true: String,
|
pub r#true: String,
|
||||||
pub r#false: String,
|
pub r#false: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Errors {
|
pub struct Errors {
|
||||||
pub r#ref: String,
|
pub r#ref: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -25,14 +25,14 @@ pub struct Errors {
|
|||||||
pub null: String,
|
pub null: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
pub booleans: Booleans,
|
pub booleans: Booleans,
|
||||||
pub errors: Errors,
|
pub errors: Errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
||||||
serde_json::from_str(include_str!("language.json")).expect("Failed parsing language file")
|
bitcode::decode(include_bytes!("language.bin")).expect("Failed parsing language file")
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn get_language(id: &str) -> Result<&Language, String> {
|
pub fn get_language(id: &str) -> Result<&Language, String> {
|
||||||
|
|||||||
BIN
base/src/locale/locales.bin
Normal file
@@ -1,32 +1,29 @@
|
|||||||
|
use bitcode::{Decode, Encode};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Locale {
|
pub struct Locale {
|
||||||
pub dates: Dates,
|
pub dates: Dates,
|
||||||
pub numbers: NumbersProperties,
|
pub numbers: NumbersProperties,
|
||||||
pub currency: Currency,
|
pub currency: Currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Currency {
|
pub struct Currency {
|
||||||
pub iso: String,
|
pub iso: String,
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct NumbersProperties {
|
pub struct NumbersProperties {
|
||||||
#[serde(rename = "symbols-numberSystem-latn")]
|
|
||||||
pub symbols: NumbersSymbols,
|
pub symbols: NumbersSymbols,
|
||||||
#[serde(rename = "decimalFormats-numberSystem-latn")]
|
|
||||||
pub decimal_formats: DecimalFormats,
|
pub decimal_formats: DecimalFormats,
|
||||||
#[serde(rename = "currencyFormats-numberSystem-latn")]
|
|
||||||
pub currency_formats: CurrencyFormats,
|
pub currency_formats: CurrencyFormats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Dates {
|
pub struct Dates {
|
||||||
pub day_names: Vec<String>,
|
pub day_names: Vec<String>,
|
||||||
pub day_names_short: Vec<String>,
|
pub day_names_short: Vec<String>,
|
||||||
@@ -35,8 +32,7 @@ pub struct Dates {
|
|||||||
pub months_letter: Vec<String>,
|
pub months_letter: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct NumbersSymbols {
|
pub struct NumbersSymbols {
|
||||||
pub decimal: String,
|
pub decimal: String,
|
||||||
pub group: String,
|
pub group: String,
|
||||||
@@ -54,31 +50,23 @@ pub struct NumbersSymbols {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct CurrencyFormats {
|
pub struct CurrencyFormats {
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
#[serde(rename = "standard-alphaNextToNumber")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub standard_alpha_next_to_number: Option<String>,
|
pub standard_alpha_next_to_number: Option<String>,
|
||||||
#[serde(rename = "standard-noCurrency")]
|
|
||||||
pub standard_no_currency: String,
|
pub standard_no_currency: String,
|
||||||
pub accounting: String,
|
pub accounting: String,
|
||||||
#[serde(rename = "accounting-alphaNextToNumber")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub accounting_alpha_next_to_number: Option<String>,
|
pub accounting_alpha_next_to_number: Option<String>,
|
||||||
#[serde(rename = "accounting-noCurrency")]
|
|
||||||
pub accounting_no_currency: String,
|
pub accounting_no_currency: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct DecimalFormats {
|
pub struct DecimalFormats {
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
static LOCALES: Lazy<HashMap<String, Locale>> = Lazy::new(|| {
|
static LOCALES: Lazy<HashMap<String, Locale>> =
|
||||||
serde_json::from_str(include_str!("locales.json")).expect("Failed parsing locale")
|
Lazy::new(|| bitcode::decode(include_bytes!("locales.bin")).expect("Failed parsing locale"));
|
||||||
});
|
|
||||||
|
|
||||||
pub fn get_locale(id: &str) -> Result<&Locale, String> {
|
pub fn get_locale(id: &str) -> Result<&Locale, String> {
|
||||||
// TODO: pass the locale once we implement locales in Rust
|
// TODO: pass the locale once we implement locales in Rust
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ pub struct Model {
|
|||||||
pub(crate) language: Language,
|
pub(crate) language: Language,
|
||||||
/// The timezone used to evaluate the model
|
/// The timezone used to evaluate the model
|
||||||
pub(crate) tz: Tz,
|
pub(crate) tz: Tz,
|
||||||
|
/// The view id. A view consist of a selected sheet and ranges.
|
||||||
|
pub(crate) view_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Maybe this should be the same as CellReference
|
// FIXME: Maybe this should be the same as CellReference
|
||||||
@@ -886,6 +888,7 @@ impl Model {
|
|||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
tz,
|
tz,
|
||||||
|
view_id: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
model.parse_formulas();
|
model.parse_formulas();
|
||||||
|
|||||||
@@ -6,14 +6,18 @@ use crate::{
|
|||||||
calc_result::Range,
|
calc_result::Range,
|
||||||
expressions::{
|
expressions::{
|
||||||
lexer::LexerMode,
|
lexer::LexerMode,
|
||||||
parser::stringify::{rename_sheet_in_node, to_rc_format},
|
parser::{
|
||||||
parser::Parser,
|
stringify::{rename_sheet_in_node, to_rc_format},
|
||||||
|
Parser,
|
||||||
|
},
|
||||||
types::CellReferenceRC,
|
types::CellReferenceRC,
|
||||||
},
|
},
|
||||||
language::get_language,
|
language::get_language,
|
||||||
locale::get_locale,
|
locale::get_locale,
|
||||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||||
types::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
|
types::{
|
||||||
|
Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView,
|
||||||
|
},
|
||||||
utils::ParsedReference,
|
utils::ParsedReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,7 +37,20 @@ fn is_valid_sheet_name(name: &str) -> bool {
|
|||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
||||||
fn new_empty_worksheet(name: &str, sheet_id: u32) -> Worksheet {
|
fn new_empty_worksheet(name: &str, sheet_id: u32, view_ids: &[&u32]) -> Worksheet {
|
||||||
|
let mut views = HashMap::new();
|
||||||
|
for id in view_ids {
|
||||||
|
views.insert(
|
||||||
|
**id,
|
||||||
|
WorksheetView {
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
top_row: 1,
|
||||||
|
left_column: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Worksheet {
|
Worksheet {
|
||||||
cols: vec![],
|
cols: vec![],
|
||||||
rows: vec![],
|
rows: vec![],
|
||||||
@@ -48,6 +65,7 @@ impl Model {
|
|||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
frozen_columns: 0,
|
frozen_columns: 0,
|
||||||
frozen_rows: 0,
|
frozen_rows: 0,
|
||||||
|
views,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +140,7 @@ impl Model {
|
|||||||
self.parsed_defined_names = parsed_defined_names;
|
self.parsed_defined_names = parsed_defined_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reparses all formulas and defined names
|
/// Reparses all formulas and defined names
|
||||||
pub(crate) fn reset_parsed_structures(&mut self) {
|
pub(crate) fn reset_parsed_structures(&mut self) {
|
||||||
self.parser
|
self.parser
|
||||||
.set_worksheets(self.workbook.get_worksheet_names());
|
.set_worksheets(self.workbook.get_worksheet_names());
|
||||||
@@ -153,7 +171,8 @@ impl Model {
|
|||||||
let sheet_name = format!("{}{}", base_name, index);
|
let sheet_name = format!("{}{}", base_name, index);
|
||||||
// Now we need a sheet_id
|
// Now we need a sheet_id
|
||||||
let sheet_id = self.get_new_sheet_id();
|
let sheet_id = self.get_new_sheet_id();
|
||||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
||||||
|
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id, &view_ids);
|
||||||
self.workbook.worksheets.push(worksheet);
|
self.workbook.worksheets.push(worksheet);
|
||||||
self.reset_parsed_structures();
|
self.reset_parsed_structures();
|
||||||
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
||||||
@@ -184,7 +203,8 @@ impl Model {
|
|||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => self.get_new_sheet_id(),
|
None => self.get_new_sheet_id(),
|
||||||
};
|
};
|
||||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id);
|
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
||||||
|
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id, &view_ids);
|
||||||
if sheet_index as usize > self.workbook.worksheets.len() {
|
if sheet_index as usize > self.workbook.worksheets.len() {
|
||||||
return Err("Sheet index out of range".to_string());
|
return Err("Sheet index out of range".to_string());
|
||||||
}
|
}
|
||||||
@@ -331,11 +351,14 @@ impl Model {
|
|||||||
// "2020-08-06T21:20:53Z
|
// "2020-08-06T21:20:53Z
|
||||||
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
||||||
|
|
||||||
|
let mut views = HashMap::new();
|
||||||
|
views.insert(0, WorkbookView { sheet: 0 });
|
||||||
|
|
||||||
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
||||||
let workbook = Workbook {
|
let workbook = Workbook {
|
||||||
shared_strings: vec![],
|
shared_strings: vec![],
|
||||||
defined_names: vec![],
|
defined_names: vec![],
|
||||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)],
|
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1, &[&0])],
|
||||||
styles: Default::default(),
|
styles: Default::default(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
settings: WorkbookSettings {
|
settings: WorkbookSettings {
|
||||||
@@ -351,6 +374,7 @@ impl Model {
|
|||||||
last_modified: now,
|
last_modified: now,
|
||||||
},
|
},
|
||||||
tables: HashMap::new(),
|
tables: HashMap::new(),
|
||||||
|
views,
|
||||||
};
|
};
|
||||||
let parsed_formulas = Vec::new();
|
let parsed_formulas = Vec::new();
|
||||||
let worksheets = &workbook.worksheets;
|
let worksheets = &workbook.worksheets;
|
||||||
@@ -371,6 +395,7 @@ impl Model {
|
|||||||
locale,
|
locale,
|
||||||
language,
|
language,
|
||||||
tz,
|
tz,
|
||||||
|
view_id: 0,
|
||||||
};
|
};
|
||||||
model.parse_formulas();
|
model.parse_formulas();
|
||||||
Ok(model)
|
Ok(model)
|
||||||
|
|||||||
@@ -76,10 +76,16 @@ fn fn_imconjugate() {
|
|||||||
fn fn_imcos() {
|
fn fn_imcos() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", r#"=IMCOS("4+3i")"#);
|
model._set("A1", r#"=IMCOS("4+3i")"#);
|
||||||
|
// In macos non intel this is "-6.58066304055116+7.58155274274655i"
|
||||||
|
model._set("A2", r#"=COMPLEX(-6.58066304055116, 7.58155274274654)"#);
|
||||||
|
model._set("A3", r#"=IMABS(IMSUB(A1, A2)) < G1"#);
|
||||||
|
|
||||||
|
// small number
|
||||||
|
model._set("G1", "0.0000001");
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text("A1"), "-6.58066304055116+7.58155274274654i");
|
assert_eq!(model._get_text("A3"), "TRUE");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -53,4 +53,5 @@ mod test_frozen_rows_and_columns;
|
|||||||
mod test_get_cell_content;
|
mod test_get_cell_content;
|
||||||
mod test_percentage;
|
mod test_percentage;
|
||||||
mod test_today;
|
mod test_today;
|
||||||
|
mod test_types;
|
||||||
mod user_model;
|
mod user_model;
|
||||||
|
|||||||
24
base/src/test/test_types.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::types::{Alignment, HorizontalAlignment, VerticalAlignment};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alignment_default() {
|
||||||
|
let alignment = Alignment::default();
|
||||||
|
assert_eq!(
|
||||||
|
alignment,
|
||||||
|
Alignment {
|
||||||
|
horizontal: HorizontalAlignment::General,
|
||||||
|
vertical: VerticalAlignment::Bottom,
|
||||||
|
wrap_text: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let s = serde_json::to_string(&alignment).unwrap();
|
||||||
|
// defaults stringifies as an empty object
|
||||||
|
assert_eq!(s, "{}");
|
||||||
|
|
||||||
|
let a: Alignment = serde_json::from_str("{}").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(a, alignment)
|
||||||
|
}
|
||||||
@@ -8,3 +8,4 @@ mod test_row_column;
|
|||||||
mod test_styles;
|
mod test_styles;
|
||||||
mod test_to_from_bytes;
|
mod test_to_from_bytes;
|
||||||
mod test_undo_redo;
|
mod test_undo_redo;
|
||||||
|
mod test_view;
|
||||||
|
|||||||
@@ -144,13 +144,18 @@ fn basic_fill() {
|
|||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||||
assert_eq!(style.fill.bg_color, None);
|
assert_eq!(style.fill.bg_color, None);
|
||||||
|
assert_eq!(style.fill.fg_color, None);
|
||||||
|
|
||||||
// bg_color
|
// bg_color
|
||||||
model
|
model
|
||||||
.update_range_style(&range, "fill.bg_color", "#F2F2F2")
|
.update_range_style(&range, "fill.bg_color", "#F2F2F2")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
model
|
||||||
|
.update_range_style(&range, "fill.fg_color", "#F3F4F5")
|
||||||
|
.unwrap();
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||||
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
|
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
|
||||||
|
assert_eq!(style.fill.fg_color, Some("#F3F4F5".to_owned()));
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
let send_queue = model.flush_send_queue();
|
||||||
|
|
||||||
@@ -159,6 +164,7 @@ fn basic_fill() {
|
|||||||
|
|
||||||
let style = model2.get_cell_style(0, 1, 1).unwrap();
|
let style = model2.get_cell_style(0, 1, 1).unwrap();
|
||||||
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
|
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
|
||||||
|
assert_eq!(style.fill.fg_color, Some("#F3F4F5".to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -171,9 +177,15 @@ fn fill_errors() {
|
|||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
};
|
};
|
||||||
assert!(model
|
assert_eq!(
|
||||||
.update_range_style(&range, "fill.bg_color", "#FFF")
|
model.update_range_style(&range, "fill.bg_color", "#FFF"),
|
||||||
.is_err());
|
Err("Invalid color: '#FFF'.".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model.update_range_style(&range, "fill.fg_color", "#FFF"),
|
||||||
|
Err("Invalid color: '#FFF'.".to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
216
base/src/test/user_model/test_view.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
constants::{LAST_COLUMN, LAST_ROW},
|
||||||
|
test::util::new_empty_model,
|
||||||
|
user_model::SelectedView,
|
||||||
|
UserModel,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initial_view() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let model = UserModel::from_model(model);
|
||||||
|
assert_eq!(model.get_selected_sheet(), 0);
|
||||||
|
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||||
|
assert_eq!(
|
||||||
|
model.get_selected_view(),
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
top_row: 1,
|
||||||
|
left_column: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_the_cell_sets_the_range() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
model.set_selected_cell(5, 4).unwrap();
|
||||||
|
assert_eq!(model.get_selected_sheet(), 0);
|
||||||
|
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
||||||
|
assert_eq!(
|
||||||
|
model.get_selected_view(),
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 5,
|
||||||
|
column: 4,
|
||||||
|
range: [5, 4, 5, 4],
|
||||||
|
top_row: 1,
|
||||||
|
left_column: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_the_range_does_not_set_the_cell() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
model.set_selected_range(5, 4, 10, 6).unwrap();
|
||||||
|
assert_eq!(model.get_selected_sheet(), 0);
|
||||||
|
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||||
|
assert_eq!(
|
||||||
|
model.get_selected_view(),
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [5, 4, 10, 6],
|
||||||
|
top_row: 1,
|
||||||
|
left_column: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_new_sheet_and_back() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
model.new_sheet();
|
||||||
|
assert_eq!(model.get_selected_sheet(), 0);
|
||||||
|
model.set_selected_cell(5, 4).unwrap();
|
||||||
|
model.set_selected_sheet(1).unwrap();
|
||||||
|
assert_eq!(model.get_selected_cell(), (1, 1, 1));
|
||||||
|
model.set_selected_sheet(0).unwrap();
|
||||||
|
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_selected_cell_errors() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_cell(-5, 4),
|
||||||
|
Err("Invalid row: '-5'".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_cell(5, -4),
|
||||||
|
Err("Invalid column: '-4'".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_range(-1, 1, 1, 1),
|
||||||
|
Err("Invalid row: '-1'".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_range(1, 0, 1, 1),
|
||||||
|
Err("Invalid column: '0'".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_range(1, 1, LAST_ROW + 1, 1),
|
||||||
|
Err("Invalid row: '1048577'".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_range(1, 1, 1, LAST_COLUMN + 1),
|
||||||
|
Err("Invalid column: '16385'".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_selected_cell_errors_wrong_sheet() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
// forcefully set a wrong index
|
||||||
|
model.workbook.views.get_mut(&0).unwrap().sheet = 2;
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
// It's returning the wrong number
|
||||||
|
assert_eq!(model.get_selected_sheet(), 2);
|
||||||
|
|
||||||
|
// But we can't set the selected cell anymore
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_cell(3, 4),
|
||||||
|
Err("Invalid worksheet index 2".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_selected_range(3, 4, 5, 6),
|
||||||
|
Err("Invalid worksheet index 2".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model.set_top_left_visible_cell(3, 4),
|
||||||
|
Err("Invalid worksheet index 2".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
// we can fix it by setting the right cell
|
||||||
|
model.set_selected_sheet(0).unwrap();
|
||||||
|
model.set_selected_cell(3, 4).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_visible_cell() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
model.set_top_left_visible_cell(100, 12).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model.get_selected_view(),
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
top_row: 100,
|
||||||
|
left_column: 12
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let s = serde_json::to_string(&model.get_selected_view()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_str::<SelectedView>(&s).unwrap(),
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
top_row: 100,
|
||||||
|
left_column: 12
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_visible_cell_errors() {
|
||||||
|
let model = new_empty_model();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_top_left_visible_cell(-100, 12),
|
||||||
|
Err("Invalid row: '-100'".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.set_top_left_visible_cell(100, -12),
|
||||||
|
Err("Invalid column: '-12'".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn errors_no_views() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
// forcefully remove the view
|
||||||
|
model.workbook.views = HashMap::new();
|
||||||
|
// also in the sheet
|
||||||
|
model.workbook.worksheets[0].views = HashMap::new();
|
||||||
|
let mut model = UserModel::from_model(model);
|
||||||
|
// get methods will return defaults
|
||||||
|
assert_eq!(model.get_selected_sheet(), 0);
|
||||||
|
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||||
|
assert_eq!(
|
||||||
|
model.get_selected_view(),
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
top_row: 1,
|
||||||
|
left_column: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// set methods won't complain. but won't work either
|
||||||
|
model.set_selected_sheet(0).unwrap();
|
||||||
|
model.set_selected_cell(5, 6).unwrap();
|
||||||
|
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||||
|
}
|
||||||
@@ -4,37 +4,15 @@ use std::{collections::HashMap, fmt::Display};
|
|||||||
|
|
||||||
use crate::expressions::token::Error;
|
use crate::expressions::token::Error;
|
||||||
|
|
||||||
// Useful for `#[serde(default = "default_as_true")]`
|
|
||||||
fn default_as_true() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_as_false() -> bool {
|
fn default_as_false() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useful for `#[serde(skip_serializing_if = "is_true")]`
|
|
||||||
fn is_true(b: &bool) -> bool {
|
|
||||||
*b
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_false(b: &bool) -> bool {
|
fn is_false(b: &bool) -> bool {
|
||||||
!*b
|
!*b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_zero(num: &i32) -> bool {
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
*num == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_default_alignment(o: &Option<Alignment>) -> bool {
|
|
||||||
o.is_none() || *o == Some(Alignment::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hashmap_is_empty(h: &HashMap<String, Table>) -> bool {
|
|
||||||
h.values().len() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub application: String,
|
pub application: String,
|
||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
@@ -44,14 +22,21 @@ pub struct Metadata {
|
|||||||
pub last_modified: String, //"2020-11-20T16:24:35"
|
pub last_modified: String, //"2020-11-20T16:24:35"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct WorkbookSettings {
|
pub struct WorkbookSettings {
|
||||||
pub tz: String,
|
pub tz: String,
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Workbook View tracks of the selected sheet for each view
|
||||||
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
|
pub struct WorkbookView {
|
||||||
|
/// The index of the currently selected sheet.
|
||||||
|
pub sheet: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// An internal representation of an IronCalc Workbook
|
/// An internal representation of an IronCalc Workbook
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct Workbook {
|
pub struct Workbook {
|
||||||
pub shared_strings: Vec<String>,
|
pub shared_strings: Vec<String>,
|
||||||
pub defined_names: Vec<DefinedName>,
|
pub defined_names: Vec<DefinedName>,
|
||||||
@@ -60,28 +45,22 @@ pub struct Workbook {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub settings: WorkbookSettings,
|
pub settings: WorkbookSettings,
|
||||||
pub metadata: Metadata,
|
pub metadata: Metadata,
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "hashmap_is_empty")]
|
|
||||||
pub tables: HashMap<String, Table>,
|
pub tables: HashMap<String, Table>,
|
||||||
|
pub views: HashMap<u32, WorkbookView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct DefinedName {
|
pub struct DefinedName {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub formula: String,
|
pub formula: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub sheet_id: Option<u32>,
|
pub sheet_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move to worksheet.rs make frozen_rows/columns private and u32
|
|
||||||
/// Internal representation of a worksheet Excel object
|
|
||||||
|
|
||||||
/// * state:
|
/// * state:
|
||||||
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
||||||
/// hidden, veryHidden, visible
|
/// hidden, veryHidden, visible
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum SheetState {
|
pub enum SheetState {
|
||||||
Visible,
|
Visible,
|
||||||
Hidden,
|
Hidden,
|
||||||
@@ -98,8 +77,25 @@ impl Display for SheetState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the state of the worksheet as seen by the user. This includes
|
||||||
|
/// details such as the currently selected cell, the visible range, and the
|
||||||
|
/// position of the viewport.
|
||||||
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
|
pub struct WorksheetView {
|
||||||
|
/// The row index of the currently selected cell.
|
||||||
|
pub row: i32,
|
||||||
|
/// The column index of the currently selected cell.
|
||||||
|
pub column: i32,
|
||||||
|
/// The selected range in the worksheet, specified as [start_row, start_column, end_row, end_column].
|
||||||
|
pub range: [i32; 4],
|
||||||
|
/// The row index of the topmost visible cell in the worksheet view.
|
||||||
|
pub top_row: i32,
|
||||||
|
/// The column index of the leftmost visible cell in the worksheet view.
|
||||||
|
pub left_column: i32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal representation of a worksheet Excel object
|
/// Internal representation of a worksheet Excel object
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
pub struct Worksheet {
|
pub struct Worksheet {
|
||||||
pub dimension: String,
|
pub dimension: String,
|
||||||
pub cols: Vec<Col>,
|
pub cols: Vec<Col>,
|
||||||
@@ -109,16 +105,12 @@ pub struct Worksheet {
|
|||||||
pub shared_formulas: Vec<String>,
|
pub shared_formulas: Vec<String>,
|
||||||
pub sheet_id: u32,
|
pub sheet_id: u32,
|
||||||
pub state: SheetState,
|
pub state: SheetState,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
pub merge_cells: Vec<String>,
|
pub merge_cells: Vec<String>,
|
||||||
pub comments: Vec<Comment>,
|
pub comments: Vec<Comment>,
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "is_zero")]
|
|
||||||
pub frozen_rows: i32,
|
pub frozen_rows: i32,
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "is_zero")]
|
|
||||||
pub frozen_columns: i32,
|
pub frozen_columns: i32,
|
||||||
|
pub views: HashMap<u32, WorksheetView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal representation of Excel's sheet_data
|
/// Internal representation of Excel's sheet_data
|
||||||
@@ -126,7 +118,7 @@ pub struct Worksheet {
|
|||||||
pub type SheetData = HashMap<i32, HashMap<i32, Cell>>;
|
pub type SheetData = HashMap<i32, HashMap<i32, Cell>>;
|
||||||
|
|
||||||
// ECMA-376-1:2016 section 18.3.1.73
|
// ECMA-376-1:2016 section 18.3.1.73
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
pub struct Row {
|
pub struct Row {
|
||||||
/// Row index
|
/// Row index
|
||||||
pub r: i32,
|
pub r: i32,
|
||||||
@@ -134,23 +126,19 @@ pub struct Row {
|
|||||||
pub custom_format: bool,
|
pub custom_format: bool,
|
||||||
pub custom_height: bool,
|
pub custom_height: bool,
|
||||||
pub s: i32,
|
pub s: i32,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECMA-376-1:2016 section 18.3.1.13
|
// ECMA-376-1:2016 section 18.3.1.13
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
pub struct Col {
|
pub struct Col {
|
||||||
// Column definitions are defined on ranges, unlike rows which store unique, per-row entries.
|
// Column definitions are defined on ranges, unlike rows which store unique, per-row entries.
|
||||||
/// First column affected by this record. Settings apply to column in \[min, max\] range.
|
/// First column affected by this record. Settings apply to column in \[min, max\] range.
|
||||||
pub min: i32,
|
pub min: i32,
|
||||||
/// Last column affected by this record. Settings apply to column in \[min, max\] range.
|
/// Last column affected by this record. Settings apply to column in \[min, max\] range.
|
||||||
pub max: i32,
|
pub max: i32,
|
||||||
|
|
||||||
pub width: f64,
|
pub width: f64,
|
||||||
pub custom_width: bool,
|
pub custom_width: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub style: Option<i32>,
|
pub style: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,32 +153,55 @@ pub enum CellType {
|
|||||||
CompoundData = 128,
|
CompoundData = 128,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, Clone, PartialEq)]
|
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
|
||||||
#[serde(tag = "t", deny_unknown_fields)]
|
|
||||||
pub enum Cell {
|
pub enum Cell {
|
||||||
#[serde(rename = "empty")]
|
EmptyCell {
|
||||||
EmptyCell { s: i32 },
|
s: i32,
|
||||||
#[serde(rename = "b")]
|
},
|
||||||
BooleanCell { v: bool, s: i32 },
|
|
||||||
#[serde(rename = "n")]
|
BooleanCell {
|
||||||
NumberCell { v: f64, s: i32 },
|
v: bool,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
|
|
||||||
|
NumberCell {
|
||||||
|
v: f64,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
// Maybe we should not have this type. In Excel this is just a string
|
// Maybe we should not have this type. In Excel this is just a string
|
||||||
#[serde(rename = "e")]
|
ErrorCell {
|
||||||
ErrorCell { ei: Error, s: i32 },
|
ei: Error,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
// Always a shared string
|
// Always a shared string
|
||||||
#[serde(rename = "s")]
|
SharedString {
|
||||||
SharedString { si: i32, s: i32 },
|
si: i32,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
// Non evaluated Formula
|
// Non evaluated Formula
|
||||||
#[serde(rename = "u")]
|
CellFormula {
|
||||||
CellFormula { f: i32, s: i32 },
|
f: i32,
|
||||||
#[serde(rename = "fb")]
|
s: i32,
|
||||||
CellFormulaBoolean { f: i32, v: bool, s: i32 },
|
},
|
||||||
#[serde(rename = "fn")]
|
|
||||||
CellFormulaNumber { f: i32, v: f64, s: i32 },
|
CellFormulaBoolean {
|
||||||
|
f: i32,
|
||||||
|
v: bool,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
|
|
||||||
|
CellFormulaNumber {
|
||||||
|
f: i32,
|
||||||
|
v: f64,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
// always inline string
|
// always inline string
|
||||||
#[serde(rename = "str")]
|
CellFormulaString {
|
||||||
CellFormulaString { f: i32, v: String, s: i32 },
|
f: i32,
|
||||||
#[serde(rename = "fe")]
|
v: String,
|
||||||
|
s: i32,
|
||||||
|
},
|
||||||
|
|
||||||
CellFormulaError {
|
CellFormulaError {
|
||||||
f: i32,
|
f: i32,
|
||||||
ei: Error,
|
ei: Error,
|
||||||
@@ -209,17 +220,16 @@ impl Default for Cell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub author_name: String,
|
pub author_name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub author_id: Option<String>,
|
pub author_id: Option<String>,
|
||||||
pub cell_ref: String,
|
pub cell_ref: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECMA-376-1:2016 section 18.5.1.2
|
// ECMA-376-1:2016 section 18.5.1.2
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
@@ -227,34 +237,24 @@ pub struct Table {
|
|||||||
pub reference: String,
|
pub reference: String,
|
||||||
pub totals_row_count: u32,
|
pub totals_row_count: u32,
|
||||||
pub header_row_count: u32,
|
pub header_row_count: u32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub header_row_dxf_id: Option<u32>,
|
pub header_row_dxf_id: Option<u32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub data_dxf_id: Option<u32>,
|
pub data_dxf_id: Option<u32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub totals_row_dxf_id: Option<u32>,
|
pub totals_row_dxf_id: Option<u32>,
|
||||||
pub columns: Vec<TableColumn>,
|
pub columns: Vec<TableColumn>,
|
||||||
pub style_info: TableStyleInfo,
|
pub style_info: TableStyleInfo,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub has_filters: bool,
|
pub has_filters: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// totals_row_label vs totals_row_function might be mutually exclusive. Use an enum?
|
// totals_row_label vs totals_row_function might be mutually exclusive. Use an enum?
|
||||||
// the totals_row_function is an enum not String methinks
|
// the totals_row_function is an enum not String methinks
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct TableColumn {
|
pub struct TableColumn {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub totals_row_label: Option<String>,
|
pub totals_row_label: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub header_row_dxf_id: Option<u32>,
|
pub header_row_dxf_id: Option<u32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub data_dxf_id: Option<u32>,
|
pub data_dxf_id: Option<u32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub totals_row_dxf_id: Option<u32>,
|
pub totals_row_dxf_id: Option<u32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub totals_row_function: Option<String>,
|
pub totals_row_function: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,25 +272,16 @@ impl Default for TableColumn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct TableStyleInfo {
|
pub struct TableStyleInfo {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub show_first_column: bool,
|
pub show_first_column: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub show_last_column: bool,
|
pub show_last_column: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub show_row_stripes: bool,
|
pub show_row_stripes: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub show_column_stripes: bool,
|
pub show_column_stripes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Styles {
|
pub struct Styles {
|
||||||
pub num_fmts: Vec<NumFmt>,
|
pub num_fmts: Vec<NumFmt>,
|
||||||
pub fonts: Vec<Font>,
|
pub fonts: Vec<Font>,
|
||||||
@@ -326,7 +317,7 @@ pub struct Style {
|
|||||||
pub quote_prefix: bool,
|
pub quote_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct NumFmt {
|
pub struct NumFmt {
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
pub format_code: String,
|
pub format_code: String,
|
||||||
@@ -516,29 +507,17 @@ pub struct Alignment {
|
|||||||
pub wrap_text: bool,
|
pub wrap_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct CellStyleXfs {
|
pub struct CellStyleXfs {
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
pub font_id: i32,
|
pub font_id: i32,
|
||||||
pub fill_id: i32,
|
pub fill_id: i32,
|
||||||
pub border_id: i32,
|
pub border_id: i32,
|
||||||
#[serde(default = "default_as_true")]
|
|
||||||
#[serde(skip_serializing_if = "is_true")]
|
|
||||||
pub apply_number_format: bool,
|
pub apply_number_format: bool,
|
||||||
#[serde(default = "default_as_true")]
|
|
||||||
#[serde(skip_serializing_if = "is_true")]
|
|
||||||
pub apply_border: bool,
|
pub apply_border: bool,
|
||||||
#[serde(default = "default_as_true")]
|
|
||||||
#[serde(skip_serializing_if = "is_true")]
|
|
||||||
pub apply_alignment: bool,
|
pub apply_alignment: bool,
|
||||||
#[serde(default = "default_as_true")]
|
|
||||||
#[serde(skip_serializing_if = "is_true")]
|
|
||||||
pub apply_protection: bool,
|
pub apply_protection: bool,
|
||||||
#[serde(default = "default_as_true")]
|
|
||||||
#[serde(skip_serializing_if = "is_true")]
|
|
||||||
pub apply_font: bool,
|
pub apply_font: bool,
|
||||||
#[serde(default = "default_as_true")]
|
|
||||||
#[serde(skip_serializing_if = "is_true")]
|
|
||||||
pub apply_fill: bool,
|
pub apply_fill: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,39 +538,24 @@ impl Default for CellStyleXfs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct CellXfs {
|
pub struct CellXfs {
|
||||||
pub xf_id: i32,
|
pub xf_id: i32,
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
pub font_id: i32,
|
pub font_id: i32,
|
||||||
pub fill_id: i32,
|
pub fill_id: i32,
|
||||||
pub border_id: i32,
|
pub border_id: i32,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub apply_number_format: bool,
|
pub apply_number_format: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub apply_border: bool,
|
pub apply_border: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub apply_alignment: bool,
|
pub apply_alignment: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub apply_protection: bool,
|
pub apply_protection: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub apply_font: bool,
|
pub apply_font: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub apply_fill: bool,
|
pub apply_fill: bool,
|
||||||
#[serde(default = "default_as_false")]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
pub quote_prefix: bool,
|
pub quote_prefix: bool,
|
||||||
#[serde(skip_serializing_if = "is_default_alignment")]
|
|
||||||
pub alignment: Option<Alignment>,
|
pub alignment: Option<Alignment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct CellStyles {
|
pub struct CellStyles {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub xf_id: i32,
|
pub xf_id: i32,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::{collections::HashMap, fmt::Debug};
|
use std::{collections::HashMap, fmt::Debug};
|
||||||
|
|
||||||
use bitcode::{Decode, Encode};
|
use bitcode::{Decode, Encode};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants,
|
constants,
|
||||||
@@ -18,6 +19,17 @@ use crate::{
|
|||||||
utils::is_valid_hex_color,
|
utils::is_valid_hex_color,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||||
|
pub struct SelectedView {
|
||||||
|
pub sheet: u32,
|
||||||
|
pub row: i32,
|
||||||
|
pub column: i32,
|
||||||
|
pub range: [i32; 4],
|
||||||
|
pub top_row: i32,
|
||||||
|
pub left_column: i32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
#[derive(Clone, Encode, Decode)]
|
||||||
struct RowData {
|
struct RowData {
|
||||||
row: Option<Row>,
|
row: Option<Row>,
|
||||||
@@ -118,6 +130,7 @@ enum Diff {
|
|||||||
old_value: String,
|
old_value: String,
|
||||||
new_value: String,
|
new_value: String,
|
||||||
},
|
},
|
||||||
|
// FIXME: we are missing SetViewDiffs
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiffList = Vec<Diff>;
|
type DiffList = Vec<Diff>;
|
||||||
@@ -249,7 +262,7 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// # A wrapper around [`Model`] for a spreadsheet end user.
|
/// # A wrapper around [`Model`] for a spreadsheet end user.
|
||||||
/// UserModel is a wrapper around Model with undo/redo history, _diffs_ and automatic evaluation.
|
/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management.
|
||||||
///
|
///
|
||||||
/// A diff in this context (or more correctly a _user diff_) is a change created by a user.
|
/// A diff in this context (or more correctly a _user diff_) is a change created by a user.
|
||||||
///
|
///
|
||||||
@@ -849,7 +862,7 @@ impl UserModel {
|
|||||||
style.fill.fg_color = color(value)?;
|
style.fill.fg_color = color(value)?;
|
||||||
}
|
}
|
||||||
"num_fmt" => {
|
"num_fmt" => {
|
||||||
style.num_fmt = value.to_owned();
|
value.clone_into(&mut style.num_fmt);
|
||||||
}
|
}
|
||||||
"border.left" => {
|
"border.left" => {
|
||||||
style.border.left = border(value)?;
|
style.border.left = border(value)?;
|
||||||
@@ -935,6 +948,165 @@ impl UserModel {
|
|||||||
self.model.get_worksheets_properties()
|
self.model.get_worksheets_properties()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the selected sheet index
|
||||||
|
pub fn get_selected_sheet(&self) -> u32 {
|
||||||
|
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||||
|
view.sheet
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the selected cell
|
||||||
|
pub fn get_selected_cell(&self) -> (u32, i32, i32) {
|
||||||
|
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||||
|
view.sheet
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
||||||
|
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
||||||
|
return (sheet, view.row, view.column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return a safe default
|
||||||
|
(0, 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns selected view
|
||||||
|
pub fn get_selected_view(&self) -> SelectedView {
|
||||||
|
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||||
|
view.sheet
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
||||||
|
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
||||||
|
return SelectedView {
|
||||||
|
sheet,
|
||||||
|
row: view.row,
|
||||||
|
column: view.column,
|
||||||
|
range: view.range,
|
||||||
|
top_row: view.top_row,
|
||||||
|
left_column: view.left_column,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return a safe default
|
||||||
|
SelectedView {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
top_row: 1,
|
||||||
|
left_column: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the the selected sheet
|
||||||
|
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), String> {
|
||||||
|
if self.model.workbook.worksheet(sheet).is_err() {
|
||||||
|
return Err(format!("Invalid worksheet index {}", sheet));
|
||||||
|
}
|
||||||
|
if let Some(view) = self.model.workbook.views.get_mut(&0) {
|
||||||
|
view.sheet = sheet;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the selected cell
|
||||||
|
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
|
||||||
|
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||||
|
view.sheet
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
if !is_valid_column_number(column) {
|
||||||
|
return Err(format!("Invalid column: '{column}'"));
|
||||||
|
}
|
||||||
|
if !is_valid_column_number(row) {
|
||||||
|
return Err(format!("Invalid row: '{row}'"));
|
||||||
|
}
|
||||||
|
if self.model.workbook.worksheet(sheet).is_err() {
|
||||||
|
return Err(format!("Invalid worksheet index {}", sheet));
|
||||||
|
}
|
||||||
|
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
||||||
|
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||||
|
view.row = row;
|
||||||
|
view.column = column;
|
||||||
|
view.range = [row, column, row, column];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the selected range
|
||||||
|
pub fn set_selected_range(
|
||||||
|
&mut self,
|
||||||
|
start_row: i32,
|
||||||
|
start_column: i32,
|
||||||
|
end_row: i32,
|
||||||
|
end_column: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||||
|
view.sheet
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_valid_column_number(start_column) {
|
||||||
|
return Err(format!("Invalid column: '{start_column}'"));
|
||||||
|
}
|
||||||
|
if !is_valid_column_number(start_row) {
|
||||||
|
return Err(format!("Invalid row: '{start_row}'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_valid_column_number(end_column) {
|
||||||
|
return Err(format!("Invalid column: '{end_column}'"));
|
||||||
|
}
|
||||||
|
if !is_valid_column_number(end_row) {
|
||||||
|
return Err(format!("Invalid row: '{end_row}'"));
|
||||||
|
}
|
||||||
|
if self.model.workbook.worksheet(sheet).is_err() {
|
||||||
|
return Err(format!("Invalid worksheet index {}", sheet));
|
||||||
|
}
|
||||||
|
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
||||||
|
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||||
|
view.range = [start_row, start_column, end_row, end_column];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the first visible cell
|
||||||
|
pub fn set_top_left_visible_cell(
|
||||||
|
&mut self,
|
||||||
|
top_row: i32,
|
||||||
|
left_column: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||||
|
view.sheet
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_valid_column_number(left_column) {
|
||||||
|
return Err(format!("Invalid column: '{left_column}'"));
|
||||||
|
}
|
||||||
|
if !is_valid_column_number(top_row) {
|
||||||
|
return Err(format!("Invalid row: '{top_row}'"));
|
||||||
|
}
|
||||||
|
if self.model.workbook.worksheet(sheet).is_err() {
|
||||||
|
return Err(format!("Invalid worksheet index {}", sheet));
|
||||||
|
}
|
||||||
|
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
||||||
|
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||||
|
view.top_row = top_row;
|
||||||
|
view.left_column = left_column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
// **** Private methods ****** //
|
// **** Private methods ****** //
|
||||||
|
|
||||||
fn push_diff_list(&mut self, diff_list: DiffList) {
|
fn push_diff_list(&mut self, diff_list: DiffList) {
|
||||||
|
|||||||
@@ -286,4 +286,56 @@ impl Model {
|
|||||||
pub fn get_worksheets_properties(&self) -> JsValue {
|
pub fn get_worksheets_properties(&self) -> JsValue {
|
||||||
serde_wasm_bindgen::to_value(&self.model.get_worksheets_properties()).unwrap()
|
serde_wasm_bindgen::to_value(&self.model.get_worksheets_properties()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "getSelectedSheet")]
|
||||||
|
pub fn get_selected_sheet(&self) -> u32 {
|
||||||
|
self.model.get_selected_sheet()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "getSelectedCell")]
|
||||||
|
pub fn get_selected_cell(&self) -> Vec<i32> {
|
||||||
|
let (sheet, row, column) = self.model.get_selected_cell();
|
||||||
|
vec![sheet as i32, row, column]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "getSelectedView")]
|
||||||
|
pub fn get_selected_view(&self) -> JsValue {
|
||||||
|
serde_wasm_bindgen::to_value(&self.model.get_selected_view()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "setSelectedSheet")]
|
||||||
|
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), JsError> {
|
||||||
|
self.model.set_selected_sheet(sheet).map_err(to_js_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "setSelectedCell")]
|
||||||
|
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), JsError> {
|
||||||
|
self.model
|
||||||
|
.set_selected_cell(row, column)
|
||||||
|
.map_err(to_js_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "setSelectedRange")]
|
||||||
|
pub fn set_selected_range(
|
||||||
|
&mut self,
|
||||||
|
start_row: i32,
|
||||||
|
start_column: i32,
|
||||||
|
end_row: i32,
|
||||||
|
end_column: i32,
|
||||||
|
) -> Result<(), JsError> {
|
||||||
|
self.model
|
||||||
|
.set_selected_range(start_row, start_column, end_row, end_column)
|
||||||
|
.map_err(to_js_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "setTopLeftVisibleCell")]
|
||||||
|
pub fn set_top_left_visible_cell(
|
||||||
|
&mut self,
|
||||||
|
top_row: i32,
|
||||||
|
top_column: i32,
|
||||||
|
) -> Result<(), JsError> {
|
||||||
|
self.model
|
||||||
|
.set_top_left_visible_cell(top_row, top_column)
|
||||||
|
.map_err(to_js_error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
webapp/.gitignore → solidjs_app/.gitignore
vendored
@@ -1,3 +1,2 @@
|
|||||||
node_modules/*
|
node_modules/*
|
||||||
dist/*
|
dist/*
|
||||||
example.json
|
|
||||||
19
solidjs_app/.storybook/main.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { StorybookConfig } from "storybook-solidjs-vite";
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||||
|
addons: [
|
||||||
|
"@storybook/addon-links",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"@chromatic-com/storybook",
|
||||||
|
"@storybook/addon-interactions",
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: "storybook-solidjs-vite",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
autodocs: "tag",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
12
solidjs_app/.storybook/preview.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
8
solidjs_app/Makefile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
lint:
|
||||||
|
pnpm biome lint *
|
||||||
|
|
||||||
|
format:
|
||||||
|
pnpm biome format *
|
||||||
|
build:
|
||||||
|
pnpm run build
|
||||||
70
solidjs_app/README.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Web IronCalc
|
||||||
|
|
||||||
|
## Widgets
|
||||||
|
|
||||||
|
Toolbar
|
||||||
|
NavigationBar
|
||||||
|
FormulaBar
|
||||||
|
ColorPicker
|
||||||
|
Number Formatter
|
||||||
|
Border Picker
|
||||||
|
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
Vite
|
||||||
|
TypeScript
|
||||||
|
SolidJs
|
||||||
|
Lucide Icons
|
||||||
|
BiomeJs
|
||||||
|
Storybook
|
||||||
|
pnpm
|
||||||
|
|
||||||
|
## Recreate
|
||||||
|
|
||||||
|
Install nodejs
|
||||||
|
Activate pnpm
|
||||||
|
corepack enable pnpm
|
||||||
|
Create app
|
||||||
|
pnpm create vite
|
||||||
|
pnpm install
|
||||||
|
add biomejs
|
||||||
|
pnpm add --save-dev --save-exact @biomejs/biome
|
||||||
|
pnpm biome init
|
||||||
|
add solidjs
|
||||||
|
add storybook
|
||||||
|
pnpm dlx storybook@latest init
|
||||||
|
add i18n
|
||||||
|
pnpm add @solid-primitives/i18n
|
||||||
|
(https://github.com/jfgodoy/vite-plugin-solid-svg)
|
||||||
|
add vite-plugin-solid-svg
|
||||||
|
add script: "restore": "cp node_modules/@ironcalc/wasm/wasm_bg.wasm node_modules/.vite/deps/",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pnpm install # or npm install or yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `pnpm run dev`
|
||||||
|
|
||||||
|
Runs the app in the development mode.<br>
|
||||||
|
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
|
||||||
|
|
||||||
|
### `pnpm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `dist` folder.<br>
|
||||||
|
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.<br>
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html)
|
||||||
15
solidjs_app/biome.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.7.0/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "space"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
solidjs_app/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/ironcalc_icon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + Solid + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
solidjs_app/package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"restore": "cp node_modules/@ironcalc/wasm/wasm_bg.wasm node_modules/.vite/deps/",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"build-storybook": "storybook build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ironcalc/wasm": "file:../bindings/wasm/pkg",
|
||||||
|
"@solid-primitives/i18n": "^2.1.1",
|
||||||
|
"lucide-solid": "^0.379.0",
|
||||||
|
"solid-js": "^1.8.15"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "1.7.0",
|
||||||
|
"@chromatic-com/storybook": "^1.3.3",
|
||||||
|
"@storybook/addon-essentials": "^8.0.8",
|
||||||
|
"@storybook/addon-interactions": "^8.0.8",
|
||||||
|
"@storybook/addon-links": "^8.0.8",
|
||||||
|
"@storybook/blocks": "^8.0.8",
|
||||||
|
"storybook": "^8.0.8",
|
||||||
|
"storybook-solidjs": "^1.0.0-beta.2",
|
||||||
|
"storybook-solidjs-vite": "^1.0.0-beta.2",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"vite": "^5.2.0",
|
||||||
|
"vite-plugin-solid": "^2.10.2",
|
||||||
|
"vite-plugin-solid-svg": "^0.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
6864
solidjs_app/pnpm-lock.yaml
generated
Normal file
8
solidjs_app/public/ironcalc_icon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="600" height="600" rx="20" fill="#F2994A"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 100C348.98 166.034 322.748 229.362 276.055 276.055C268.163 283.947 259.796 291.255 251.021 297.95L251.021 500L348.98 500H251.021C251.021 433.966 277.252 370.637 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05L348.98 100Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M251.021 100.068C251.003 140.096 235.094 178.481 206.788 206.787C178.466 235.109 140.053 251.02 100 251.02V348.979C154.873 348.979 207.877 330.866 251.021 297.95V100.068Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 499.882C349.011 459.872 364.918 421.507 393.213 393.213C421.534 364.891 459.947 348.98 500 348.98V251.02C445.128 251.02 392.123 269.134 348.98 302.05V499.882Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.055 276.055C322.748 229.362 348.98 166.034 348.98 100H251.021V297.95C259.796 291.255 268.163 283.947 276.055 276.055Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M348.98 302.05V499.895C348.98 499.93 348.98 499.965 348.98 500L251.021 500C251.021 499.946 251.02 499.891 251.021 499.837C251.064 433.862 277.291 370.599 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
7
solidjs_app/src/App.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#root {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
29
solidjs_app/src/App.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Show, createResource } from "solid-js";
|
||||||
|
// import "./App.css";
|
||||||
|
// import solidLogo from "./assets/solid.svg";
|
||||||
|
|
||||||
|
import init, { Model } from "@ironcalc/wasm";
|
||||||
|
import Workbook from "./components/Workbook";
|
||||||
|
|
||||||
|
const fetchModel = async () => {
|
||||||
|
await init();
|
||||||
|
// const model_bytes = new Uint8Array(
|
||||||
|
// await (await fetch("./example.ic")).arrayBuffer(),
|
||||||
|
// );
|
||||||
|
// const _model = Model.from_bytes(model_bytes);*/
|
||||||
|
const model = new Model("en", "UTC");
|
||||||
|
model.setUserInput(0, 1, 1, "=1+1");
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [model] = createResource(fetchModel);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={model()} fallback={<div>Loading...</div>}>
|
||||||
|
{(model) => <Workbook model={model()} />}
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
BIN
solidjs_app/src/assets/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
solidjs_app/src/assets/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
solidjs_app/src/assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
solidjs_app/src/assets/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 441 B |
BIN
solidjs_app/src/assets/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
solidjs_app/src/assets/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
solidjs_app/src/assets/ironcalc_icon.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
8
solidjs_app/src/assets/ironcalc_icon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="600" height="600" rx="20" fill="#F2994A"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 100C348.98 166.034 322.748 229.362 276.055 276.055C268.163 283.947 259.796 291.255 251.021 297.95L251.021 500L348.98 500H251.021C251.021 433.966 277.252 370.637 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05L348.98 100Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M251.021 100.068C251.003 140.096 235.094 178.481 206.788 206.787C178.466 235.109 140.053 251.02 100 251.02V348.979C154.873 348.979 207.877 330.866 251.021 297.95V100.068Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 499.882C349.011 459.872 364.918 421.507 393.213 393.213C421.534 364.891 459.947 348.98 500 348.98V251.02C445.128 251.02 392.123 269.134 348.98 302.05V499.882Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.055 276.055C322.748 229.362 348.98 166.034 348.98 100H251.021V297.95C259.796 291.255 268.163 283.947 276.055 276.055Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M348.98 302.05V499.895C348.98 499.93 348.98 499.965 348.98 500L251.021 500C251.021 499.946 251.02 499.891 251.021 499.837C251.064 433.862 277.291 370.599 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
solidjs_app/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
23
solidjs_app/src/components/Workbook.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Model } from "@ironcalc/wasm";
|
||||||
|
import styles from "./workbook.module.css";
|
||||||
|
import Toolbar from "./toolbar/Toolbar";
|
||||||
|
import Navigation from "./navigation/Navigation";
|
||||||
|
import FormulaBar from "./formulabar/FormulaBar";
|
||||||
|
import Worksheet from "./Worksheet/Worksheet";
|
||||||
|
|
||||||
|
function Workbook(props: { model: Model }) {
|
||||||
|
const onkeydown = (event: KeyboardEvent) => {
|
||||||
|
console.log("key pressed: ", event);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div class={styles.workbook} onkeydown={onkeydown} tabIndex={0}>
|
||||||
|
<Toolbar></Toolbar>
|
||||||
|
{/* {props.model.getFormattedCellValue(0, 1, 1)} */}
|
||||||
|
<FormulaBar></FormulaBar>
|
||||||
|
<Worksheet></Worksheet>
|
||||||
|
<Navigation></Navigation>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Workbook;
|
||||||
14
solidjs_app/src/components/Worksheet/Worksheet.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import styles from "./worksheet.module.css";
|
||||||
|
|
||||||
|
function Worksheet() {
|
||||||
|
const onkeydown = (event: KeyboardEvent) => {
|
||||||
|
console.log("key pressed: ", event);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div class={styles.worksheet} onkeydown={onkeydown} tabIndex={0}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Worksheet;
|
||||||
14
solidjs_app/src/components/formulabar/FormulaBar.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import styles from "./formulabar.module.css";
|
||||||
|
|
||||||
|
function FormulaBar() {
|
||||||
|
const onkeydown = (event: KeyboardEvent) => {
|
||||||
|
console.log("key pressed: ", event);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div class={styles.toolbar} onkeydown={onkeydown} tabIndex={0}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormulaBar;
|
||||||
14
solidjs_app/src/components/navigation/Navigation.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import styles from "./navigation.module.css";
|
||||||
|
|
||||||
|
function Navigation() {
|
||||||
|
const onkeydown = (event: KeyboardEvent) => {
|
||||||
|
console.log("key pressed: ", event);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div class={styles.navigation} onkeydown={onkeydown} tabIndex={0}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navigation;
|
||||||
299
solidjs_app/src/components/toolbar/Toolbar.tsx
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
import { JSX } from "solid-js/jsx-runtime";
|
||||||
|
import styles from "./toolbar.module.css";
|
||||||
|
import {
|
||||||
|
AlignCenter,
|
||||||
|
AlignLeft,
|
||||||
|
AlignRight,
|
||||||
|
ArrowDownToLine,
|
||||||
|
ArrowUpToLine,
|
||||||
|
Bold,
|
||||||
|
ChevronDown,
|
||||||
|
Euro,
|
||||||
|
Grid2X2,
|
||||||
|
Italic,
|
||||||
|
Paintbrush2,
|
||||||
|
PaintBucket,
|
||||||
|
Percent,
|
||||||
|
Redo2,
|
||||||
|
Strikethrough,
|
||||||
|
Type,
|
||||||
|
Underline,
|
||||||
|
Undo2,
|
||||||
|
} from "lucide-solid";
|
||||||
|
import { DecimalPlacesDecreaseIcon, DecimalPlacesIncreaseIcon, ArrowMiddleFromLine } from "../../icons";
|
||||||
|
|
||||||
|
function Toolbar() {
|
||||||
|
const onkeydown = (event: KeyboardEvent) => {
|
||||||
|
console.log("key pressed: ", event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = (s: string): string => s;
|
||||||
|
|
||||||
|
const properties = {
|
||||||
|
onUndo: () => {},
|
||||||
|
canUndo: true,
|
||||||
|
onRedo: () => {},
|
||||||
|
canRedo: true,
|
||||||
|
onCopyStyles: () => {},
|
||||||
|
canEdit: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={styles.toolbar} onkeydown={onkeydown} tabIndex={0}>
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
onClick={properties.onUndo}
|
||||||
|
disabled={!properties.canUndo}
|
||||||
|
title={t("toolbar.undo")}
|
||||||
|
>
|
||||||
|
<Undo2 />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
onClick={properties.onRedo}
|
||||||
|
disabled={!properties.canRedo}
|
||||||
|
title={t("toolbar.redo")}
|
||||||
|
>
|
||||||
|
<Redo2 />
|
||||||
|
</StyledButton>
|
||||||
|
<div class={styles.divider} />
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
onClick={properties.onCopyStyles}
|
||||||
|
title={t("toolbar.copy_styles")}
|
||||||
|
>
|
||||||
|
<Paintbrush2 />
|
||||||
|
</StyledButton>
|
||||||
|
<div class={styles.divider} />
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
// onClick={(): void => {
|
||||||
|
// properties.onNumberFormatPicked(NumberFormats.CURRENCY_EUR);
|
||||||
|
// }}
|
||||||
|
title={t("toolbar.euro")}
|
||||||
|
>
|
||||||
|
<Euro />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
// onClick={(): void => {
|
||||||
|
// properties.onNumberFormatPicked(NumberFormats.PERCENTAGE);
|
||||||
|
// }}
|
||||||
|
title={t("toolbar.percentage")}
|
||||||
|
>
|
||||||
|
<Percent />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
// onClick={(): void => {
|
||||||
|
// properties.onNumberFormatPicked(
|
||||||
|
// decreaseDecimalPlaces(properties.numFmt)
|
||||||
|
// );
|
||||||
|
// }}
|
||||||
|
title={t("toolbar.decimal_places_decrease")}
|
||||||
|
>
|
||||||
|
<div><DecimalPlacesDecreaseIcon /></div>
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
// onClick={(): void => {
|
||||||
|
// properties.onNumberFormatPicked(
|
||||||
|
// increaseDecimalPlaces(properties.numFmt)
|
||||||
|
// );
|
||||||
|
// }}
|
||||||
|
title={t("toolbar.decimal_places_increase")}
|
||||||
|
>
|
||||||
|
<DecimalPlacesIncreaseIcon />
|
||||||
|
</StyledButton>
|
||||||
|
{/* // <FormatMenu
|
||||||
|
// numFmt={properties.numFmt}
|
||||||
|
// onChange={(numberFmt): void => {
|
||||||
|
// properties.onNumberFormatPicked(numberFmt);
|
||||||
|
// }}
|
||||||
|
// onExited={(): void => {}}
|
||||||
|
// anchorOrigin={{
|
||||||
|
// horizontal: 20, // Aligning the menu to the middle of FormatButton
|
||||||
|
// vertical: "bottom",
|
||||||
|
// }}
|
||||||
|
// >*/
|
||||||
|
<StyledButton
|
||||||
|
|
||||||
|
pressed={false}
|
||||||
|
|
||||||
|
title={t("toolbar.format_number")}
|
||||||
|
>
|
||||||
|
<div class={styles.format_menu}>{"123"}<ChevronDown /></div>
|
||||||
|
|
||||||
|
</StyledButton>
|
||||||
|
/* </FormatMenu> */}
|
||||||
|
<div class={styles.divider} />
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.bold}
|
||||||
|
// onClick={() => properties.onToggleBold(!properties.bold)}
|
||||||
|
title={t("toolbar.bold")}
|
||||||
|
>
|
||||||
|
<Bold />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.italic}
|
||||||
|
// onClick={() => properties.onToggleItalic(!properties.italic)}
|
||||||
|
title={t("toolbar.italic")}
|
||||||
|
>
|
||||||
|
<Italic />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.underline}
|
||||||
|
// onClick={() => properties.onToggleUnderline(!properties.underline)}
|
||||||
|
title={t("toolbar.underline")}
|
||||||
|
>
|
||||||
|
<Underline />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.strike}
|
||||||
|
// onClick={() => properties.onToggleStrike(!properties.strike)}
|
||||||
|
title={t("toolbar.strike_trough")}
|
||||||
|
>
|
||||||
|
<Strikethrough />
|
||||||
|
</StyledButton>
|
||||||
|
<div class={styles.divider} />
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
title={t("toolbar.font_color")}
|
||||||
|
// ref={fontColorButton}
|
||||||
|
// underlinedColor={properties.fontColor}
|
||||||
|
// onClick={() => setFontColorPickerOpen(true)}
|
||||||
|
>
|
||||||
|
<Type />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
title={t("toolbar.fill_color")}
|
||||||
|
// ref={fillColorButton}
|
||||||
|
// underlinedColor={properties.fillColor}
|
||||||
|
// onClick={() => setFillColorPickerOpen(true)}
|
||||||
|
>
|
||||||
|
<PaintBucket />
|
||||||
|
</StyledButton>
|
||||||
|
<div class={styles.divider} />
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.horizontalAlign === "left"}
|
||||||
|
// onClick={() =>
|
||||||
|
// properties.onToggleHorizontalAlign(
|
||||||
|
// properties.horizontalAlign === "left" ? "general" : "left"
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
title={t("toolbar.align_left")}
|
||||||
|
>
|
||||||
|
<AlignLeft />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.horizontalAlign === "center"}
|
||||||
|
// onClick={() =>
|
||||||
|
// properties.onToggleHorizontalAlign(
|
||||||
|
// properties.horizontalAlign === "center" ? "general" : "center"
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
title={t("toolbar.align_center")}
|
||||||
|
>
|
||||||
|
<AlignCenter />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.horizontalAlign === "right"}
|
||||||
|
// onClick={() =>
|
||||||
|
// properties.onToggleHorizontalAlign(
|
||||||
|
// properties.horizontalAlign === "right" ? "general" : "right"
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
title={t("toolbar.align_right")}
|
||||||
|
>
|
||||||
|
<AlignRight />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.verticalAlign === "top"}
|
||||||
|
// onClick={() =>
|
||||||
|
// properties.onToggleVerticalAlign(
|
||||||
|
// properties.verticalAlign === "top" ? "bottom" : "top"
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
title={t("toolbar.vertical_align_top")}
|
||||||
|
>
|
||||||
|
<ArrowUpToLine />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.verticalAlign === "center"}
|
||||||
|
// onClick={() =>
|
||||||
|
// properties.onToggleVerticalAlign(
|
||||||
|
// properties.verticalAlign === "center" ? "bottom" : "center"
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
title={t("toolbar.vertical_align_center")}
|
||||||
|
>
|
||||||
|
<ArrowMiddleFromLine />
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
// pressed={properties.verticalAlign === "bottom"}
|
||||||
|
// onClick={() => properties.onToggleVerticalAlign("bottom")}
|
||||||
|
title={t("toolbar.vertical_align_bottom")}
|
||||||
|
>
|
||||||
|
<ArrowDownToLine />
|
||||||
|
</StyledButton>
|
||||||
|
<div class={styles.divider} />
|
||||||
|
<StyledButton
|
||||||
|
pressed={false}
|
||||||
|
// onClick={() => setBorderPickerOpen(true)}
|
||||||
|
// ref={borderButton}
|
||||||
|
title={t("toolbar.borders")}
|
||||||
|
>
|
||||||
|
<Grid2X2 />
|
||||||
|
</StyledButton>
|
||||||
|
{/* // <ColorPicker
|
||||||
|
// color={properties.fontColor}
|
||||||
|
// onChange={(color): void => {
|
||||||
|
// properties.onTextColorPicked(color);
|
||||||
|
// setFontColorPickerOpen(false);
|
||||||
|
// }}
|
||||||
|
// anchorEl={fontColorButton}
|
||||||
|
// open={fontColorPickerOpen}
|
||||||
|
// />
|
||||||
|
// <ColorPicker
|
||||||
|
// color={properties.fillColor}
|
||||||
|
// onChange={(color): void => {
|
||||||
|
// properties.onFillColorPicked(color);
|
||||||
|
// setFillColorPickerOpen(false);
|
||||||
|
// }}
|
||||||
|
// anchorEl={fillColorButton}
|
||||||
|
// open={fillColorPickerOpen}
|
||||||
|
// />
|
||||||
|
// <BorderPicker
|
||||||
|
// onChange={(border): void => {
|
||||||
|
// properties.onBorderChanged(border);
|
||||||
|
// setBorderPickerOpen(false);
|
||||||
|
// }}
|
||||||
|
// anchorEl={borderButton}
|
||||||
|
// open={borderPickerOpen}
|
||||||
|
// /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StyledButton(props: {
|
||||||
|
children: JSX.Element;
|
||||||
|
title: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
pressed?: boolean;
|
||||||
|
underlinedColor?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
disabled={props.disabled || false}
|
||||||
|
onClick={props.onClick}
|
||||||
|
title={props.title}
|
||||||
|
class={styles.button}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Toolbar;
|
||||||
67
solidjs_app/src/components/toolbar/toolbar.module.css
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
/* ${({ theme }) => theme.palette.background.paper}; */
|
||||||
|
background: #fff;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
/* theme.palette.grey["600"] */
|
||||||
|
border-bottom: 1px solid #757575;
|
||||||
|
font-family: Inter;
|
||||||
|
border-radius: 4px 4px 0px 0px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 26px;
|
||||||
|
border: 0px solid #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-right: 5px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: white;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
color: grey;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:not(disabled) {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
color: #21243a;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background-color: #f1f2f8;
|
||||||
|
border-top-color: #f1f2f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 0px;
|
||||||
|
height: 10px;
|
||||||
|
border-left: 1px solid #d3d6e9;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.format_menu {
|
||||||
|
width: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
6
solidjs_app/src/components/workbook.module.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/* @import './theme.css'; */
|
||||||
|
|
||||||
|
.workbook {
|
||||||
|
/* background-color: var(--main-bg-color); */
|
||||||
|
/* color: var(--other); */
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 869 B After Width: | Height: | Size: 869 B |
|
Before Width: | Height: | Size: 538 B After Width: | Height: | Size: 538 B |
|
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B |
|
Before Width: | Height: | Size: 513 B After Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 542 B |
|
Before Width: | Height: | Size: 538 B After Width: | Height: | Size: 538 B |
|
Before Width: | Height: | Size: 744 B After Width: | Height: | Size: 744 B |
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 1004 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 929 B After Width: | Height: | Size: 929 B |
46
solidjs_app/src/icons/index.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import DecimalPlacesDecreaseIcon from "./decrease-decimal.svg";
|
||||||
|
import DecimalPlacesIncreaseIcon from "./increase-decimal.svg";
|
||||||
|
|
||||||
|
import BorderBottomIcon from "./border-bottom.svg";
|
||||||
|
import BorderCenterHIcon from "./border-center-h.svg";
|
||||||
|
import BorderCenterVIcon from "./border-center-v.svg";
|
||||||
|
import BorderInnerIcon from "./border-inner.svg";
|
||||||
|
import BorderLeftIcon from "./border-left.svg";
|
||||||
|
import BorderOuterIcon from "./border-outer.svg";
|
||||||
|
import BorderRightIcon from "./border-right.svg";
|
||||||
|
import BorderTopIcon from "./border-top.svg";
|
||||||
|
import BorderNoneIcon from "./border-none.svg";
|
||||||
|
import BorderStyleIcon from "./border-style.svg";
|
||||||
|
|
||||||
|
import DeleteColumnIcon from "./delete-column.svg";
|
||||||
|
import DeleteRowIcon from "./delete-row.svg";
|
||||||
|
import InsertColumnLeftIcon from "./insert-column-left.svg";
|
||||||
|
import InsertColumnRightIcon from "./insert-column-right.svg";
|
||||||
|
import InsertRowAboveIcon from "./insert-row-above.svg";
|
||||||
|
import InsertRowBelow from "./insert-row-below.svg";
|
||||||
|
import ArrowMiddleFromLine from "./arrow-middle-from-line.svg";
|
||||||
|
|
||||||
|
import Fx from "./fx.svg";
|
||||||
|
|
||||||
|
export {
|
||||||
|
ArrowMiddleFromLine,
|
||||||
|
DecimalPlacesDecreaseIcon,
|
||||||
|
DecimalPlacesIncreaseIcon,
|
||||||
|
BorderBottomIcon,
|
||||||
|
BorderCenterHIcon,
|
||||||
|
BorderCenterVIcon,
|
||||||
|
BorderInnerIcon,
|
||||||
|
BorderLeftIcon,
|
||||||
|
BorderOuterIcon,
|
||||||
|
BorderRightIcon,
|
||||||
|
BorderTopIcon,
|
||||||
|
BorderNoneIcon,
|
||||||
|
BorderStyleIcon,
|
||||||
|
DeleteColumnIcon,
|
||||||
|
DeleteRowIcon,
|
||||||
|
InsertColumnLeftIcon,
|
||||||
|
InsertColumnRightIcon,
|
||||||
|
InsertRowAboveIcon,
|
||||||
|
InsertRowBelow,
|
||||||
|
Fx,
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 726 B After Width: | Height: | Size: 726 B |
|
Before Width: | Height: | Size: 725 B After Width: | Height: | Size: 725 B |
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 706 B |
68
solidjs_app/src/index.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
solidjs_app/src/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* @refresh reload */
|
||||||
|
import { render } from "solid-js/web";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
|
// import "./index.css";
|
||||||
|
import "./theme.css";
|
||||||
|
|
||||||
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
render(() => <App />, root);
|
||||||
|
}
|
||||||
4
solidjs_app/src/theme.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
:root {
|
||||||
|
--main-bg-color: brown;
|
||||||
|
--other: blue;
|
||||||
|
}
|
||||||
1
solidjs_app/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module "*.module.css";
|
||||||
1
solidjs_app/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
@@ -12,14 +12,18 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esModuleInterop": true
|
"types": [
|
||||||
|
"vite-plugin-solid-svg/types-component-solid",
|
||||||
|
"vite/client"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
8
solidjs_app/vite.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import solid from "vite-plugin-solid";
|
||||||
|
import solidPlugin from "vite-plugin-solid";
|
||||||
|
import solidSvg from "vite-plugin-solid-svg";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solid(), solidPlugin(), solidSvg()],
|
||||||
|
});
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { StorybookConfig } from '@storybook/react-vite';
|
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
|
||||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
|
||||||
addons: [
|
|
||||||
'@storybook/addon-links',
|
|
||||||
'@storybook/addon-essentials',
|
|
||||||
'@storybook/addon-onboarding',
|
|
||||||
'@storybook/addon-interactions',
|
|
||||||
],
|
|
||||||
framework: {
|
|
||||||
name: '@storybook/react-vite',
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
docs: {
|
|
||||||
autodocs: 'tag',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default config;
|
|
||||||