// shortcut: icon list v2

Comment
| Made by pfg
| https://routinehub.co/user/pfg
| /u/pfg___
| pfg#4865

GetDictionaryFromInput
SetVariable Input

Text "Icon List V2" -> v:ThisShortcut

createfolder service="iCloud Drive" "/IconListV2"
createfolder service="iCloud Drive" "/IconListV2/MyLists"

Dictionary{
    name=v:ThisShortcut
    version="0.1.0"
} -> mv:CFUDictionary
getfile "iCloud Drive" false "AutoUpdateData.json" false
SetDictionaryValue "2140" mv:CFUDictionary
savefile "iCloud Drive" false "AutoUpdateData.json" true

/*
// Usage: Escape "text"
@defmacro Escape @:text{type:text}
    runShortcut v:ThisShortcut false ^(Dictionary{ action: Escape, text: "\(@:text)" })
@defend
*/

GetVariable v:Input:action

@set Escape (runShortcut v:ThisShortcut false ^(Dictionary{ action: Escape, text: @:text }))
// @:Escape{text:hi}
// ————————————————————-----
// escapes a string for use in a vcard
if Equals "Escape"
    GetVariable v:Input:text
    replacetext "\\" "\\\\"
    replacetext (Text "\n") " "
    replacetext ";" "\\;"
    exitShortcut
otherwise
end

@set CreateCard (runShortcut v:ThisShortcut false ^(Dictionary{ action: CreateCard, carddata: @:carddata }))
// runShortcut v:ThisShortcut false ^(Dictionary{ action: CreateCard, carddata: {name, description, note, photo?} })
// ————————————————————-----
// creates a text out of a dictionary containing card data
if Equals "CreateCard" 
    getVariable v:Input:carddata -> v:CardData

    if ^(Count "Characters" ^(v:CardData:photo)) "Equals" 1
        text ""
    otherwise
        text "PHOTO;ENCODING=b:\(v:CardData:photo)"
    end -> mv:Photo

    if ^(Count "Characters" ^(v:CardData:name)) "Equals" 0
        text "No Title"
    otherwise
        getVariable v:CardData:name
    end -> mv:Name

    text -> mv:Result
    | BEGIN:VCARD
    | VERSION:3.0
    | N;CHARSET=utf-8:\(@:Escape{text: mv:Name});;;
    | ORG;CHARSET=utf-8:\(@:Escape{text: v:CardData:description})
    | NOTE;CHARSET=utf-8:\(@:Escape{text: v:CardData:note})
    | \(mv:Photo)
    | END:VCARD

    // return as text
    exitShortcut ^(mv:Result)
otherwise
end

@set AsContact (runShortcut v:ThisShortcut false ^(Dictionary{ action: AsContact, vcard: @:vcard })) // could be vcard: sv:ActionInput and then you could call it like Text "hi"; @:AsContact
// runShortcut v:ThisShortcut false ^(Dictionary{ action: AsContact, vcard: "<vCard Text Here>" }) 
// ————————————————————-----
// converts a given vcard string to a contact
if Equals "AsContact"
    getVariable v:Input:vcard
    setName "a.vcf" -> mv:vCard
    exitShortcut ^(mv:vCard{as:Contact})
otherwise
end

@set ChoosePhoto (runShortcut v:ThisShortcut false ^(Dictionary{ action: ChoosePhoto }))
// runShortcut v:ThisShortcut false ^(Dictionary{ action: ChoosePhoto }) 
// ————————————————————-----
// prompts to select a photo
if Equals "ChoosePhoto"
    selectphotos selectmultiple=false -> mv:SelectedPhoto

    // resize to 123x123 (@3x)
    resizeimage width=123 -> v:Image
    getVariable v:Image{as:Image,get:Height}
    
    if "Is Greater Than" 123
        getVariable ^(v:Image) -> v:FilledImage
        resizeimage ^(mv:SelectedPhoto) width="" height=123 -> v:Image
    otherwise
        resizeimage ^(mv:SelectedPhoto) width="" height=123 -> v:FilledImage
    end
    
    // place on top of a transparent image to make a 123x123
    Text "R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAICTAEAOw=="
    base64encode mode="Decode"
    resizeimage width=123 height=123 -> mv:TransparentImage

    overlayimage ^(mv:TransparentImage) v:Image showimageeditor=false position="Center" -> v:Image
    overlayimage ^(mv:TransparentImage) v:Image showimageeditor=false position="Center" width=87 height=87 -> v:FitImage
    overlayimage ^(mv:TransparentImage) v:FilledImage showimageeditor=false position="Center" -> v:FilledImage
    
    base64encode ^(v:Image) mode="Encode" linebreaks="None" -> v:Image
    base64encode ^(v:FitImage) mode="Encode" linebreaks="None" -> v:FitImage
    base64encode ^(v:FilledImage) mode="Encode" linebreaks="None" -> v:FilledImage
    

    // show both and pick which to use
    text -> v:vCard
    | BEGIN:VCARD\nVERSION:3.0\nPHOTO;ENCODING=b:\(v:FilledImage)\nNOTE;CHARSET=utf-8:filledImage\nN;CHARSET=utf-8:Fill Circle;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nPHOTO;ENCODING=b:\(v:Image)\nNOTE;CHARSET=utf-8:standardImage\nN;CHARSET=utf-8:Original Size;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nPHOTO;ENCODING=b:\(v:FitImage)\nNOTE;CHARSET=utf-8:fitImage\nN;CHARSET=utf-8:Fit in Circle;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:noImage\nN;CHARSET=utf-8:No Image;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:differentPhoto\nN;CHARSET=utf-8:Choose Different Photo;;;\nEND:VCARD
    
    @:AsContact{vcard:vCard}
    chooseFromList -> mv:ChosenItem

    getVariable mv:ChosenItem{as:Contact,get:Notes}

    if Equals "filledImage"; getVariable v:FilledImage; otherwise; end
    if Equals "standardImage"; getVariable v:Image; otherwise; end
    if Equals "fitImage"; getVariable v:FitImage; otherwise; end
    if Equals "noImage"; text " "; otherwise; end
    if Equals "differentPhoto"; runShortcut v:ThisShortcut false ^(Dictionary{ action: ChoosePhoto }); otherwise; end

    exitShortcut
otherwise
end

@set EditItem (runShortcut v:ThisShortcut false ^(Dictionary{ action: EditItem, carddata: @:carddata }))
// runShortcut v:ThisShortcut false ^(Dictionary{ action: EditItem, carddata: {name, description, note, photo} }) 
// ————————————————————-----
// edits a given item
if Equals "EditItem"

    getVariable v:Input:carddata -> v:CardData

    nothing -> v:ActionList
    
    // 
    text -> mv:vCard
    | \(@:CreateCard{carddata: v:CardData})
    | BEGIN:VCARD\nVERSION:3.0\nN;CHARSET=utf-8:—————;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:setIcon\nN;CHARSET=utf-8:Icon;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:setName\nN;CHARSET=utf-8:Title;;;\nORG;CHARSET=utf-8:\(@:Escape{text: v:CardData:name})\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:setDescription\nN;CHARSET=utf-8:Description;;;\nORG;CHARSET=utf-8:\(@:Escape{text: v:CardData:description})\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:setNote\nN;CHARSET=utf-8:Note (Hidden);;;\nORG;CHARSET=utf-8:\(@:Escape{text: v:CardData:note})\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nN;CHARSET=utf-8:—————;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:doneEditing\nN;CHARSET=utf-8:Save;;;\nEND:VCARD

    @:AsContact{vcard: mv:vCard}

    chooseFromList -> mv:ChosenItem

    getVariable mv:ChosenItem{as:Contact,get:Notes} -> mv:Notes

    if Equals "setIcon"
        v:CardData = setDictionaryValue ^(v:CardData) "photo" (@:ChoosePhoto)

        nothing
    otherwise
    end
    

    if Equals "setName"
        askforinput question="New Name" defaultanswer=v:CardData:name -> mv:NewValue
        v:CardData = setDictionaryValue ^(v:CardData) "name" mv:NewValue

        nothing
    otherwise
    end

    if Equals "setDescription"
        askforinput question="New Description" defaultanswer=v:CardData:description -> mv:NewValue
        v:CardData = setDictionaryValue ^(v:CardData) "description" mv:NewValue
        
        nothing
    otherwise
    end

    if Equals "setNote"
        askforinput question="New Note" defaultanswer=v:CardData:note -> mv:NewValue
        v:CardData = setDictionaryValue ^(v:CardData) "note" mv:NewValue
        
        nothing
    otherwise
    end

    if Equals "doneEditing"
        exitShortcut ^(v:CardData)
    otherwise
    end

    @:EditItem{carddata: v:CardData}
    exitShortcut
