diff --git a/src/ReactView.tsx b/src/ReactView.tsx new file mode 100644 index 0000000..e1368a7 --- /dev/null +++ b/src/ReactView.tsx @@ -0,0 +1,8 @@ +import * as React from "react"; +import App from "./view/App"; + +export default function ReactView(props: any){ + return( + + ) +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..4ea0038 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,65 @@ +import { Notice, Plugin } from "obsidian"; +import { HabiticaSyncSettingsTab } from "./settings"; +import { HabiticaSyncView, VIEW_TYPE} from "./view" + +interface HabiticaSyncSettings { + userID: string + apiToken: string +} +const DEFAULT_SETTINGS: Partial = { + userID: "", + apiToken: "" +} +export default class HabiticaSync extends Plugin { + settings: HabiticaSyncSettings; + view: HabiticaSyncView; + + displayNotice(message: string){ + new Notice(message) + } + async onload() { + await this.loadSettings(); + this.addSettingTab(new HabiticaSyncSettingsTab(this.app, this)); + this.registerView( + VIEW_TYPE, + (leaf) => (this.view = new HabiticaSyncView(leaf, this)) + ); + this.addRibbonIcon("popup-open", "Open Habitica Pane", () => { //activate view + this.activateView(); + }); + this.addCommand({ + id: "habitica-view-open", + name: "Habitica: Open Pane", + hotkeys: [{ modifiers: ["Mod", "Shift"], key: "h"}], + callback: () => { + this.activateView(); + } + }); + } + async loadSettings() { + this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()) + } + async saveSettings() { + await this.saveData(this.settings); + } + async onunload() { + await this.view.onClose(); + + this.app.workspace + .getLeavesOfType(VIEW_TYPE) + .forEach((leaf) => leaf.detach()); + } + async activateView() { + this.app.workspace.detachLeavesOfType(VIEW_TYPE); + + await this.app.workspace.getRightLeaf(false).setViewState({ + type: VIEW_TYPE, + active: true, + }); + + this.app.workspace.revealLeaf( + this.app.workspace.getLeavesOfType(VIEW_TYPE)[0] + ); + } + +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..bd298e5 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,42 @@ +import HabiticaSync from "./main"; +import { App, PluginSettingTab, Setting } from "obsidian"; + +export class HabiticaSyncSettingsTab extends PluginSettingTab { + plugin: HabiticaSync; + + constructor(app: App, plugin: HabiticaSync) { + super(app, plugin) + this.plugin = plugin + } + + display(): void { + let { containerEl } = this; + containerEl.empty(); + + new Setting(containerEl) + .setName("Habitica User ID") + .setDesc("Can be found in Settings > API") + .addText((text) => + text + .setPlaceholder("User ID") + .setValue(this.plugin.settings.userID) + .onChange(async (value) => { + this.plugin.settings.userID = value; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Habitica API Token") + .setDesc("Can be found in Settings > API") + .addText((text) => + text + .setPlaceholder("API Token") + .setValue(this.plugin.settings.apiToken) + .onChange(async (value) => { + this.plugin.settings.apiToken = value; + await this.plugin.saveSettings(); + }) + ); + } +} \ No newline at end of file diff --git a/src/view.tsx b/src/view.tsx new file mode 100644 index 0000000..44fc80b --- /dev/null +++ b/src/view.tsx @@ -0,0 +1,38 @@ +import { ItemView,WorkspaceLeaf } from "obsidian"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import ReactView from "./ReactView"; +import HabiticaSync from "./main"; + + +export const VIEW_TYPE = "example-view" + +export class HabiticaSyncView extends ItemView { + plugin: HabiticaSync; + constructor(leaf: WorkspaceLeaf, plugin: HabiticaSync) { + super(leaf) + this.plugin = plugin + } + + getViewType() { + return VIEW_TYPE + } + + getDisplayText() { + return "Habitica Pane" + } + getIcon(): string { + return "popup-open" + } + + async onOpen() { + ReactDOM.render( + , + this.containerEl.children[1] + ) + } + + async onClose(){ + ReactDOM.unmountComponentAtNode(this.containerEl.children[1]); + } +} \ No newline at end of file diff --git a/src/view/App.tsx b/src/view/App.tsx new file mode 100644 index 0000000..4b4ab8d --- /dev/null +++ b/src/view/App.tsx @@ -0,0 +1,122 @@ +import * as React from "react"; +import { getStats, scoreTask } from "./habiticaAPI" +import Statsview from "./Components/Statsview" +import Taskview from "./Components/Taskview" + +let username = "" +let credentials = "" + +class App extends React.Component { + constructor(props: any) { + super(props) + username = this.props.username + credentials = this.props.apiToken + this.state = { + isLoaded: false, + user_data: { + profile: { + name: "", + }, + stats: { + hp: 0, + lvl: 0, + } + }, + todos: [] + } + this.handleChange = this.handleChange.bind(this) + } + sendNotice(message: string){ + this.props.plugin.displayNotice(message) + } + reloadData() { + getStats(username, credentials) + .then(res => res.json()) + .then( + result => { + if(result.success === false){ + this.sendNotice("Login Failed, Please check credentials and try again!") + console.log(result) + } else { + console.log(result) + console.log("data reloaded") + this.setState({ + isLoaded: true, + user_data: result, + todos: result.tasks.todos + }) + } + }, + (error) => { + this.setState({ + isLoaded: true, + error + }) + } + ) + } + componentDidMount() { + this.reloadData() + } + handleChange(event: any){ + this.state.todos.forEach((element: any) => { + if(element.id == event.target.id){ + if(!element.completed){ + scoreTask(username, credentials, event.target.id, "up") + .then(res => res.json()) + .then( + result => { + if(result.success) { + this.sendNotice("Checked!") + console.log(result) + this.reloadData() + } else { + this.sendNotice("Resyncing, please try again") + this.reloadData() + } + }, + (error) => { + this.sendNotice("API Error: Please Check crendentials and try again") + console.log(error) + } + ) + } else { + scoreTask(username, credentials, event.target.id, "down") + .then(res => res.json()) + .then( + result => { + if(result.success){ + this.sendNotice("Un-checked!") + console.log(result) + this.reloadData() + } else { + this.sendNotice("Resyncing, please try again") + this.reloadData() + } + }, + (error) => { + this.sendNotice("API Error: Please Check crendentials and try again") + console.log(error) + } + ) + } + } + }) + } + + render(){ + if(this.state.error) + return(
Loading....
) + else if(!this.state.isLoaded) + return
Loading....
+ else { + return (
+ + + +
+ ); + } + } +} +export default App \ No newline at end of file diff --git a/src/view/Components/Statsview/index.css b/src/view/Components/Statsview/index.css new file mode 100644 index 0000000..e69de29 diff --git a/src/view/Components/Statsview/index.tsx b/src/view/Components/Statsview/index.tsx new file mode 100644 index 0000000..f15d886 --- /dev/null +++ b/src/view/Components/Statsview/index.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +export default function Index(props: any) { + return( +
+
{props.user_data.profile.name}
+
favoriteHP: {(props.user_data.stats.hp).toPrecision(3)}
+
starLVL: {props.user_data.stats.lvl}
+
+ ); +} \ No newline at end of file diff --git a/src/view/Components/Taskview/TodoItem.tsx b/src/view/Components/Taskview/TodoItem.tsx new file mode 100644 index 0000000..4adc70f --- /dev/null +++ b/src/view/Components/Taskview/TodoItem.tsx @@ -0,0 +1,12 @@ + import * as React from "react"; + +function TodoItem(props: any) { + return ( +
+ +

{props.todo_text}

+
+ ) +} + +export default TodoItem \ No newline at end of file diff --git a/src/view/Components/Taskview/index.tsx b/src/view/Components/Taskview/index.tsx new file mode 100644 index 0000000..978c356 --- /dev/null +++ b/src/view/Components/Taskview/index.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; +import TodoItem from "./TodoItem" +import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; + +export default function Index(props: any){ + const incompleteTodos = props.todos.map((todo: any) => { + if(!todo.completed) + return + }) + const completedTodos = props.todos.map((todo: any) => { + if(todo.completed) + return + }) + const display =
+ + + Active Todos + Completed Todos + + +
    {incompleteTodos}
+
+ +
    {completedTodos}
+
+
+
+ + return(display); +} \ No newline at end of file diff --git a/src/view/Components/Taskview/react-tabs.css b/src/view/Components/Taskview/react-tabs.css new file mode 100644 index 0000000..8bb668b --- /dev/null +++ b/src/view/Components/Taskview/react-tabs.css @@ -0,0 +1,56 @@ +.react-tabs { + -webkit-tap-highlight-color: transparent; +} + +.react-tabs__tab-list { + border-bottom: 1px solid #aaa; + margin: 0 0 10px; + padding: 0; +} + +.react-tabs__tab { + display: inline-block; + border: 1px solid transparent; + border-bottom: none; + bottom: -1px; + position: relative; + list-style: none; + padding: 6px 12px; + cursor: pointer; +} + +.react-tabs__tab--selected { + background: #fff; + border-color: #aaa; + color: black; + border-radius: 5px 5px 0 0; +} + +.react-tabs__tab--disabled { + color: GrayText; + cursor: default; +} + +.react-tabs__tab:focus { + box-shadow: 0 0 5px hsl(208, 99%, 50%); + border-color: hsl(208, 99%, 50%); + outline: none; +} + +.react-tabs__tab:focus:after { + content: ""; + position: absolute; + height: 5px; + left: -4px; + right: -4px; + bottom: -5px; + background: #fff; +} + +.react-tabs__tab-panel { + display: none; +} + +.react-tabs__tab-panel--selected { + display: block; +} diff --git a/src/view/Components/Taskview/tabs.ts b/src/view/Components/Taskview/tabs.ts new file mode 100644 index 0000000..55153e7 --- /dev/null +++ b/src/view/Components/Taskview/tabs.ts @@ -0,0 +1 @@ +import * as React from "react" diff --git a/src/view/habiticaAPI.ts b/src/view/habiticaAPI.ts new file mode 100644 index 0000000..70d7667 --- /dev/null +++ b/src/view/habiticaAPI.ts @@ -0,0 +1,29 @@ +// import fetch from "node-fetch"; + +export async function getStats(username: string, credentials: string){ + const url = "https://habitica.com/export/userdata.json" + const response = fetch(url, { + method: 'GET', + headers: { + "Content-Type": "application/json", + "x-client": username.concat("-testAPI"), + "x-api-user": username, + "x-api-key": credentials, + }, + }) + return (response) +} + +export async function scoreTask(username: string, credentials: string, taskID: string, direction: string) { + const url = "https://habitica.com/api/v3/tasks/".concat(taskID).concat("/score/").concat(direction) + const response = fetch(url, { + method: 'POST', + headers: { + "Content-Type": "application/json", + "x-client": username.concat("-testAPI"), + "x-api-user": username, + "x-api-key": credentials, + } + }) + return(response) +} \ No newline at end of file