Best Apps GFX Editor
Use Best Apps' GFX Editor as a React component or in vanilla JS
Table of contents
Installation
npm install @best-apps/gfx-editor
yarn add @best-apps/gfx-editor
Usage in React
TLDR
See demo in codesandbox
import { GFXInstanceProvider, GFXInstance } from "@best-apps/gfx-editor";
function App() {
return (
<GFXInstanceProvider>
<div
style={{
width: 400,
height: 600,
margin: 20,
border: "1px solid black",
position: "relative", // You have to wrap the editor in an element with width/height and position: fixed/relative/absolute
}}
>
<GFXInstance
v1TemplateId={123} // The v1TemplateId for you product and design
/>
</div>
</GFXInstanceProvider>
);
}
That will render the editor in an iframe and give you the full interface for interacting with your
design/template/product
GFXInstanceProvider
Wherever you want to put the Editor you have to put it inside <GFXInstanceProvider />
. This creates a React context so
that every component nested within it has access to gfx
with useGFXInstance()
.
GFXInstance
Use <GFXInstance {...props} />
wherever you want to render the Editor in an iframe.
- First create a
div
with width
, height
, and position
styles so that the editor can fill this container element. - Wrap this and whatever other components need access to
gfx
within a GFXInstanceProvider component. - Provide a react ref as
gfxRef
. It will get initialized with a gfx
instance object.
useGFXInstance()
This hook will get gfx
from context. Here is an example:
import { useGFXInstance } from "@best-app/gfx-editor";
function Button() {
const gfx = useGFXInstance();
return <button onClick={() => gfx?.actions.flipCanvas()}>Flip canvas</button>;
}
GFXInstanceContext
If you are attempting to get the parent gfx
from context in a React class component, use GFXInstanceContext
directly. Here is an example:
import { GFXInstanceContext } from "@best-app/gfx-editor";
class ButtonWrapper extends React.Component {
static contextType = GFXInstanceContext;
render() {
const gfx = this.context;
return (
<button onClick={() => gfx?.actions.flipCanvas()}>Flip canvas</button>
);
}
}
GFXInstanceConsumer
You can also use GFXInstanceConsumer with a child render callback to access the parent gfx
from context. Here is an example:
import { GFXInstanceConsumer } from "@best-app/gfx-editor";
function Button() {
return (
<GFXInstanceConsumer>
{(gfx) => {
<button onClick={() => gfx?.actions.flipCanvas()}>Flip canvas</button>;
}}
</GFXInstanceConsumer>
);
}
useActiveObjectType()
NOTE: This is only useful if you are creating your own UI for the Editor.
When creating your own toolbars and buttons, you will often need to know exactly what TYPE OF object has been
actively selected. This is where useActiveObjectType
is useful.
This hook will return:
CustomImage
LikeCustomTextbox
Sticker
CustomizableTextSlot
CustomizableImageSlot
null
Here is an example:
function TopToolbar() {
const activeObjectType = useActiveObjectType(gfx?.state);
if (activeObjectType === "LikeCustomTextbox") {
return (
<button
onClick={() =>
gfx?.actions.openMenu({
menuType: "strokeColorOnTextbox",
})
}
>
Open stroke color drawer
</button>
);
}
}
Use as es6 module
Sort of like ReactDOM.render
, you call this with a config object and an html element where we should insert the
Editor.
import { embedGFX } from '@best-apps/gfx-editor';
const gfx = embedGFX({
v1TemplateId: 1234
}, document.getElementById(#editor - container))
Once you have the gfx
object, you can build a UI that incorporates gfx.actions
and gfx.state
Use as script and window.embedGFX
It may be necessary to use our hosted script in some instances
Using GFX Instance
GFX actions
You call these like gfx.actions.flipCanvas()
flipCanvas()
: This flips the canvas (if there is a front and back to the design and product). Wall art, for example,
will not flip.rotateCanvas()
: This will rotate the canvas (only when using wall art or poster printables)openMenu: (activeMenu: ActiveMenuType) => Promise<void>
. This opens a menu/drawer. ActiveMenuType
is one of these:
openMenu({ menuType: 'sticker' })
: to open the sticker draweropenMenu({ menuType: 'printableColor' })
: to open the color selection drawer to change the color on the garment or wall art (what we call "printables")openMenu({ menuType: 'strokeColorOnTextbox' })
: to open the color selection drawer to select the color for the stroke on text in a textboxopenMenu({ menuType: 'fillColorOnTextbox' })
: to open the color selection drawer to select fill color on text in a textboxopenMenu({ menuType: 'fontFamilyOnTextbox' })
: to open the sticker draweropenMenu({ menuType: 'fillColorOnAllTextboxes' })
: to open the color selection drawer to select fill color on ALL text in ALL textboxes and text slots
closeMenu: () => void
: Closes the menu (any menu)setFillColorOnTextbox: (color: string) => void
: This will set the fill color on text in a textboxsetFillColorOnTextboxSlot: (slotId: number, color: string) => void
: Set fill color on text in a text slotsetFillColorOnAllTextboxes: (color: string) => void
: Sets the fill color on ALL text boxessetFontFamilyOnTextbox: (...args: any) => void
: Sets the font family on a textboxsetStrokeColorOnTextbox: (...args: any) => Promise<void>
: Sets the stroke color on a given textboxsetImageOnImageSlot: (...args: any) => Promise<void>
: Sets the image on an image slotsetPrintableColor: (color: string) => void
: Sets the printable color to the specified colornextColor: () => void
: Toggles the color on the "printable"updateDesignOnState: () => void
: This just creates an export of the design and put is on gfx.state.design
debouncedUpdateDesignOnState: () => Promise<void>
: A debounced version of the updateDesignOnState
methodtoggleZoom: () => void
: Toggles the zoom on the editor canvasaddText: () => Promise<void>
: Adds a customizable textbox to the canvasremoveBg: () => Promise<void>
: Removes bg on the currently selected imageaddImage: (urlOrBase64: string, addImageOptions?: AddImageOptions) => Promise<void>
: Adds an image to the canvasrotateActiveObject: (angle: number, animate?: boolean) => void
: Rotates the active objectalignActiveObject: (position: AlignPosition, animate?: boolean) => void
: Aligns the active object depending on the arguments you pass in:
AlignPosition: 'center' | 'centerHorizontally' | 'centerVertically' | 'alignToTop'
shareDesign: (args: { url: string; base64Image: string; title: string }) => void
showAlert: (alertOptions: AlertOptions) => void
: Shows an alert you specify with alertOptions
title: string
: The title of the alertbody?: string
: The body of the alerttimeout?: number
: How long before the alert is dismissed automaticallydismissable: boolean
: Whether or not the alert can be dismisseddismissableButtonLabel?: string
: And what the dismiss button label is
hideAlert: () => void
: Hides any active alertgetProofs: (quality?: number) => Promise<GFXProofs>
: Gets proofs and sends back their base64 representationsaveDesign: () => Promise<void>
: Saves the design to our dbhandleImageUpload: (event: any) => Promise<void>
: TBD
GFX State
These are properties on gfx state. You might access them like gfx.state.isLoading
isLoading: boolean
isAsyncLoading: boolean
isSyncing: boolean
cornerIcons: CornerIcons
canFlip: boolean
canRotate: boolean
canChangeColor: boolean
status: Status
statusCode: StatusCode
orientation: PrintableInfoOrientation
activeObject: GFXCanvasObjectType | null
isZooming: boolean
scale: number
centerPoints: GFXCenterPointsBySection
alert?: AlertOptions | null
activeMenu
design: V2Design
activeSection: SectionType
activeV2Printable: V2Printable
isSupportedBrowser: boolean
productId: number | string | null
designId: number | string | null
designNumber: string | null
v1TemplateId: number | string | null
initialData: GFXInitialDataType
windowWidth: number | null
windowHeight: number | null
stickers: number[]
GFX events
You call these like gfx.actions.flipCanvas()
onStateChange: (payload: GFXStateType) => void
: your favorite probably, a way to listen for state changesonColorChanged: (color: string) => void
: when the printable/garment color is changedonTextFillColorChanged: (color: string) => void
: when text fill color is changedonObjectRemoved: (obj: GFXCanvasObjectType) => void
:onObjectDoubleClicked: (obj: GFXCanvasObjectType) => void
:onSelectionCleared: (payload: {
:deselected: GFXCanvasObjectType[] | null
:selected: GFXCanvasObjectType[] | null
:}) => void
:onImageSelectedInSlot: (obj: GFXCanvasObjectType) => void
:onBeforeSelectionCleared: (payload: { deselected: GFXCanvasObjectType[] }) => void
:onObjectSelectionUpdated: (payload: {
:onImageIdUpdatedOnSlot: (payload: { slotId: number; imageId: number }) => void
:onUpdatedCustomSlot: (payload: any) => void
:
Example usage:
var gfx = window.embedGFX(
{
v1TemplateId: 829,
interfaceType: "full",
},
document.getElementById("gfx-product")
);
gfx.addEventListener("onStateChange", (state) => console.log(state));
Types
SavedDesign
designNumber
: the uuid v4 string that represents the users custom design on this product or templatedesign
: the users' custom design
Styling the editor
We use BEM style classnames throughout the editor UI, and you can provide a stylehseet as a text string in the gfxConfig.customOptions.css
property in order to override our existing styles.
var gfx = window.embedGFX(
{
v1TemplateId: 829,
interfaceType: "full",
gfxConfig: {
disableTOS: true,
showWatermark: false,
showInitialToast: true,
customOptions: {
css: `
.AbstractDrawer {
background: #fff !important;
border-top: 1px solid #2b2c2d18;
border-radius: 0;
}
`,
},
},
},
document.getElementById("gfx-product")
);
Integrations
Shopify
When integrating your Shopify store with gfx you must
- Add a property to line items:
design_number
- Add an attribute to the order:
app_flow: 'embedded'
To get the design_number
, you must call gfx.actions.saveDesign()
which will return a SavedDesign
object
Here is an example of how you would do this with an ajax request with Jquery:
With AJAX
const savedDesign = await gfx.actions.saveDesign();
await $.post(
"/cart/add.js",
{
quantity: parseInt(quantity.value),
id: parseInt(window.currentVariant),
attributes: {
app_flow: "embedded",
},
properties: {
design_number: savedDesign.designNumber,
},
},
null,
"json"
);
With liquid templates
This usually requires custom implementation support right now. Please contact your GFX account manager.
Basically, you must add the properties to your form:
<form method="post" action="/cart/add">
<input type="hidden" name="properties[design_number]" value="DESIGN_NUMBER" />
<input type="text" name="id" value="VARIANT_ID" />
<input type="submit" value="submit" />
</form>
BigCommerce
Coming soon
FAQ
Coming soon.
Contributing
This is a private repo. If you are part of the Best Apps team, see CONTRIBUTING.md