Skip to content

Commit

Permalink
fix: refactor with new hooks, and fix theme mode issue
Browse files Browse the repository at this point in the history
  • Loading branch information
ahonn committed Mar 14, 2023
1 parent e01a884 commit 65d5706
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 142 deletions.
85 changes: 14 additions & 71 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,121 +1,64 @@
import 'virtual:windi.css';
import React, { useEffect, useRef } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { AppUserConfigs } from '@logseq/libs/dist/LSPlugin.user';
import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import TaskInput, { ITaskInputRef } from './components/TaskInput';
import TaskSection, { GroupBy } from './components/TaskSection';
import TaskFilter from './components/TaskFilter';
import { logseq as plugin } from '../package.json';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { visibleState } from './state/visible';
import { userConfigsState } from './state/user-configs';
import { themeModeState, themeStyleState } from './state/theme';
import { themeStyleState } from './state/theme';
import getTodayTaskQuery from './querys/today';
import getScheduledTaskQuery from './querys/scheduled';
import getAnytimeTaskQuery from './querys/anytime';
import { settingsState } from './state/settings';
import * as api from './api';
import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind';
import getNextNDaysTaskQuery from './querys/next-n-days';
import { fixPreferredDateFormat } from './utils';
import './style.css';
import { markerState, priorityState } from './state/filter';
import { TaskPriority } from './models/TaskEntity';
import { useRefreshAll } from './hooks/useRefreshAll';
import { useHotKey } from './hooks/useHotKey';

dayjs.extend(advancedFormat);

function ErrorFallback({ error }: FallbackProps) {
useEffect(() => {
window.logseq.App.showMsg(`[${plugin.id}]: ${error.message}`, 'error');
}, [error.message]);

return (
<div role="alert" className="text-red-500 font-semibold">
<p>Todo list failed to render.</p>
<p>Can you re-index your graph and try again?</p>
<div role="alert" className="text-red-500">
<p className="font-semibold">Todo list failed to render.</p>
<p className="text-sm">Can you re-index your graph and try again?</p>
<p className="text-sm">[Error]: {error.message}</p>
</div>
);
}

interface IAppProps {
userConfigs: AppUserConfigs;
}

function App(props: IAppProps) {
function App() {
const innerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<ITaskInputRef>(null);
const visible = useRecoilValue(visibleState);
const [userConfigs, setUserConfigs] = useRecoilState(userConfigsState);
const themeStyle = useRecoilValue(themeStyleState);
const themeMode = useRecoilValue(themeModeState);
const settings = useRecoilValue(settingsState);
const marker = useRecoilValue(markerState);
const priority = useRecoilValue(priorityState);
const refreshAll = useRefreshAll();

const refreshAll = useRecoilCallback(
({ snapshot, refresh }) =>
() => {
for (const node of snapshot.getNodes_UNSTABLE()) {
refresh(node);
}
},
[],
);

useEffect(() => {
setUserConfigs(props.userConfigs);
}, [props.userConfigs, setUserConfigs]);

useEffect(() => {
if (!settings.hotkey) {
return;
}

// @ts-ignore
Mousetrap.bindGlobal(
settings.hotkey,
() => window.logseq.hideMainUI(),
'keydown',
);
return () => {
// @ts-ignore
Mousetrap.unbindGlobal(settings.hotkey, 'keydown');
};
}, [settings.hotkey]);
useHotKey(settings.hotkey);

useEffect(() => {
if (visible) {
setTimeout(() => {
inputRef.current?.focus();
}, 0);
refreshAll();
window.logseq.App.getUserConfigs().then(setUserConfigs);

const keydownHandler = (ev: KeyboardEvent) => {
if (ev.key === 'Escape') {
window.logseq.hideMainUI();
return;
}
};
document.addEventListener('keydown', keydownHandler);
return () => {
document.removeEventListener('keydown', keydownHandler);
};
}
}, [visible, refreshAll, setUserConfigs]);

useEffect(() => {
if (themeMode === 'dark') {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
} else {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
}
}, [themeMode]);
window.logseq.App.getUserConfigs().then(setUserConfigs);
}, [visible, refreshAll, setUserConfigs]);