otherwise
end

@set NewItem (runShortcut v:ThisShortcut false ^(Dictionary{ action: EditItem, carddata: {name: "", description: "", note: "", photo: " "} }))
// ————————————————————-----
// creates a new list item and starts editing it



@set RemoveItemAtIndex @{
    Dictionary{ action: RemoveItemAtIndex, list: [], index: @:index }
    setDictionaryValue list @:list // not needed here but for other shortcuts, count items if equals 0 don't do this end
    runshortcut v:ThisShortcut false
}
// Dictionary{ action: RemoveItemAtIndex, list: [], index: num }
// setDictionaryValue list [...items]
// runShortcut v:ThisShortcut false
// ————————————————————-----
// removes a specified item from a given list
if Equals "RemoveItemAtIndex"
    getVariable v:Input:index -> v:Index
    getVariable v:Input:list -> v:List
    count Items -> v:Length

    // If the list has one item, return an empty list
    if ^(v:Length) Equals 1
        list []
        exitShortcut
    otherwise
    end

    // If the index is 1, return 2..
    if ^(v:Index) Equals 1
        getitemfromlist ^(v:List) get="Items in Range" 2 v:Length
        exitShortcut
    otherwise
    end

    // If the index is equal to the length, return 1..length-1
    if ^(v:Index) Equals v:Length
        getitemfromlist ^(v:List) get="Items in Range" 1 (Calculate ^(v:Length) "-" 1)
        exitShortcut
    otherwise
    end

    // Otherwise get items 1..Index-1 and get items Index+1..Length

    getitemfromlist ^(v:List) get="Items in Range" 1 (Calculate ^(v:Index) "-" 1)
    setVariable v:ResList
    getitemfromlist ^(v:List) get="Items in Range" (Calculate ^(v:Index) "+" 1) v:Length
    addToVariable v:ResList

    exitShortcut
otherwise
end


@set AddItemAtIndex @{
    Dictionary{ action: AddItemAtIndex, list: [], item: @:item, index: @:index }
    setDictionaryValue list @:list // not needed here but for other shortcuts, count items if equals 0 don't do this end
    runshortcut v:ThisShortcut false
}
// Dictionary{ action: AddItemAtIndex, list: [], item: any, index: num }
// setDictionaryValue list [...items]
// runShortcut v:ThisShortcut false
// ————————————————————-----
// inserts a specified item at a specified index of a specified list
if Equals "AddItemAtIndex"
    getVariable v:Input:index -> v:Index
    getVariable v:Input:item -> v:ItemToAdd
    getVariable v:Input:list -> v:List
    count Items -> v:Length

    // If the list has zero items, return a list with the item
    if ^(v:Length) Equals 0
        getVariable v:ItemToAdd
        exitShortcut
    otherwise
    end

    // If the index is 1, return a list with the item and the list
    if ^(v:Index) Equals 1
        getVariable v:ItemToAdd
        setVariable v:ResList
        getVariable v:List
        addToVariable v:ResList
        
        exitShortcut
    otherwise
    end

    // If the index is 1, return a list with the list and the item
    if ^(v:Index) Equals (Calculate ^(v:Length) "+" 1)
        getVariable v:List
        setVariable v:ResList
        getVariable v:ItemToAdd
        addToVariable v:ResList
        
        exitShortcut
    otherwise
    end

    // Otherwise, get items 1..index-1 and index..end and add the item in the middle
    getitemfromlist ^(v:List) get="Items in Range" 1 (Calculate ^(v:Index) "-" 1)
    setVariable v:ResList

    getVariable v:ItemToAdd
    addToVariable v:ResList

    getitemfromlist ^(v:List) get="Items in Range" v:Index v:Length
    addToVariable v:ResList

    exitShortcut
otherwise
end

