Files
nat-archive-tool/src/nat/ArchiveController.kiss

188 lines
9.0 KiB
Plaintext

(load "Lib.kiss")
(method :Void _collectAndValidateArg [:CommandArg arg :Dynamic->Void continuation]
(case arg.type
(SelectedEntry
(if (= 1 selectedEntries.length)
(continuation (first selectedEntries))
(ui.reportError "The requested command expects 1 entry to be selected. You have selected: $selectedEntries.length")))
((SelectedEntries min max)
(unless min (set min 0))
// TODO might want to optimize this O(n) count operation by pre-calculating it
(unless max (set max (count archive.entries)))
(if !(<= min selectedEntries.length max)
(ui.reportError "The requested command expects between $min and $max entries to be selected. You have selected: $selectedEntries.length")
(continuation selectedEntries)))
((Text maxLength)
(unless maxLength (set maxLength Math.POSITIVE_INFINITY))
(ui.enterText
"${arg.name} (up to ${maxLength} characters):"
(lambda :Void [text]
(if !(<= text.length maxLength)
(ui.reportError "The requested command expected a string up to $maxLength characters long. You entered: $text.length characters")
(continuation text)))
maxLength))
((VarText maxLength)
(unless maxLength (set maxLength Math.POSITIVE_INFINITY))
(let [collectedText
[]
&mut :Void->Void enterTextAgain
null
_enterTextAgain
->:Void
(ui.enterText
"${arg.name} (up to ${maxLength} characters):"
(lambda :Void [text]
(if !text
(continuation collectedText)
(if !(<= text.length maxLength)
(ui.reportError "The requested command expected a list of strings up to $maxLength characters long. You entered: $text.length characters")
{(collectedText.push text)
(enterTextAgain)})))
maxLength)]
(set enterTextAgain _enterTextAgain)
(enterTextAgain)))
((Number min max inStepsOf)
(unless min (set min Math.NEGATIVE_INFINITY))
(unless max (set max Math.POSITIVE_INFINITY))
(let [&mut prompt "${arg.name} (${min}-${max}"]
(when inStepsOf
(+= prompt " in steps of ${inStepsOf}"))
(+= prompt "):")
(ui.enterNumber
prompt
(lambda :Void [number]
(let [minMaxError
"The requested command expected a number between $min and $max"
stepError
"$minMaxError in steps of $inStepsOf"
youEntered
". You entered: $number"]
(if (or
!(<= min number max)
(and inStepsOf !(= 0 (% (- number min) inStepsOf))))
(if inStepsOf
(ui.reportError "${stepError}$youEntered")
(ui.reportError "${minMaxError}$youEntered"))
(continuation number))))
min
max
inStepsOf)))
(OneEntry
(ui.chooseEntry
"${arg.name}:"
archive
continuation))
((Entries min max)
(unless min (set min 1))
// TODO might want to optimize this O(n) count operation by pre-calculating it
(unless max (set max (count archive.entries)))
(ui.chooseEntries
"${arg.name}:"
archive
(lambda :Void [:Array<Entry> entries]
(if (or
(> min entries.length)
(< max entries.length))
(ui.reportError "The requested command expects between $min and $max entries. You chose: $entries.length")
(continuation entries)))
min
max))))
(method :Void->Void _composeArgCollector [:Array<Dynamic> collectedArgs :CommandArg arg :Void->Void lastCollector]
(lambda :Void []
(_collectAndValidateArg arg ->:Void [:Dynamic argValue] {(collectedArgs.push argValue) (lastCollector)})))
(method :Void runCommand [:Command command]
(let [collectedArgs
[]
&mut lastCollector
(lambda []
(set lastChangeSet (the ChangeSet (Reflect.callMethod null command.handler collectedArgs)))
(ui.handleChanges archive lastChangeSet))]
// To facilitate asynchronous arg input via UI, we need to construct an insanely complicated nested callback to give the UI
(doFor arg (reversed command.args)
(set lastCollector (_composeArgCollector collectedArgs arg lastCollector)))
(lastCollector)))
(defMacro defCommand [name args &body body]
(let [argPairs
(groups (expList args) 2)
methodArgs
(for [name type] argPairs
// TODO write an exprCase macro that simplifies this terrible mess,
// and maybe adds back precise pattern matching instead of relying
// on partial string matching
(exprCase type
((exprOr SelectedEntry OneEntry) `:nat.Entry ,name)
((exprOr (SelectedEntries _ _) (Entries _ _)) `:Array<nat.Entry> ,name)
((Text _) `:String ,name)
((VarText _) `:Array<String> ,name)
((Number _ _ _) `:Float ,name)))
commandArgs
(for [name type] argPairs
`(object name ,(symbolName name) type ,type))]
`{
(method ,name [,@methodArgs] ,@body)
(dictSet commands ,(symbolName name) (object args [,@commandArgs] handler (the Function ,name)))}))
(defNew [&prop :Archive archive
&prop :ArchiveUI ui]
[&mut :Array<Entry> selectedEntries []
&mut :ChangeSet lastChangeSet []
:Map<String,Command> commands (new Map)]
(defCommand selectEntry [e OneEntry]
(set selectedEntries [e]) [])
(defCommand selectEntries [entries (Entries null null)]
(set selectedEntries entries) [])
(defCommand selectAllEntries []
(set selectedEntries (for =>id e archive.entries e)) [])
(defCommand selectLastChangeSet []
(set selectedEntries lastChangeSet) [])
(defCommand printSelectedEntries [entries (SelectedEntries null null)]
(doFor e entries (ui.displayMessage (archive.fullString e))) [])
(defCommand printComponent [entries (SelectedEntries null null)
componentType (Text null)]
(doFor e entries (ui.displayMessage (archive.componentData e componentType))) [])
(defCommand createEntry [name (Text null)]
[(archive.createEntry ->e
(addComponent archive e Name name))])
(defCommand createEntries [names (VarText null)]
(flatten (for name names
(createEntry name))))
(defCommand addTags [entries (SelectedEntries 1 null)
tagsToAdd (VarText null)]
(doFor e entries
(withWritableEntry archive e
(if (hasComponent e Tags)
(withWritableComponents archive e [tags Tags]
(doFor tag tagsToAdd (dictSet tags tag 1)))
(addComponent archive e Tags (for tag tagsToAdd =>tag 1)))))
entries) // TODO this includes entries that already had the tag in the changeset
(defCommand removeTags [entries (SelectedEntries 1 null)
tagsToRemove (VarText null)]
(doFor e entries
(withWritableEntry archive e
(when (hasComponent e Tags)
(withWritableComponents archive e [tags Tags]
(doFor tag tagsToRemove (tags.remove tag))))))
entries) // TODO this includes entries that didn't have the tag in the changeset
(defCommand selectByTags [tagsBoolExp (Text null)]
(selectEntries (filter archive.entries ->e (tagsMatch archive e tagsBoolExp))))
(defCommand selectByComponents [componentsBoolExp (Text null)]
(selectEntries (filter archive.entries ->e (componentsMatch e componentsBoolExp)))))