mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-31 10:17:21 +00:00
Add demo artwork
This commit is contained in:
parent
0e97a256b7
commit
0dcfafbf64
33 changed files with 2133 additions and 1050 deletions
1
demo-artwork/just-a-potted-cactus.graphite
Normal file
1
demo-artwork/just-a-potted-cactus.graphite
Normal file
File diff suppressed because one or more lines are too long
1
demo-artwork/valley-of-spires.graphite
Normal file
1
demo-artwork/valley-of-spires.graphite
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -79,7 +79,7 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
|
|||
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
|
||||
|
||||
// Document
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.17"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.17"; // Remember to update the demo artwork in /demos with both this version number and the contents so it remains editable
|
||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
||||
|
|
|
@ -555,7 +555,8 @@ mod test {
|
|||
println!("-------------------------------------------------");
|
||||
println!("Failed test due to receiving a DisplayDialogError while loading the Graphite sample file.");
|
||||
println!("This is most likely caused by forgetting to bump the `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`");
|
||||
println!("After bumping this version number, replace `graphite-test-document.graphite` with a valid file by saving a document from the editor.");
|
||||
println!("After bumping this version number, update the documents in `/demo-artwork` by editing their JSON to");
|
||||
println!("ensure they remain compatible with both the bumped version number and the serialization format change.");
|
||||
println!("DisplayDialogError details:");
|
||||
println!();
|
||||
println!("Description: {}", value);
|
||||
|
@ -567,19 +568,25 @@ mod test {
|
|||
init_logger();
|
||||
let mut editor = Editor::create();
|
||||
|
||||
let test_file = include_str!("../graphite-test-document.graphite");
|
||||
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||
document_name: "Graphite Version Test".into(),
|
||||
document_serialized_content: test_file.into(),
|
||||
});
|
||||
let test_files = [
|
||||
("Just a Potted Cactus", include_str!("../../demo-artwork/just-a-potted-cactus.graphite")),
|
||||
("Valley of Spires", include_str!("../../demo-artwork/valley-of-spires.graphite")),
|
||||
];
|
||||
|
||||
for response in responses {
|
||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||
if let FrontendMessage::UpdateDialogDetails { layout_target: _, diff } = response {
|
||||
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
|
||||
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
|
||||
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
|
||||
print_problem_to_terminal_on_failure(value);
|
||||
for (document_name, document_serialized_content) in test_files {
|
||||
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||
document_name: document_name.into(),
|
||||
document_serialized_content: document_serialized_content.into(),
|
||||
});
|
||||
|
||||
for response in responses {
|
||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||
if let FrontendMessage::UpdateDialogDetails { layout_target: _, diff } = response {
|
||||
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
|
||||
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
|
||||
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
|
||||
print_problem_to_terminal_on_failure(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ pub enum DialogMessage {
|
|||
RequestComingSoonDialog {
|
||||
issue: Option<i32>,
|
||||
},
|
||||
RequestDemoArtworkDialog,
|
||||
RequestExportDialog,
|
||||
RequestNewDocumentDialog,
|
||||
RequestPreferencesDialog,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog};
|
||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DialogMessageHandler {
|
||||
export_dialog: ExportDialogMessageHandler,
|
||||
|
@ -53,6 +54,11 @@ impl MessageHandler<DialogMessage, (&PortfolioMessageHandler, &PreferencesMessag
|
|||
coming_soon.send_layout(responses, LayoutTarget::DialogDetails);
|
||||
responses.add(FrontendMessage::DisplayDialog { icon: "Warning".to_string() });
|
||||
}
|
||||
DialogMessage::RequestDemoArtworkDialog => {
|
||||
let demo_artwork_dialog = DemoArtworkDialog;
|
||||
demo_artwork_dialog.send_layout(responses, LayoutTarget::DialogDetails);
|
||||
responses.add(FrontendMessage::DisplayDialog { icon: "Image".to_string() });
|
||||
}
|
||||
DialogMessage::RequestExportDialog => {
|
||||
if let Some(document) = portfolio.active_document() {
|
||||
let artboard_handler = &document.artboard_message_handler;
|
||||
|
|
|
@ -50,7 +50,7 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
|||
|
||||
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
fn layout(&self) -> Layout {
|
||||
let title = vec![TextLabel::new("New document").bold(true).widget_holder()];
|
||||
let title = vec![TextLabel::new("New Document").bold(true).widget_holder()];
|
||||
|
||||
let name = vec![
|
||||
TextLabel::new("Name").table_align(true).widget_holder(),
|
||||
|
|
|
@ -8,6 +8,7 @@ impl LayoutHolder for CloseAllDocumentsDialog {
|
|||
fn layout(&self) -> Layout {
|
||||
let discard = TextButton::new("Discard All")
|
||||
.min_width(96)
|
||||
.emphasized(true)
|
||||
.on_update(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![PortfolioMessage::CloseAllDocuments.into()],
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// A dialog to let the user browse a gallery of demo artwork that can be opened.
|
||||
pub struct DemoArtworkDialog;
|
||||
|
||||
impl LayoutHolder for DemoArtworkDialog {
|
||||
fn layout(&self) -> Layout {
|
||||
let artwork = [
|
||||
(
|
||||
"Valley of Spires",
|
||||
"ThumbnailValleyOfSpires",
|
||||
"https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/demo-artwork/valley-of-spires.graphite",
|
||||
),
|
||||
(
|
||||
"Just a Potted Cactus",
|
||||
"ThumbnailJustAPottedCactus",
|
||||
"https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/demo-artwork/just-a-potted-cactus.graphite",
|
||||
),
|
||||
];
|
||||
|
||||
let image_widgets = artwork
|
||||
.into_iter()
|
||||
.map(|(_, thumbnail, _)| ImageLabel::new(thumbnail.to_string()).width(Some("256px".into())).widget_holder())
|
||||
.collect();
|
||||
|
||||
let button_widgets = artwork
|
||||
.into_iter()
|
||||
.map(|(label, _, url)| {
|
||||
TextButton::new(label)
|
||||
.min_width(256)
|
||||
.on_update(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![FrontendMessage::TriggerFetchAndOpenDocument { url: url.to_string() }.into()],
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder()
|
||||
})
|
||||
.collect();
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![TextLabel::new("Demo Artwork".to_string()).bold(true).widget_holder()],
|
||||
},
|
||||
LayoutGroup::Row { widgets: image_widgets },
|
||||
LayoutGroup::Row { widgets: button_widgets },
|
||||
]))
|
||||
}
|
||||
}
|
|
@ -2,10 +2,12 @@ mod about_graphite_dialog;
|
|||
mod close_all_documents_dialog;
|
||||
mod close_document_dialog;
|
||||
mod coming_soon_dialog;
|
||||
mod demo_artwork_dialog;
|
||||
mod error_dialog;
|
||||
|
||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||
pub use close_document_dialog::CloseDocumentDialog;
|
||||
pub use coming_soon_dialog::ComingSoonDialog;
|
||||
pub use demo_artwork_dialog::DemoArtworkDialog;
|
||||
pub use error_dialog::ErrorDialog;
|
||||
|
|
|
@ -67,6 +67,9 @@ pub enum FrontendMessage {
|
|||
document: String,
|
||||
name: String,
|
||||
},
|
||||
TriggerFetchAndOpenDocument {
|
||||
url: String,
|
||||
},
|
||||
TriggerFontLoad {
|
||||
font: Font,
|
||||
#[serde(rename = "isDefault")]
|
||||
|
|
|
@ -158,6 +158,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
|||
responses.add(callback_message);
|
||||
}
|
||||
Widget::IconLabel(_) => {}
|
||||
Widget::ImageLabel(_) => {}
|
||||
Widget::InvisibleStandinInput(invisible) => {
|
||||
let callback_message = (invisible.on_update.callback)(&());
|
||||
responses.add(callback_message);
|
||||
|
|
|
@ -288,6 +288,7 @@ impl LayoutGroup {
|
|||
Widget::FontInput(x) => &mut x.tooltip,
|
||||
Widget::IconButton(x) => &mut x.tooltip,
|
||||
Widget::IconLabel(x) => &mut x.tooltip,
|
||||
Widget::ImageLabel(x) => &mut x.tooltip,
|
||||
Widget::LayerReferenceInput(x) => &mut x.tooltip,
|
||||
Widget::NumberInput(x) => &mut x.tooltip,
|
||||
Widget::OptionalInput(x) => &mut x.tooltip,
|
||||
|
@ -438,6 +439,7 @@ pub enum Widget {
|
|||
FontInput(FontInput),
|
||||
IconButton(IconButton),
|
||||
IconLabel(IconLabel),
|
||||
ImageLabel(ImageLabel),
|
||||
InvisibleStandinInput(InvisibleStandinInput),
|
||||
LayerReferenceInput(LayerReferenceInput),
|
||||
NumberInput(NumberInput),
|
||||
|
@ -516,6 +518,7 @@ impl DiffUpdate {
|
|||
Widget::PopoverButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::IconLabel(_)
|
||||
| Widget::ImageLabel(_)
|
||||
| Widget::CurveInput(_)
|
||||
| Widget::InvisibleStandinInput(_)
|
||||
| Widget::PivotAssist(_)
|
||||
|
|
|
@ -12,6 +12,18 @@ pub struct IconLabel {
|
|||
pub tooltip: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)]
|
||||
pub struct ImageLabel {
|
||||
#[widget_builder(constructor)]
|
||||
pub image: String,
|
||||
|
||||
pub width: Option<String>,
|
||||
|
||||
pub height: Option<String>,
|
||||
|
||||
pub tooltip: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
||||
pub struct Separator {
|
||||
pub direction: SeparatorDirection,
|
||||
|
|
|
@ -49,10 +49,17 @@ impl LayoutHolder for MenuBarMessageHandler {
|
|||
},
|
||||
MenuBarEntry {
|
||||
label: "Open…".into(),
|
||||
icon: Some("Folder".into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
MenuBarEntry {
|
||||
label: "Open Demo Artwork…".into(),
|
||||
icon: Some("Image".into()),
|
||||
action: MenuBarEntry::create_action(|_| DialogMessage::RequestDemoArtworkDialog.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![
|
||||
MenuBarEntry {
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
{
|
||||
"extends": "@parcel/config-default",
|
||||
"transformers": {
|
||||
"*.svelte": [
|
||||
"parcel-transformer-svelte3-plus"
|
||||
],
|
||||
"*.svg": [
|
||||
"...",
|
||||
"@parcel/transformer-inline-string"
|
||||
],
|
||||
"*.svelte": [
|
||||
"parcel-transformer-svelte3-plus"
|
||||
"*.png, *.jpg": [
|
||||
"...",
|
||||
"@parcel/transformer-inline-string"
|
||||
]
|
||||
},
|
||||
"optimizers": {
|
||||
"*.png, *.jpg": [
|
||||
"@parcel/optimizer-data-url"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 189 B |
Binary file not shown.
After Width: | Height: | Size: 7 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
2880
frontend/package-lock.json
generated
2880
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -34,17 +34,18 @@
|
|||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/config-default": "^2.8.3",
|
||||
"@parcel/packager-raw-url": "^2.8.3",
|
||||
"@parcel/transformer-inline-string": "^2.8.3",
|
||||
"@parcel/transformer-webmanifest": "^2.8.3",
|
||||
"@parcel/config-default": "^2.9.3",
|
||||
"@parcel/packager-raw-url": "^2.9.3",
|
||||
"@parcel/optimizer-data-url": "^2.9.3",
|
||||
"@parcel/transformer-inline-string": "^2.9.3",
|
||||
"@parcel/transformer-webmanifest": "^2.9.3",
|
||||
"@types/license-checker-webpack-plugin": "^0.2.1",
|
||||
"@types/node": "^18.16.2",
|
||||
"@types/webpack": "^5.28.1",
|
||||
"buffer": "^5.7.1",
|
||||
"concurrently": "^8.0.1",
|
||||
"license-checker-webpack-plugin": "^0.2.1",
|
||||
"parcel": "^2.8.3",
|
||||
"parcel": "^2.9.3",
|
||||
"parcel-transformer-svelte3-plus": "^0.2.9",
|
||||
"postcss": "^8.4.23",
|
||||
"process": "^0.11.10",
|
||||
|
|
|
@ -84,6 +84,10 @@
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.image-label {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.panic-buttons-row {
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
import TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte";
|
||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import { getContext } from "svelte";
|
||||
|
@ -120,6 +121,10 @@
|
|||
{#if iconLabel}
|
||||
<IconLabel {...exclude(iconLabel)} />
|
||||
{/if}
|
||||
{@const imageLabel = narrowWidgetProps(component.props, "ImageLabel")}
|
||||
{#if imageLabel}
|
||||
<ImageLabel {...exclude(imageLabel)} />
|
||||
{/if}
|
||||
{@const layerReferenceInput = narrowWidgetProps(component.props, "LayerReferenceInput")}
|
||||
{#if layerReferenceInput}
|
||||
<LayerReferenceInput {...exclude(layerReferenceInput)} on:value={({ detail }) => updateLayout(index, detail)} />
|
||||
|
|
29
frontend/src/components/widgets/labels/ImageLabel.svelte
Normal file
29
frontend/src/components/widgets/labels/ImageLabel.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { IMAGE_BASE64_STRINGS } from "@graphite/utility-functions/images";
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
|
||||
export let image: string;
|
||||
export let width: string | undefined;
|
||||
export let height: string | undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap((classAndState) => (classAndState[1] ? [classAndState[0]] : []))
|
||||
.join(" ");
|
||||
</script>
|
||||
|
||||
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" />
|
||||
|
||||
<style lang="scss" global>
|
||||
.image-label {
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
+ .image-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -40,14 +40,6 @@
|
|||
|
||||
let tabElements: (LayoutRow | undefined)[] = [];
|
||||
|
||||
function newDocument() {
|
||||
editor.instance.newDocumentDialog();
|
||||
}
|
||||
|
||||
function openDocument() {
|
||||
editor.instance.documentOpen();
|
||||
}
|
||||
|
||||
function platformModifiers(reservedKey: boolean): LayoutKeysGroup {
|
||||
// TODO: Remove this by properly feeding these keys from a layout provided by the backend
|
||||
|
||||
|
@ -128,7 +120,7 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<TextButton label="New Document" icon="File" action={() => newDocument()} />
|
||||
<TextButton label="New Document" icon="File" action={() => editor.instance.newDocumentDialog()} />
|
||||
</td>
|
||||
<td>
|
||||
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(true), { key: "KeyN", label: "N" }]]} />
|
||||
|
@ -136,12 +128,17 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<TextButton label="Open Document" icon="Folder" action={() => openDocument()} />
|
||||
<TextButton label="Open Document" icon="Folder" action={() => editor.instance.openDocument()} />
|
||||
</td>
|
||||
<td>
|
||||
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(false), { key: "KeyO", label: "O" }]]} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<TextButton label="Open Demo Artwork" icon="Image" action={() => editor.instance.demoArtworkDialog()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
|
|
10
frontend/src/imports.d.ts
vendored
10
frontend/src/imports.d.ts
vendored
|
@ -5,3 +5,13 @@ declare module "*.svg" {
|
|||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.jpg" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import { type Editor } from "@graphite/wasm-communication/editor";
|
|||
import {
|
||||
type FrontendDocumentDetails,
|
||||
TriggerCopyToClipboardBlobUrl,
|
||||
TriggerFetchAndOpenDocument,
|
||||
TriggerDownloadBlobUrl,
|
||||
TriggerDownloadRaster,
|
||||
TriggerDownloadTextFile,
|
||||
TriggerImaginateCheckServerStatus,
|
||||
TriggerImport,
|
||||
TriggerOpenDocument,
|
||||
TriggerRasterizeRegionBelowLayer,
|
||||
|
@ -45,6 +45,19 @@ export function createPortfolioState(editor: Editor) {
|
|||
return state;
|
||||
})
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerFetchAndOpenDocument, async (triggerFetchAndOpenDocument) => {
|
||||
try {
|
||||
const url = new URL(triggerFetchAndOpenDocument.url);
|
||||
const data = await fetch(url);
|
||||
|
||||
const filename = url.pathname.split("/").pop() || "Untitled";
|
||||
const content = await data.text();
|
||||
|
||||
editor.instance.openDocumentFile(filename, content);
|
||||
} catch {
|
||||
editor.instance.errorDialog("Failed to open document", "The file could not be reached over the internet. You may be offline, or it may be missing.");
|
||||
}
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerOpenDocument, async () => {
|
||||
const extension = editor.instance.fileSaveSuffix();
|
||||
const data = await upload(extension, "text");
|
||||
|
|
|
@ -113,7 +113,7 @@ import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg";
|
|||
import NodeBrushwork from "@graphite-frontend/assets/icon-16px-solid/node-brushwork.svg";
|
||||
import NodeColorCorrection from "@graphite-frontend/assets/icon-16px-solid/node-color-correction.svg";
|
||||
import NodeGradient from "@graphite-frontend/assets/icon-16px-solid/node-gradient.svg";
|
||||
import NodeImage from "@graphite-frontend/assets/icon-16px-solid/node-image.svg";
|
||||
import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg";
|
||||
import NodeImaginate from "@graphite-frontend/assets/icon-16px-solid/node-imaginate.svg";
|
||||
import NodeMagicWand from "@graphite-frontend/assets/icon-16px-solid/node-magic-wand.svg";
|
||||
import NodeMask from "@graphite-frontend/assets/icon-16px-solid/node-mask.svg";
|
||||
|
@ -176,7 +176,7 @@ const SOLID_16PX = {
|
|||
NodeBrushwork: { svg: NodeBrushwork, size: 16 },
|
||||
NodeColorCorrection: { svg: NodeColorCorrection, size: 16 },
|
||||
NodeGradient: { svg: NodeGradient, size: 16 },
|
||||
NodeImage: { svg: NodeImage, size: 16 },
|
||||
Image: { svg: Image, size: 16 },
|
||||
NodeImaginate: { svg: NodeImaginate, size: 16 },
|
||||
NodeMagicWand: { svg: NodeMagicWand, size: 16 },
|
||||
NodeMask: { svg: NodeMask, size: 16 },
|
||||
|
|
23
frontend/src/utility-functions/images.ts
Normal file
23
frontend/src/utility-functions/images.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable import/first */
|
||||
|
||||
// Demo artwork
|
||||
import ThumbnailJustAPottedCactus from "@graphite-frontend/assets/images/demo-artwork/thumbnail-just-a-potted-cactus.png";
|
||||
import ThumbnailValleyOfSpires from "@graphite-frontend/assets/images/demo-artwork/thumbnail-valley-of-spires.png";
|
||||
|
||||
const DEMO_ARTWORK = {
|
||||
ThumbnailJustAPottedCactus,
|
||||
ThumbnailValleyOfSpires,
|
||||
} as const;
|
||||
|
||||
// All images
|
||||
const IMAGE_LIST = {
|
||||
...DEMO_ARTWORK,
|
||||
} as const;
|
||||
|
||||
// Exported images and types
|
||||
export const IMAGES: ImageDefinitionType<typeof IMAGE_LIST> = IMAGE_LIST;
|
||||
export const IMAGE_BASE64_STRINGS = Object.fromEntries(Object.entries(IMAGES).map(([name, data]) => [name, data]));
|
||||
|
||||
// See `icons.ts` for explanation about how this works
|
||||
type EvaluateType<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
||||
type ImageDefinitionType<T extends Record<string, string>> = EvaluateType<{ [key in keyof T]: string }>;
|
|
@ -89,6 +89,21 @@ export function createEditor() {
|
|||
// Subscriptions: Allows subscribing to messages in JS that are sent from the WASM backend
|
||||
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
|
||||
|
||||
// Check if the URL hash fragment has any demo artwork to be loaded
|
||||
(async () => {
|
||||
const demoArtwork = window.location.hash.trim().match(/#demo\/(.*)/)?.[1];
|
||||
if (!demoArtwork) return;
|
||||
|
||||
try {
|
||||
const url = new URL(`https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/demo-artwork/${demoArtwork}.graphite`);
|
||||
const data = await fetch(url);
|
||||
|
||||
const filename = url.pathname.split("/").pop() || "Untitled";
|
||||
const content = await data.text();
|
||||
instance.openDocumentFile(filename, content);
|
||||
} catch {}
|
||||
})();
|
||||
|
||||
return {
|
||||
raw,
|
||||
instance,
|
||||
|
|
|
@ -513,6 +513,10 @@ export class TriggerLoadAutoSaveDocuments extends JsMessage { }
|
|||
|
||||
export class TriggerLoadPreferences extends JsMessage { }
|
||||
|
||||
export class TriggerFetchAndOpenDocument extends JsMessage {
|
||||
readonly url!: string;
|
||||
}
|
||||
|
||||
export class TriggerOpenDocument extends JsMessage { }
|
||||
|
||||
export class TriggerImport extends JsMessage { }
|
||||
|
@ -874,6 +878,19 @@ export class IconLabel extends WidgetProps {
|
|||
tooltip!: string | undefined;
|
||||
}
|
||||
|
||||
export class ImageLabel extends WidgetProps {
|
||||
image!: IconName;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
width!: string | undefined;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
height!: string | undefined;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
tooltip!: string | undefined;
|
||||
}
|
||||
|
||||
export class LayerReferenceInput extends WidgetProps {
|
||||
@Transform(({ value }: { value: BigUint64Array | undefined }) => (value ? String(value) : undefined))
|
||||
value!: string | undefined;
|
||||
|
@ -1120,6 +1137,7 @@ const widgetSubTypes = [
|
|||
{ value: FontInput, name: "FontInput" },
|
||||
{ value: IconButton, name: "IconButton" },
|
||||
{ value: IconLabel, name: "IconLabel" },
|
||||
{ value: ImageLabel, name: "ImageLabel" },
|
||||
{ value: LayerReferenceInput, name: "LayerReferenceInput" },
|
||||
{ value: NumberInput, name: "NumberInput" },
|
||||
{ value: OptionalInput, name: "OptionalInput" },
|
||||
|
@ -1367,6 +1385,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
DisplayRemoveEditableTextbox,
|
||||
TriggerAboutGraphiteLocalizedCommitDate,
|
||||
TriggerCopyToClipboardBlobUrl,
|
||||
TriggerFetchAndOpenDocument,
|
||||
TriggerDownloadBlobUrl,
|
||||
TriggerDownloadRaster,
|
||||
TriggerDownloadTextFile,
|
||||
|
|
|
@ -308,12 +308,18 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = documentOpen)]
|
||||
pub fn document_open(&self) {
|
||||
#[wasm_bindgen(js_name = openDocument)]
|
||||
pub fn open_document(&self) {
|
||||
let message = PortfolioMessage::OpenDocument;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = demoArtworkDialog)]
|
||||
pub fn demo_artwork_dialog(&self) {
|
||||
let message = DialogMessage::RequestDemoArtworkDialog;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = openDocumentFile)]
|
||||
pub fn open_document_file(&self, document_name: String, document_serialized_content: String) {
|
||||
let message = PortfolioMessage::OpenDocumentFile {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue