Merge pull request #17 from kkzzhizhou/main

Show task notes and add rewards panel. UI Overhaul. Credit to @kkzzhizhou!
This commit is contained in:
Zain 2021-11-21 11:43:37 +05:30 committed by GitHub
commit 8deee0ecc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 240 additions and 93 deletions

3
.gitignore vendored
View file

@ -6,6 +6,9 @@
node_modules node_modules
package-lock.json package-lock.json
# yarn
yarn.lock
*.js.map *.js.map

0
.hotreload Normal file
View file

View file

@ -5,7 +5,8 @@
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "rollup --config rollup.config.js -w", "dev": "rollup --config rollup.config.js -w",
"build": "rollup --config rollup.config.js --environment BUILD:production" "build": "rollup --config rollup.config.js --environment BUILD:production",
"dev2": "obsidian-plugin dev src/main.ts"
}, },
"keywords": [], "keywords": [],
"author": "Leonard and Ran", "author": "Leonard and Ran",
@ -21,6 +22,7 @@
"css-loader": "^6.4.0", "css-loader": "^6.4.0",
"extract-text-webpack-plugin": "^2.1.2", "extract-text-webpack-plugin": "^2.1.2",
"obsidian": "^0.12.0", "obsidian": "^0.12.0",
"obsidian-plugin-cli": "^0.4.3",
"rollup": "^2.32.1", "rollup": "^2.32.1",
"style-loader": "^3.3.0", "style-loader": "^3.3.0",
"tslib": "^2.2.0", "tslib": "^2.2.0",
@ -32,6 +34,7 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-emoji-render": "^1.2.4", "react-emoji-render": "^1.2.4",
"react-markdown": "^7.1.0",
"react-tabs": "^3.2.2" "react-tabs": "^3.2.2"
} }
} }

View file

@ -15,6 +15,7 @@ export default class HabiticaSync extends Plugin {
view: HabiticaSyncView; view: HabiticaSyncView;
async onload() { async onload() {
console.log("load plugin: habitica-sync")
await this.loadSettings(); await this.loadSettings();
this.addSettingTab(new HabiticaSyncSettingsTab(this.app, this)); this.addSettingTab(new HabiticaSyncSettingsTab(this.app, this));
this.registerView( this.registerView(
@ -32,6 +33,8 @@ export default class HabiticaSync extends Plugin {
this.activateView(); this.activateView();
} }
}); });
this.activateView();
} }
async loadSettings() { async loadSettings() {
this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()) this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData())
@ -39,8 +42,9 @@ export default class HabiticaSync extends Plugin {
async saveSettings() { async saveSettings() {
await this.saveData(this.settings); await this.saveData(this.settings);
} }
async onunload() { async onunload() {
await this.view.onClose(); // await this.view.onClose();
this.app.workspace this.app.workspace
.getLeavesOfType(VIEW_TYPE) .getLeavesOfType(VIEW_TYPE)

View file

@ -31,7 +31,6 @@ export class HabiticaSyncView extends ItemView {
this.containerEl.children[1] this.containerEl.children[1]
) )
} }
async onClose(){ async onClose(){
ReactDOM.unmountComponentAtNode(this.containerEl.children[1]); ReactDOM.unmountComponentAtNode(this.containerEl.children[1]);
} }

View file