@set QuickAdd (runShortcut v:ThisShortcut false ^(Dictionary{ action: QuickAdd }))
// runShortcut v:ThisShortcut false ^(Dictionary{ action: QuickAdd })
// ————————————————————-----
// starts QuickAdd to quickly add a list of items
if Equals "QuickAdd"
    showResult
    | QuickAdd lets you quickly add a list of items with titles, note values, and descriptions all at once.
    |
    | Items should be added as 'Item Title/Note Value/Description'
    

    choosefrommenu "Use QuickAdd?"
    | Start QuickAdd
    | Don't
    case "start"
    case "don't"
        nothing
        exitShortcut
    end
    
    List ["Title/Note/Description\(s:askwhenrun)"]
    repeatWithEach
        if Equals "Title/Note/Description"
            nothing
        otherwise

            splittext separator="Custom" custom="/" -> v:TitleNoteDescription

            List ["", "", ""]
            addToVariable v:TitleNoteDescription // To prevent out of range errors

            getitemfromlist ^(v:TitleNoteDescription) "Item At Index" 1 -> mv:Title
            getitemfromlist ^(v:TitleNoteDescription) "Item At Index" 2 -> mv:Note
            getitemfromlist ^(v:TitleNoteDescription) "Item At Index" 3 -> mv:Description
            
            Dictionary{
                name: mv:Title
                note: mv:Note
                description: mv:Description
                photo: " "
            }
        end if
    end
    
    exitShortcut
otherwise
end


@set FindUnusedFilename (runShortcut v:ThisShortcut false ^(Dictionary{ action: FindUnusedFilename }))
// runShortcut v:ThisShortcut false ^(Dictionary{ action: FindUnusedFilename })
// ————————————————————-----
// asks for a name
if Equals "FindUnusedFilename"

    askForInput "Pick a name for this list" -> mv:Filename
    
    getFile "iCloud Drive" false "/IconListV2/MyLists/\(mv:Filename).iconlist2" false
    count Items

    if Equals 0
        getVariable mv:Filename
        exitShortcut
    otherwise
        showResult "The filename \(mv:Filename) is already taken"
    end

    @:FindUnusedFilename

    exitShortcut
otherwise
end

// to move an item, repeat over each item of the list adding an item before it with "Add here" and note index

@set EditList @{
    Dictionary{ action: EditList, items: [], filename: @:filename }
    setDictionaryValue items @:items // not needed here but for other shortcuts, count items if equals 0 don't do this end
    runshortcut v:ThisShortcut false
}
// Dictionary{ action: EditList, items: [], filename: string||"" }
// setDictionaryValue items [...items]
// runShortcut v:ThisShortcut false
// ————————————————————-----
// edits a given list of items
// remember, to pass a list into a dictionary you need to create a dictionary with no list and set dictionary value
if Equals "EditList"

    getVariable v:Input:items -> v:Items
    getVariable v:Input:filename -> v:Filename

    if ^(count ^(v:Filename) Characters) Equals 0
        text -> v:SaveItem
        | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:saveList\nN;CHARSET=utf-8:! Save List;;;\nORG;CHARSET=utf-8:This list is not saved and may be lost.\nEND:VCARD

        text "⚠️ This list is not saved." -> v:StatusText
    otherwise
        // save the file
        dictionary{list = []}
        setDictionaryValue list v:Items // Saves the file as {list: [...items]}
        setName "\(v:Filename).iconlist2" advanced=true true
        // why do you have to "don't include extension" to include extension? because you do.
        savefile a{
          service="iCloud Drive",
          askwheretosave=false,
          destinationpath="/IconListV2/MyLists/\(v:Filename).iconlist2",
          overwriteiffileexists=true
        }
        
        text "✅ This list is saved to `IconListV2/MyLists/\(v:Filename)`" -> v:StatusText
        text -> v:SaveItem
        | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:saveList\nN;CHARSET=utf-8:Save As;;;\nEND:VCARD
    end

    repeatWithEach ^(v:Items)

        if Equals "_blank_" // to make sure there is always at least one item in the list

            nothing
        otherwise

            getVariable v:"Repeat Item"
            setdictionaryvalue key="note" value=(v:"Repeat Index") -> mv:CardData
            @:CreateCard{carddata: mv:CardData}

        end
        
    end repeat

    combinetext separator="New Lines" -> mv:CurrentListvCard

    text -> mv:vCard
    | \(mv:CurrentListvCard)
    | BEGIN:VCARD\nVERSION:3.0\nN;CHARSET=utf-8:—————;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:addItem\nN;CHARSET=utf-8:+ Add Item;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:quickAdd\nN;CHARSET=utf-8:QuickAdd;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nN;CHARSET=utf-8:—————;;;\nEND:VCARD
    | \(v:SaveItem)
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:copyToClipboard\nN;CHARSET=utf-8:Export List;;;\nEND:VCARD

    @:AsContact{vcard: mv:vCard}
    
    chooseFromList v:StatusText -> mv:ChosenItem
    getVariable mv:ChosenItem{as:Contact, get:Notes} -> mv:ChosenData

    if Equals "addItem"
        @:NewItem
        addToVariable v:Items

        text "" // text is used because of an upcoming count characters
    otherwise
    end

    if Equals "quickAdd"
        @:QuickAdd
        addToVariable v:Items

        text ""
    otherwise
    end

    if Equals "saveList"
        @:FindUnusedFilename
        setVariable v:Filename

        text ""
    otherwise
    end

    if Equals "copyToClipboard"
        chooseFromMenu "Copy list to clipboard?"
        | Copy to clipboard
        | Nevermind
        case copy
            getVariable mv:CurrentListvCard
            copytoclipboard
            chooseFromMenu "The list has been copied.\n\(v:StatusText)"
            | Continue Editing
            case continue
            end
        case nevermind
        end

        text ""
    otherwise
    end

    count Characters // notes has 1 item but 0 characters even when the notes field is undefined... :thinking:

    if Equals 0
        nothing
    otherwise
        number mv:ChosenData -> mv:Index

        getVariable v:Items
        getitemfromlist a{
          get="Item At Index"
          index=mv:Index
        } -> mv:ItemToEdit

        @:EditItem{carddata: mv:ItemToEdit}
        setVariable v:NewItem

        // remove item from list
        @:RemoveItemAtIndex{list: v:Items, index: mv:Index}
        setVariable v:Items

        // insert item back into list at the index
        @:AddItemAtIndex{list: v:Items, index: mv:Index, item: v:NewItem}
        setVariable v:Items

        nothing
    end

    @:EditList{items: v:Items, filename: v:Filename}

    exitShortcut
