Add clock updates setting (#106)

* feat: add enable clock updates setting

* feat: add event handler for clock updates change

* refactor: update description

* chore: bump version
This commit is contained in:
DecafDev 2024-06-23 13:25:46 -06:00 committed by GitHub
parent 8543d800aa
commit f4f5471ad7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 419 additions and 138 deletions

View file

@ -1,7 +1,7 @@
{
"id": "vault-explorer",
"name": "Vault Explorer",
"version": "1.12.1",
"version": "1.13.0",
"minAppVersion": "1.4.13",
"description": "Explore your vault in visual format",
"author": "DecafDev",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-vault-explorer",
"version": "1.12.1",
"version": "1.13.0",
"description": "Explore your vault in visual format",
"main": "main.js",
"scripts": {

View file

@ -40,7 +40,8 @@ export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
views: {
currentView: ViewType.GRID,
order: [ViewType.GRID, ViewType.LIST],
titleWrapping: "normal"
titleWrapping: "normal",
enableClockUpdates: true
},
pageSize: 50,
pluginVersion: null

View file

@ -1,3 +1,3 @@
export type PluginEvent = "file-rename" | "file-create" | "file-delete" | "file-modify" | "metadata-change" | "properties-filter-update" | "folder-rename" | "folder-delete" | "folder-create" | "page-size-setting-change" | "title-wrapping-setting-change" | "property-setting-change" | "device-registration-change";
export type PluginEvent = "file-rename" | "file-create" | "file-delete" | "file-modify" | "metadata-change" | "properties-filter-update" | "folder-rename" | "folder-delete" | "folder-create" | "page-size-setting-change" | "title-wrapping-setting-change" | "property-setting-change" | "device-registration-change" | "clock-updates-setting-change"
export type EventCallback = (...data: unknown[]) => void;

View file

@ -23,6 +23,7 @@ import { loadDeviceId } from './svelte/shared/services/device-id-utils';
import License from './svelte/shared/services/license';
import { VaultExplorerPluginSettings_1_8_1 } from './types/types-1.8.1';
import { VaultExplorerPluginSettings_1_9_1 } from './types/types-1.9.1';
import { VaultExplorerPluginSettings_1_12_1 } from './types/types-1.12.1';
export default class VaultExplorerPlugin extends Plugin {
settings: VaultExplorerPluginSettings = DEFAULT_SETTINGS;
@ -292,7 +293,7 @@ export default class VaultExplorerPlugin extends Plugin {
if (isVersionLessThan(settingsVersion, "1.10.0")) {
console.log("Upgrading settings from version 1.9.1 to 1.10.0");
const typedData = (data as unknown) as VaultExplorerPluginSettings_1_9_1;
const newData: VaultExplorerPluginSettings = {
const newData: VaultExplorerPluginSettings_1_12_1 = {
...typedData,
filters: {
...typedData.filters,
@ -321,6 +322,19 @@ export default class VaultExplorerPlugin extends Plugin {
}
data = newData as unknown as Record<string, unknown>;
}
if (isVersionLessThan(settingsVersion, "1.13.0")) {
console.log("Upgrading settings from version 1.12.1 to 1.13.0");
const typedData = (data as unknown) as VaultExplorerPluginSettings_1_12_1;
const newData: VaultExplorerPluginSettings = {
...typedData,
views: {
...typedData.views,
enableClockUpdates: true
}
}
data = newData as unknown as Record<string, unknown>;
}
}
//Apply default settings. This will make it so we don't need to do migrations for just adding new settings

View file

@ -162,6 +162,19 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
EventManager.getInstance().emit("property-setting-change");
}));
new Setting(containerEl).setName("Updates").setHeading();
new Setting(containerEl)
.setName("Enable clock updates")
.setDesc("When enabled, time values will update every minute. This will cause a refresh of the Vault Explorer view. When disabled, time values will only update when the view is opened.")
.addToggle(toggle => toggle
.setValue(this.plugin.settings.views.enableClockUpdates)
.onChange(async (value) => {
this.plugin.settings.views.enableClockUpdates = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("clock-updates-setting-change");
}));
new Setting(containerEl).setName("Premium").setHeading();
this.component = new Component({
@ -193,16 +206,6 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
});
}
private getSettingMessageClassName(type: "success" | "failure" | "default" = "default") {
let className = "vault-explorer-setting-message";
if (type === "success") {
className += " vault-explorer-setting-message--success";
} else if (type === "failure") {
className += " vault-explorer-setting-message--failure";
}
return className;
}
onClose() {
this.component?.$destroy();
}

View file

@ -1,11 +1,14 @@
<script lang="ts">
// ============================================
// Imports
// ============================================
import Stack from "../shared/components/stack.svelte";
import Flex from "../shared/components/flex.svelte";
import IconButton from "../shared/components/icon-button.svelte";
import Checkbox from "../shared/components/checkbox.svelte";
import TabList from "../shared/components/tab-list.svelte";
import Tab from "../shared/components/tab.svelte";
import { Menu, TFile, TFolder } from "obsidian";
import { Menu, TFile } from "obsidian";
import PropertiesFilterModal from "src/obsidian/properties-filter-modal";
import {
FilterGroup,
@ -35,30 +38,16 @@
import { FileRenderData } from "./types";
import Logger from "js-logger";
// ============================================
// Variables
// ============================================
let plugin: VaultExplorerPlugin;
let startOfTodayMillis: number;
let startOfThisWeekMillis: number;
let startOfLastWeekMillis: number;
function updateTimeValues() {
startOfTodayMillis = getStartOfTodayMillis();
startOfThisWeekMillis = getStartOfThisWeekMillis();
startOfLastWeekMillis = getStartOfLastWeekMillis();
}
onMount(() => {
updateTimeValues();
const interval = setInterval(updateTimeValues, 60000); // Update every minute
return () => {
clearInterval(interval);
};
});
let folders: string[] = [];
let pageSize: number = 0;
let searchFilter: string = "";
let sortFilter: SortFilter = "file-name-asc";
let timestampFilter: TimestampFilter = "all";
@ -67,52 +56,69 @@
let currentView: ViewType = ViewType.GRID;
let filterGroups: FilterGroup[] = [];
let selectedFilterGroupId: string = "";
let frontmatterCacheTime: number = Date.now();
let propertySettingTime: number = Date.now();
let files: TFile[] = [];
let timeValuesUpdateInterval: NodeJS.Timer | null = null;
const debounceSearchFilter = _.debounce((e) => {
searchFilter = e.target.value;
}, 300);
const debounceFavoriteFilter = _.debounce((value) => {
onlyFavorites = value;
}, 300);
function updateFrontmatterCacheTime() {
Logger.trace({
fileName: "app/index.ts",
functionName: "updateFrontmatterCacheTime",
message: "called",
});
frontmatterCacheTime = Date.now();
}
function updatePropertySettingTime() {
propertySettingTime = Date.now();
}
// ============================================
// Lifecycle hooks
// ============================================
store.plugin.subscribe((p) => {
plugin = p;
const allFiles = plugin.app.vault.getAllLoadedFiles();
folders = allFiles
.filter((file) => file instanceof TFolder)
.map((folder) => folder.path);
const { app, settings } = plugin;
files = app.vault.getFiles();
pageSize = settings.pageSize;
searchFilter = settings.filters.search;
sortFilter = settings.filters.sort;
timestampFilter = settings.filters.timestamp;
onlyFavorites = settings.filters.onlyFavorites;
currentView = settings.views.currentView;
viewOrder = settings.views.order;
filterGroups = settings.filters.custom.groups;
selectedFilterGroupId = settings.filters.custom.selectedGroupId;
files = plugin.app.vault.getFiles();
if (settings.views.enableClockUpdates) {
setTimeValuesUpdateInterval();
}
});
pageSize = plugin.settings.pageSize;
onMount(() => {
function handleClockUpdatesSettingChange() {
Logger.trace({
fileName: "app/index.ts",
functionName: "handleClockUpdatesSettingChange",
message: "called",
});
searchFilter = plugin.settings.filters.search;
sortFilter = plugin.settings.filters.sort;
timestampFilter = plugin.settings.filters.timestamp;
onlyFavorites = plugin.settings.filters.onlyFavorites;
currentView = plugin.settings.views.currentView;
viewOrder = plugin.settings.views.order;
filterGroups = plugin.settings.filters.custom.groups;
selectedFilterGroupId = plugin.settings.filters.custom.selectedGroupId;
const isEnabled = plugin.settings.views.enableClockUpdates;
if (isEnabled) {
updateTimeValues();
setTimeValuesUpdateInterval();
} else if (timeValuesUpdateInterval != null) {
clearInterval(timeValuesUpdateInterval);
}
}
updateTimeValues();
EventManager.getInstance().on(
"clock-updates-setting-change",
handleClockUpdatesSettingChange,
);
return () => {
if (timeValuesUpdateInterval != null)
clearInterval(timeValuesUpdateInterval);
EventManager.getInstance().off(
"clock-updates-setting-change",
handleClockUpdatesSettingChange,
);
};
});
onMount(() => {
@ -275,6 +281,46 @@
};
});
// ============================================
// Functions
// ============================================
const debounceSearchFilter = _.debounce((e) => {
searchFilter = e.target.value;
}, 300);
const debounceFavoriteFilter = _.debounce((value) => {
onlyFavorites = value;
}, 300);
function updateTimeValues() {
Logger.trace({
fileName: "app/index.ts",
functionName: "updateTimeValues",
message: "called",
});
startOfTodayMillis = getStartOfTodayMillis();
startOfThisWeekMillis = getStartOfThisWeekMillis();
startOfLastWeekMillis = getStartOfLastWeekMillis();
}
function setTimeValuesUpdateInterval() {
const MILLIS_MINUTE = 60000;
timeValuesUpdateInterval = setInterval(updateTimeValues, MILLIS_MINUTE);
}
function updateFrontmatterCacheTime() {
Logger.trace({
fileName: "app/index.ts",
functionName: "updateFrontmatterCacheTime",
message: "called",
});
frontmatterCacheTime = Date.now();
}
function updatePropertySettingTime() {
propertySettingTime = Date.now();
}
async function filterByCustomFilter() {
const promises: Promise<TFile | null>[] = [];
@ -312,66 +358,6 @@
return results.filter((file) => file !== null) as TFile[];
}
let filteredCustom: TFile[] = [];
$: if (frontmatterCacheTime && filterGroups) {
filterByCustomFilter().then((files) => {
filteredCustom = files;
});
}
let formatted: FileRenderData[] = [];
$: if (propertySettingTime) {
formatted = filteredCustom.map((file) => {
const frontmatter =
plugin.app.metadataCache.getFileCache(file)?.frontmatter;
return formatFileDataForRender(plugin.settings, file, frontmatter);
});
}
$: filteredSearch = formatted.filter((file) =>
filterBySearch(file, searchFilter),
);
$: filteredFavorites = filteredSearch.filter((file) =>
filterByFavorites(file, onlyFavorites),
);
$: filteredTimestamp = filteredFavorites.filter((file) => {
const { modifiedMillis, createdMillis } = file;
return filterByTimestamp({
createdMillis,
modifiedMillis,
timestampFilter,
startOfTodayMillis,
startOfThisWeekMillis,
startOfLastWeekMillis,
});
});
$: renderData = [...filteredTimestamp].sort((a, b) => {
if (sortFilter === "file-name-asc") {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
} else if (sortFilter === "file-name-desc") {
return b.name.toLowerCase().localeCompare(a.name.toLowerCase());
} else if (sortFilter === "modified-asc") {
return a.modifiedMillis - b.modifiedMillis;
} else if (sortFilter === "modified-desc") {
return b.modifiedMillis - a.modifiedMillis;
}
return 0;
});
$: searchFilter,
sortFilter,
timestampFilter,
onlyFavorites,
currentView,
viewOrder,
filterGroups,
selectedFilterGroupId,
saveSettings();
async function saveSettings() {
plugin.settings.filters.search = searchFilter;
plugin.settings.filters.sort = sortFilter;
@ -493,6 +479,10 @@
});
}
function handlePageChange(newPage: number) {
currentPage = newPage;
}
function openPropertiesFilterModal() {
new PropertiesFilterModal(plugin).open();
}
@ -578,17 +568,75 @@
debounceFavoriteFilter(value);
}
let currentPage = 1;
// ============================================
// Reactive statements and computed data
// ============================================
let filteredCustom: TFile[] = [];
$: if (frontmatterCacheTime && filterGroups) {
filterByCustomFilter().then((files) => {
filteredCustom = files;
});
}
let formatted: FileRenderData[] = [];
$: if (propertySettingTime) {
formatted = filteredCustom.map((file) => {
const frontmatter =
plugin.app.metadataCache.getFileCache(file)?.frontmatter;
return formatFileDataForRender(plugin.settings, file, frontmatter);
});
}
$: filteredSearch = formatted.filter((file) =>
filterBySearch(file, searchFilter),
);
$: filteredFavorites = filteredSearch.filter((file) =>
filterByFavorites(file, onlyFavorites),
);
$: filteredTimestamp = filteredFavorites.filter((file) => {
const { modifiedMillis, createdMillis } = file;
return filterByTimestamp({
createdMillis,
modifiedMillis,
timestampFilter,
startOfTodayMillis,
startOfThisWeekMillis,
startOfLastWeekMillis,
});
});
$: renderData = [...filteredTimestamp].sort((a, b) => {
if (sortFilter === "file-name-asc") {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
} else if (sortFilter === "file-name-desc") {
return b.name.toLowerCase().localeCompare(a.name.toLowerCase());
} else if (sortFilter === "modified-asc") {
return a.modifiedMillis - b.modifiedMillis;
} else if (sortFilter === "modified-desc") {
return b.modifiedMillis - a.modifiedMillis;
}
return 0;
});
$: searchFilter,
sortFilter,
timestampFilter,
onlyFavorites,
currentView,
viewOrder,
filterGroups,
selectedFilterGroupId,
saveSettings();
$: totalItems = renderData.length;
$: totalPages = Math.ceil(totalItems / pageSize);
let currentPage = 1;
$: startIndex = (currentPage - 1) * pageSize;
$: pageLength = Math.min(pageSize, renderData.length - startIndex);
$: endIndex = startIndex + pageLength;
function changePage(newPage: number) {
currentPage = newPage;
}
</script>
<div class="vault-explorer">
@ -691,24 +739,24 @@
<IconButton
iconId="chevrons-left"
ariaLabel="First page"
on:click={() => changePage(1)}
on:click={() => handlePageChange(1)}
/>
<IconButton
iconId="chevron-left"
ariaLabel="Previous page"
disabled={currentPage === 1}
on:click={() => changePage(currentPage - 1)}
on:click={() => handlePageChange(currentPage - 1)}
/>
<IconButton
iconId="chevron-right"
ariaLabel="Next page"
disabled={currentPage === totalPages}
on:click={() => changePage(currentPage + 1)}
on:click={() => handlePageChange(currentPage + 1)}
/>
<IconButton
iconId="chevrons-right"
ariaLabel="Last page"
on:click={() => changePage(totalPages)}
on:click={() => handlePageChange(totalPages)}
/>
</Flex>
</Stack>