@ -1,10 +1,10 @@
import * as React from "react"; import * as React from "react";
import { Notice } from "obsidian"; import { Notice } from "obsidian";
import { getStats, scoreTask, makeCronReq } from "./habiticaAPI" import { getStats, scoreTask, makeCronReq, costReward } from "./habiticaAPI"
import Statsview from "./Components/Statsview" import Statsview from "./Components/Statsview"
import Taskview from "./Components/Taskview" import Taskview from "./Components/Taskview"
class App extends React.Component<any,any> { class App extends React.Component<any, any> {
private _username = ""; private _username = "";
public get username() { public get username() {
return this._username; return this._username;
@ -43,6 +43,7 @@ class App extends React.Component<any,any> {
this.handleChangeTodos = this.handleChangeTodos.bind(this); this.handleChangeTodos = this.handleChangeTodos.bind(this);
this.handleChangeDailys = this.handleChangeDailys.bind(this); this.handleChangeDailys = this.handleChangeDailys.bind(this);
this.handleChangeHabits = this.handleChangeHabits.bind(this); this.handleChangeHabits = this.handleChangeHabits.bind(this);
this.handleChangeRewards = this.handleChangeRewards.bind(this);
this.runCron = this.runCron.bind(this); this.runCron = this.runCron.bind(this);
} }
@ -50,15 +51,17 @@ class App extends React.Component<any,any> {
let cronDate = new Date(lastCron); let cronDate = new Date(lastCron);
let now = new Date(); let now = new Date();
if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) { if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) {
return( // return (
<div className="cron"> // <div className="cron">
<div id="cronMessage"> Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards. </div> // <div id="cronMessage"> Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards. </div>
<button onClick={this.runCron}>Continue</button> // <button onClick={this.runCron}>Continue</button>
</div> // </div>
); // );
return (
<div className="cron"></div>
)
} }
else { else {
console.log("Cron is up to date");
return null return null
}; };
} }
@ -77,100 +80,128 @@ class App extends React.Component<any,any> {
} }
async reloadData() { async reloadData() {
try { try {
let response = await getStats(this.username, this.credentials); let response = await getStats(this.username, this.credentials);
let result = await response.json(); let result = await response.json();
if (result.success === false) { if (result.success === false) {
new Notice('Login Failed, Please check credentials and try again!'); new Notice('Login Failed, Please check credentials and try again!');
} }
else { else {
console.log(result); this.setState({
this.setState({ isLoaded: true,
isLoaded: true, user_data: result,
user_data: result, tasks: result.tasks,
tasks: result.tasks, });
}); }
} } catch (e) {
} catch (e) {
console.log(e); console.log(e);
new Notice("API Error: Please check credentials") new Notice("API Error: Please check credentials")
} }
} }
componentDidMount() { componentDidMount() {
this.reloadData() this.reloadData()
} }
async sendScore(id:string , score: string, message: string){ async sendScore(id: string, score: string, message: string) {
try { try {
let response = await scoreTask(this.username, this.credentials, id, score); let response = await scoreTask(this.username, this.credentials, id, score);
let result = await response.json(); let result = await response.json();
if(result.success === true){ if (result.success === true) {
new Notice(message); new Notice(message);
this.reloadData(); this.reloadData();
} else { } else {
new Notice("Resyncing, please try again"); new Notice("Resyncing, please try again");
this.reloadData(); this.reloadData();
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
new Notice("API Error: Please check credentials") new Notice("API Error: Please check credentials")
} }
} }
handleChangeTodos(event: any){ async sendReward(id: string, score: string, message: string) {
try {
let response = await costReward(this.username, this.credentials, id, score);
let result = await response.json();
if (result.success === true) {
new Notice(message);
this.reloadData();
} else {
new Notice("Resyncing, please try again");
this.reloadData();
}
} catch (e) {
console.log(e);
new Notice("API Error: Please check credentials")
}
}
handleChangeTodos(event: any) {
this.state.tasks.todos.forEach((element: any) => { this.state.tasks.todos.forEach((element: any) => {
if(element.id == event.target.id){ if (element.id == event.target.id) {
if(!element.completed){ if (!element.completed) {
this.sendScore(event.target.id,"up", "Checked!") this.sendScore(event.target.id, "up", "Checked!")
} else { } else {
this.sendScore(event.target.id,"down", "Un-Checked!") this.sendScore(event.target.id, "down", "Un-Checked!")
} }
} }
}) })
} }
handleChangeDailys(event: any){
handleChangeDailys(event: any) {
this.state.tasks.dailys.forEach((element: any) => { this.state.tasks.dailys.forEach((element: any) => {
if(element.id == event.target.id){ if (element.id == event.target.id) {
if(element.id == event.target.id){ if (element.id == event.target.id) {
if(!element.completed){ if (!element.completed) {
this.sendScore(event.target.id,"up", "Checked!") this.sendScore(event.target.id, "up", "Checked!")
} else { } else {
this.sendScore(event.target.id,"down", "Un-Checked!") this.sendScore(event.target.id, "down", "Un-Checked!")
} }
} }
} }
}) })
} }
handleChangeHabits(event: any){ handleChangeHabits(event: any) {
const target_id = event.target.id.slice(4) const target_id = event.target.id.slice(4)
if(event.target.id.slice(0,4) == "plus"){ if (event.target.id.slice(0, 4) == "plus") {
this.state.tasks.habits.forEach((element: any) => { this.state.tasks.habits.forEach((element: any) => {
if(element.id == target_id){ if (element.id == target_id) {
this.sendScore(target_id,"up", "Plus!") this.sendScore(target_id, "up", "Plus!")
} }
}) })
} }
else { else {
this.state.tasks.habits.forEach((element: any) => { this.state.tasks.habits.forEach((element: any) => {
if(element.id == target_id){ if (element.id == target_id) {
this.sendScore(target_id,"down", "Minus :(") this.sendScore(target_id, "down", "Minus :(")
} }
}) })
} }
} }
handleChangeRewards(event: any) {
render(){ const target_id = event.target.id
this.state.tasks.rewards.forEach((element: any) => {
if (element.id == event.target.id) {
if (element.id == target_id) {
this.sendReward(target_id, "down", "Cost!")
}
}
})
}
render() {
let content = this.CheckCron(this.state.user_data.lastCron); let content = this.CheckCron(this.state.user_data.lastCron);
if(this.state.error) if (this.state.error)
return(<div className="loading">Loading....</div>) return (<div className="loading">Loading....</div>)
else if(!this.state.isLoaded) else if (!this.state.isLoaded)
return <div className="loading">Loading....</div> return <div className="loading">Loading....</div>
else { else {
return (<div className="plugin-root"> return (<div className="plugin-root">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" /> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Statsview user_data={this.state.user_data} /> <Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits} handleChangeRewards={this.handleChangeRewards}/>
<Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits}/>
{content} {content}
</div> <div></div>
<Statsview user_data={this.state.user_data} />
</div>
); );
} }
} }

View file

@ -3,9 +3,11 @@ import * as React from 'react';
export default function Index(props: any) { export default function Index(props: any) {
return( return(
<div className="stats"> <div className="stats">
<div id="profile-name">{props.user_data.profile.name}</div> {/* <div id="profile-name">{props.user_data.profile.name}</div> */}
<div className = "substats" id="hp"><i className="material-icons">favorite</i>HP: {(props.user_data.stats.hp).toPrecision(3)}</div> {console.log(props)}
<div className = "substats" id="lvl"><i className="material-icons">star</i>LVL: {props.user_data.stats.lvl}</div> <div className = "substats" id="hp">HP: {(props.user_data.stats.hp).toPrecision(3)}</div>
<div className = "substats" id="lvl">LEVEL: {props.user_data.stats.lvl}</div>
<div className = "substats" id="gold">GOLD: {(props.user_data.stats.gp).toPrecision(3)}</div>
</div> </div>
); );
} }

View file

@ -1,11 +1,16 @@
import Emoji from "react-emoji-render"; import Emoji from "react-emoji-render";
import * as React from "react"; import * as React from "react";
import ReactMarkdown from "react-markdown";
function DailyItem(props: any) { function DailyItem(props: any) {
return ( return (
<div className="todo-item" id={props.id}> <div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/> <input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed} />
<p><Emoji text={props.daily_text}></Emoji></p> <div>
<p><Emoji text={props.daily_text}></Emoji></p>
<ReactMarkdown children={props.daily_notes} />
</div>
</div> </div>
) )
} }

