Added basic task input

This commit is contained in:
Akaswan 2025-01-03 19:04:48 -06:00
parent 68cfbe1ff8
commit b2d2e215c7
9 changed files with 554 additions and 201 deletions

15
package-lock.json generated
View file

@ -10,6 +10,8 @@
"license": "MIT",
"dependencies": {
"date-fns": "^4.1.0",
"lucide": "^0.469.0",
"lucide-react": "^0.469.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
@ -1718,6 +1720,19 @@
"dev": true,
"peer": true
},
"node_modules/lucide": {
"version": "0.469.0",
"resolved": "https://registry.npmjs.org/lucide/-/lucide-0.469.0.tgz",
"integrity": "sha512-ZmMtOekJ6g1UFqTvV9aWbkk+u/QxpfDpgUrbNj8jJEK+CtBdHVQ4akCA0TcTyeOvnL8RWo271UzFTimBRT0gKw=="
},
"node_modules/lucide-react": {
"version": "0.469.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz",
"integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",

View file

@ -25,6 +25,7 @@
},
"dependencies": {
"date-fns": "^4.1.0",
"lucide-react": "^0.469.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}

191
src/main/components/App.tsx Normal file
View file

@ -0,0 +1,191 @@
import { useEffect, useState } from "react";
import { useTableContext } from "../views/TableView";
import Table from "./Table";
import TopBar from "./TopBar";
import { format, addDays, startOfWeek, subDays } from "date-fns";
export interface Project {
id: number;
name: string;
tasks: Task[];
}
export interface Task {
id: number;
name: string;
date: Date;
parentProjectId: number;
}
const getWeekDates = (date: Date) => {
const startDate = startOfWeek(date, { weekStartsOn: 1 });
const dates = Array.from({ length: 7 }, (_, i) => {
return format(addDays(startDate, i), "yyyy-MM-dd");
});
return dates;
};
const App: React.FC = () => {
const tableContext = useTableContext();
const [dates, setDates] = useState(getWeekDates(new Date()));
const [data, setData] = useState(() => tableContext!.loadData());
const [projects, setProjects] = useState(
() => tableContext!.loadData().projects as Project[]
);
const [nextProjectId, setNextProjectId] = useState(
() => tableContext!.loadData().nextProjectId as number
);
const [nextTaskId, setNextTaskId] = useState(
() => tableContext!.loadData().nextProjectId as number
);
const incrementDates = () => {
setDates((prevDates) => {
const newDates = getWeekDates(addDays(new Date(prevDates[0]), 8));
return newDates;
});
};
const decrementDates = () => {
setDates((prevDates) => {
const newDates = getWeekDates(subDays(new Date(prevDates[0]), 8));
return newDates;
});
};
const setDatesToThisWeek = () => {
setDates((prevDates) => {
const newDates = getWeekDates(new Date());
return newDates;
});
};
const handleProjectNameChange = (id: number, newName: string) => {
setProjects((prevProjects) => {
const newProjects = prevProjects.map((project) =>
project.id === id ? { ...project, name: newName } : project
);
saveSpecificData("projects", newProjects);
return newProjects;
});
};
const removeProject = (id: number) => {
setProjects((prevProjects) => {
const newProjects = prevProjects.filter(
(project) => project.id !== id
);
saveSpecificData("projects", newProjects);
return newProjects;
});
};
const removeTask = (id: number) => {
setProjects((prevProjects) => {
const newProjects = prevProjects.map((project) => {
const newTasks = project.tasks.filter((task) => task.id !== id);
return { ...project, tasks: newTasks };
});
saveSpecificData("projects", newProjects);
return newProjects;
});
};
const createNewProject = (
newName: string,
newProjectInputRef: React.RefObject<HTMLInputElement | null>
) => {
if (!newName.trim()) return;
setProjects((prevProjects) => {
const newProjects = [
...prevProjects,
{ id: nextProjectId, name: newName, tasks: [] },
];
saveSpecificData("projects", newProjects);
return newProjects;
});
setNextProjectId((prevId) => {
const newId = prevId + 1;
saveSpecificData("nextProjectId", newId);
return newId;
});
setTimeout(() => {
if (newProjectInputRef.current) {
newProjectInputRef.current.focus();
}
}, 0);
};
const addTaskToProject = (project: Project, date: string) => {
const newTask = {
id: nextTaskId,
name: "",
date: new Date(date),
parentProjectId: project.id,
};
setProjects((prevProjects) => {
const newProjects = prevProjects.map((p) => {
if (p.id === project.id) {
return { ...p, tasks: [...p.tasks, newTask] };
}
return p;
});
saveSpecificData("projects", newProjects);
return newProjects;
});
setNextTaskId((prevId) => {
const newId = prevId + 1;
saveSpecificData("nextTaskId", newId);
return newId;
});
};
const saveSpecificData = (key: string, value: any): void => {
setData((prevData: any) => {
const newData = { ...prevData, [key]: value };
return newData;
});
};
useEffect(() => {
if (tableContext) {
tableContext.saveData(data);
} else {
console.log("not exist");
}
}, [projects, nextProjectId]);
return (
<div className="app-wrapper">
<TopBar
incrementDates={incrementDates}
decrementDates={decrementDates}
setDatesToThisWeek={setDatesToThisWeek}
/>
<Table
projects={projects}
nextProjectId={nextProjectId}
handleProjectNameChange={handleProjectNameChange}
createNewProject={createNewProject}
dates={dates}
addTaskToProject={addTaskToProject}
removeProject={removeProject}
removeTask={removeTask}
nextTaskId={nextTaskId}
/>
</div>
);
};
export default App;

View file

@ -0,0 +1,158 @@
import { format } from "date-fns";
import { useEffect, useRef } from "react";
import * as React from "react";
import { Project } from "./App";
import TaskCell from "./TaskCell";
interface TableProps {
projects: Project[];
nextProjectId: number;
handleProjectNameChange: (id: number, newName: string) => void;
createNewProject: (
newName: string,
newProjectInputRef: React.RefObject<HTMLInputElement | null>
) => void;
dates: string[];
addTaskToProject: (project: Project, date: string) => void;
removeProject: (id: number) => void;
removeTask: (id: number) => void;
nextTaskId: number;
}
const Table: React.FC<TableProps> = ({
projects,
nextProjectId,
handleProjectNameChange,
createNewProject,
dates,
addTaskToProject,
removeProject,
removeTask,
nextTaskId
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const newProjectInputRef = useRef<HTMLInputElement | null>(null);
const newTaskInputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
const handleResize = () => {
const containerWidth = containerRef.current?.clientWidth;
if (containerWidth) {
document.documentElement.style.setProperty(
"--taskcell-enclosure-width",
`${(containerWidth - 128) / 7}px`
);
}
};
const resizeObserver = new ResizeObserver(handleResize);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
// Initial call to set the variable
handleResize();
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);
return (
<div>
<div className="table-container" ref={containerRef}>
<table className="table headings-center">
<thead className="table-header">
<tr>
<th>Projects</th>
{dates.map((date) => (
<th className="date" key={date}>{`${format(
date,
"EEEE"
)} - ${format(date, "yyyy-MM-dd")}`}</th>
))}
</tr>
</thead>
<tbody>
{projects.map((project) => (
<tr key={project.id}>
<td key={project.id}>
<input
ref={
project.id === nextProjectId - 1
? newProjectInputRef
: null
}
type="text"
value={project.name}
className="project-input"
placeholder="New Project"
onBlur={(e) => {if (e.target.value === "") removeProject(project.id)}}
onChange={(e) =>
handleProjectNameChange(
project.id,
e.target.value
)
}
/>
</td>
{dates.map((date, index) => (
<td
className="taskcell-enclosure"
key={`${project.id}:${date}:${index}`}
onClick={(e) => {
if (e.target === e.currentTarget) {
addTaskToProject(project, date);
setTimeout(() => {
if (newTaskInputRef.current) {
newTaskInputRef.current.focus();
}
}, 0);
}
}}
>
{project.tasks
.filter(
(task) =>
new Date(
task.date
).toISOString() ===
new Date(date).toISOString()
)
.map((task) => (
<TaskCell
task={task}
removeTask={removeTask}
key={task.id}
inputRef={
task.id === nextTaskId - 1
? newTaskInputRef
: null
}
/>
))}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className="new-project-container">
<input
type="text"
className="project-input"
placeholder="New Project"
onChange={(e) => {
createNewProject(e.target.value, newProjectInputRef);
e.target.value = "";
}}
/>
</div>
</div>
);
};
export default Table;

View file

@ -0,0 +1,61 @@
import { Task } from "./App";
const transColor = (color: string, percent: number): string => {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const B = ((num >> 8) & 0x00ff) + amt;
const G = (num & 0x0000ff) + amt;
return (
"#" +
(
0x1000000 +
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
(B < 255 ? (B < 1 ? 0 : B) : 255) * 0x100 +
(G < 255 ? (G < 1 ? 0 : G) : 255)
)
.toString(16)
.slice(1)
);
};
interface TaskCellProps {
task: Task;
removeTask: (id: number) => void;
inputRef: React.RefObject<HTMLInputElement | null> | null;
}
const TaskCell: React.FC<TaskCellProps> = ({task, removeTask, inputRef}) => {
const color = "#35CC70";
document.documentElement.style.setProperty("--task-color", color);
document.documentElement.style.setProperty(
"--task-background",
`${color}33`
);
document.documentElement.style.setProperty(
"--dark-task-text-color",
transColor(color, -40)
);
document.documentElement.style.setProperty(
"--light-task-text-color",
transColor(color, 25)
);
return (
<div className="task-cell">
<div className="task-cell-header">Apush</div>
<div className="task-cell-content">
<input
placeholder="Task Name"
onBlur={(e) => {
if (e.target.value === "") removeTask(task.id);
}}
ref={inputRef}
></input>
</div>
</div>
);
};
export default TaskCell;

View file

@ -0,0 +1,23 @@
import { ChevronLeft, ChevronRight } from "lucide-react";
interface TopBarProps {
incrementDates: () => void;
decrementDates: () => void;
setDatesToThisWeek: () => void;
}
const TopBar: React.FC<TopBarProps> = ({ incrementDates, decrementDates, setDatesToThisWeek }) => {
return (
<div className="top-bar">
<div className="date-increment-container">
<ChevronLeft onClick={decrementDates} className="date-increment left-increment" />
<div onClick={setDatesToThisWeek} className="date-display">
{new Date().toDateString()}
</div>
<ChevronRight onClick={incrementDates} className="date-increment right-increment" />
</div>
</div>
);
};
export default TopBar;

View file

@ -1,195 +0,0 @@
// import "styles.css";
import { format, addDays, startOfWeek } from "date-fns";
import { useEffect, useRef, useState } from "react";
import * as React from "react";
import { useTableContext } from "../views/TableView";
interface Project {
id: number;
name: string;
tasks: Task[];
}
interface Task {
id: number;
name: string;
date: Date;
parentProjectId: number;
}
const getInitialDates = () => {
const startDate = startOfWeek(new Date(), { weekStartsOn: 1 });
const dates = Array.from({ length: 7 }, (_, i) => {
return format(addDays(startDate, i), "yyyy-MM-dd");
});
return dates;
};
const Table: React.FC = () => {
const tableContext = useTableContext();
const [data, setData] = useState(() => tableContext!.loadData());
const [dates, setDates] = useState(getInitialDates);
const [projects, setProjects] = useState(
() => tableContext!.loadData().projects as Project[]
);
const [nextProjectId, setNextProjectId] = useState(
() => tableContext!.loadData().nextProjectId as number
);
const containerRef = useRef<HTMLDivElement>(null);
const newProjectInputRef = useRef<HTMLInputElement | null>(null);
const saveSpecificData = (key: string, value: any): void => {
setData((prevData: any) => {
const newData = { ...prevData, [key]: value };
return newData;
});
};
useEffect(() => {
const handleResize = () => {
const containerWidth = containerRef.current?.clientWidth;
if (containerWidth) {
document.documentElement.style.setProperty(
"--taskcell-enclosure-width",
`${(containerWidth - 128) / 7}px`
);
}
};
const resizeObserver = new ResizeObserver(handleResize);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
// Initial call to set the variable
handleResize();
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);
const handleProjectNameChange = (id: number, newName: string) => {
if (!newName.trim()) {
setProjects((prevProjects) => {
const newProjects = prevProjects.filter(
(project) => project.id !== id
);
saveSpecificData("projects", newProjects);
return newProjects;
});
} else {
setProjects((prevProjects) => {
const newProjects = prevProjects.map((project) =>
project.id === id ? { ...project, name: newName } : project
);
saveSpecificData("projects", newProjects);
return newProjects;
});
}
};
const createNewProject = (newName: string) => {
if (!newName.trim()) return;
setProjects((prevProjects) => {
const newProjects = [
...prevProjects,
{ id: nextProjectId, name: newName, tasks: [] },
];
saveSpecificData("projects", newProjects);
return newProjects;
});
setNextProjectId((prevId) => {
const newId = prevId + 1;
saveSpecificData("nextProjectId", newId);
return newId;
});
setTimeout(() => {
if (newProjectInputRef.current) {
newProjectInputRef.current.focus();
}
}, 0);
};
useEffect(() => {
if (tableContext) {
tableContext.saveData(data);
} else {
console.log("not exist");
}
}, [projects, nextProjectId]);
return (
<div>
<div className="table-container" ref={containerRef}>
<table className="table headings-center">
<thead className="table-header">
<tr>
<th>Projects</th>
{dates.map((date) => (
<th className="date" key={date}>{`${format(
date,
"EEEE"
)} - ${format(date, "yyyy-MM-dd")}`}</th>
))}
</tr>
</thead>
<tbody>
{projects.map((project) => (
<tr key={project.id}>
<td key={project.id}>
<input
ref={
project.id === nextProjectId - 1
? newProjectInputRef
: null
} // Attach ref to the newly created project
type="text"
value={project.name}
className="project-input"
placeholder="New Project"
onChange={(e) =>
handleProjectNameChange(
project.id,
e.target.value
)
}
/>
</td>
{dates.map((date, index) => (
<td
className="taskcell-enclosure"
key={`${project.id}:${date}:${index}`}
></td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className="new-project-container">
<input
type="text"
className="project-input"
placeholder="New Project"
onChange={(e) => {
createNewProject(e.target.value);
e.target.value = "";
}}
/>
</div>
</div>
);
};
export default Table;

View file

@ -1,7 +1,7 @@
import { createContext, useContext } from 'react';
import { ItemView, WorkspaceLeaf, App } from 'obsidian';
import { Root, createRoot } from 'react-dom/client';
import Table from 'src/main/react/Table';
import AppComponent from 'src/main/components/App';
export interface AppContext {
app: App;
@ -45,7 +45,7 @@ export class TableView extends ItemView {
this.root = createRoot(this.containerEl.children[1]);
this.root.render(
<TableContext.Provider value={{app: this.app, saveData: this.saveData, loadData: this.loadData}}>
<Table />
<AppComponent />
</TableContext.Provider>
);
}

View file

@ -1,4 +1,102 @@
/* styles.css */
/* App */
.app-wrapper {
display: flex;
flex-direction: column;
}
/* Top Bar */
.top-bar {
margin-bottom: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.date-increment-container {
display: flex;
justify-content: center;
align-items: center;
width: min-content;
}
.date-display {
width: max-content;
font-weight: bold !important;
font-size: large !important;
padding: 0px 24px;
height: 36px;
display: flex;
align-items: center;
background-color: var(--background-secondary);
border-left: var(--border-width) solid var(--background-modifier-border);
border-top: var(--border-width) solid var(--background-modifier-border);
border-bottom: var(--border-width) solid var(--background-modifier-border);
}
.date-display:hover {
background-color: var(--background-secondary-alt);
}
.date-increment {
height: 36px;
width: 36px;
padding: 6px;
border-left: var(--border-width) solid var(--background-modifier-border);
border-top: var(--border-width) solid var(--background-modifier-border);
border-bottom: var(--border-width) solid var(--background-modifier-border);
background-color: var(--background-secondary);
}
.date-increment:hover {
background-color: var(--background-secondary-alt);
}
.left-increment {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.right-increment {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
border-right: var(--border-width) solid var(--background-modifier-border);
}
/* Task Cell */
.task-cell {
background: var(--task-background);
opacity: 0.8;
border-radius: 8px;
padding: 4px;
display: flex;
flex-direction: column;
margin: 4px;
}
.task-cell-header {
background: var(--task-background);
border-radius: 4px;
margin-bottom: 4px;
color: var(--light-task-text-color);
padding-left: 4px;
}
.task-cell-content input {
background: transparent;
border: none;
color: var(--light-task-text-color);
margin-bottom: 4px;
font-size: 14px;
width: 100%;
}
.task-cell-content input::placeholder {
color: var(--light-task-text-color);
opacity: 0.5;
}
/* Table */
.table-container {
border: var(--border-width) solid var(--background-modifier-border);
border-top-left-radius: 8px;
@ -38,9 +136,10 @@
width: 128px;
}
.table td {
padding: 12px 16px;
padding: 8px;
font-size: small;
height: 68px;
height: 80px;
/* height: 128px; */
border-top: var(--border-width) solid var(--background-modifier-border);
}
.taskcell-enclosure {
@ -58,4 +157,4 @@
border-left: var(--border-width) solid var(--background-modifier-border);
width: var(--taskcell-enclosure-width);
}
/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3R5bGVzLmNzcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyogRGVmaW5lIGEgY3VzdG9tIGJvcmRlciBjb2xvciAqL1xyXG4udGFibGUtY29udGFpbmVyIHtcclxuICBib3JkZXI6IHZhcigtLWJvcmRlci13aWR0aCkgc29saWQgdmFyKC0tYmFja2dyb3VuZC1tb2RpZmllci1ib3JkZXIpOyAvKiBDdXN0b20gYm9yZGVyIGNvbG9yICovXHJcbiAgYm9yZGVyLXJhZGl1czogOHB4OyAvKiBSb3VuZGVkIGNvcm5lcnMgKi9cclxuICBvdmVyZmxvdzogaGlkZGVuO1xyXG4gIG1heC13aWR0aDogMTAwJTtcclxuICAtLW9uZS1zZXZlbnRoLXdpZHRoOiAwcHg7XHJcbn1cclxuXHJcbi50YWJsZSB7XHJcbiAgd2lkdGg6IDEwMCU7XHJcbiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTtcclxuICB0ZXh0LWFsaWduOiBsZWZ0O1xyXG59XHJcblxyXG4udGFibGUtaGVhZGVyIHtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1iYWNrZ3JvdW5kLXNlY29uZGFyeSk7XHJcbn1cclxuXHJcbi50YWJsZSB0aCB7XHJcbiAgcGFkZGluZzogMTJweCAxNnB4O1xyXG4gIGZvbnQtc2l6ZTogc21hbGw7XHJcbiAgZm9udC13ZWlnaHQ6IDUwMDtcclxufSBcclxuXHJcbi50b3AtbGVmdCB7XHJcbiAgd2lkdGg6IDEyOHB4O1xyXG59XHJcblxyXG4udGFibGUgdGQge1xyXG4gIHBhZGRpbmc6IDEycHggMTZweDtcclxuICBmb250LXNpemU6IHNtYWxsO1xyXG4gIGhlaWdodDogNjhweDtcclxuICBib3JkZXItdG9wOiB2YXIoLS1ib3JkZXItd2lkdGgpIHNvbGlkIHZhcigtLWJhY2tncm91bmQtbW9kaWZpZXItYm9yZGVyKTtcclxufVxyXG5cclxuLnRhc2tjZWxsLWVuY2xvc3VyZSB7XHJcbiAgYm9yZGVyLWxlZnQ6IHZhcigtLWJvcmRlci13aWR0aCkgc29saWQgdmFyKC0tYmFja2dyb3VuZC1tb2RpZmllci1ib3JkZXIpO1xyXG59XHJcblxyXG4ucHJvamVjdC1pbnB1dCB7XHJcbiAgYm9yZGVyOiBub25lICFpbXBvcnRhbnQ7XHJcbiAgd2lkdGg6IDEwMCU7XHJcbn1cclxuXHJcbi5kYXRlIHtcclxuICBib3JkZXItbGVmdDogdmFyKC0tYm9yZGVyLXdpZHRoKSBzb2xpZCB2YXIoLS1iYWNrZ3JvdW5kLW1vZGlmaWVyLWJvcmRlcik7IFxyXG4gIHdpZHRoOiB2YXIoLS10YXNrY2VsbC1lbmNsb3N1cmUtd2lkdGgpO1xyXG59Il0sCiAgIm1hcHBpbmdzIjogIjtBQUNBO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBOyIsCiAgIm5hbWVzIjogW10KfQo= */
/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3R5bGVzLmNzcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyogRGVmaW5lIGEgY3VzdG9tIGJvcmRlciBjb2xvciAqL1xyXG4udGFibGUtY29udGFpbmVyIHtcclxuICBib3JkZXI6IHZhcigtLWJvcmRlci13aWR0aCkgc29saWQgdmFyKC0tYmFja2dyb3VuZC1tb2RpZmllci1ib3JkZXIpOyAvKiBDdXN0b20gYm9yZGVyIGNvbG9yICovXHJcbiAgYm9yZGVyLXJhZGl1czogOHB4OyAvKiBSb3VuZGVkIGNvcm5lcnMgKi9cclxuICBvdmVyZmxvdzogaGlkZGVuO1xyXG4gIG1heC13aWR0aDogMTAwJTtcclxuICAtLW9uZS1zZXZlbnRoLXdpZHRoOiAwcHg7XHJcbn1cclxuXHJcbi50YWJsZSB7XHJcbiAgd2lkdGg6IDEwMCU7XHJcbiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTtcclxuICB0ZXh0LWFsaWduOiBsZWZ0O1xyXG59XHJcblxyXG4udGFibGUtaGVhZGVyIHtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1iYWNrZ3JvdW5kLXNlY29uZGFyeSk7XHJcbn1cclxuXHJcbi50YWJsZSB0aCB7XHJcbiAgcGFkZGluZzogMTJweCAxNnB4O1xyXG4gIGZvbnQtc2l6ZTogc21hbGw7XHJcbiAgZm9udC13ZWlnaHQ6IDUwMDtcclxufSBcclxuXHJcbi50b3AtbGVmdCB7XHJcbiAgd2lkdGg6IDEyOHB4O1xyXG59XHJcblxyXG4udGFibGUgdGQge1xyXG4gIHBhZGRpbmc6IDEycHggMTZweDtcclxuICBmb250LXNpemU6IHNtYWxsO1xyXG4gIGhlaWdodDogNjhweDtcclxuICBib3JkZXItdG9wOiB2YXIoLS1ib3JkZXItd2lkdGgpIHNvbGlkIHZhcigtLWJhY2tncm91bmQtbW9kaWZpZXItYm9yZGVyKTtcclxufVxyXG5cclxuLnRhc2tjZWxsLWVuY2xvc3VyZSB7XHJcbiAgYm9yZGVyLWxlZnQ6IHZhcigtLWJvcmRlci13aWR0aCkgc29saWQgdmFyKC0tYmFja2dyb3VuZC1tb2RpZmllci1ib3JkZXIpO1xyXG59XHJcblxyXG4ucHJvamVjdC1pbnB1dCB7XHJcbiAgYm9yZGVyOiBub25lICFpbXBvcnRhbnQ7XHJcbiAgd2lkdGg6IDEwMCU7XHJcbn1cclxuXHJcbi5kYXRlIHtcclxuICBib3JkZXItbGVmdDogdmFyKC0tYm9yZGVyLXdpZHRoKSBzb2xpZCB2YXIoLS1iYWNrZ3JvdW5kLW1vZGlmaWVyLWJvcmRlcik75mdzIjogIjtBQUNBO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBOyIsCiAgIm5hbWVzIjogW10KfQo= */