View file

@ -23,6 +23,7 @@ export interface VaultExplorerPluginSettings {
currentView: ViewType;
order: ViewType[];
titleWrapping: WordBreak;
enableClockUpdates: boolean;
}
pageSize: number;
pluginVersion: string | null;

213
src/types/types-1.12.1.ts Normal file
View file

@ -0,0 +1,213 @@
export interface VaultExplorerPluginSettings_1_12_1 {
logLevel: string;
properties: {
favorite: string;
url: string;
createdDate: string;
modifiedDate: string;
custom1: string;
custom2: string;
custom3: string;
},
filters: {
search: string;
onlyFavorites: boolean;
sort: SortFilter;
timestamp: TimestampFilter;
custom: {
selectedGroupId: string;
groups: FilterGroup[];
}
},
views: {
currentView: ViewType;
order: ViewType[];
titleWrapping: WordBreak;
}
pageSize: number;
pluginVersion: string | null;
}
export type WordBreak = "normal" | "break-word";
export enum ViewType {
GRID = "grid",
LIST = "list",
}
export type FilterOperator = "and" | "or";
export enum TextFilterCondition {
IS = "is",
IS_NOT = "is-not",
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
STARTS_WITH = "starts-with",
ENDS_WITH = "ends-with",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
export enum ListFilterCondition {
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
export enum NumberFilterCondition {
IS_EQUAL = "is-equal",
IS_NOT_EQUAL = "is-not-equal",
IS_GREATER = "is-greater",
IS_LESS = "is-less",
IS_GREATER_OR_EQUAL = "is-greater-or-equal",
IS_LESS_OR_EQUAL = "is-less-or-equal",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
export enum CheckboxFilterCondition {
IS = "is",
IS_NOT = "is-not",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
//TODO: add is between
export enum DateFilterCondition {
IS = "is",
IS_BEFORE = "is-before",
IS_AFTER = "is-after",
IS_ON_OR_BEFORE = "is-on-or-before",
IS_ON_OR_AFTER = "is-on-or-after",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
export enum ContentFilterCondition {
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
IS_EMPTY = "is-empty",
IS_NOT_EMPTY = "is-not-empty",
}
//TODO add is child of and is parent of?
export enum FolderFilterCondition {
IS = "is",
IS_NOT = "is-not",
}
export enum FileNameFilterCondition {
IS = "is",
IS_NOT = "is-not",
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
STARTS_WITH = "starts-with",
ENDS_WITH = "ends-with",
}
export type FilterCondition = TextFilterCondition | NumberFilterCondition | DateFilterCondition | CheckboxFilterCondition | ListFilterCondition | ContentFilterCondition | FolderFilterCondition | FileNameFilterCondition;
//This matches the Obsidian property types
export enum PropertyType {
TEXT = "text",
NUMBER = "number",
LIST = "list",
CHECKBOX = "checkbox",
DATE = "date",
DATETIME = "datetime",
}
export enum FilterRuleType {
PROPERTY = "property",
FOLDER = "folder",
FILE_NAME = "file-name",
CONTENT = "content",
}
export enum DatePropertyFilterValue {
TODAY = "today",
TOMORROW = "tomorrow",
YESTERDAY = "yesterday",
ONE_WEEK_FROM_NOW = "one-week-from-now",
ONE_WEEK_AGO = "one-week-ago",
ONE_MONTH_FROM_NOW = "one-month-from-now",
ONE_MONTH_AGO = "one-month-ago",
CUSTOM = "custom"
}
interface BaseFilterRule {
id: string;
operator: FilterOperator;
type: FilterRuleType;
condition: FilterCondition;
isEnabled: boolean;
value: string;
matchWhenPropertyDNE: boolean;
}
export interface TextPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.TEXT;
propertyName: string;
condition: TextFilterCondition;
}
export interface NumberPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.NUMBER;
propertyName: string;
condition: NumberFilterCondition;
}
export interface ListPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.LIST;
propertyName: string;
condition: ListFilterCondition;
}
export interface CheckboxPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.CHECKBOX;
propertyName: string;
condition: CheckboxFilterCondition;
}
export interface DatePropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.DATE | PropertyType.DATETIME;
propertyName: string;
condition: DateFilterCondition;
valueData: string;
}
export interface FolderFilterRule extends BaseFilterRule {
type: FilterRuleType.FOLDER;
condition: FolderFilterCondition;
includeSubfolders: boolean;
}
export interface FileNameFilterRule extends BaseFilterRule {
type: FilterRuleType.FILE_NAME;
condition: FileNameFilterCondition;
}
export interface ContentFilterRule extends BaseFilterRule {
type: FilterRuleType.CONTENT;
condition: ContentFilterCondition;
}
export type FilterRule = PropertyFilterRule | FolderFilterRule | FileNameFilterRule | ContentFilterRule;
export type PropertyFilterRule = TextPropertyFilterRule | NumberPropertyFilterRule | ListPropertyFilterRule | CheckboxPropertyFilterRule | DatePropertyFilterRule;
export interface FilterGroup {
id: string;
name: string;
rules: FilterRule[];
isEnabled: boolean;
}
export type SortFilter = "file-name-asc" | "file-name-desc" | "modified-asc" | "modified-desc";
export type TimestampFilter = "created-today" | "modified-today" | "created-this-week" | "modified-this-week" | "created-2-weeks" | "modified-2-weeks" | "all";

View file

@ -65,5 +65,6 @@
"1.10.0": "1.4.13",
"1.11.0": "1.4.13",
"1.12.0": "1.4.13",
"1.12.1": "1.4.13"
"1.12.1": "1.4.13",
"1.13.0": "1.4.13"
}