View file

@ -9,11 +9,11 @@ export default function Index(props: any){
else { else {
const incompleteDailies = props.dailys.map((daily: any) => { const incompleteDailies = props.dailys.map((daily: any) => {
if(!daily.completed) if(!daily.completed)
return <DailyItem key={daily.id} id={daily.id}daily_text={daily.text} onChange={props.onChange} completed={daily.completed}/> return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text} daily_notes={daily.notes} onChange={props.onChange} completed={daily.completed}/>
}) })
const completedDailies = props.dailys.map((daily: any) => { const completedDailies = props.dailys.map((daily: any) => {
if(daily.completed) if(daily.completed)
return <DailyItem key={daily.id} id={daily.id}daily_text={daily.text} onChange={props.onChange} completed={daily.completed}/> return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text} daily_notes={daily.notes} onChange={props.onChange} completed={daily.completed}/>
}) })
const display = <div id="classDisplay"> const display = <div id="classDisplay">
<Tabs> <Tabs>

View file

@ -1,16 +1,20 @@
import Emoji from "react-emoji-render"; import Emoji from "react-emoji-render";
import * as React from "react"; import * as React from "react";
import ReactMarkdown from "react-markdown";
function HabitItem(props: any) { function HabitItem(props: any) {
return ( return (
<div className="habit-item" id={props.id}> <div className="habit-item" id={props.id}>
<button className="habit-plus" id={"plus"+props.id} onClick={props.onChange}> <button className="habit-plus" id={"plus" + props.id} onClick={props.onChange}>
+{props.upCount} +{props.upCount}
</button> </button>
<p className="habit-text"><Emoji text = {props.habit_text}></Emoji></p> <button className="habit-minus" id={"mins" + props.id} onClick={props.onChange}>
<button className="habit-minus" id={"mins"+props.id} onClick={props.onChange}>
-{props.downCount} -{props.downCount}
</button> </button>
<div>
<p className="habit-text"><Emoji text={props.habit_text}></Emoji></p>
<ReactMarkdown children={props.habit_notes} />
</div>
</div> </div>
) )
} }

View file

@ -9,7 +9,7 @@ export default function Index(props: any){
} }
else { else {
const allHabits = props.habits.map((habit: any) => { const allHabits = props.habits.map((habit: any) => {
return <HabitItem key={habit.id} id={habit.id} habit_text={habit.text} upCount={habit.counterUp} downCount={habit.counterDown} onChange={props.onChange}/> return <HabitItem key={habit.id} id={habit.id} habit_text={habit.text} habit_notes={habit.notes} upCount={habit.counterUp} downCount={habit.counterDown} onChange={props.onChange}/>
}) })
const display = <div id="classDisplay"> const display = <div id="classDisplay">
<ul>{allHabits}</ul> <ul>{allHabits}</ul>

View file

@ -0,0 +1,17 @@
import Emoji from "react-emoji-render";
import * as React from "react";
import ReactMarkdown from "react-markdown";
function RewardItem(props: any) {
return (
<div className="reward-item" id={props.id}>
<button className="reward-click" id={props.id} onClick={props.onChange}>-{props.reward_value}</button>
<div>
<p className="reward-text"><Emoji text={props.reward_text}></Emoji></p>
<ReactMarkdown children={props.reward_notes} />
</div>
</div>
)
}
export default RewardItem

View file

@ -0,0 +1,21 @@
import * as React from "react";
import RewardItem from "./RewardItem"
export default function Index(props: any){
if(props.rewards == undefined) {
return (<div id="classDisplay">
No Rewards present.
</div>)
}
else {
const allRewards = props.rewards.map((reward: any) => {
return <RewardItem key={reward.id} id={reward.id} reward_text={reward.text} reward_notes={reward.notes} reward_value={reward.value} onChange={props.onChange}/>
})
const display = <div id="classDisplay">
<ul>{allRewards}</ul>
</div>
return(display);
}
}

View file

@ -5,7 +5,7 @@ function TodoItem(props: any) {
return ( return (
<div className="todo-item" id={props.id}> <div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/> <input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/>
<Emoji text = {props.todo_text}></Emoji> <Emoji text = {props.todo_text}></Emoji><Emoji text = {props.todo_text}></Emoji>
</div> </div>
) )
} }

View file

@ -1,11 +1,16 @@
import Emoji from "react-emoji-render"; import Emoji from "react-emoji-render";
import * as React from "react"; import * as React from "react";
import ReactMarkdown from "react-markdown";
function TodoItem(props: any) { function TodoItem(props: any) {
return ( return (
<div className="todo-item" id={props.id}> <div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/> <input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/>
<p><Emoji text ={props.todo_text}></Emoji></p> {/* <p><Emoji text ={props.todo_text}></Emoji></p> */}
<div>
<p><Emoji text={props.todo_text}></Emoji></p>
<ReactMarkdown children={props.todo_notes} />
</div>
</div> </div>
) )
} }

View file

@ -9,11 +9,11 @@ export default function Index(props: any){
else { else {
const incompleteTodos = props.todos.map((todo: any) => { const incompleteTodos = props.todos.map((todo: any) => {
if(!todo.completed) if(!todo.completed)
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} onChange={props.onChange} completed={todo.completed}/> return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} todo_notes={todo.notes} onChange={props.onChange} completed={todo.completed}/>
}) })
const completedTodos = props.todos.map((todo: any) => { const completedTodos = props.todos.map((todo: any) => {
if(todo.completed) if(todo.completed)
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} onChange={props.onChange} completed={todo.completed}/> return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} todo_notes={todo.notes} onChange={props.onChange} completed={todo.completed}/>
}) })
const display = <div id="classDisplay"> const display = <div id="classDisplay">
<Tabs> <Tabs>

