commit
d29bfd9995
9 changed files with 264 additions and 170 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "obsidian-habitica-integration",
|
"id": "obsidian-habitica-integration",
|
||||||
"name": "Habitica Sync",
|
"name": "Habitica Sync",
|
||||||
"version": "0.9.7",
|
"version": "0.9.15",
|
||||||
"minAppVersion": "0.9.12",
|
"minAppVersion": "0.9.12",
|
||||||
"description": "This plugin helps integrate Habitica user tasks and stats into Obsidian",
|
"description": "This plugin helps integrate Habitica user tasks and stats into Obsidian",
|
||||||
"author": "Leoh and Ran",
|
"author": "Leoh and Ran",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
"node-fetch": "^3.0.0",
|
"node-fetch": "^3.0.0",
|
||||||
"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-tabs": "^3.2.2"
|
"react-tabs": "^3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Notice } from "obsidian";
|
import { Notice } from "obsidian";
|
||||||
import { getStats, scoreTask } from "./habiticaAPI"
|
import { getStats, scoreTask, makeCronReq } 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> {
|
||||||
username = ""
|
private _username = "";
|
||||||
credentials = ""
|
public get username() {
|
||||||
|
return this._username;
|
||||||
|
}
|
||||||
|
public set username(value) {
|
||||||
|
this._username = value;
|
||||||
|
}
|
||||||
|
private _credentials = "";
|
||||||
|
public get credentials() {
|
||||||
|
return this._credentials;
|
||||||
|
}
|
||||||
|
public set credentials(value) {
|
||||||
|
this._credentials = value;
|
||||||
|
}
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.username = this.props.plugin.settings.userID
|
this.username = this.props.plugin.settings.userID
|
||||||
this.credentials = this.props.plugin.settings.apiToken
|
this.credentials = this.props.plugin.settings.apiToken
|
||||||
this.state = {
|
this.state = {
|
||||||
|
needCron: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
user_data: {
|
user_data: {
|
||||||
profile: {
|
profile: {
|
||||||
|
|
@ -20,18 +33,48 @@ class App extends React.Component<any,any> {
|
||||||
stats: {
|
stats: {
|
||||||
hp: 0,
|
hp: 0,
|
||||||
lvl: 0,
|
lvl: 0,
|
||||||
}
|
},
|
||||||
|
lastCron: "",
|
||||||
},
|
},
|
||||||
todos: [],
|
todos: [],
|
||||||
dailys: [],
|
dailys: [],
|
||||||
habits: [],
|
habits: [],
|
||||||
}
|
}
|
||||||
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.runCron = this.runCron.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
CheckCron(lastCron: string) {
|
||||||
|
let cronDate = new Date(lastCron);
|
||||||
|
let now = new Date();
|
||||||
|
if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) {
|
||||||
|
return(
|
||||||
|
<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>
|
||||||
|
<button onClick={this.runCron}>Continue</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Cron is up to date");
|
||||||
|
return null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async runCron() {
|
||||||
|
console.log("running cron");
|
||||||
|
try {
|
||||||
|
let response = await makeCronReq(this.username, this.credentials);
|
||||||
|
this.setState({
|
||||||
|
needCron: false,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
new Notice("There was an error running the cron. Please try again later.");
|
||||||
|
}
|
||||||
|
this.reloadData();
|
||||||
|
}
|
||||||
async reloadData() {
|
async reloadData() {
|
||||||
try {
|
try {
|
||||||
let response = await getStats(this.username, this.credentials);
|
let response = await getStats(this.username, this.credentials);
|
||||||
|
|
@ -40,6 +83,7 @@ class App extends React.Component<any,any> {
|
||||||
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,
|
||||||
|
|
@ -115,6 +159,7 @@ class App extends React.Component<any,any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
|
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)
|
||||||
|
|
@ -124,6 +169,7 @@ class App extends React.Component<any,any> {
|
||||||
<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} />
|
<Statsview user_data={this.state.user_data} />
|
||||||
<Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits}/>
|
<Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits}/>
|
||||||
|
{content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import * as React from "react";
|
import Emoji from "react-emoji-render";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
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>{props.daily_text}</p>
|
<p><Emoji text={props.daily_text}></Emoji></p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from "react";
|
import Emoji from "react-emoji-render";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
function HabitItem(props: any) {
|
function HabitItem(props: any) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -6,7 +7,7 @@ function HabitItem(props: any) {
|
||||||
<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">{props.habit_text}</p>
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import * as React from "react";
|
import { Emoji } from 'react-emoji-render';
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
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>{props.todo_text}</p>
|
<Emoji text = {props.todo_text}></Emoji>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import * as React from "react";
|
import Emoji from "react-emoji-render";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
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>{props.todo_text}</p>
|
<p><Emoji text ={props.todo_text}></Emoji></p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export async function getStats(username: string, credentials: string){
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"x-client": username.concat("-testAPI"),
|
"x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync",
|
||||||
"x-api-user": username,
|
"x-api-user": username,
|
||||||
"x-api-key": credentials,
|
"x-api-key": credentials,
|
||||||
},
|
},
|
||||||
|
|
@ -20,7 +20,20 @@ export async function scoreTask(username: string, credentials: string, taskID: s
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"x-client": username.concat("-testAPI"),
|
"x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync",
|
||||||
|
"x-api-user": username,
|
||||||
|
"x-api-key": credentials,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return(response)
|
||||||
|
}
|
||||||
|
export async function makeCronReq(username: string, credentials: string){
|
||||||
|
const url = "https://habitica.com/api/v3/cron";
|
||||||
|
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-user": username,
|
||||||
"x-api-key": credentials,
|
"x-api-key": credentials,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
332
styles.css
332
styles.css
|
|
@ -1,151 +1,181 @@
|
||||||
|
|
||||||
#profile-name {
|
#profile-name {
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding-bottom: 3%;
|
padding-bottom: 3%;
|
||||||
}
|
}
|
||||||
.stats {
|
.stats {
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-item {
|
.todo-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
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: 80%;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.habit-text {
|
.habit-text {
|
||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
width: 80px;
|
width: 50%;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.habit-plus {
|
.habit-plus {
|
||||||
/* background-color: #fff; */
|
/* background-color: #fff; */
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.habit-minus {
|
.habit-minus {
|
||||||
/* background-color: #fff; */
|
/* background-color: #fff; */
|
||||||
/* border-radius: 50%; */
|
/* border-radius: 50%; */
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.habit-item {
|
.habit-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
/* justify-content: center; */
|
/* justify-content: center; */
|
||||||
align-content: space-between;
|
align-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0px 0px 0;
|
padding: 0px 0px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid #cecece;
|
border-bottom: 1px solid #cecece;
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
font-weight: 50%;
|
font-weight: 50%;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox]:focus {
|
input[type=checkbox]:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-root {
|
.plugin-root {
|
||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.substats {
|
.substats {
|
||||||
font-size: medium;
|
font-size: medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* react-tabs internal file :wink: */
|
/* react-tabs internal file :wink: */
|
||||||
.material-icons {
|
.material-icons {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons.md-18 { font-size: 18px !important; }
|
.material-icons.md-18 { font-size: 18px !important; }
|
||||||
.material-icons.md-24 { font-size: 24px !important; }
|
.material-icons.md-24 { font-size: 24px !important; }
|
||||||
.material-icons.md-32 { font-size: 32px !important; }
|
.material-icons.md-32 { font-size: 32px !important; }
|
||||||
.material-icons.md-48 { font-size: 48px !important; }
|
.material-icons.md-48 { font-size: 48px !important; }
|
||||||
|
|
||||||
|
|
||||||
.react-tabs {
|
.react-tabs {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab-list {
|
.react-tabs__tab-list {
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid #aaa;
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab {
|
.react-tabs__tab {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
bottom: -1px;
|
bottom: -1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 1% 2%;
|
padding: 1% 2%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: medium;
|
font-size: medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab--selected {
|
.react-tabs__tab--selected {
|
||||||
background: var(--interactive-accent);
|
background: var(--interactive-accent);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab--disabled {
|
.react-tabs__tab--disabled {
|
||||||
color: GrayText;
|
color: GrayText;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab:focus {
|
.react-tabs__tab:focus {
|
||||||
box-shadow: 0 0 5px hsl(208, 99%, 50%);
|
box-shadow: 0 0 5px hsl(208, 99%, 50%);
|
||||||
border-color: hsl(208, 99%, 50%);
|
border-color: hsl(208, 99%, 50%);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab:focus:after {
|
.react-tabs__tab:focus:after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
left: -4px;
|
left: -4px;
|
||||||
right: -4px;
|
right: -4px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab-panel {
|
.react-tabs__tab-panel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-tabs__tab-panel--selected {
|
.react-tabs__tab-panel--selected {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
ul li:not(.task-list-item)::before {
|
||||||
|
content: "•";
|
||||||
|
color: transparent;
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
margin-left: -1em;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 0 0 0.5em transparent;
|
||||||
|
}
|
||||||
|
body > div.app-container.is-left-sidedock-collapsed.is-right-sidedock-collapsed > div.horizontal-main-container > div > div.workspace-split.mod-horizontal.mod-right-split > div.workspace-tabs > div.workspace-leaf > div > div.view-content > div > div.cron > button {
|
||||||
|
margin: auto;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.cron {
|
||||||
|
display: inline-grid;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-right: 10%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: var(--background-secondary-alt);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
#cronMessage {
|
||||||
|
margin: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--text-normal)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue