generated from obsidianmd/obsidian-sample-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
486 additions
and
270 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { Plugin, TFile, WorkspaceLeaf } from 'obsidian' | ||
import { pick } from 'lodash' | ||
import { Indexer } from './backend/Indexer' | ||
import { | ||
DEFAULT_SETTINGS, | ||
ImagePickerSettings, | ||
ImagePickerSettingTab, | ||
} from './ImagePickerSettings' | ||
import { ImagePickerView } from './ImagePickerView' | ||
import { VALID_IMAGE_EXTENSIONS, VIEW_TYPE_IMAGE_PICKER } from './constants' | ||
|
||
export class ImagePicker extends Plugin { | ||
settings: ImagePickerSettings | ||
images: TFile[] = [] | ||
indexer: Indexer = new Indexer(this) | ||
|
||
log = (...args: any[]) => { | ||
if (this.settings?.debugMode) { | ||
console.log('ImagePicker -> ', ...args) | ||
} | ||
} | ||
|
||
async onload() { | ||
await this.loadSettings() | ||
|
||
// This adds a settings tab so the user can configure various aspects of the plugin | ||
this.addSettingTab(new ImagePickerSettingTab(this.app, this)) | ||
|
||
this.addRibbonIcon('image', 'Open Image Picker', async () => { | ||
this.activateView() | ||
}) | ||
|
||
this.registerView( | ||
VIEW_TYPE_IMAGE_PICKER, | ||
(leaf) => new ImagePickerView(this, leaf) | ||
) | ||
|
||
this.app.vault.on('create', this.onFileCreate) | ||
this.app.vault.on('modify', this.onFileChange) | ||
this.app.vault.on('delete', this.onFileDelete) | ||
} | ||
|
||
onunload() { | ||
this.app.vault.off('create', this.onFileCreate) | ||
this.app.vault.off('modify', this.onFileChange) | ||
this.app.vault.off('delete', this.onFileDelete) | ||
} | ||
|
||
onFileCreate = async (file: TFile) => { | ||
if (file instanceof TFile) { | ||
if ( | ||
file.path.startsWith(this.settings.imageFolder) && | ||
VALID_IMAGE_EXTENSIONS.includes(file.extension) | ||
) { | ||
this.log('onFileCreate:', file.path) | ||
this.indexer.setIndex({ | ||
[file.path]: { | ||
...pick(file, ['basename', 'extension', 'stat', 'path', 'name']), | ||
uri: this.app.vault.getResourcePath(file), | ||
}, | ||
}) | ||
this.indexer.notifySubscribers() | ||
} | ||
} | ||
} | ||
|
||
onFileDelete = async (file: TFile) => { | ||
if (file instanceof TFile) { | ||
if ( | ||
file.path.startsWith(this.settings.imageFolder) && | ||
VALID_IMAGE_EXTENSIONS.includes(file.extension) | ||
) { | ||
this.log('onFileDelete:', file.path) | ||
this.indexer.removeIndex(file.path) | ||
this.indexer.notifySubscribers() | ||
} | ||
} | ||
} | ||
|
||
onFileChange = async (file: TFile) => { | ||
if (file instanceof TFile) { | ||
if ( | ||
file.path.startsWith(this.settings.imageFolder) && | ||
VALID_IMAGE_EXTENSIONS.includes(file.extension) | ||
) { | ||
this.indexer.setIndex({ | ||
[file.path]: { | ||
...pick(file, ['basename', 'extension', 'stat', 'path', 'name']), | ||
uri: this.app.vault.getResourcePath(file), | ||
}, | ||
}) | ||
this.indexer.notifySubscribers() | ||
} | ||
} | ||
} | ||
|
||
async activateView() { | ||
const { workspace } = this.app | ||
|
||
let leaf: WorkspaceLeaf | null = null | ||
const leaves = workspace.getLeavesOfType(VIEW_TYPE_IMAGE_PICKER) | ||
|
||
if (leaves.length > 0) { | ||
// A leaf with our view already exists, use that | ||
leaf = leaves[0] | ||
} else { | ||
// Our view could not be found in the workspace, create a new leaf | ||
// in the right sidebar for it | ||
leaf = workspace.getRightLeaf(false) | ||
await leaf?.setViewState({ type: VIEW_TYPE_IMAGE_PICKER, active: true }) | ||
} | ||
|
||
// "Reveal" the leaf in case it is in a collapsed sidebar | ||
if (leaf) { | ||
workspace.revealLeaf(leaf) | ||
} | ||
} | ||
|
||
async loadSettings() { | ||
this.log('Loading settings...') | ||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) | ||
} | ||
|
||
async saveSettings() { | ||
this.log('Saving settings:', this.settings) | ||
await this.saveData(this.settings) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { App, PluginSettingTab, Setting } from 'obsidian' | ||
import { ImagePicker } from './ImagePicker' | ||
|
||
export interface ImagePickerSettings { | ||
imageFolder: string | ||
animateGifs: boolean | ||
debugMode: boolean | ||
} | ||
|
||
export const DEFAULT_SETTINGS: ImagePickerSettings = { | ||
imageFolder: '', | ||
animateGifs: false, | ||
debugMode: false, | ||
} | ||
|
||
export class ImagePickerSettingTab extends PluginSettingTab { | ||
plugin: ImagePicker | ||
|
||
constructor(app: App, plugin: ImagePicker) { | ||
super(app, plugin) | ||
this.plugin = plugin | ||
} | ||
|
||
display(): void { | ||
const { containerEl } = this | ||
containerEl.empty() | ||
|
||
// Input for selecting the image folder | ||
new Setting(containerEl) | ||
.setName('Image Folder') | ||
.setDesc( | ||
'Image picker will look for images in this folder and its subfolders, by default it will look in the root of the vault' | ||
) | ||
.addText((text) => | ||
text | ||
.setPlaceholder('Image Folder') | ||
.setValue(this.plugin.settings.imageFolder) | ||
.onChange(async (value) => { | ||
this.plugin.settings.imageFolder = value || '' | ||
await this.plugin.saveSettings() | ||
}) | ||
) | ||
|
||
// Button for resetting the image index | ||
new Setting(containerEl) | ||
.setName('Reset Image Index') | ||
.setDesc( | ||
'Clears the image index and rebuilds it from the image folder. Obsidian will reload immediately after. Please run this after changing the image folder.' | ||
) | ||
.addButton((button) => | ||
button.setButtonText('Reset Index').onClick(async () => { | ||
this.plugin.images = [] | ||
// delete the database and rebuild it | ||
await this.plugin.indexer.resetDB() | ||
// reload obsidian | ||
// @ts-ignore | ||
this.app.commands.executeCommandById('app:reload') | ||
}) | ||
) | ||
|
||
// Toggle whether gifs are animated | ||
new Setting(containerEl) | ||
.setName('Animate GIFs') | ||
.setDesc('Warning: large gifs can slow down or crash Obsidian') | ||
.addToggle((toggle) => | ||
toggle | ||
.setValue(this.plugin.settings.animateGifs) | ||
.onChange(async (value) => { | ||
this.plugin.settings.animateGifs = value | ||
await this.plugin.saveSettings() | ||
}) | ||
) | ||
|
||
// Toggle whether to log debug messages | ||
new Setting(containerEl) | ||
.setName('Debug Mode') | ||
.setDesc('Log debug messages to the console') | ||
.addToggle((toggle) => | ||
toggle | ||
.setValue(this.plugin.settings.debugMode) | ||
.onChange(async (value) => { | ||
this.plugin.settings.debugMode = value | ||
await this.plugin.saveSettings() | ||
}) | ||
) | ||
|
||
containerEl.createEl('hr') | ||
|
||
const credits = containerEl.createEl('div') | ||
credits.innerHTML = ` | ||
Built with 💚 by <a href="https://ari.the.elk.wtf">ari.the.elk</a><br /> | ||
📖 <a href="https://ari.the.elk.wtf/obsidian/plugins/image-picker">documentation</a><br /> | ||
💝 <a href="https://ari.the.elk.wtf/donate">donate</a>` | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import React from 'react' | ||
import { ItemView, WorkspaceLeaf } from 'obsidian' | ||
import { Root, createRoot } from 'react-dom/client' | ||
import { ImagePickerView as ReactImagePickerView } from './client/ImagePickerView' | ||
import { ImagePickerContext } from './client/ImagePickerContext' | ||
import { ImagePicker } from './ImagePicker' | ||
import { VIEW_TYPE_IMAGE_PICKER } from './constants' | ||
|
||
// Image picker view class | ||
export class ImagePickerView extends ItemView { | ||
root: Root | null = null | ||
|
||
constructor(public plugin: ImagePicker, leaf: WorkspaceLeaf) { | ||
super(leaf) | ||
} | ||
|
||
getViewType() { | ||
return VIEW_TYPE_IMAGE_PICKER | ||
} | ||
|
||
getDisplayText() { | ||
return 'Image Picker' | ||
} | ||
|
||
getIcon(): string { | ||
return 'image' | ||
} | ||
|
||
mountReact = async () => { | ||
this.root = createRoot(this.containerEl.children[1]) | ||
this.root.render( | ||
<ImagePickerContext.Provider | ||
value={{ | ||
app: this.app, | ||
plugin: this.plugin, | ||
files: Object.values(await this.plugin.indexer.getIndex()), | ||
}} | ||
> | ||
<ReactImagePickerView /> | ||
</ImagePickerContext.Provider> | ||
) | ||
} | ||
|
||
unmountReact = () => { | ||
this.root?.unmount() | ||
this.containerEl.children[1].empty() | ||
} | ||
|
||
async onOpen() { | ||
this.plugin.log('Opening root:', this.plugin.images.length) | ||
await this.mountReact() | ||
|
||
this.plugin.indexer.subscribe(async (newIndex) => { | ||
this.plugin.log('Rerendering root:', Object.keys(newIndex).length) | ||
// this.mountReact() | ||
this.root?.render( | ||
<ImagePickerContext.Provider | ||
value={{ | ||
app: this.app, | ||
plugin: this.plugin, | ||
files: Object.values(newIndex), | ||
}} | ||
> | ||
<ReactImagePickerView /> | ||
</ImagePickerContext.Provider> | ||
) | ||
}) | ||
} | ||
|
||
async onClose() { | ||
this.root?.unmount() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.