sindresorhus / KeyboardShortcuts

⌨️ Add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes
https://swiftpackageindex.com/sindresorhus/KeyboardShortcuts/documentation/keyboardshortcuts/keyboardshortcuts
MIT License
2.02k stars 186 forks source link

Custom storage #18

Open sindresorhus opened 4 years ago

sindresorhus commented 4 years ago

The defaults here are great and will work for most, but some users will need to store the keyboard shortcuts differently. For example, they are migrating from a different keyboard shortcut solution or they have a different preferred way of storing the shortcuts. I think we could expose a few storage-related hooks that would solve a lot of use-cases in one go.

We could add a protocol with a few required methods:

The default implementation of this protocol would be the current UserDefaults storage, but a user could override it like this KeyboardShortcuts.storage = myStorage if needed.

Thoughts? Am I over-designing this?

fredlb commented 1 year ago

I would love this! My use case is that I register shortcuts dynamically, and would preferably store this like I store the rest of the data (a JSON file that the user can load/save). In fact, just NOT storing it in defaults would make it better for my use case (but I agree that its a sane default behavior).

fredlb commented 1 year ago

@sindresorhus I took a stab at this here https://github.com/fredlb/KeyboardShortcuts/pull/1 Heavy disclaimer: I'm a Swift-noob so this is likely not the abstraction you want, but it works for my case. I'm struggling a bit with understanding how to initialize KeyboardShortcuts unless I do through Name, so that's what I went for here.

An implementation example of StorageProvider:

class InMemoryStorageProvider: StorageProvider {

    private var storage: [String:String?] = [:]

    func set(_ value: String?, forKey defaultName: String) {
        self.storage.updateValue(value, forKey: defaultName)
    }

    func remove(forKey defaultName: String) {
        self.storage.removeValue(forKey: defaultName)
    }

    func get(forKey defaultName: String) -> String? {
        guard let data = self.storage[defaultName] else {
            return nil
        }
        return data
    }

    func getAll() -> [String: String?] {
        return storage
    }

}

I would love some feedback on this, and I'll file a proper PR if we want to go down this route :)

sindresorhus commented 1 year ago

The problem with a solution like this (that I did not think of when initially proposing it) is that in an app you may want to both use the default storage, but also have a custom storage for some shortcuts. That's actually the case for one of my apps. I have a global keyboard shortcut for toggling the app (using the default storage), but also keyboard shortcuts per item (custom storage).

sindresorhus commented 1 year ago

I wonder if it would be simpler and more flexible to make KeyboardShortcuts.Recorder have an overload that accepts a Binding<String>. That way you could store it any way you'd like. The problem then is coming up with a good way to handle events and toggling the shortcut.