View file

@ -2,31 +2,39 @@ import * as React from "react";
import Dailiesview from "./Dailiesview" import Dailiesview from "./Dailiesview"
import Habitsview from "./Habitsview" import Habitsview from "./Habitsview"
import Todoview from "./Todoview" import Todoview from "./Todoview"
import Rewardview from "./Rewardview"
import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
export default function Index(props: any){ export default function Index(props: any){
const display = <div className="task-view"> const display = <div className="task-view">
<Tabs> <Tabs>
<TabList> <TabList>
<Tab >
<span className="material-icons md-24">task_alt</span>
</Tab>
<Tab> <Tab>
<span className="material-icons md-24">today</span> <span className="material-icons md-24">today</span>
</Tab> </Tab>
<Tab >
<span className="material-icons md-24">add_chart</span>
</Tab>
<Tab> <Tab>
<span className="material-icons md-24">add_circle_outline</span> <span className="material-icons md-24">assignment_turned_in</span>
</Tab>
<Tab>
<span className="material-icons md-24">account_balance</span>
</Tab> </Tab>
</TabList> </TabList>
<TabPanel>
<Habitsview habits={props.data.habits} onChange={props.handleChangeHabits}/>
</TabPanel>
<TabPanel> <TabPanel>
<Dailiesview dailys={props.data.dailys} onChange={props.handleChangeDailys} /> <Dailiesview dailys={props.data.dailys} onChange={props.handleChangeDailys} />
</TabPanel> </TabPanel>
<TabPanel>
<Habitsview habits={props.data.habits} onChange={props.handleChangeHabits}/>
</TabPanel>
<TabPanel> <TabPanel>
<Todoview todos={props.data.todos} onChange={props.handleChangeTodos} /> <Todoview todos={props.data.todos} onChange={props.handleChangeTodos} />
</TabPanel> </TabPanel>
<TabPanel>
<Rewardview rewards={props.data.rewards} onChange={props.handleChangeRewards} />
</TabPanel>
</Tabs> </Tabs>
</div> </div>
return(display); return(display);

View file

@ -40,3 +40,17 @@ export async function makeCronReq(username: string, credentials: string){
}) })
return(response) return(response)
} }
export async function costReward(username: string, credentials: string, taskID: string, direction: string) {
const url = "https://habitica.com/api/v4/tasks/".concat(taskID).concat("/score/").concat(direction)
const response = fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync",
"x-api-user": username,
"x-api-key": credentials,
}
})
return(response)
}

