Skip to content

Commit

Permalink
Log view (#219)
Browse files Browse the repository at this point in the history
* feat: action notification

* feat: view detailed result

* fix: ui icons
  • Loading branch information
debanjandhar12 authored Oct 20, 2023
1 parent 2754f32 commit ba157e8
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 66 deletions.
9 changes: 7 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const OhmStrToListGrammar = ohm.grammar(String.raw`
lineTerminator = "\n" | "\r" | "\u2028" | "\u2029"
}`);

export const ANKI_ICON = `<svg height="18px" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" preserveAspectRatio="xMidYMid meet" viewBox="0 0 197.99999 198" id="svg2" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="application-icon.svg" enable-background="new" inkscape:export-filename="/home/tim/development/Anki-Android/docs/marketing/android_market/logo512-512-alpha.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
export const ANKI_ICON = `<svg height="1em" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" preserveAspectRatio="xMidYMid meet" viewBox="0 0 197.99999 198" id="svg2" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="application-icon.svg" enable-background="new" inkscape:export-filename="/home/tim/development/Anki-Android/docs/marketing/android_market/logo512-512-alpha.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
<title id="title4207">Anki Flat Design</title>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2.8" inkscape:cx="44.732536" inkscape:cy="87.724961" inkscape:document-units="px" inkscape:current-layer="layer5" showgrid="false" units="px" borderlayer="true" inkscape:window-width="1855" inkscape:window-height="1056" inkscape:window-x="65" inkscape:window-y="24" inkscape:window-maximized="1" inkscape:snap-bbox="true" inkscape:snap-bbox-midpoints="true" inkscape:bbox-paths="true" showguides="false" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-global="true">
<sodipodi:guide position="0.0,0" orientation="198.0,0" id="guide4139"/>
Expand Down Expand Up @@ -183,6 +183,11 @@ export const DONATE_ICON =

export const GRAPH_ICON = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hierarchy" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="5" r="2"></circle><circle cx="5" cy="19" r="2"></circle><circle cx="19" cy="19" r="2"></circle><path d="M6.5 17.5l5.5 -4.5l5.5 4.5"></path><line x1="12" y1="7" x2="12" y2="13"></line></svg>`;

export const LOGSEQ_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256.000000 256.000000" preserveAspectRatio="xMidYMid meet"><metadata></metadata><g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)" fill="currentColor" stroke="none"><path d="M0 1280 l0 -1280 1280 0 1280 0 0 1280 0 1280 -1280 0 -1280 0 0 -1280z m1556 805 c55 -16 109 -64 119 -107 17 -68 -65 -167 -173 -209 -84 -32 -219 -33 -284 -1 -68 34 -92 71 -86 133 4 40 12 57 39 85 94 97 254 138 385 99z m-783 -65 c164 -83 228 -300 125 -424 -117 -142 -356 -87 -453 104 -26 51 -32 160 -11 213 17 46 62 94 111 121 46 25 166 18 228 -14z m792 -510 c290 -42 519 -230 555 -458 22 -136 -23 -256 -139 -373 -128 -128 -289 -190 -501 -191 -532 -4 -862 445 -584 796 132 167 413 262 669 226z"/></g></svg>`
export const ADD_OCCLUSION_ICON = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" stroke="none" fill="currentColor"><path d="M176-120q-19-4-35.5-20.5T120-176l664-664q21 5 36 20.5t21 35.5L176-120Zm-56-252v-112l356-356h112L120-372Zm0-308v-80q0-33 23.5-56.5T200-840h80L120-680Zm720 92v112l-19 19q-20-10-42.5-15.5T732-480l108-108ZM372-120l108-108q2 24 7.5 46.5T503-139l-19 19H372Zm308-80H560v-80h120v-120h80v120h120v80H760v120h-80v-120Z"/></svg>`

export const REMOVE_OCCLUSION_ICON = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" stroke="none" fill="currentColor"><path d="M176-120q-19-4-35.5-20.5T120-176l664-664q21 5 36 20.5t21 35.5L176-120Zm196 0 108-108v108H372Zm188-80v-80h320v80H560ZM120-372v-112l356-356h112L120-372Zm492 12 228-228v112L724-360H612ZM120-680v-80q0-33 23.5-56.5T200-840h80L120-680Z"/></svg>`
export const REMOVE_OCCLUSION_ICON = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" stroke="none" fill="currentColor"><path d="M176-120q-19-4-35.5-20.5T120-176l664-664q21 5 36 20.5t21 35.5L176-120Zm196 0 108-108v108H372Zm188-80v-80h320v80H560ZM120-372v-112l356-356h112L120-372Zm492 12 228-228v112L724-360H612ZM120-680v-80q0-33 23.5-56.5T200-840h80L120-680Z"/></svg>`;

export const SUCCESS_ICON = `<span class="text-success"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-circle-check" width="20" height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="9"></circle><path d="M9 12l2 2l4 -4"></path></svg></span>`;

export const WARNING_ICON = `<span class="text-warning"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-circle" width="20" height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="9"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span>`;
8 changes: 5 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {SelectionModal} from "./ui/SelectionModal";
import {Note} from "./notes/Note";
import {showModelWithButtons} from "./ui/ModelWithBtns";
import {UI} from "./ui/UI";
import * as AnkiConnect from "./anki-connect/AnkiConnect";

async function main(baseInfo: LSPluginBaseInfo) {
// Register UI and Commands
Expand All @@ -34,14 +35,14 @@ async function main(baseInfo: LSPluginBaseInfo) {
syncLogseqToAnki,
);
logseq.provideStyle(`
.logseq-anki-toolbar-item-${baseInfo.id} {
logseq-anki-toolbar-item-${baseInfo.id} {
display: flex;
align-items: center;
position: relative;
top: 0px;
opacity: 0.8;
}
.logseq-anki-toolbar-item-${baseInfo.id}:hover {
logseq-anki-toolbar-item-${baseInfo.id}:hover {
opacity: 1;
}
`);
Expand All @@ -51,7 +52,7 @@ async function main(baseInfo: LSPluginBaseInfo) {
}`,
template: String.raw`
<a title="Start Logseq to Anki Sync" data-on-click="syncLogseqToAnki" class="button logseq-anki-toolbar-item-${baseInfo.id}">
<i class="ti">${ANKI_ICON}</i>
<i class="ui__icon ti" style="font-size: 18px;">${ANKI_ICON}</i>
</a>
`,
});
Expand All @@ -71,6 +72,7 @@ async function main(baseInfo: LSPluginBaseInfo) {
ImageOcclusionNote.initLogseqOperations();
AddonRegistry.getAll().forEach((addon) => addon.init());
UI.init();
window.parent.AnkiConnect = AnkiConnect; // Make AnkiConnect available globally
console.log("Window Parent:", window.parent);

// The lines below are needed for vite build and dev to work properly.
Expand Down
69 changes: 54 additions & 15 deletions src/syncLogseqToAnki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
sortAsync,
} from "./utils/utils";
import path from "path-browserify";
import { ANKI_CLOZE_REGEXP, MD_PROPERTIES_REGEXP } from "./constants";
import {ANKI_CLOZE_REGEXP, ANKI_ICON, LOGSEQ_ICON, MD_PROPERTIES_REGEXP, SUCCESS_ICON, WARNING_ICON} from "./constants";
import { convertToHTMLFile } from "./converter/Converter";
import { LogseqProxy } from "./logseq/LogseqProxy";
import pkg from "../package.json";
Expand All @@ -29,6 +29,8 @@ import NoteHashCalculator from "./notes/NoteHashCalculator";
import { cancelable, CancelablePromise } from "cancelable-promise";
import { DepGraph } from "dependency-graph";
import {NoteUtils} from "./notes/NoteUtils";
import {ActionNotification} from "./ui/ActionNotification";
import {showModelWithButtons} from "./ui/ModelWithBtns";
export class LogseqToAnkiSync {
static isSyncing: boolean;
graphName: string;
Expand Down Expand Up @@ -168,8 +170,6 @@ export class LogseqToAnkiSync {
},
);
// Prompt the user
// @ts-ignore
window.parent.AnkiConnect = AnkiConnect; // Make AnkiConnect available to the confirm dialog
const confirm_msg = `<div><b>The logseq to anki sync plugin will attempt to perform the following actions:</b></div>
<div>Create ${
toCreateNotes.length
Expand All @@ -190,8 +190,6 @@ export class LogseqToAnkiSync {
}<div><br/>
<div>Are you sure you want to continue?<div>`;
const confirm_result = await Confirm(confirm_msg);
// @ts-ignore
window.parent.AnkiConnect = null; // Remove AnkiConnect from the global scope
if (!confirm_result) {
buildNoteHashes.cancel();
window.parent.LogseqAnkiSync.dispatchEvent("syncLogseqToAnkiComplete");
Expand Down Expand Up @@ -261,22 +259,63 @@ export class LogseqToAnkiSync {
} \n Updated Blocks: ${
toUpdateNotes.length - failedUpdated.size
} \n Deleted Blocks: ${toDeleteNotes.length - failedDeleted.size}`;
let status = "success";
if (failedCreated.size > 0)
summery += `\nFailed Created: ${failedCreated.size} `;
if (failedUpdated.size > 0)
summery += `\nFailed Updated: ${failedUpdated.size} `;
if (failedDeleted.size > 0)
summery += `\nFailed Deleted: ${failedDeleted.size} `;
if (
failedCreated.size > 0 ||
failedUpdated.size > 0 ||
failedDeleted.size > 0
)
status = "warning";
logseq.UI.showMsg(summery, status, {
timeout: status == "success" ? 1200 : 4000,
});

console.log(toCreateNotes, toUpdateNotes, toDeleteNotes);
// logseq.UI.showMsg(summery, status, {
// timeout: status == "success" ? 1200 : 4000,
// });
const buildAnkiLink = (ankiId) => { return `<a class="inline-flex flex-row items-center button" style="color:inherit; display: inline-flex; padding: 0; height: auto; user-select: text;" onMouseOver="this.style.color='var(--ctp-link-text-hover-color)'" onMouseOut="this.style.color='inherit'" onclick="window.AnkiConnect.guiBrowse('nid:${ankiId}')"><i>${ANKI_ICON}</i><span>${ankiId}</span></a>` }
const buildLogseqLink = (uuid) => { return `<a class="inline-flex flex-row items-center button" style="color:inherit; display: inline-flex; padding: 0; height:auto; user-select: text;" onMouseOver="this.style.color='var(--ctp-link-text-hover-color)'" onMouseOut="this.style.color='inherit'" href="logseq://graph/${encodeURIComponent(this.graphName)}?page=${encodeURIComponent(uuid)}"><i>${LOGSEQ_ICON}</i><span>${uuid}</span></a>` }
ActionNotification([{name: "View Details", func: () => {
showModelWithButtons(`
<div style="font-size: 16px" class="w-100">
<div class="p-4" style="background-color: var(--ls-tertiary-background-color); border-radius: 0.25rem; cursor: pointer; margin-bottom: 0.5rem; padding: 0.25rem 0.5rem; -webkit-user-select: none; -moz-user-select: none; user-select: none; z-index: 1;">Created</div>
<ul style="font-size: 14px">
${toCreateNotes.length == 0 ? `No notes were created.` : ``}
${toCreateNotes.map((note) => {
if (!failedCreated.has(`${note.uuid}-${note.type}`))
return `<li><span class="inline-flex items-center"><span class="opacity-50 px-1" style="user-select: none">[${note.type}]</span>
${note.uuid} <span class="px-1" style="user-select: none">--></span> ${buildAnkiLink(note.ankiId)} <small class="px-1">(Synced Successfully)</small></span></li>`
else return ``;
}).join("")}
${Array.from(failedCreated).map((note) => {
const noteUuid = note.substring(0,note.lastIndexOf('-'));
const noteType = note.substring(note.lastIndexOf('-')+1);
return `<li><span class="inline-flex items-center"><span class="opacity-50 px-1" style="user-select: none">[${noteType}]</span>
${buildLogseqLink(noteUuid)} <small class="px-1">(Failed to Sync)</small></span></li>`
}).join("")}
</ul>
<div class="p-4" style="background-color: var(--ls-tertiary-background-color); border-radius: 0.25rem; cursor: pointer; margin-bottom: 0.5rem; padding: 0.25rem 0.5rem; -webkit-user-select: none; -moz-user-select: none; user-select: none; z-index: 1; margin-top: 1rem;">Updated</div>
<ul style="font-size: 14px">
${toUpdateNotes.length == 0 ? `No notes were updated.` : ``}
${toUpdateNotes.map((note) => {
if (!failedUpdated.has(`${note.uuid}-${note.type}`))
return `<li><span class="inline-flex items-center"><span class="opacity-50 px-1" style="user-select: none">[${note.type}]</span>
${buildLogseqLink(note.uuid)} <span class="px-1" style="user-select: none">--></span> ${buildAnkiLink(note.ankiId)} <small class="px-1">(Synced Successfully)</small></span></li>`
else return ``;
}).join("")}
${Array.from(failedUpdated).map((note) => {
const noteUuid = note.substring(0,note.lastIndexOf('-'));
const noteType = note.substring(note.lastIndexOf('-')+1);
return `<li><span class="inline-flex items-center"><span class="opacity-50 px-1" style="user-select: none">[${noteType}]</span>
${buildLogseqLink(noteUuid)} <small class="px-1">(Failed to Sync)</small></span></li>`
}).join("")}
</ul>
<div class="p-4" style="background-color: var(--ls-tertiary-background-color); border-radius: 0.25rem; cursor: pointer; margin-bottom: 0.5rem; padding: 0.25rem 0.5rem; -webkit-user-select: none; -moz-user-select: none; user-select: none; z-index: 1; margin-top: 1rem;">Deleted</div>
<span style="font-size: 14px">
${toDeleteNotes.length > 0 ? `The ${toDeleteNotes.length} notes from anki were deleted successfully.` : `No notes were deleted.`}
${failedDeleted.size > 0 ? `The ${Array.from(failedDeleted).join(",")} notes from anki failed to be deleted.` : ``}
</span>
</div>
`, [])
}}], summery, 20000,
failedCreated.size > 0 || failedUpdated.size > 0 || failedDeleted.size > 0 ? WARNING_ICON : SUCCESS_ICON);
console.log(summery);
if (failedCreated.size > 0)
console.log("\nFailed Created:", failedCreated);
Expand Down
1 change: 1 addition & 0 deletions src/types/global.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ declare global {
LogseqAnkiSync: any;
fabric: any;
Image: any;
AnkiConnect: any;
logseq: {api: any};
}
}
80 changes: 80 additions & 0 deletions src/ui/ActionNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {Modal} from "./Modal";
import React from "react";
import {LogseqButton} from "./basic/LogseqButton";
import ReactDOM from "react-dom";
import {waitForElement} from "../utils/waitForElement";
import {Notification} from "./Notification";

export async function ActionNotification(btns: {name : string, func: Function}[], msg : string, timeout?: number, icon? : string): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
const uniqueNotificationId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
try {
await logseq.UI.showMsg(uniqueNotificationId, 'success', {timeout: 0, key: uniqueNotificationId});
let main = await waitForElement(`//div[text()='${uniqueNotificationId}']`, 360*1000, window.parent.document);
if (!main) throw new Error("Failed to find notification");
main = main.closest(".notifications > .ui__notifications-content");
main.innerHTML = "";
let onClose = () => {
ReactDOM.unmountComponentAtNode(main);
// main.remove();
logseq.UI.closeMsg(uniqueNotificationId);
}
onClose = onClose.bind(this);
ReactDOM.render(<ActionNotificationComponent btns={btns} msg={msg} resolve={resolve} reject={reject} icon={icon} onClose={onClose} timeout={timeout}/>, main as HTMLElement);
} catch (e) {
logseq.UI.closeMsg(uniqueNotificationId);
await logseq.UI.showMsg(msg, 'success');
console.log(e);
reject(e);
}
});
}

