@ -1435,7 +1435,7 @@ class App extends React.Component<AppProps, AppState> {
onMagicSettingsConfirm = { this . onMagicSettingsConfirm }
>
{ this . props . children }
{ this . state . openDialog === "mermaid" && (
{ this . state . openDialog ? . name === "mermaid" && (
< MermaidToExcalidraw / >
) }
< / LayerUI >
@ -1467,6 +1467,7 @@ class App extends React.Component<AppProps, AppState> {
onChange = { ( ) = >
this . onMagicFrameGenerate (
firstSelectedElement ,
"button" ,
)
}
/ >
@ -1697,11 +1698,15 @@ class App extends React.Component<AppProps, AppState> {
return text ;
}
private async onMagicFrameGenerate ( magicFrame : ExcalidrawMagicFrameElement ) {
private async onMagicFrameGenerate (
magicFrame : ExcalidrawMagicFrameElement ,
source : "button" | "upstream" ,
) {
if ( ! this . OPENAI_KEY ) {
this . setState ( {
openDialog : "magicSettings" ,
openDialog : { name : "magicSettings" , source : "generation" } ,
} ) ;
trackEvent ( "ai" , "d2c-generate" , "missing-key" ) ;
return ;
}
@ -1712,7 +1717,12 @@ class App extends React.Component<AppProps, AppState> {
} ) . filter ( ( el ) = > ! isMagicFrameElement ( el ) ) ;
if ( ! magicFrameChildren . length ) {
this . setState ( { errorMessage : "Cannot generate from an empty frame" } ) ;
if ( source === "button" ) {
this . setState ( { errorMessage : "Cannot generate from an empty frame" } ) ;
trackEvent ( "ai" , "d2c-generate" , "no-children" ) ;
} else {
this . setActiveTool ( { type : "magicframe" } ) ;
}
return ;
}
@ -1751,6 +1761,8 @@ class App extends React.Component<AppProps, AppState> {
const textFromFrameChildren = this . getTextFromElements ( magicFrameChildren ) ;
trackEvent ( "ai" , "d2c-generate" , "generating" ) ;
const result = await diagramToHTML ( {
image : dataURL ,
apiKey : this.OPENAI_KEY ,
@ -1759,6 +1771,7 @@ class App extends React.Component<AppProps, AppState> {
} ) ;
if ( ! result . ok ) {
trackEvent ( "ai" , "d2c-generate" , "generating-failed" ) ;
console . error ( result . error ) ;
this . updateMagicGeneration ( {
frameElement ,
@ -1770,6 +1783,7 @@ class App extends React.Component<AppProps, AppState> {
} ) ;
return ;
}
trackEvent ( "ai" , "d2c-generate" , "generating-done" ) ;
if ( result . choices [ 0 ] . message . content == null ) {
this . updateMagicGeneration ( {
@ -1813,7 +1827,10 @@ class App extends React.Component<AppProps, AppState> {
private OPENAI_KEY_IS_PERSISTED : boolean =
EditorLocalStorage . has ( EDITOR_LS_KEYS . OAI_API_KEY ) || false ;
private onOpenAIKeyChange = ( openAIKey : string , shouldPersist : boolean ) = > {
private onOpenAIKeyChange = (
openAIKey : string | null ,
shouldPersist : boolean ,
) = > {
this . OPENAI_KEY = openAIKey || null ;
if ( shouldPersist ) {
const didPersist = EditorLocalStorage . set (
@ -1826,26 +1843,41 @@ class App extends React.Component<AppProps, AppState> {
}
} ;
private onMagicSettingsConfirm = ( apiKey : string , shouldPersist : boolean ) = > {
this . onOpenAIKeyChange ( apiKey , shouldPersist ) ;
private onMagicSettingsConfirm = (
apiKey : string ,
shouldPersist : boolean ,
source : "tool" | "generation" | "settings" ,
) = > {
this . OPENAI_KEY = apiKey || null ;
this . onOpenAIKeyChange ( this . OPENAI_KEY , shouldPersist ) ;
if ( source === "settings" ) {
return ;
}
const selectedElements = this . scene . getSelectedElements ( {
selectedElementIds : this.state.selectedElementIds ,
} ) ;
if ( apiKey ) {
const selectedElements = this . scene . getSelectedElements ( {
selectedElementIds : this.state.selectedElementIds ,
} ) ;
if ( selectedElements . length ) {
this . onMagicButtonSelect ( ) ;
this . onMagicframeToolSelect ( ) ;
} else {
this . setActiveTool ( { type : "magicframe" } ) ;
}
} else {
this . OPENAI_KEY = null ;
} else if ( ! isMagicFrameElement ( selectedElements [ 0 ] ) ) {
// even if user didn't end up setting api key, let's pick the tool
// so they can draw up a frame and move forward
this . setActiveTool ( { type : "magicframe" } ) ;
}
} ;
public onMagic Button Select = ( ) = > {
public onMagic frameTool Select = ( ) = > {
if ( ! this . OPENAI_KEY ) {
this . setState ( {
openDialog : "magicSettings" ,
openDialog : { name : "magicSettings" , source : "tool" } ,
} ) ;
trackEvent ( "ai" , "d2c-tool" , "missing-key" ) ;
return ;
}
@ -1855,19 +1887,33 @@ class App extends React.Component<AppProps, AppState> {
if ( selectedElements . length === 0 ) {
this . setActiveTool ( { type : TOOL_TYPE . magicframe } ) ;
trackEvent ( "ai" , "d2c-tool" , "empty-selection" ) ;
} else {
if ( selectedElements . some ( ( el ) = > isFrameLikeElement ( el ) ) ) {
const selectedMagicFrame : ExcalidrawMagicFrameElement | false =
selectedElements . length === 1 &&
isMagicFrameElement ( selectedElements [ 0 ] ) &&
selectedElements [ 0 ] ;
// case: user selected elements containing frame-like(s) or are frame
// members, we don't want to wrap into another magicframe
// (unless the only selected element is a magic frame which we reuse)
if (
! selectedMagicFrame &&
selectedElements . some ( ( el ) = > isFrameLikeElement ( el ) || el . frameId )
) {
this . setActiveTool ( { type : TOOL_TYPE . magicframe } ) ;
return ;
}
let frame : ExcalidrawMagicFrameElement | null = null ;
if (
selectedElements . length === 1 &&
isMagicFrameElement ( selectedElements [ 0 ] )
) {
frame = selectedElements [ 0 ] ;
trackEvent ( "ai" , "d2c-tool" , "existing-selection" ) ;
let frame : ExcalidrawMagicFrameElement ;
if ( selectedMagicFrame ) {
// a single magicframe already selected -> use it
frame = selected MagicFrame ;
} else {
// selected elements aren't wrapped in magic frame yet -> wrap now
const [ minX , minY , maxX , maxY ] = getCommonBounds ( selectedElements ) ;
const padding = 50 ;
@ -1880,19 +1926,19 @@ class App extends React.Component<AppProps, AppState> {
opacity : 100 ,
locked : false ,
} ) ;
}
this . scene . addNewElement ( frame ) ;
this . scene . addNewElement ( frame ) ;
for ( const child of selectedElements ) {
mutateElement ( child , { frameId : frame.id } ) ;
}
for ( const child of selectedElements ) {
mutateElement ( child , { frameId : frame.id } ) ;
}
this . setState ( {
selectedElementIds : { [ frame . id ] : true } ,
} ) ;
this . setState ( {
selectedElementIds : { [ frame . id ] : true } ,
} ) ;
}
this . onMagicFrameGenerate ( frame );
this . onMagicFrameGenerate ( frame , "upstream" );
}
} ;
@ -3551,7 +3597,7 @@ class App extends React.Component<AppProps, AppState> {
if ( event . key === KEYS . QUESTION_MARK ) {
this . setState ( {
openDialog : "help" ,
openDialog : { name : "help" } ,
} ) ;
return ;
} else if (
@ -3560,7 +3606,7 @@ class App extends React.Component<AppProps, AppState> {
event [ KEYS . CTRL_OR_CMD ]
) {
event . preventDefault ( ) ;
this . setState ( { openDialog : "imageExport" } ) ;
this . setState ( { openDialog : { name : "imageExport" } } ) ;
return ;
}