View file

@ -1,3 +1,8 @@
* {
/* margin: 0; */
padding: 0;
}
#profile-name { #profile-name {
font-size: x-large; font-size: x-large;
@ -5,7 +10,8 @@
padding-bottom: 3%; padding-bottom: 3%;
} }
.stats { .stats {
padding-bottom: 6px; display: flex;
justify-content: space-between;
} }
.todo-item { .todo-item {
@ -13,25 +19,38 @@
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
padding: 0px 0px 0; padding: 0px 0px 0;
width: 80%; width: 100%;
border-bottom: 1px solid #cecece; border-bottom: 1px solid #cecece;
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;
font-weight: normal; font-weight: normal;
font-size: 16px; font-size: 16px;
} }
.reward-item {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0px 0px 0;
width: 100%;
border-bottom: 1px solid #cecece;
font-family: Roboto, sans-serif;
font-weight: normal;
font-size: 16px;
}
.habit-text { .habit-text {
text-align: center !important; text-align: left !important;
padding: 0px; padding: 0px;
width: 50%; width: 80%;
margin-right: 20px; margin-right: 20px;
} }
.habit-plus { .habit-plus {
/* background-color: #fff; */ /* background-color: #fff; */
border: none; border: none;
color: white; /* color: black; */
padding: 7px 10px; padding: 7px 5px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
font-size: 16px; font-size: 16px;
@ -41,8 +60,8 @@
/* background-color: #fff; */ /* background-color: #fff; */
/* border-radius: 50%; */ /* border-radius: 50%; */
border: none; border: none;
color: white; /* color: black; */
padding: 7px 10px; padding: 7px 5px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
font-size: 16px; font-size: 16px;
@ -70,8 +89,19 @@ input[type=checkbox]:focus {
outline: 0; outline: 0;
} }
.task-view {
overflow: scroll;
}
::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.plugin-root { .plugin-root {
min-width: 260px; min-width: 260px;
display: grid;
grid-template-rows: 93% 5% 2%;
height: inherit;
} }
.substats { .substats {
@ -144,6 +174,7 @@ input[type=checkbox]:focus {
.react-tabs__tab-panel { .react-tabs__tab-panel {
display: none; display: none;
left: 0px;
} }
.react-tabs__tab-panel--selected { .react-tabs__tab-panel--selected {