175 lines
5.8 KiB
Go
175 lines
5.8 KiB
Go
package editor
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"aego/art"
|
|
"aego/asset"
|
|
"aego/asset/pipeline"
|
|
"aego/core/ids"
|
|
"aego/editor/core/artedit"
|
|
"aego/editor/core/assetops"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/document"
|
|
"aego/engine/i18n"
|
|
"aego/project"
|
|
)
|
|
|
|
// A stable, project-local recovery identity lets a later editor session offer
|
|
// the previous blank drawing explicitly. It is never published as an asset ID.
|
|
// An actual indexed asset using this value blocks the blank rather than being
|
|
// confused with it. Recovery files remain in the project's existing directory.
|
|
var artWorkspaceBlankID = ids.AssetID{Hi: 0xe49fba64808b3ec1, Lo: 0x8b53129d8c996c02}
|
|
|
|
type blankArtDraft struct {
|
|
ed *context.Editor
|
|
docs *document.Manager
|
|
pipe *pipeline.Pipeline
|
|
project *project.Project
|
|
doc *document.AssetDocument
|
|
model *artedit.Document
|
|
}
|
|
|
|
func newWorkspaceBlank(ed *context.Editor) (*blankArtDraft, error) {
|
|
if ed == nil || ed.Docs == nil || ed.Pipeline == nil || ed.Project == nil {
|
|
return nil, fmt.Errorf("art: an open project is required for the blank canvas")
|
|
}
|
|
b := &blankArtDraft{ed: ed, docs: ed.Docs, pipe: ed.Pipeline, project: ed.Project}
|
|
if asset.IsBuiltin(artWorkspaceBlankID) {
|
|
return nil, fmt.Errorf("art: the blank canvas identity conflicts with a built-in asset")
|
|
}
|
|
if _, ok := b.pipe.Entry(artWorkspaceBlankID); ok {
|
|
return nil, fmt.Errorf("art: the blank canvas identity is already used by a project asset")
|
|
}
|
|
if _, ok := b.pipe.PathOf(artWorkspaceBlankID); ok {
|
|
return nil, fmt.Errorf("art: the blank canvas identity is already used by a project source")
|
|
}
|
|
name := i18n.Tr("editor.art.blank_title", "Untitled drawing")
|
|
doc, err := b.docs.NewTransientAsset(artWorkspaceBlankID, name, artHandlerID, asset.ArtTexture, document.View|document.EditSource|document.Preview)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.doc = doc
|
|
pixels, err := art.New(32, 32, art.RGBA, art.DefaultLimits())
|
|
if err != nil {
|
|
_ = b.docs.CloseAsset(doc.Asset, false)
|
|
return nil, err
|
|
}
|
|
key := sha256.Sum256([]byte("aego-art-workspace-blank-recovery-v1"))
|
|
b.model, err = artedit.BindNew(b.docs, doc, pixels, hex.EncodeToString(key[:]), b.verify)
|
|
if err != nil {
|
|
_ = b.docs.CloseAsset(doc.Asset, false)
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func (b *blankArtDraft) verify() error {
|
|
if b.ed.Docs != b.docs || b.ed.Pipeline != b.pipe || b.ed.Project != b.project || b.doc == nil || !b.doc.Transient || b.docs.AssetDocument(b.doc.Asset) != b.doc || b.doc.Asset != artWorkspaceBlankID {
|
|
return fmt.Errorf("art: the project or blank drawing changed")
|
|
}
|
|
if err := b.pipe.CheckSourceRecovery(); err != nil {
|
|
return err
|
|
}
|
|
if _, ok := b.pipe.Entry(b.doc.Asset); ok {
|
|
return fmt.Errorf("art: the blank canvas identity is now used by a project asset")
|
|
}
|
|
if _, ok := b.pipe.PathOf(b.doc.Asset); ok {
|
|
return fmt.Errorf("art: the blank canvas identity is now used by a project source")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *blankArtDraft) askSave(panel *artPanel, transferred func(*document.AssetDocument) error) error {
|
|
if err := b.verify(); err != nil {
|
|
return err
|
|
}
|
|
if b.model.PendingRecovery || b.model.Session.Info().Gesture {
|
|
return fmt.Errorf("art: resolve recovery and finish the current gesture before saving")
|
|
}
|
|
session := b.model.Session
|
|
snapshot, err := session.Document.Snapshot()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
encoded, err := snapshot.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
revision := b.model.PreviewRevision()
|
|
current := func() bool {
|
|
return b.doc.Binding.Model == b.model && b.model.Draft && !b.model.PendingRecovery && b.model.Session == session && !session.Info().Gesture && b.model.PreviewCurrent(revision)
|
|
}
|
|
b.ed.Ask(context.Question{
|
|
Title: i18n.Tr("editor.art.save_blank", "Save drawing as an asset"),
|
|
Message: i18n.Tr("editor.art.save_blank_notice", "Choose a project source. Replacing an existing drawing requires confirmation."),
|
|
Fields: []string{i18n.Tr("editor.art.source_path", "Source path")},
|
|
Values: []string{assetops.UniqueFile(b.ed, "assets", "drawing", art.SourceExtension)},
|
|
Buttons: []string{i18n.Tr("editor.art.save", "Save"), i18n.Tr("editor.art.cancel", "Cancel")},
|
|
DestructiveLabel: i18n.Tr("editor.art.replace", "Replace"),
|
|
DestructiveWhen: func(values []string) bool { return artSaveReplaces(b.ed, values) },
|
|
Submit: func(values []string) {
|
|
fail := func(err error) { panel.report(err) }
|
|
if len(values) != 1 || !current() {
|
|
fail(art.ErrConflict)
|
|
return
|
|
}
|
|
if err := b.verify(); err != nil {
|
|
fail(err)
|
|
return
|
|
}
|
|
target, err := prepareArtSaveTarget(b.ed, b.doc, strings.TrimSpace(values[0]), snapshot)
|
|
if err != nil {
|
|
fail(err)
|
|
return
|
|
}
|
|
target.confirm(func() {
|
|
if !current() {
|
|
fail(art.ErrConflict)
|
|
return
|
|
}
|
|
if err := b.verify(); err != nil {
|
|
fail(err)
|
|
return
|
|
}
|
|
if err := target.verify(); err != nil {
|
|
fail(err)
|
|
return
|
|
}
|
|
edits := target.edits(encoded)
|
|
var batch *pipeline.Batch
|
|
err = session.Document.Publish(snapshot, func() error {
|
|
var err error
|
|
batch, err = b.pipe.ApplySources(edits, false)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fail(err)
|
|
return
|
|
}
|
|
b.ed.ApplyBatch(batch)
|
|
next, err := b.ed.DocumentForAsset(target.meta.ID)
|
|
if err == nil {
|
|
err = b.model.RebindSaved(next, snapshot)
|
|
}
|
|
if err != nil {
|
|
fail(fmt.Errorf("art: the source was published, but the drawing remains open as an unsaved draft: %w", err))
|
|
return
|
|
}
|
|
if err := b.docs.ClearOwnedAssetRecovery(b.doc); err != nil {
|
|
b.ed.Notify(context.LevelWarn, err.Error())
|
|
}
|
|
if err := transferred(next); err != nil {
|
|
fail(err)
|
|
return
|
|
}
|
|
b.ed.Notify(context.LevelInfo, i18n.Tr("editor.art.blank_saved", "Saved drawing: {path}", i18n.Str("path", target.path)))
|
|
})
|
|
},
|
|
})
|
|
return document.ErrSavePending
|
|
}
|