mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 11:18:11 +08:00
Improve font import; replace Inconsolata with Source Code Pro; show third-party licenses in editor dialog (#3079)
* Improve font import; replace Inconsolata with Source Code Pro; show third-party licenses in editor dialog * Code review
This commit is contained in:
Vendored
+5
-6
@@ -39,12 +39,11 @@
|
|||||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||||
// Svelte config
|
// Svelte config
|
||||||
"svelte.plugin.svelte.compilerWarnings": {
|
"svelte.plugin.svelte.compilerWarnings": {
|
||||||
// NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
"css-unused-selector": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"css-unused-selector": "ignore",
|
"vite-plugin-svelte-css-no-scopable-elements": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"vite-plugin-svelte-css-no-scopable-elements": "ignore",
|
"a11y-no-static-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"a11y-no-static-element-interactions": "ignore",
|
"a11y-no-noninteractive-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"a11y-no-noninteractive-element-interactions": "ignore",
|
"a11y-click-events-have-key-events": "ignore" // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"a11y-click-events-have-key-events": "ignore"
|
|
||||||
},
|
},
|
||||||
// VS Code config
|
// VS Code config
|
||||||
"html.format.wrapLineLength": 200,
|
"html.format.wrapLineLength": 200,
|
||||||
|
|||||||
+18
-18
@@ -1,22 +1,22 @@
|
|||||||
# Keep this list in sync with those in `/deny.toml` and `/frontend/vite.config.ts`.
|
|
||||||
accepted = [
|
accepted = [
|
||||||
"Apache-2.0 WITH LLVM-exception",
|
"Apache-2.0 WITH LLVM-exception", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Apache-2.0",
|
"Apache-2.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"BSD-2-Clause",
|
"BSD-2-Clause", # Keep this list in sync with those in `/deny.toml`
|
||||||
"BSD-3-Clause",
|
"BSD-3-Clause", # Keep this list in sync with those in `/deny.toml`
|
||||||
"BSL-1.0",
|
"BSL-1.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"CC0-1.0",
|
"CC0-1.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"CDLA-Permissive-2.0",
|
"CDLA-Permissive-2.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"ISC",
|
"ISC", # Keep this list in sync with those in `/deny.toml`
|
||||||
"MIT-0",
|
"MIT-0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"MIT",
|
"MIT", # Keep this list in sync with those in `/deny.toml`
|
||||||
"MPL-2.0",
|
"MPL-2.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"OpenSSL",
|
"OpenSSL", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Unicode-3.0",
|
"Unicode-3.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Zlib",
|
"Zlib", # Keep this list in sync with those in `/deny.toml`
|
||||||
"NCSA",
|
"NCSA", # Keep this list in sync with those in `/deny.toml`
|
||||||
"bzip2-1.0.6",
|
"bzip2-1.0.6", # Keep this list in sync with those in `/deny.toml`
|
||||||
|
"OFL-1.1", # Keep this list in sync with those in `/deny.toml`
|
||||||
]
|
]
|
||||||
workarounds = ["ring"]
|
workarounds = ["ring"]
|
||||||
ignore-build-dependencies = true
|
ignore-build-dependencies = true
|
||||||
|
|||||||
@@ -63,25 +63,25 @@ ignore = [
|
|||||||
# See https://spdx.org/licenses/ for list of possible licenses
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||||
#
|
#
|
||||||
# Keep this list in sync with those in `/about.toml` and `/frontend/vite.config.ts`.
|
|
||||||
allow = [
|
allow = [
|
||||||
"Apache-2.0 WITH LLVM-exception",
|
"Apache-2.0 WITH LLVM-exception", # Keep this list in sync with those in `/about.toml`
|
||||||
"Apache-2.0",
|
"Apache-2.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"BSD-2-Clause",
|
"BSD-2-Clause", # Keep this list in sync with those in `/about.toml`
|
||||||
"BSD-3-Clause",
|
"BSD-3-Clause", # Keep this list in sync with those in `/about.toml`
|
||||||
"BSL-1.0",
|
"BSL-1.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"CC0-1.0",
|
"CC0-1.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"CDLA-Permissive-2.0",
|
"CDLA-Permissive-2.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"ISC",
|
"ISC", # Keep this list in sync with those in `/about.toml`
|
||||||
"MIT-0",
|
"MIT-0", # Keep this list in sync with those in `/about.toml`
|
||||||
"MIT",
|
"MIT", # Keep this list in sync with those in `/about.toml`
|
||||||
"MPL-2.0",
|
"MPL-2.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"OpenSSL",
|
"OpenSSL", # Keep this list in sync with those in `/about.toml`
|
||||||
"Unicode-3.0",
|
"Unicode-3.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016", # Keep this list in sync with those in `/about.toml`
|
||||||
"Zlib",
|
"Zlib", # Keep this list in sync with those in `/about.toml`
|
||||||
"NCSA",
|
"NCSA", # Keep this list in sync with those in `/about.toml`
|
||||||
"bzip2-1.0.6",
|
"bzip2-1.0.6", # Keep this list in sync with those in `/about.toml`
|
||||||
|
"OFL-1.1", # Keep this list in sync with those in `/about.toml`
|
||||||
]
|
]
|
||||||
# The confidence threshold for detecting a license from license text.
|
# The confidence threshold for detecting a license from license text.
|
||||||
# The higher the value, the more closely the license text must be to the
|
# The higher the value, the more closely the license text must be to the
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ pub enum DialogMessage {
|
|||||||
RequestLicensesDialogWithLocalizedCommitDate {
|
RequestLicensesDialogWithLocalizedCommitDate {
|
||||||
localized_commit_year: String,
|
localized_commit_year: String,
|
||||||
},
|
},
|
||||||
|
RequestLicensesThirdPartyDialogWithLicenseText {
|
||||||
|
license_text: String,
|
||||||
|
},
|
||||||
RequestNewDocumentDialog,
|
RequestNewDocumentDialog,
|
||||||
RequestPreferencesDialog,
|
RequestPreferencesDialog,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
||||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
||||||
|
use crate::messages::dialog::simple_dialogs::LicensesThirdPartyDialog;
|
||||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
@@ -103,6 +104,10 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
|||||||
|
|
||||||
dialog.send_dialog_to_frontend(responses);
|
dialog.send_dialog_to_frontend(responses);
|
||||||
}
|
}
|
||||||
|
DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text } => {
|
||||||
|
let dialog = LicensesThirdPartyDialog { license_text };
|
||||||
|
dialog.send_dialog_to_frontend(responses);
|
||||||
|
}
|
||||||
DialogMessage::RequestNewDocumentDialog => {
|
DialogMessage::RequestNewDocumentDialog => {
|
||||||
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
||||||
name: portfolio.generate_new_document_name(),
|
name: portfolio.generate_new_document_name(),
|
||||||
|
|||||||
@@ -92,13 +92,13 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let export_type = vec![
|
let export_type = vec![
|
||||||
TextLabel::new("File Type").table_align(true).min_width(100).widget_holder(),
|
TextLabel::new("File Type").table_align(true).min_width("100px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
RadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_holder(),
|
RadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let resolution = vec![
|
let resolution = vec![
|
||||||
TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(),
|
TextLabel::new("Scale Factor").table_align(true).min_width("100px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
NumberInput::new(Some(self.scale_factor))
|
NumberInput::new(Some(self.scale_factor))
|
||||||
.unit("")
|
.unit("")
|
||||||
@@ -144,14 +144,14 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let export_area = vec![
|
let export_area = vec![
|
||||||
TextLabel::new("Bounds").table_align(true).min_width(100).widget_holder(),
|
TextLabel::new("Bounds").table_align(true).min_width("100px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
let transparent_background = vec![
|
let transparent_background = vec![
|
||||||
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_holder(),
|
TextLabel::new("Transparency").table_align(true).min_width("100px").for_checkbox(checkbox_id).widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
CheckboxInput::new(self.transparent_background)
|
CheckboxInput::new(self.transparent_background)
|
||||||
.disabled(self.file_type == FileType::Jpg)
|
.disabled(self.file_type == FileType::Jpg)
|
||||||
|
|||||||
+3
-3
@@ -79,7 +79,7 @@ impl DialogLayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let name = vec![
|
let name = vec![
|
||||||
TextLabel::new("Name").table_align(true).min_width(90).widget_holder(),
|
TextLabel::new("Name").table_align(true).min_width("90px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
TextInput::new(&self.name)
|
TextInput::new(&self.name)
|
||||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
|
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
|
||||||
@@ -89,7 +89,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
let infinite = vec![
|
let infinite = vec![
|
||||||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
|
TextLabel::new("Infinite Canvas").table_align(true).min_width("90px").for_checkbox(checkbox_id).widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
CheckboxInput::new(self.infinite)
|
CheckboxInput::new(self.infinite)
|
||||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
|
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
|
||||||
@@ -98,7 +98,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let scale = vec![
|
let scale = vec![
|
||||||
TextLabel::new("Dimensions").table_align(true).min_width(90).widget_holder(),
|
TextLabel::new("Dimensions").table_align(true).min_width("90px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
NumberInput::new(Some(self.dimensions.x as f64))
|
NumberInput::new(Some(self.dimensions.x as f64))
|
||||||
.label("W")
|
.label("W")
|
||||||
|
|||||||
@@ -16,22 +16,31 @@ impl DialogLayoutHolder for LicensesDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout_column_2(&self) -> Layout {
|
fn layout_column_2(&self) -> Layout {
|
||||||
let icons_license_link = "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md";
|
#[allow(clippy::type_complexity)]
|
||||||
let links = [
|
let button_definitions: &[(&str, &str, fn() -> Message)] = &[
|
||||||
("GraphiteLogo", "Graphite Logo", "https://graphite.rs/logo/"),
|
("GraphiteLogo", "Graphite Logo", || {
|
||||||
("IconsGrid", "Graphite Icons", icons_license_link),
|
FrontendMessage::TriggerVisitLink {
|
||||||
("License", "Graphite License", "https://graphite.rs/license/"),
|
url: "https://graphite.rs/logo/".into(),
|
||||||
("License", "Other Licenses", "/third-party-licenses.txt"),
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
("IconsGrid", "Graphite Icons", || {
|
||||||
|
FrontendMessage::TriggerVisitLink {
|
||||||
|
url: "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md".into(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
("License", "Graphite License", || {
|
||||||
|
FrontendMessage::TriggerVisitLink {
|
||||||
|
url: "https://graphite.rs/license/".into(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
("License", "Other Licenses", || FrontendMessage::TriggerDisplayThirdPartyLicensesDialog.into()),
|
||||||
];
|
];
|
||||||
let widgets = links
|
let widgets = button_definitions
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|(icon, label, url)| {
|
.map(|&(icon, label, message_factory)| TextButton::new(label).icon(Some((icon).into())).flush(true).on_update(move |_| message_factory()).widget_holder())
|
||||||
TextButton::new(label)
|
|
||||||
.icon(Some(icon.into()))
|
|
||||||
.flush(true)
|
|
||||||
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
|
|
||||||
.widget_holder()
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Column { widgets }]))
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Column { widgets }]))
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
pub struct LicensesThirdPartyDialog {
|
||||||
|
pub license_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogLayoutHolder for LicensesThirdPartyDialog {
|
||||||
|
const ICON: &'static str = "License12px";
|
||||||
|
const TITLE: &'static str = "Third-Party Software License Notices";
|
||||||
|
|
||||||
|
fn layout_buttons(&self) -> Layout {
|
||||||
|
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder()];
|
||||||
|
|
||||||
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutHolder for LicensesThirdPartyDialog {
|
||||||
|
fn layout(&self) -> Layout {
|
||||||
|
// Remove the header and begin with the line containing the first license section (we otherwise keep the title for standalone viewing of the licenses text file)
|
||||||
|
let license_text = if let Some(first_underscore_line) = self.license_text.lines().position(|line| line.contains('_')) {
|
||||||
|
// Find the byte position where the line with underscore starts
|
||||||
|
let char_position = self.license_text.split('\n').take(first_underscore_line).map(|line| line.len() + '\n'.len_utf8()).sum();
|
||||||
|
self.license_text[char_position..].to_string()
|
||||||
|
} else {
|
||||||
|
// This shouldn't be encountered, but if no underscore line is found, we use the full text as a safety fallback
|
||||||
|
self.license_text.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar
|
||||||
|
let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count()).max().unwrap_or(0) + 2 + 1;
|
||||||
|
|
||||||
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
|
widgets: vec![
|
||||||
|
TextLabel::new(license_text)
|
||||||
|
.monospace(true)
|
||||||
|
.multiline(true)
|
||||||
|
.min_width(format!("{non_wrapping_column_width}ch"))
|
||||||
|
.widget_holder(),
|
||||||
|
],
|
||||||
|
}]))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ mod coming_soon_dialog;
|
|||||||
mod demo_artwork_dialog;
|
mod demo_artwork_dialog;
|
||||||
mod error_dialog;
|
mod error_dialog;
|
||||||
mod licenses_dialog;
|
mod licenses_dialog;
|
||||||
|
mod licenses_third_party_dialog;
|
||||||
|
|
||||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||||
@@ -14,3 +15,4 @@ pub use demo_artwork_dialog::ARTWORK;
|
|||||||
pub use demo_artwork_dialog::DemoArtworkDialog;
|
pub use demo_artwork_dialog::DemoArtworkDialog;
|
||||||
pub use error_dialog::ErrorDialog;
|
pub use error_dialog::ErrorDialog;
|
||||||
pub use licenses_dialog::LicensesDialog;
|
pub use licenses_dialog::LicensesDialog;
|
||||||
|
pub use licenses_third_party_dialog::LicensesThirdPartyDialog;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "commitDate")]
|
#[serde(rename = "commitDate")]
|
||||||
commit_date: String,
|
commit_date: String,
|
||||||
},
|
},
|
||||||
|
TriggerDisplayThirdPartyLicensesDialog,
|
||||||
TriggerSaveDocument {
|
TriggerSaveDocument {
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
name: String,
|
name: String,
|
||||||
|
|||||||
@@ -44,16 +44,18 @@ pub struct TextLabel {
|
|||||||
|
|
||||||
pub italic: bool,
|
pub italic: bool,
|
||||||
|
|
||||||
|
pub monospace: bool,
|
||||||
|
|
||||||
|
pub multiline: bool,
|
||||||
|
|
||||||
#[serde(rename = "centerAlign")]
|
#[serde(rename = "centerAlign")]
|
||||||
pub center_align: bool,
|
pub center_align: bool,
|
||||||
|
|
||||||
#[serde(rename = "tableAlign")]
|
#[serde(rename = "tableAlign")]
|
||||||
pub table_align: bool,
|
pub table_align: bool,
|
||||||
|
|
||||||
pub multiline: bool,
|
|
||||||
|
|
||||||
#[serde(rename = "minWidth")]
|
#[serde(rename = "minWidth")]
|
||||||
pub min_width: u32,
|
pub min_width: String,
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
|||||||
@@ -1021,6 +1021,8 @@ impl OverlayContextInternal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Load Source Sans Pro font data
|
// Load Source Sans Pro font data
|
||||||
|
// TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo.
|
||||||
|
// TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size.
|
||||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||||
let font_blob = Some(load_font(FONT_DATA));
|
let font_blob = Some(load_font(FONT_DATA));
|
||||||
|
|
||||||
@@ -1046,6 +1048,8 @@ impl OverlayContextInternal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Load Source Sans Pro font data
|
// Load Source Sans Pro font data
|
||||||
|
// TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo.
|
||||||
|
// TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size.
|
||||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||||
let font_blob = Some(load_font(FONT_DATA));
|
let font_blob = Some(load_font(FONT_DATA));
|
||||||
|
|
||||||
|
|||||||
Generated
+28
-29
@@ -7,11 +7,11 @@
|
|||||||
"name": "graphite-web-frontend",
|
"name": "graphite-web-frontend",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/inconsolata": "^5.2.5",
|
|
||||||
"@fontsource/source-sans-pro": "^5.2.5",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"reflect-metadata": "^0.2.2"
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"source-code-pro": "github:adobe-fonts/source-code-pro#2.042R-u/1.062R-i/1.026R-vf",
|
||||||
|
"source-sans": "github:adobe-fonts/source-sans#2.045R-ro%2F1.095R-it"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-svelte": "^3.2.6",
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"rollup-plugin-license": "^3.5.3",
|
"rollup-plugin-license": "^3.6.0",
|
||||||
"sass": "1.78.0",
|
"sass": "1.78.0",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"svelte-preprocess": "^6.0.2",
|
"svelte-preprocess": "^6.0.2",
|
||||||
@@ -556,21 +556,6 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/inconsolata": {
|
|
||||||
"version": "5.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.5.tgz",
|
|
||||||
"integrity": "sha512-OvzkZY5qYghv/jEV6cfGZzFhdFTvSnU+ExPC7WcZ7w8PdRhtiu/SpcBWOBt+3LXgS0n9qyepgq4zZmxlDTlGGQ==",
|
|
||||||
"license": "OFL-1.1",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fontsource/source-sans-pro": {
|
|
||||||
"version": "5.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-5.2.5.tgz",
|
|
||||||
"integrity": "sha512-ypendqc4pYUc+EgF7qqPY9iVYEz1t/Qr03VojKxG/2g3dnpHa1B6DOlDxWQjQXDj5QrG6inEqGT0g+edjALZyg==",
|
|
||||||
"license": "OFL-1.1"
|
|
||||||
},
|
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
@@ -4622,14 +4607,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup-plugin-license": {
|
"node_modules/rollup-plugin-license": {
|
||||||
"version": "3.5.3",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-3.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-3.6.0.tgz",
|
||||||
"integrity": "sha512-r3wImZSo2d6sEk9BRJtlzeI/upjyjnpthy06Fdl0EzqRrlg3ULb9KQR7xHJI0zuayW/8bchEXSF5dO6dha4OyA==",
|
"integrity": "sha512-1ieLxTCaigI5xokIfszVDRoy6c/Wmlot1fDEnea7Q/WXSR8AqOjYljHDLObAx7nFxHC2mbxT3QnTSPhaic2IYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commenting": "~1.1.0",
|
"commenting": "~1.1.0",
|
||||||
"fdir": "6.3.0",
|
"fdir": "^6.4.3",
|
||||||
"lodash": "~4.17.21",
|
"lodash": "~4.17.21",
|
||||||
"magic-string": "~0.30.0",
|
"magic-string": "~0.30.0",
|
||||||
"moment": "~2.30.1",
|
"moment": "~2.30.1",
|
||||||
@@ -4645,11 +4630,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup-plugin-license/node_modules/fdir": {
|
"node_modules/rollup-plugin-license/node_modules/fdir": {
|
||||||
"version": "6.3.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
"integrity": "sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==",
|
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"picomatch": "^3 || ^4"
|
"picomatch": "^3 || ^4"
|
||||||
},
|
},
|
||||||
@@ -4660,9 +4648,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup-plugin-license/node_modules/picomatch": {
|
"node_modules/rollup-plugin-license/node_modules/picomatch": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -4864,6 +4852,11 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-code-pro": {
|
||||||
|
"version": "2.38.0",
|
||||||
|
"resolved": "git+ssh://git@github.com/adobe-fonts/source-code-pro.git#d3f1a5962cde503f9409c21e58527611d4a19ef1",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -4874,6 +4867,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-sans": {
|
||||||
|
"name": "source-sans-pro",
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "git+ssh://git@github.com/adobe-fonts/source-sans.git#ce77773581f4d454f0fa985c073bb25c721bfcf5",
|
||||||
|
"license": "SIL Open Font License 1.1"
|
||||||
|
},
|
||||||
"node_modules/spdx-compare": {
|
"node_modules/spdx-compare": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz",
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
"wasm:watch-production": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --release --target=web -- --color=always\""
|
"wasm:watch-production": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --release --target=web -- --color=always\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/inconsolata": "^5.2.5",
|
|
||||||
"@fontsource/source-sans-pro": "^5.2.5",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"reflect-metadata": "^0.2.2"
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"source-code-pro": "github:adobe-fonts/source-code-pro#2.042R-u/1.062R-i/1.026R-vf",
|
||||||
|
"source-sans": "github:adobe-fonts/source-sans#2.045R-ro%2F1.095R-it"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-svelte": "^3.2.6",
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"rollup-plugin-license": "^3.5.3",
|
"rollup-plugin-license": "^3.6.0",
|
||||||
"sass": "1.78.0",
|
"sass": "1.78.0",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"svelte-preprocess": "^6.0.2",
|
"svelte-preprocess": "^6.0.2",
|
||||||
|
|||||||
@@ -352,4 +352,44 @@
|
|||||||
:not(.optional-input) > .checkbox-input input:focus-visible + label.checked {
|
:not(.optional-input) > .checkbox-input input:focus-visible + label.checked {
|
||||||
outline: 1px dashed var(--color-2-mildblack);
|
outline: 1px dashed var(--color-2-mildblack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url("@graphite/../node_modules/source-sans/WOFF2/TTF/SourceSansPro-Regular.ttf.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url("@graphite/../node_modules/source-sans/WOFF2/TTF/SourceSansPro-It.ttf.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url("@graphite/../node_modules/source-sans/WOFF2/TTF/SourceSansPro-Bold.ttf.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: italic;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url("@graphite/../node_modules/source-sans/WOFF2/TTF/SourceSansPro-BoldIt.ttf.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Code Pro";
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url("@graphite/../node_modules/source-code-pro/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2") format("woff2");
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
.header-area,
|
.header-area,
|
||||||
.footer-area {
|
.footer-area {
|
||||||
background: var(--color-1-nearblack);
|
background: var(--color-1-nearblack);
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-area,
|
.header-area,
|
||||||
@@ -113,6 +114,8 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
margin: -4px 0;
|
margin: -4px 0;
|
||||||
|
padding-right: calc(24px + 1px * var(--even-integer-subpixel-expansion-x));
|
||||||
|
padding-bottom: calc(16px + 1px * var(--even-integer-subpixel-expansion-y));
|
||||||
|
|
||||||
&.center .row {
|
&.center .row {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -126,18 +129,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.details.text-label {
|
|
||||||
-webkit-user-select: text; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
|
|
||||||
user-select: text;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
max-width: 400px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-input button {
|
.radio-input button {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-label.multiline {
|
||||||
|
-webkit-user-select: text; // Still required by Safari as of 2025
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the "Third-Party Software License Notices" dialog
|
||||||
|
.details:has(.text-label.multiline.monospace) {
|
||||||
|
max-height: 60vh;
|
||||||
|
max-width: 80vw;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
// Used by the "Open Demo Artwork" dialog
|
// Used by the "Open Demo Artwork" dialog
|
||||||
.image-label {
|
.image-label {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|||||||
@@ -137,20 +137,23 @@
|
|||||||
// This solves antialiasing issues when the content isn't cleanly divisible by 2 and gets translated by (-50%, -50%) causing all its content to be blurry.
|
// This solves antialiasing issues when the content isn't cleanly divisible by 2 and gets translated by (-50%, -50%) causing all its content to be blurry.
|
||||||
const floatingMenuContentDiv = floatingMenuContent?.div?.();
|
const floatingMenuContentDiv = floatingMenuContent?.div?.();
|
||||||
if (type === "Dialog" && floatingMenuContentDiv) {
|
if (type === "Dialog" && floatingMenuContentDiv) {
|
||||||
// TODO: Also use https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver to detect any changes which may affect the size of the content.
|
|
||||||
// TODO: The current method only notices when the dialog size increases but can't detect when it decreases.
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
let { width, height } = entry.contentRect;
|
const existingWidth = Number(floatingMenuContentDiv.style.getPropertyValue("--even-integer-subpixel-expansion-x"));
|
||||||
|
const existingHeight = Number(floatingMenuContentDiv.style.getPropertyValue("--even-integer-subpixel-expansion-y"));
|
||||||
|
|
||||||
width = Math.ceil(width);
|
let { width, height } = entry.contentRect;
|
||||||
if (width % 2 === 1) width += 1;
|
width -= existingWidth;
|
||||||
height = Math.ceil(height);
|
height -= existingHeight;
|
||||||
if (height % 2 === 1) height += 1;
|
|
||||||
|
let targetWidth = Math.ceil(width);
|
||||||
|
if (targetWidth % 2 === 1) targetWidth += 1;
|
||||||
|
let targetHeight = Math.ceil(height);
|
||||||
|
if (targetHeight % 2 === 1) targetHeight += 1;
|
||||||
|
|
||||||
// We have to set the style properties directly because attempting to do it through a Svelte bound property results in `afterUpdate()` being triggered
|
// We have to set the style properties directly because attempting to do it through a Svelte bound property results in `afterUpdate()` being triggered
|
||||||
floatingMenuContentDiv.style.setProperty("min-width", width === 0 ? "unset" : `${width}px`);
|
floatingMenuContentDiv.style.setProperty("--even-integer-subpixel-expansion-x", `${targetWidth - width}`);
|
||||||
floatingMenuContentDiv.style.setProperty("min-height", height === 0 ? "unset" : `${height}px`);
|
floatingMenuContentDiv.style.setProperty("--even-integer-subpixel-expansion-y", `${targetHeight - height}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
resizeObserver.observe(floatingMenuContentDiv);
|
resizeObserver.observe(floatingMenuContentDiv);
|
||||||
@@ -158,14 +161,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterUpdate(() => {
|
afterUpdate(() => {
|
||||||
// Remove the size constraint after the content updates so the resize observer can measure the content and reapply a newly calculated one
|
|
||||||
const floatingMenuContentDiv = floatingMenuContent?.div?.();
|
|
||||||
if (type === "Dialog" && floatingMenuContentDiv) {
|
|
||||||
// We have to set the style properties directly because attempting to do it through a Svelte bound property results in `afterUpdate()` being triggered
|
|
||||||
floatingMenuContentDiv.style.setProperty("min-width", "unset");
|
|
||||||
floatingMenuContentDiv.style.setProperty("min-height", "unset");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the client bounds of the elements and apply relevant styles to them.
|
// Gets the client bounds of the elements and apply relevant styles to them.
|
||||||
// TODO: Use DOM attribute bindings more whilst not causing recursive updates. Turning measuring on and off both causes the component to change,
|
// TODO: Use DOM attribute bindings more whilst not causing recursive updates. Turning measuring on and off both causes the component to change,
|
||||||
// TODO: which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop).
|
// TODO: which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop).
|
||||||
|
|||||||
@@ -770,7 +770,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
-webkit-user-select: none; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
|
-webkit-user-select: none; // Still required by Safari as of 2025
|
||||||
user-select: none;
|
user-select: none;
|
||||||
// Workaround for `user-select: none` not working on <input> elements
|
// Workaround for `user-select: none` not working on <input> elements
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let bold = false;
|
export let bold = false;
|
||||||
export let italic = false;
|
export let italic = false;
|
||||||
|
export let monospace = false;
|
||||||
export let centerAlign = false;
|
export let centerAlign = false;
|
||||||
export let tableAlign = false;
|
export let tableAlign = false;
|
||||||
export let minWidth = 0;
|
export let minWidth = "";
|
||||||
export let multiline = false;
|
export let multiline = false;
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
export let forCheckbox: bigint | undefined = undefined;
|
export let forCheckbox: bigint | undefined = undefined;
|
||||||
@@ -28,10 +29,11 @@
|
|||||||
class:disabled
|
class:disabled
|
||||||
class:bold
|
class:bold
|
||||||
class:italic
|
class:italic
|
||||||
|
class:monospace
|
||||||
class:multiline
|
class:multiline
|
||||||
class:center-align={centerAlign}
|
class:center-align={centerAlign}
|
||||||
class:table-align={tableAlign}
|
class:table-align={tableAlign}
|
||||||
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
|
style:min-width={minWidth || undefined}
|
||||||
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
|
for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
|
||||||
@@ -58,6 +60,11 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.monospace {
|
||||||
|
font-family: "Source Code Pro", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
&.multiline {
|
&.multiline {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
|
|||||||
@@ -183,7 +183,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: "Inconsolata", monospace;
|
font-family: "Source Code Pro", monospace;
|
||||||
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
// This file is the browser's entry point for the JS bundle
|
// This file is the browser's entry point for the JS bundle
|
||||||
|
|
||||||
// Fonts
|
|
||||||
import "@fontsource/inconsolata";
|
|
||||||
import "@fontsource/source-sans-pro/400-italic.css";
|
|
||||||
import "@fontsource/source-sans-pro/400.css";
|
|
||||||
import "@fontsource/source-sans-pro/700-italic.css";
|
|
||||||
import "@fontsource/source-sans-pro/700.css";
|
|
||||||
|
|
||||||
// `reflect-metadata` allows for runtime reflection of types in JavaScript.
|
// `reflect-metadata` allows for runtime reflection of types in JavaScript.
|
||||||
// It is needed for class-transformer to work and is imported as a side effect.
|
// It is needed for class-transformer to work and is imported as a side effect.
|
||||||
// The library replaces the Reflect API on the window to support more features.
|
// The library replaces the Reflect API on the window to support more features.
|
||||||
|
|||||||
@@ -946,6 +946,8 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
|
|||||||
readonly commitDate!: string;
|
readonly commitDate!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TriggerDisplayThirdPartyLicensesDialog extends JsMessage {}
|
||||||
|
|
||||||
// WIDGET PROPS
|
// WIDGET PROPS
|
||||||
|
|
||||||
export abstract class WidgetProps {
|
export abstract class WidgetProps {
|
||||||
@@ -1370,13 +1372,15 @@ export class TextLabel extends WidgetProps {
|
|||||||
|
|
||||||
italic!: boolean;
|
italic!: boolean;
|
||||||
|
|
||||||
|
monospace!: boolean;
|
||||||
|
|
||||||
|
multiline!: boolean;
|
||||||
|
|
||||||
centerAlign!: boolean;
|
centerAlign!: boolean;
|
||||||
|
|
||||||
tableAlign!: boolean;
|
tableAlign!: boolean;
|
||||||
|
|
||||||
minWidth!: number;
|
minWidth!: string;
|
||||||
|
|
||||||
multiline!: boolean;
|
|
||||||
|
|
||||||
@Transform(({ value }: { value: string }) => value || undefined)
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
tooltip!: string | undefined;
|
tooltip!: string | undefined;
|
||||||
@@ -1692,6 +1696,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
DisplayRemoveEditableTextbox,
|
DisplayRemoveEditableTextbox,
|
||||||
SendUIMetadata,
|
SendUIMetadata,
|
||||||
TriggerAboutGraphiteLocalizedCommitDate,
|
TriggerAboutGraphiteLocalizedCommitDate,
|
||||||
|
TriggerDisplayThirdPartyLicensesDialog,
|
||||||
TriggerSaveDocument,
|
TriggerSaveDocument,
|
||||||
TriggerSaveFile,
|
TriggerSaveFile,
|
||||||
TriggerExportImage,
|
TriggerExportImage,
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { type Editor } from "@graphite/editor";
|
import { type Editor } from "@graphite/editor";
|
||||||
import { defaultWidgetLayout, DisplayDialog, DisplayDialogDismiss, UpdateDialogButtons, UpdateDialogColumn1, UpdateDialogColumn2, patchWidgetLayout } from "@graphite/messages";
|
import {
|
||||||
|
defaultWidgetLayout,
|
||||||
|
DisplayDialog,
|
||||||
|
DisplayDialogDismiss,
|
||||||
|
UpdateDialogButtons,
|
||||||
|
UpdateDialogColumn1,
|
||||||
|
UpdateDialogColumn2,
|
||||||
|
patchWidgetLayout,
|
||||||
|
TriggerDisplayThirdPartyLicensesDialog,
|
||||||
|
} from "@graphite/messages";
|
||||||
import { type IconName } from "@graphite/utility-functions/icons";
|
import { type IconName } from "@graphite/utility-functions/icons";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
@@ -78,6 +87,17 @@ export function createDialogState(editor: Editor) {
|
|||||||
});
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(DisplayDialogDismiss, dismissDialog);
|
editor.subscriptions.subscribeJsMessage(DisplayDialogDismiss, dismissDialog);
|
||||||
|
|
||||||
|
editor.subscriptions.subscribeJsMessage(TriggerDisplayThirdPartyLicensesDialog, async () => {
|
||||||
|
const BACKUP_URL = "https://editor.graphite.rs/third-party-licenses.txt";
|
||||||
|
let licenseText = `Content was not able to load. Please check your network connection and try again.\n\nOr visit ${BACKUP_URL} for the license notices.`;
|
||||||
|
if (editor.handle.inDevelopmentMode()) licenseText = `Third-party licenses are not available in development builds.\n\nVisit ${BACKUP_URL} for the license notices.`;
|
||||||
|
|
||||||
|
const response = await fetch("/third-party-licenses.txt");
|
||||||
|
if (response.ok && response.headers.get("Content-Type")?.includes("text/plain")) licenseText = await response.text();
|
||||||
|
|
||||||
|
editor.handle.requestLicensesThirdPartyDialogWithLicenseText(licenseText);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
dismissDialog,
|
dismissDialog,
|
||||||
|
|||||||
+83
-38
@@ -13,34 +13,19 @@ import { DynamicPublicDirectory as viteMultipleAssets } from "vite-multiple-asse
|
|||||||
|
|
||||||
const projectRootDir = path.resolve(__dirname);
|
const projectRootDir = path.resolve(__dirname);
|
||||||
|
|
||||||
// Keep this list in sync with those in `/about.toml` and `/deny.toml`.
|
|
||||||
const ALLOWED_LICENSES = [
|
|
||||||
"Apache-2.0 WITH LLVM-exception",
|
|
||||||
"Apache-2.0",
|
|
||||||
"BSD-2-Clause",
|
|
||||||
"BSD-3-Clause",
|
|
||||||
"BSL-1.0",
|
|
||||||
"CC0-1.0",
|
|
||||||
"CDLA-Permissive-2.0",
|
|
||||||
"ISC",
|
|
||||||
"MIT-0",
|
|
||||||
"MIT",
|
|
||||||
"MPL-2.0",
|
|
||||||
"OpenSSL",
|
|
||||||
"Unicode-3.0",
|
|
||||||
"Unicode-DFS-2016",
|
|
||||||
"Zlib",
|
|
||||||
"NCSA",
|
|
||||||
];
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte({
|
svelte({
|
||||||
preprocess: [sveltePreprocess()],
|
preprocess: [sveltePreprocess()],
|
||||||
onwarn(warning, defaultHandler) {
|
onwarn(warning, defaultHandler) {
|
||||||
// NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
|
const suppressed = [
|
||||||
const suppressed = ["css-unused-selector", "vite-plugin-svelte-css-no-scopable-elements", "a11y-no-static-element-interactions", "a11y-no-noninteractive-element-interactions"];
|
"css-unused-selector", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
|
||||||
|
"vite-plugin-svelte-css-no-scopable-elements", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
|
||||||
|
"a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
|
||||||
|
"a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
|
||||||
|
"a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
|
||||||
|
];
|
||||||
if (suppressed.includes(warning.code)) return;
|
if (suppressed.includes(warning.code)) return;
|
||||||
|
|
||||||
defaultHandler?.(warning);
|
defaultHandler?.(warning);
|
||||||
@@ -66,8 +51,10 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
rollupPluginLicense({
|
rollupPluginLicense({
|
||||||
thirdParty: {
|
thirdParty: {
|
||||||
|
includePrivate: false,
|
||||||
|
multipleVersions: true,
|
||||||
allow: {
|
allow: {
|
||||||
test: `(${ALLOWED_LICENSES.join(" OR ")})`,
|
test: `(${getAcceptedLicenses()})`,
|
||||||
failOnUnlicensed: true,
|
failOnUnlicensed: true,
|
||||||
failOnViolation: true,
|
failOnViolation: true,
|
||||||
},
|
},
|
||||||
@@ -103,10 +90,11 @@ type PackageInfo = {
|
|||||||
|
|
||||||
function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
||||||
// Generate the Rust license information.
|
// Generate the Rust license information.
|
||||||
let licenses = generateRustLicenses() || [];
|
const rustLicenses = generateRustLicenses();
|
||||||
|
const additionalLicenses = generateAdditionalLicenses();
|
||||||
|
|
||||||
// Ensure we have license information to work with before proceeding.
|
// Ensure we have the required license information to work with before proceeding.
|
||||||
if (licenses.length === 0) {
|
if (rustLicenses.length === 0) {
|
||||||
// This is probably caused by `cargo about` not being installed.
|
// This is probably caused by `cargo about` not being installed.
|
||||||
console.error("Could not run `cargo about`, which is required to generate license information.");
|
console.error("Could not run `cargo about`, which is required to generate license information.");
|
||||||
console.error("To install cargo-about on your system, you can run `cargo install cargo-about`.");
|
console.error("To install cargo-about on your system, you can run `cargo install cargo-about`.");
|
||||||
@@ -121,9 +109,11 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find then duplicate this license if one of its packages is `path-bool`, adding its notice text.
|
let licenses = rustLicenses.concat(additionalLicenses);
|
||||||
let foundLicensesIndex;
|
|
||||||
let foundPackagesIndex;
|
// SPECIAL CASE: Find then duplicate this license if one of its packages is `path-bool`, adding its notice text.
|
||||||
|
let foundLicensesIndex: number | undefined = undefined;
|
||||||
|
let foundPackagesIndex: number | undefined = undefined;
|
||||||
licenses.forEach((license, licenseIndex) => {
|
licenses.forEach((license, licenseIndex) => {
|
||||||
license.packages.forEach((pkg, pkgIndex) => {
|
license.packages.forEach((pkg, pkgIndex) => {
|
||||||
if (pkg.name === "path-bool") {
|
if (pkg.name === "path-bool") {
|
||||||
@@ -147,7 +137,7 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Augment the imported Rust license list with the provided JS license list.
|
// Extend the license list with the provided JS licenses.
|
||||||
jsLicenses.forEach((jsLicense) => {
|
jsLicenses.forEach((jsLicense) => {
|
||||||
const name = jsLicense.name || "";
|
const name = jsLicense.name || "";
|
||||||
const version = jsLicense.version || "";
|
const version = jsLicense.version || "";
|
||||||
@@ -158,14 +148,12 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||||||
|
|
||||||
let repository = jsLicense.repository || "";
|
let repository = jsLicense.repository || "";
|
||||||
if (repository && typeof repository === "object") repository = repository.url;
|
if (repository && typeof repository === "object") repository = repository.url;
|
||||||
// Remove the `git+` or `git://` prefix and `.git` suffix.
|
|
||||||
const repo = repository ? repository.replace(/^.*(github.com\/.*?\/.*?)(?:.git)/, "https://$1") : repository;
|
|
||||||
|
|
||||||
const matchedLicense = licenses.find(
|
const matchedLicense = licenses.find(
|
||||||
(license) => license.licenseName === licenseName && trimBlankLines(license.licenseText || "") === licenseText && trimBlankLines(license.noticeText || "") === noticeText,
|
(license) => license.licenseName === licenseName && trimBlankLines(license.licenseText || "") === licenseText && trimBlankLines(license.noticeText || "") === noticeText,
|
||||||
);
|
);
|
||||||
|
|
||||||
const pkg: PackageInfo = { name, version, author, repository: repo };
|
const pkg: PackageInfo = { name, version, author, repository };
|
||||||
if (matchedLicense) matchedLicense.packages.push(pkg);
|
if (matchedLicense) matchedLicense.packages.push(pkg);
|
||||||
else licenses.push({ licenseName, licenseText, noticeText, packages: [pkg] });
|
else licenses.push({ licenseName, licenseText, noticeText, packages: [pkg] });
|
||||||
});
|
});
|
||||||
@@ -232,7 +220,15 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||||||
licenses.forEach((license) => {
|
licenses.forEach((license) => {
|
||||||
let packagesWithSameLicense = license.packages.map((packageInfo) => {
|
let packagesWithSameLicense = license.packages.map((packageInfo) => {
|
||||||
const { name, version, author, repository } = packageInfo;
|
const { name, version, author, repository } = packageInfo;
|
||||||
return `${name} ${version}${author ? ` - ${author}` : ""}${repository ? ` - ${repository}` : ""}`;
|
|
||||||
|
// Remove the `git+` or `git://` prefix and `.git` suffix.
|
||||||
|
let repo = repository;
|
||||||
|
if (repo.startsWith("git+")) repo = repo.slice("git+".length);
|
||||||
|
if (repo.startsWith("git://")) repo = repo.slice("git://".length);
|
||||||
|
if (repo.endsWith(".git")) repo = repo.slice(0, -".git".length);
|
||||||
|
if (repo.endsWith(".git#release")) repo = repo.slice(0, -".git#release".length);
|
||||||
|
|
||||||
|
return `${name} ${version}${author ? ` - ${author}` : ""}${repo ? ` - ${repo}` : ""}`;
|
||||||
});
|
});
|
||||||
const multi = packagesWithSameLicense.length !== 1;
|
const multi = packagesWithSameLicense.length !== 1;
|
||||||
const saysLicense = license.licenseName.toLowerCase().includes("license");
|
const saysLicense = license.licenseName.toLowerCase().includes("license");
|
||||||
@@ -249,10 +245,44 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||||||
formattedLicenseNotice += ` ${"‾".repeat(packagesLineLength + 2)}\n`;
|
formattedLicenseNotice += ` ${"‾".repeat(packagesLineLength + 2)}\n`;
|
||||||
formattedLicenseNotice += `${license.licenseText}\n`;
|
formattedLicenseNotice += `${license.licenseText}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
formattedLicenseNotice += "\n";
|
||||||
return formattedLicenseNotice;
|
return formattedLicenseNotice;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRustLicenses(): LicenseInfo[] | undefined {
|
// Include additional licenses that aren't automatically generated by `cargo about` or `rollup-plugin-license`.
|
||||||
|
function generateAdditionalLicenses(): LicenseInfo[] {
|
||||||
|
const ADDITIONAL_LICENSES = [
|
||||||
|
{
|
||||||
|
licenseName: "SIL Open Font License 1.1",
|
||||||
|
licenseTextPath: "node_modules/source-sans/LICENSE.txt",
|
||||||
|
manifestPath: "node_modules/source-sans/package.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
licenseName: "SIL Open Font License 1.1",
|
||||||
|
licenseTextPath: "node_modules/source-code-pro/LICENSE.md",
|
||||||
|
manifestPath: "node_modules/source-code-pro/package.json",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return ADDITIONAL_LICENSES.map(({ licenseName, licenseTextPath, manifestPath }) => {
|
||||||
|
const licenseText = (fs.existsSync(licenseTextPath) && fs.readFileSync(licenseTextPath, "utf8")) || "";
|
||||||
|
|
||||||
|
const manifestJSON = (fs.existsSync(manifestPath) && JSON.parse(fs.readFileSync(manifestPath, "utf8"))) || {};
|
||||||
|
const name = manifestJSON.name || "";
|
||||||
|
const version = manifestJSON.version || "";
|
||||||
|
const author = manifestJSON.author.name || manifestJSON.author || "";
|
||||||
|
const repository = manifestJSON.repository?.url || "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
licenseName,
|
||||||
|
licenseText: trimBlankLines(licenseText),
|
||||||
|
packages: [{ name, version, author, repository }],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRustLicenses(): LicenseInfo[] {
|
||||||
// Log the starting status to the build output.
|
// Log the starting status to the build output.
|
||||||
console.info("\n\nGenerating license information for Rust code\n");
|
console.info("\n\nGenerating license information for Rust code\n");
|
||||||
|
|
||||||
@@ -272,14 +302,14 @@ function generateRustLicenses(): LicenseInfo[] | undefined {
|
|||||||
if (status !== 101) {
|
if (status !== 101) {
|
||||||
console.error("cargo-about failed", status, stderr);
|
console.error("cargo-about failed", status, stderr);
|
||||||
}
|
}
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the output starts with this expected label, which lets us know the file generated with expected output.
|
// Make sure the output starts with this expected label, which lets us know the file generated with expected output.
|
||||||
// We don't want to eval an error message or something else, so we fail early if that happens.
|
// We don't want to eval an error message or something else, so we fail early if that happens.
|
||||||
if (!stdout.trim().startsWith("GENERATED_BY_CARGO_ABOUT:")) {
|
if (!stdout.trim().startsWith("GENERATED_BY_CARGO_ABOUT:")) {
|
||||||
console.error("Unexpected output from cargo-about", stdout);
|
console.error("Unexpected output from cargo-about", stdout);
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the array JS syntax string into an actual JS array in memory.
|
// Convert the array JS syntax string into an actual JS array in memory.
|
||||||
@@ -308,7 +338,7 @@ function generateRustLicenses(): LicenseInfo[] | undefined {
|
|||||||
|
|
||||||
return rustLicenses;
|
return rustLicenses;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,3 +385,18 @@ function trimBlankLines(input: string): string {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAcceptedLicenses() {
|
||||||
|
const tomlContent = fs.readFileSync(path.resolve(__dirname, "../about.toml"), "utf8");
|
||||||
|
|
||||||
|
const licensesBlock = tomlContent?.match(/accepted\s*=\s*\[([^\]]*)\]/)?.[1] || "";
|
||||||
|
|
||||||
|
return licensesBlock
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.replace(/#.*$/, "")) // Remove comments
|
||||||
|
.join("\n")
|
||||||
|
.split(",")
|
||||||
|
.map((license) => license.trim().replace(/"/g, ""))
|
||||||
|
.filter((license) => license.length > 0)
|
||||||
|
.join(" OR ");
|
||||||
|
}
|
||||||
|
|||||||
@@ -466,6 +466,12 @@ impl EditorHandle {
|
|||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = requestLicensesThirdPartyDialogWithLicenseText)]
|
||||||
|
pub fn request_licenses_third_party_dialog_with_license_text(&self, license_text: String) {
|
||||||
|
let message = DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text };
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
/// Send new bounds when document panel viewports get resized or moved within the editor
|
/// Send new bounds when document panel viewports get resized or moved within the editor
|
||||||
/// [left, top, right, bottom]...
|
/// [left, top, right, bottom]...
|
||||||
#[wasm_bindgen(js_name = boundsOfViewports)]
|
#[wasm_bindgen(js_name = boundsOfViewports)]
|
||||||
|
|||||||
Reference in New Issue
Block a user