const handleClickOutside = (e: React.MouseEvent) => {
if (!innerRef.current?.contains(e.target as unknown as Node)) {
Expand All @@ -142,7 +85,7 @@ function App(props: IAppProps) {
>
<div ref={innerRef} id={plugin.id}>
<div
className="absolute p-4 w-90 h-120 -left-13rem bg-white shadow rounded-lg overflow-y-auto border-2"
className="absolute p-4 w-90 h-120 -left-13rem bg-white shadow rounded-lg overflow-y-auto border-2 transition-all transition-200"
style={{
backgroundColor: themeStyle.primaryBackgroundColor,
borderColor: themeStyle.secondaryBackgroundColor,
Expand Down
32 changes: 32 additions & 0 deletions src/hooks/useHotKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect } from "react";
import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind';

export function useHotKey(hotkey: string) {
useEffect(() => {
if (!hotkey) {
return;
}

// @ts-ignore
Mousetrap.bindGlobal(
hotkey,
() => window.logseq.hideMainUI(),
'keydown',
);

// @ts-ignore
Mousetrap.bindGlobal(
'Esc',
() => window.logseq.hideMainUI(),
'keydown',
);

return () => {
// @ts-ignore
Mousetrap.unbindGlobal(hotkey, 'keydown');
// @ts-ignore
Mousetrap.unbindGlobal('Esc', 'keydown');
};
}, [hotkey]);
}
15 changes: 15 additions & 0 deletions src/hooks/useRefreshAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useRecoilCallback } from "recoil";

export function useRefreshAll() {
const refreshAll = useRecoilCallback(
({ snapshot, refresh }) =>
() => {
for (const node of snapshot.getNodes_UNSTABLE()) {
refresh(node);
}
},
[],
);

return refreshAll;
}
70 changes: 32 additions & 38 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function openTaskPanel() {
// @ts-ignore
Object.assign(taskPanel.style, {
position: 'fixed',
// @ts-ignore
// @ts-ignore
top: `${rect.top + 40}px`,
// @ts-ignore
left: rect.left + 'px',
Expand All @@ -39,51 +39,45 @@ function registerHotKey(binding: string) {
}

function main() {
try {
logseq.setMainUIInlineStyle({
position: 'fixed',
zIndex: 11,
});
logseq.setMainUIInlineStyle({
position: 'fixed',
zIndex: 11,
});

logseq.App.registerUIItem('toolbar', {
key: plugin.id,
template: `
logseq.App.registerUIItem('toolbar', {
key: plugin.id,
template: `
<a id="${plugin.id}" data-on-click="openTaskPanel" data-rect class="button">
<i class="ti ti-checkbox" style="font-size: 20px"></i>
</a>
`,
});
});

if (logseq.settings?.hotkey) {
registerHotKey(logseq.settings?.hotkey);
}
logseq.onSettingsChanged((settings) => {
registerHotKey(settings?.hotkey);
});
if (logseq.settings?.hotkey) {
registerHotKey(logseq.settings?.hotkey);
}
logseq.onSettingsChanged((settings) => {
registerHotKey(settings?.hotkey);
});

logseq.App.registerCommandPalette(
{
key: 'logseq-plugin-todo',
label: 'Open todo list',
},
() => {
openTaskPanel();
},
);
logseq.App.registerCommandPalette(
{
key: 'logseq-plugin-todo',
label: 'Open todo list',
},
() => {
openTaskPanel();
},
);

logseq.App.getUserConfigs().then((configs) => {
const root = ReactDOM.createRoot(document.getElementById('app')!);
root.render(
<React.StrictMode>
<RecoilRoot>
<App userConfigs={configs} />
</RecoilRoot>
</React.StrictMode>,
);
})
} catch (e: any) {
logseq.App.showMsg(e.message, 'error');
}
const root = ReactDOM.createRoot(document.getElementById('app')!);
root.render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>,
);
}

logseq
Expand Down
26 changes: 6 additions & 20 deletions src/state/theme.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import { atom, AtomEffect, selector } from 'recoil';
import { selector } from 'recoil';
import { settingsState } from './settings';
import { userConfigsState } from './user-configs';

type ThemeMode = 'dark' | 'light';

const themeModeChangedEffect: AtomEffect<ThemeMode> = ({
setSelf,
getPromise,
}) => {
setTimeout(async () => {
const userConfigs = await getPromise(userConfigsState);
setSelf(userConfigs.preferredThemeMode ?? 'light');
}, 0);

logseq.App.onThemeModeChanged((evt) => {
setSelf(evt.mode);
});
};

export const themeModeState = atom<ThemeMode>({
export const themeModeState = selector({
key: 'themeMode',
default: 'light',
effects: [themeModeChangedEffect],
get: ({ get }) => {
const userConfigs = get(userConfigsState);
return userConfigs.preferredThemeMode;
}
});

export const themeStyleState = selector({
Expand Down
46 changes: 37 additions & 9 deletions src/state/user-configs.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import { AppUserConfigs } from '@logseq/libs/dist/LSPlugin';
import { atom, AtomEffect } from 'recoil';
import { logseq as plugin } from '../../package.json';

const updateUserConfigsEffect: AtomEffect<Partial<AppUserConfigs>> = ({
setSelf,
trigger,
}) => {
if (trigger === 'get') {
window.logseq.App.getUserConfigs().then(setSelf);
export const USER_CONFIGS_KEY = `${plugin.id}#userConfigs`;

export const DEFAULT_USER_CONFIGS: Partial<AppUserConfigs> = {
preferredLanguage: 'en',
preferredThemeMode: 'light',
preferredFormat: 'markdown',
preferredWorkflow: 'now',
preferredTodo: 'LATER',
preferredDateFormat: 'MMM do, yyyy',
};

const themeModeChangeEffect: AtomEffect<AppUserConfigs> = ({ onSet }) => {
onSet(({ preferredThemeMode }) => {
if (preferredThemeMode === 'dark') {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
} else {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
}
});
};

const localStorageEffect: AtomEffect<AppUserConfigs> = ({ setSelf, onSet }) => {
const savedValue = localStorage.getItem(USER_CONFIGS_KEY);
if (savedValue != null) {
setSelf(JSON.parse(savedValue));
}

onSet((newValue, _, isReset) => {
isReset
? localStorage.removeItem(USER_CONFIGS_KEY)
: localStorage.setItem(USER_CONFIGS_KEY, JSON.stringify(newValue));
});
};

export const userConfigsState = atom({
export const userConfigsState = atom<AppUserConfigs>({
key: 'userConfigs',
default: () => window.logseq.App.getUserConfigs(),
effects: [updateUserConfigsEffect],
default: DEFAULT_USER_CONFIGS as AppUserConfigs,
effects: [localStorageEffect, themeModeChangeEffect],
});
7 changes: 3 additions & 4 deletions src/state/visible.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { atom, AtomEffect } from 'recoil';
import { atom, AtomEffect, selector } from 'recoil';
import { themeModeState } from './theme';

const visibleChangedEffect: AtomEffect<boolean> = ({ setSelf }) => {
const eventName = 'ui:visible:changed';
Expand All @@ -14,7 +15,5 @@ const visibleChangedEffect: AtomEffect<boolean> = ({ setSelf }) => {
export const visibleState = atom({
key: 'visible',
default: logseq.isMainUIVisible,
effects: [
visibleChangedEffect,
],
effects: [visibleChangedEffect],
});

0 comments on commit 65d5706

Please sign in to comment.