otherwise
end

@set MainMenu (runShortcut v:ThisShortcut false ^(Dictionary{ action: MainMenu }))
// runShortcut v:ThisShortcut false ^(Dictionary{ action: MainMenu })
// ————————————————————-----
// the "main" screen of the shortcut
if Equals "MainMenu"
    getFile a{
      service="iCloud Drive",
      showdocumentpicker=false,
      filepath="IconListV2/MyLists/",
      errorifnotfound=false
    }
    repeatWithEach
        getVariable v:"Repeat Item"{as: File} -> mv:CurrentItem
        @:Escape{text: mv:CurrentItem{as:File,get:Name}}
        setVariable v:Name
        getVariable mv:CurrentItem
        setName "a.json"
        getDictionaryValue Value list
        count Items 
        calculate "-" 1 -> mv:Count // to remove _blank_
        if Equals 1; text "item"; otherwise; text "items"; end -> mv:IfResult
        Text
        | BEGIN:VCARD
        | VERSION:3.0
        | N;CHARSET=utf-8:\(v:Name);;;
        | ORG;CHARSET=utf-8:\(mv:Count) \(mv:IfResult)
        | NOTE;CHARSET=utf-8:editList
        | END:VCARD
    end
    combinetext separator="New Lines" -> mv:CombinedText

    Text -> mv:vCard
    | \(mv:CombinedText)
    | BEGIN:VCARD\nVERSION:3.0\nN;CHARSET=utf-8:—————;;;\nEND:VCARD
    | BEGIN:VCARD\nVERSION:3.0\nNOTE;CHARSET=utf-8:newList\nN;CHARSET=utf-8:+ New List;;;\nEND:VCARD

    @:AsContact{vcard: mv:vCard}

    chooseFromList -> mv:ChosenItem

    getVariable mv:ChosenItem{as:Contact,get:Notes} -> mv:Notes

    if Equals "newList"
        // the new list will have its own save button, we don't need to get the filename now
        runShortcut v:ThisShortcut false ^(Dictionary{ action: EditList, filename: "", items: ["_blank_"] })

        nothing
    otherwise
    end

    if Equals "oldList"

    otherwise
    end

    if Equals "editList"
        getVariable mv:ChosenItem
        getName -> mv:Name
        

        getFile "iCloud Drive" false "/IconListV2/MyLists/\(mv:Name).iconlist2" true
        setName "a.json"
        getDictionaryValue Value list -> mv:Items

        @:EditList{filename: mv:Name, items: mv:Items}

        nothing
    otherwise
    end
    
    
    @:MainMenu

    exitShortcut
otherwise
end

@:MainMenu
exitShortcut