sourceafReflux::History.fan

using afIoc
using fwt

** (Service) - 
** Maintains a history of View URIs.
@Js
mixin History {
    
    ** Loads the previous history item.
    ** Does nothing if there is no prev.
    abstract Void navBackward()
    
    ** Loads the next history item. 
    ** Does nothing if there is no next.
    abstract Void navForward()
    
    ** Returns a unique list of URIs visited.
    abstract Resource[] history()

    ** Clears the history.
    abstract Void clear()

    @NoDoc
    abstract Bool navBackwardEnabled()

    @NoDoc
    abstract Bool navForwardEnabled()
    
    @NoDoc
    abstract Void load(Resource resource, LoadCtx ctx)
}

@Js
internal class HistoryImpl : History, RefluxEvents {
            private Resource[]  backStack       := Resource[,]
            private Resource[]  forwardStack    := Resource[,]
            private Resource?   showing
            override Resource[] history         := Resource[,]
    @Inject private Scope       scope
    @Inject private Reflux      reflux

    new make(EventHub eventHub, |This|in) {
        in(this)
        eventHub.register(this)
    }
    
    override Void navBackward() {
        if (!navBackwardEnabled) return

        if (showing != null)
            forwardStack.push(showing)
        showing = backStack.pop
    
        reflux.loadResource(showing, LoadCtx { it.addToHistory = false })
    }
    
    override Void navForward() {
        if (!navForwardEnabled) return
        
        if (showing != null)
            backStack.push(showing)
        showing = forwardStack.pop
    
        reflux.loadResource(showing, LoadCtx { it.addToHistory = false })
    }
    
    override Bool navBackwardEnabled() {
        !backStack.isEmpty      
    }

    override Bool navForwardEnabled() {
        !forwardStack.isEmpty
    }

    override Void load(Resource resource, LoadCtx ctx) {        
        if (ctx.addToHistory && resource != showing) {
            if (showing != null)
                backStack.push(showing)
            showing = resource
            forwardStack.clear  // you can't return to the same future once you've changed the past!

            if (backStack.size > 50)
                backStack.size = 50 // keep 50 entries
        }

        history.insert(0, resource)
        
        // FIXME: JS sys::NotImmutableErr: key is not immutable: refluxWeb::MyResource
        if (Env.cur.runtime != "js")
            history = history.unique
        
        if (history.size > 50) history.size = 50

        showHistoryMenu
    }
    
    override Void clear() {
        backStack.clear
        forwardStack.clear
        history.clear

        showHistoryMenu
    }
    
    private Void showHistoryMenu() {
        // FIXME: JS casting issue
        if (Env.cur.runtime == "js") return
        
        historyMenu := (Menu) scope.serviceById("afReflux.historyMenu")     
        ((MenuItem[]) historyMenu.children).each { if (it.command is HistoryCommand) historyMenu.remove(it) }
        
        history := history.dup
        history.insert(0, showing)
        history = history.unique
        if (history.size > 13) history.size = 13    // TODO: move to prefs
        history.each { historyMenu.add(MenuItem.makeCommand(scope.build(HistoryCommand#, [it]))) }      
    }
    
    override Void onViewActivated(View view) {
        // treat tabbing the same as loading, so we can nav back.
        if (view.resource != null)
            load(view.resource, LoadCtx())
    }
    
    override Void onLoadSession(Str:Obj? session) {
        history = Uri?[,].addAll(session["afReflux.history"] ?: Uri#.emptyList).map |uri->Resource?| {
            try     return reflux.resolve(uri.toStr)
            catch   return null
        }.exclude { it == null }
    }

    override Void onSaveSession(Str:Obj? session) {
        session["afReflux.history"] = history.map { it.uri }
    }
}

@Js
internal class HistoryCommand : RefluxCommand {
    new make(Resource resource, Reflux reflux, |This|in) : super.make(in) {
        this.name = (resource.uri.toStr != resource.displayName) ? resource.displayName : Url(resource.uri).minusFrag.toStr
        this.icon = resource.icon
        this.onInvoke.add { reflux.loadResource(resource) }
    }
}