const ActionNotificationComponent: React.FC<{ btns: {name : string, func: Function}[], msg?:string, onClose : () => void, resolve : Function, reject : Function, icon? : string, timeout? : number}> = ({btns, msg, onClose, resolve, reject, icon,timeout}) => {
const [open, setOpen] = React.useState(true);
let [buttons, setButtons] = React.useState(btns);
const handleAction = React.useCallback((selection: number | null) => {
buttons[selection].func();
resolve(selection);
setOpen(false);
}, [resolve, buttons]);

React.useEffect(() => {
if (!open) {
resolve(null);
}
}, [open]);

React.useEffect(() => {
let timeoutId: NodeJS.Timeout;
if (timeout && timeout > 0) {
timeoutId = setTimeout(() => {
setOpen(false);
}, timeout);
}
return () => {
if(timeoutId) {
clearTimeout(timeoutId);
}
}
}, [timeout]);

return (
<Notification open={open} setOpen={setOpen} onClose={onClose} hasCloseBtn={true} icon={icon}>
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'flex-end'}}>
<span style={{alignSelf: 'flex-start'}}>{msg}</span>
{buttons.map((btn, index) => (
<LogseqButton
key={index}
onClick={() => handleAction(index)}
color='primary'
style={{marginTop: '1em'}}
size='sm'
>
{btn.name}
</LogseqButton>
))}
</div>
</Notification>
);
};
Loading

0 comments on commit ba157e8

Please sign in to comment.