generated from obsidianmd/obsidian-sample-plugin
-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): ✨ conflicts list modal (#84)
* refactor(ui): ♻️ list of conflicts files complete refactor of the UI to show the list of conflicts files. Need to check the sorting methods before merging Simpler, cleaner, more efficient UI. * fix(controllers): 🐛 sort by conflict date utility function for sorting by conflict date * refactor(ui): 💄 update conflict details
- Loading branch information
Showing
8 changed files
with
371 additions
and
105 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,45 @@ | ||
<!-- This file is for providing a reusable component to display information about a sync conflict file. --> | ||
<script lang="ts"> | ||
import { TFile } from "obsidian"; | ||
import { SyncthingController } from "src/controllers/main_controller"; | ||
import { Failure } from "src/models/failures"; | ||
import Error from "./error.svelte"; | ||
import { formatBytes } from "src/controllers/utils"; | ||
export let counter: number; | ||
export let file: TFile; | ||
export let syncthingController: SyncthingController; | ||
const filenameProps = syncthingController.parseConflictFilename(file.name); | ||
</script> | ||
|
||
{#if filenameProps instanceof Failure} | ||
<Error failure={filenameProps} /> | ||
{:else} | ||
<div> | ||
<strong>Conflict #{counter}</strong> | ||
<em> | ||
Occured on: {filenameProps.dateTime.toLocaleString()} | ||
</em> | ||
<ul> | ||
<li> | ||
Conflict date: {filenameProps.dateTime.toLocaleString()} | ||
</li> | ||
<li> | ||
Modified by: {filenameProps.modifiedBy} | ||
</li> | ||
<li> | ||
Extension: <code>{file.extension}</code> | ||
</li> | ||
<li>Size: {formatBytes(file.stat.size, 1)}</li> | ||
<li> | ||
Last modified at: {new Date(file.stat.mtime).toLocaleString()} | ||
</li> | ||
<li>Created at: {new Date(file.stat.ctime).toLocaleString()}</li> | ||
<li>Path: {file.path}</li> | ||
</ul> | ||
</div> | ||
{/if} | ||
|
||
<style> | ||
/* TODO */ | ||
</style> |
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,83 @@ | ||
<script lang="ts"> | ||
import { TFile } from "obsidian"; | ||
import { SyncthingController } from "src/controllers/main_controller"; | ||
import { Failure } from "src/models/failures"; | ||
import { ConflictsModal } from "src/views/conflicts_modal"; | ||
import { DiffModal } from "src/views/diff_modal"; | ||
import ConflictFileDetails from "./conflict_file_details.svelte"; | ||
export let conflicts: TFile[]; | ||
export let syncthingController: SyncthingController; | ||
export let parentModal: ConflictsModal; | ||
let filenameProps = syncthingController.parseConflictFilename( | ||
conflicts[0].name | ||
); | ||
if (filenameProps instanceof Failure) { | ||
console.error(filenameProps); | ||
} | ||
</script> | ||
|
||
<details> | ||
<summary> | ||
<p> | ||
<span> | ||
{#if filenameProps instanceof Failure} | ||
{filenameProps.message} | ||
{:else} | ||
{filenameProps.filename} | ||
{/if} | ||
</span> | ||
<span> | ||
({conflicts.length} conflict{conflicts.length > 1 ? "s" : ""}) | ||
</span> | ||
</p> | ||
|
||
<button | ||
class="mod-cta" | ||
on:click={() => | ||
new DiffModal( | ||
parentModal.app, | ||
conflicts[0], | ||
syncthingController | ||
).open()} | ||
> | ||
View conflict{conflicts.length > 1 ? "s" : ""} | ||
</button> | ||
</summary> | ||
{#each conflicts as conflict, i} | ||
{#if i !== 0} | ||
<div class="divider" /> | ||
{/if} | ||
<ConflictFileDetails | ||
file={conflict} | ||
{syncthingController} | ||
counter={i} | ||
/> | ||
{/each} | ||
</details> | ||
|
||
<style> | ||
.divider { | ||
margin-top: 10px; | ||
margin-bottom: 10px; | ||
border-bottom: 2px solid var(--background-modifier-border); | ||
} | ||
details { | ||
padding: 1% 2%; | ||
border-radius: 10px; | ||
} | ||
details:hover { | ||
background-color: var(--background-modifier-hover); | ||
} | ||
summary { | ||
list-style: none; | ||
display: flex; | ||
flex-direction: row; | ||
justify-content: space-between; | ||
align-items: center; | ||
} | ||
summary > p { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
</style> |
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,63 @@ | ||
<script lang="ts"> | ||
import { Setting, TFile } from "obsidian"; | ||
import { ConflictsModal } from "src/views/conflicts_modal"; | ||
import { onMount } from "svelte"; | ||
import ConflictItem from "./conflict_item.svelte"; | ||
import { sortFilesBy } from "src/controllers/utils"; | ||
export let parentModal: ConflictsModal; | ||
export let conflicts: Map<string, TFile[]>; | ||
let sortSettingContainer: HTMLDivElement; | ||
let sortOptions = { | ||
recent: "Most recent", | ||
old: "Least recent", | ||
"a-to-z": "A to Z", | ||
"z-to-a": "Z to A", | ||
}; | ||
onMount(() => { | ||
new Setting(sortSettingContainer) | ||
.setName("Sort by date") | ||
.addDropdown((dropdown) => { | ||
dropdown.addOptions(sortOptions); | ||
dropdown.onChange((value) => { | ||
conflicts = sortFilesBy( | ||
conflicts, | ||
value as keyof typeof sortOptions, | ||
parentModal.syncthingController | ||
); | ||
console.log("dropdown", conflicts); | ||
}); | ||
}); | ||
conflicts = sortFilesBy( | ||
conflicts, | ||
"recent", | ||
parentModal.syncthingController | ||
); | ||
}); | ||
parentModal.titleEl.setText("Syncthing Conflicts"); | ||
$: if (conflicts) console.log("conflicts", conflicts); | ||
</script> | ||
|
||
<div bind:this={sortSettingContainer} /> | ||
{#key conflicts} | ||
{#each conflicts.keys() as conflictNames, i} | ||
{#if i !== 0} | ||
<div class="divider" /> | ||
{/if} | ||
<ConflictItem | ||
conflicts={conflicts.get(conflictNames) ?? []} | ||
syncthingController={parentModal.syncthingController} | ||
{parentModal} | ||
/> | ||
{/each} | ||
{/key} | ||
|
||
<style> | ||
.divider { | ||
margin-top: 10px; | ||
margin-bottom: 10px; | ||
border-bottom: 2px solid var(--background-modifier-border); | ||
} | ||
</style> |
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,20 @@ | ||
<script lang="ts"> | ||
import { Failure } from "src/models/failures"; | ||
export let failure: Failure | { message: string }; // For the error message | ||
</script> | ||
|
||
<p> | ||
A failure occured : | ||
{failure.message} | ||
</p> | ||
|
||
<style> | ||
p { | ||
background-color: darkred; | ||
opacity: 0.6; | ||
border-radius: 10px; | ||
box-shadow: black 0px 0px 10px; | ||
padding: 1%; | ||
} | ||
</style> |
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,125 @@ | ||
// --- My utility functions --- // | ||
|
||
import { TFile } from "obsidian"; | ||
import { Failure } from "src/models/failures"; | ||
import { SyncthingController } from "./main_controller"; | ||
|
||
/** | ||
* This utility function is used to sort the files in conflict by date, name, etc. | ||
* @param files the file map to sort | ||
* @param type the sort type | ||
* @param syncthingController the Syncthing controller instance | ||
* @returns the sorted file map | ||
*/ | ||
export function sortFilesBy( | ||
files: Map<string, TFile[]>, | ||
type: "recent" | "old" | "a-to-z" | "z-to-a", | ||
syncthingController: SyncthingController | ||
): Map<string, TFile[]> { | ||
switch (type) { | ||
case "recent": | ||
return new Map( | ||
[...files.entries()].sort((a, b) => | ||
sortByConflictDate(a[1], b[1], syncthingController) | ||
) | ||
); | ||
case "old": | ||
return new Map( | ||
[...files.entries()] | ||
.sort((a, b) => | ||
sortByConflictDate(a[1], b[1], syncthingController) | ||
) | ||
.reverse() | ||
); | ||
case "a-to-z": | ||
return new Map([...files.entries()].sort()); | ||
case "z-to-a": | ||
return new Map([...files.entries()].sort().reverse()); | ||
} | ||
} | ||
|
||
/** | ||
* Compares two files by conflict date. | ||
* It uses {@link SyncthingController.parseConflictFilename} from the Syncthing controller. | ||
* | ||
* TODO: refactor the `parseConflictFilename` method to a utility function. | ||
* @param a file A to compare | ||
* @param b file B to compare | ||
* @param syncthingController the Syncthing controller instance | ||
*/ | ||
export function sortByConflictDate( | ||
a: TFile, | ||
b: TFile, | ||
syncthingController: SyncthingController | ||
): number; | ||
|
||
/** | ||
* Compares two lists of files by conflict date. | ||
* It uses {@link SyncthingController.parseConflictFilename} from the Syncthing controller. | ||
* | ||
* TODO: refactor the `parseConflictFilename` method to a utility function. | ||
* @param a list of files to compare | ||
* @param b list of files to compare | ||
* @param syncthingController the Syncthing controller instance | ||
*/ | ||
export function sortByConflictDate( | ||
a: TFile[], | ||
b: TFile[], | ||
syncthingController: SyncthingController | ||
): number; | ||
|
||
export function sortByConflictDate( | ||
a: TFile | TFile[], | ||
b: TFile | TFile[], | ||
syncthingController: SyncthingController | ||
): number { | ||
if (a instanceof TFile && b instanceof TFile) { | ||
const filepropsA = syncthingController.parseConflictFilename( | ||
a.basename | ||
); | ||
|
||
const filepropsB = syncthingController.parseConflictFilename( | ||
b.basename | ||
); | ||
const dateA = | ||
filepropsA instanceof Failure ? new Date() : filepropsA.dateTime; | ||
const dateB = | ||
filepropsB instanceof Failure ? new Date() : filepropsB.dateTime; | ||
return dateB.getTime() - dateA.getTime(); | ||
} | ||
// The `as` typing is because TypeScript doesn't seem to understand that we are left with the TFile[] type | ||
const fileA = (a as TFile[]).sort((a, b) => | ||
sortByConflictDate(a, b, syncthingController) | ||
)[0]; | ||
const fileB = (b as TFile[]).sort((a, b) => | ||
sortByConflictDate(a, b, syncthingController) | ||
)[0]; | ||
return sortByConflictDate(fileA, fileB, syncthingController); | ||
} | ||
|
||
/** | ||
* This utility function allows to correctly format the file size w/ a unit. | ||
* @param bytes file size to format | ||
* @param decimals number of decimals to display | ||
* @returns string with formatted file size | ||
* | ||
* | ||
* Credits for the `formatBytes` function: | ||
* @see https://github.com/CattailNu/obsidian-file-info-panel-plugin | ||
* | ||
* obsidian-file-info-panel-plugin | ||
* | ||
* @copyright T. L. Ford | ||
* @see https://www.Cattail.Nu | ||
*/ | ||
export function formatBytes(bytes: number, decimals = 2): string { | ||
if (bytes === 0) return "0 Bytes"; | ||
|
||
const k = 1024; | ||
const dm = decimals < 0 ? 0 : decimals; | ||
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; | ||
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k)); | ||
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; | ||
} |
Oops, something went wrong.