diff --git a/README.md b/README.md index c394cba..c18d8f4 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,19 @@ With this plugin, you can either import PGNs or simply start a fresh new game. I ## Table of contents -- [Motivation](#motivation) -- [Installation](#installation) -- [Usage](#usage) -- [Features](#features) -- [Settings](#settings) -- [Roadmap](#roadmap) -- [Tools Used](#tools-used) -- [Alternatives](#alternatives) -- [License](#license) +- [Obsidian Chess Study](#obsidian-chess-study) + - [Table of contents](#table-of-contents) + - [Motivation](#motivation) + - [Installation](#installation) + - [Usage](#usage) + - [Features](#features) + - [1.0.0](#100) + - [1.0.1](#101) + - [Settings](#settings) + - [Roadmap](#roadmap) + - [Tools Used](#tools-used) + - [Alternatives](#alternatives) + - [License](#license) ## Motivation @@ -40,12 +44,14 @@ Once you click `Submit`, Obsidian Chess Study will parse the PGN, generate a new ![chess-study-codeblock](imgs/chess-study-codeblock.png) -After that the PGN viewer/editor will render and you are good to go: +After that the PGN viewer/editor will render and you are good to go (styles are aligned with your theme and accent color): -![cchess-study-codeblock](imgs/chess-study-demo.gif) +![chess-study-codeblock](imgs/chess-study-demo.gif) ## Features +### 1.0.0 + - [x] Import PGNs - [x] Store game state in JSON - [x] Add custom PGN viewer @@ -54,6 +60,12 @@ After that the PGN viewer/editor will render and you are good to go: - [x] Draw and sync shapes - [x] Add and sync comments with Markdown support +### 1.0.1 + +- [x] Add support for variants (depth 1) + +![chess-study-variants](imgs/chess-study-variants.png) + ## Settings Here are the available settings for a `chessStudy` code block: @@ -68,7 +80,7 @@ You can permanently set some settings in the [Obsidian](https://obsidian.md/) pl ## Roadmap -- [ ] Add support for variants +- [ ] Add undo button - [ ] Add view to manage stored games - [ ] Add more styles - [ ] Add more settings diff --git a/imgs/chess-study-variants.png b/imgs/chess-study-variants.png new file mode 100644 index 0000000..a7e666c Binary files /dev/null and b/imgs/chess-study-variants.png differ diff --git a/manifest.json b/manifest.json index 272531c..c49c1d3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "chess-study", "name": "Chess Study", - "version": "1.0.0", + "version": "1.0.1", "minAppVersion": "0.15.0", "description": "A chess study helper and PGN viewer/editor.", "author": "Christoph Lindstädt", diff --git a/package-lock.json b/package-lock.json index 938f64b..788a45e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,15 @@ "@tiptap/starter-kit": "2.0.3", "chess.js": "1.0.0-beta.6", "chessground": "8.3.7", - "lucide-react": "0.217.0", + "immer": "^10.0.2", + "lucide-react": "0.220.0", "nanoid": "4.0.2", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "use-immer": "^0.9.0" }, "devDependencies": { - "@types/node": "20.1.7", + "@types/node": "20.2.1", "@types/react": "18.2.6", "@types/react-dom": "18.2.4", "@typescript-eslint/eslint-plugin": "5.59.6", @@ -34,7 +36,7 @@ "prettier": "2.8.8", "prettier-plugin-organize-imports": "3.2.2", "pretty-quick": "3.1.3", - "tslib": "2.5.0", + "tslib": "2.5.2", "typescript": "5.0.4" } }, @@ -1528,9 +1530,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.1.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.7.tgz", - "integrity": "sha512-WCuw/o4GSwDGMoonES8rcvwsig77dGCMbZDrZr2x4ZZiNW4P/gcoZXe/0twgtobcTkmg9TuKflxYL/DuwDyJzg==", + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz", + "integrity": "sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg==", "dev": true }, "node_modules/@types/object.omit": { @@ -3147,6 +3149,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", + "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3638,9 +3649,9 @@ } }, "node_modules/lucide-react": { - "version": "0.217.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.217.0.tgz", - "integrity": "sha512-I4DHIW9EDYuAFNFpkYHZw7lh++BM9Vim86L+gLdSAnb7WwAB2JpGrOl9j8a6c8PSw67UXc3gxj3c26smyZ+7mg==", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.220.0.tgz", + "integrity": "sha512-bYtGUsLAWBvZu+BzAU/ziP1gzE4LwMEXLnlgSr1yUKEPPalLG77JLd5GdYebOVkpm+GtqRqnp6tEKDX7Bm8ZlQ==", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } @@ -4863,9 +4874,9 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", "dev": true }, "node_modules/tsutils": { @@ -5001,6 +5012,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-immer": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.9.0.tgz", + "integrity": "sha512-/L+enLi0nvuZ6j4WlyK0US9/ECUtV5v9RUbtxnn5+WbtaXYUaOBoKHDNL9I5AETdurQ4rIFIj/s+Z5X80ATyKw==", + "peerDependencies": { + "immer": ">=2.0.0", + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, "node_modules/w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", diff --git a/package.json b/package.json index 84c6434..2659afa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chess-study", - "version": "1.0.0", + "version": "1.0.1", "description": "A chess study helper and PGN viewer/editor.", "keywords": [ "chess", @@ -22,13 +22,15 @@ "@tiptap/starter-kit": "2.0.3", "chess.js": "1.0.0-beta.6", "chessground": "8.3.7", - "lucide-react": "0.217.0", + "immer": "^10.0.2", + "lucide-react": "0.220.0", "nanoid": "4.0.2", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "use-immer": "^0.9.0" }, "devDependencies": { - "@types/node": "20.1.7", + "@types/node": "20.2.1", "@types/react": "18.2.6", "@types/react-dom": "18.2.4", "@typescript-eslint/eslint-plugin": "5.59.6", @@ -42,7 +44,7 @@ "prettier": "2.8.8", "prettier-plugin-organize-imports": "3.2.2", "pretty-quick": "3.1.3", - "tslib": "2.5.0", + "tslib": "2.5.2", "typescript": "5.0.4" } } diff --git a/src/components/ReactView.tsx b/src/components/ReactView.tsx index 7818f5b..dbe28cb 100644 --- a/src/components/ReactView.tsx +++ b/src/components/ReactView.tsx @@ -1,7 +1,7 @@ import { App, MarkdownRenderChild } from 'obsidian'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { ChessStudyDataAdapter, ChessStudyFileData } from 'src/utils'; +import { ChessStudyDataAdapter, ChessStudyFileData } from 'src/lib/storage'; import { ChessStudyPluginSettings } from './obsidian/SettingsTab'; import { ChessStudy } from './react/ChessStudy'; diff --git a/src/components/react/ChessStudy.tsx b/src/components/react/ChessStudy.tsx index f0c126d..fe9d246 100644 --- a/src/components/react/ChessStudy.tsx +++ b/src/components/react/ChessStudy.tsx @@ -2,20 +2,29 @@ import { JSONContent } from '@tiptap/react'; import { Chess, Move } from 'chess.js'; import { Api } from 'chessground/api'; import { DrawShape } from 'chessground/draw'; +import { nanoid } from 'nanoid'; import { App, Notice } from 'obsidian'; import * as React from 'react'; import { useCallback, useMemo, useState } from 'react'; import { ChessStudyPluginSettings } from 'src/components/obsidian/SettingsTab'; +import { parseUserConfig } from 'src/lib/obsidian'; import { ChessStudyDataAdapter, ChessStudyFileData, - parseUserConfig, -} from 'src/utils'; -import { ChessGroundSettings, ChessgroundWrapper } from './ChessgroundWrapper'; + ChessStudyMove, + VariantMove, +} from 'src/lib/storage'; +import { + displayMoveInHistory, + findMoveIndex, + getCurrentMove, +} from 'src/lib/ui-state'; +import { useImmerReducer } from 'use-immer'; +import { ChessgroundProps, ChessgroundWrapper } from './ChessgroundWrapper'; import { CommentSection } from './CommentSection'; import { PgnViewer } from './PgnViewer'; -export type ChessStudyConfig = ChessGroundSettings; +export type ChessStudyConfig = ChessgroundProps; interface AppProps { source: string; @@ -25,6 +34,20 @@ interface AppProps { dataAdapter: ChessStudyDataAdapter; } +export interface GameState { + currentMove: ChessStudyMove | VariantMove; + isViewOnly: boolean; + study: ChessStudyFileData; +} + +export type GameActions = + | { type: 'ADD_MOVE_TO_HISTORY'; move: Move } + | { type: 'DISPLAY_NEXT_MOVE_IN_HISTORY' } + | { type: 'DISPLAY_PREVIOUS_MOVE_IN_HISTORY' } + | { type: 'DISPLAY_SELECTED_MOVE_IN_HISTORY'; moveId: string } + | { type: 'SYNC_SHAPES'; shapes: DrawShape[] } + | { type: 'SYNC_COMMENT'; comment: JSONContent | null }; + export const ChessStudy = ({ source, pluginSettings, @@ -37,11 +60,11 @@ export const ChessStudy = ({ source ); - const [chessStudyDataModified] = useState(chessStudyData); - - // Setup Chessboard and Chess.js APIs + // Setup Chessground API const [chessView, setChessView] = useState(null); - const chessLogic = useMemo(() => { + + // Setup Chess.js API + const initialChessLogic = useMemo(() => { const chess = new Chess(); chessStudyData.moves.forEach((move) => { @@ -54,131 +77,211 @@ export const ChessStudy = ({ return chess; }, [chessStudyData.moves]); - // Track Application State - const [history, setHistory] = useState([]); - const [isViewOnly, setIsViewOnly] = useState(false); - const [currentMove, setCurrentMove] = useState(0); - const [shapes, setShapes] = useState( - chessStudyData.moves.map((data) => data.shapes) - ); - const [comments, setComments] = useState<(JSONContent | null)[]>( - chessStudyData.moves.map((data) => data.comment) - ); + const [chessLogic, setChessLogic] = useState(initialChessLogic); - //PgnViewer Functions - const onBackButtonClick = useCallback(() => { - if (currentMove >= 0) { - setCurrentMove((currentMove) => { - const move = history[currentMove]; - const tempChess = new Chess(move.before); - chessView?.set({ - fen: move.before, - check: tempChess.isCheck(), - }); - setIsViewOnly(true); - return currentMove - 1; - }); - } - }, [chessView, currentMove, history]); + const [gameState, dispatch] = useImmerReducer( + (draft, action) => { + switch (action.type) { + case 'DISPLAY_NEXT_MOVE_IN_HISTORY': { + if (!chessView) return draft; - const onForwardButtonClick = useCallback(() => { - if (currentMove < history.length - 1) { - setCurrentMove((currentMove) => { - const move = history[currentMove + 1]; - const tempChess = new Chess(move.after); - chessView?.set({ - fen: move.after, - check: tempChess.isCheck(), - }); - if (currentMove + 1 === history.length - 1) setIsViewOnly(false); - return currentMove + 1; - }); - } - }, [currentMove, chessView, history]); - - const onMoveItemClick = useCallback( - (moveIndex: number) => { - if (moveIndex !== currentMove) { - setCurrentMove(() => { - const move = history[moveIndex]; - const tempChess = new Chess(move.after); - chessView?.set({ - fen: move.after, - check: tempChess.isCheck(), + displayMoveInHistory(draft, chessView, setChessLogic, { + offset: 1, + selectedMoveId: null, }); - if (moveIndex !== history.length - 1) { - setIsViewOnly(true); + + return draft; + } + case 'DISPLAY_PREVIOUS_MOVE_IN_HISTORY': { + if (!chessView) return draft; + + displayMoveInHistory(draft, chessView, setChessLogic, { + offset: -1, + selectedMoveId: null, + }); + + return draft; + } + case 'DISPLAY_SELECTED_MOVE_IN_HISTORY': { + if (!chessView) return draft; + + const selectedMoveId = action.moveId; + + displayMoveInHistory(draft, chessView, setChessLogic, { + offset: 0, + selectedMoveId: selectedMoveId, + }); + + return draft; + } + case 'SYNC_SHAPES': { + const move = getCurrentMove(draft); + + move.shapes = action.shapes; + draft.currentMove = move; + + return draft; + } + case 'SYNC_COMMENT': { + const move = getCurrentMove(draft); + + move.comment = action.comment; + draft.currentMove = move; + + return draft; + } + case 'ADD_MOVE_TO_HISTORY': { + const newMove = action.move; + + const moves = draft.study.moves; + const currentMoveId = draft.currentMove?.moveId; + + const currentMoveIndex = moves.findIndex( + (move) => move.moveId === currentMoveId + ); + + const { variant, moveIndex } = findMoveIndex(moves, currentMoveId); + const moveId = nanoid(); + + if (variant) { + //handle variant + const parent = moves[variant.parentMoveIndex]; + const variantMoves = parent.variants[variant.variantIndex].moves; + + const isLastMove = moveIndex === variantMoves.length - 1; + + //Only push if its the last move in the variant because depth can only be 1 + if (isLastMove) { + const move = { + ...newMove, + moveId: moveId, + shapes: [], + comment: null, + }; + variantMoves.push(move); + + const tempChess = new Chess(newMove.after); + + draft.currentMove = move; + + chessView?.set({ + fen: newMove.after, + check: tempChess.isCheck(), + }); + } } else { - setIsViewOnly(false); + //handle main line + const isLastMove = currentMoveIndex === moves.length - 1; + + if (isLastMove) { + const move = { + ...newMove, + moveId: moveId, + variants: [], + shapes: [], + comment: null, + }; + moves.push(move); + + draft.currentMove = move; + } else { + const currentMove = moves[moveIndex]; + + // check if the next move is the same move + const nextMove = moves[moveIndex + 1]; + + if (nextMove.san === newMove.san) { + draft.currentMove = nextMove; + return draft; + } + + const move = { + ...newMove, + moveId: moveId, + shapes: [], + comment: null, + }; + + currentMove.variants.push({ + parentMoveId: currentMove.moveId, + variantId: nanoid(), + moves: [move], + }); + + draft.currentMove = move; + } } - return moveIndex; - }); + return draft; + } + default: + break; } }, - [chessView, currentMove, history] + { + currentMove: chessStudyData.moves[chessStudyData.moves.length - 1], + isViewOnly: false, + study: chessStudyData, + } ); const onSaveButtonClick = useCallback(async () => { - const chessStudyData: ChessStudyFileData = { - header: chessStudyDataModified.header, - moves: chessLogic.history({ verbose: true }).map((move, index) => ({ - ...move, - variants: [], - shapes: shapes[index], - comment: comments[index], - })), - }; - - await dataAdapter.saveFile(chessStudyData, chessStudyId); - - new Notice('Save successfull!'); - }, [ - chessLogic, - chessStudyDataModified.header, - chessStudyId, - comments, - dataAdapter, - shapes, - ]); + try { + await dataAdapter.saveFile(gameState.study, chessStudyId); + new Notice('Save successfull!'); + } catch (e) { + new Notice('Something went wrong during saving:', e); + } + }, [chessStudyId, dataAdapter, gameState.study]); return ( -
+
+ dispatch({ type: 'ADD_MOVE_TO_HISTORY', move }) + } + isViewOnly={gameState.isViewOnly} + syncShapes={(shapes: DrawShape[]) => + dispatch({ type: 'SYNC_SHAPES', shapes }) + } + shapes={gameState.currentMove?.shapes} />
+ dispatch({ type: 'DISPLAY_PREVIOUS_MOVE_IN_HISTORY' }) + } + onForwardButtonClick={() => + dispatch({ type: 'DISPLAY_NEXT_MOVE_IN_HISTORY' }) + } + onMoveItemClick={(moveId: string) => + dispatch({ + type: 'DISPLAY_SELECTED_MOVE_IN_HISTORY', + moveId: moveId, + }) + } onSaveButtonClick={onSaveButtonClick} />
-
+
+ dispatch({ type: 'SYNC_COMMENT', comment: comment }) + } />
diff --git a/src/components/react/ChessgroundWrapper/index.tsx b/src/components/react/ChessgroundWrapper/index.tsx index c6445fa..73a4864 100644 --- a/src/components/react/ChessgroundWrapper/index.tsx +++ b/src/components/react/ChessgroundWrapper/index.tsx @@ -1,43 +1,36 @@ -import { Chessground as ChessgroundApi } from 'chessground'; -import * as React from 'react'; -import { useEffect, useRef } from 'react'; - import { Chess, Move } from 'chess.js'; +import { Chessground as ChessgroundApi } from 'chessground'; import { Api } from 'chessground/api'; import { Config } from 'chessground/config'; import { DrawShape } from 'chessground/draw'; -import { playOtherSide, toColor, toDests } from 'src/utils'; +import * as React from 'react'; +import { useEffect, useRef } from 'react'; +import { playOtherSide, toColor, toDests } from 'src/lib/chess-logic'; -export interface ChessGroundSettings { +export interface ChessgroundProps { api: Api | null; setApi: React.Dispatch>; - chessStudyId: string; + chess: Chess; + addMoveToHistory: (move: Move) => void; + syncShapes: (shapes: DrawShape[]) => void; + isViewOnly: boolean; + shapes: DrawShape[]; config?: Config; boardColor?: 'brown' | 'green'; - chess: Chess; - setHistory: React.Dispatch>; - setShapes: React.Dispatch>; - setMoveNumber: React.Dispatch>; - currentMoveNumber: number; - isViewOnly: boolean; - currentMoveShapes: DrawShape[]; } export const ChessgroundWrapper = React.memo( ({ api, setApi, - chessStudyId, + chess, + addMoveToHistory, + syncShapes: setShapes, + isViewOnly, + shapes, boardColor = 'green', config = {}, - chess, - setHistory, - setMoveNumber, - currentMoveNumber, - setShapes, - isViewOnly, - currentMoveShapes, - }: ChessGroundSettings) => { + }: ChessgroundProps) => { const ref = useRef(null); //Chessground Init @@ -51,67 +44,52 @@ export const ChessgroundWrapper = React.memo( free: false, color: toColor(chess), dests: toDests(chess), - events: { - //Hook up the Chessground UI changes to our App State - after: (orig, dest, _metadata) => { - const handler = playOtherSide(chessgroundApi, chess); - - setHistory(handler(orig, dest)); - setMoveNumber((state) => state + 1); - }, - }, }, highlight: { check: true, }, + drawable: { + onChange: (shapes) => { + setShapes(shapes); + }, + }, turnColor: toColor(chess), ...config, }); - setHistory(chess.history({ verbose: true })); setApi(chessgroundApi); - setMoveNumber(chess.history().length - 1); } else if (ref.current && api) { api.set(config); } - }, [ - ref, - chess, - api, - chessStudyId, - config, - setApi, - setHistory, - setMoveNumber, - setShapes, - currentMoveNumber, - ]); + }, [addMoveToHistory, api, chess, config, setApi, setShapes]); + + //Sync Chess Logic + useEffect(() => { + api?.set({ + movable: { + events: { + //Hook up the Chessground UI changes to our App State + after: (orig, dest, _metadata) => { + const handler = playOtherSide(api, chess); + + addMoveToHistory(handler(orig, dest)); + }, + }, + }, + }); + }, [addMoveToHistory, api, chess]); //Sync View Only useEffect(() => { api?.set({ viewOnly: isViewOnly }); }, [isViewOnly, api]); - //Sync Shapes To State + // Load Shapes useEffect(() => { - api?.set({ - drawable: { - onChange(shapes) { - setShapes((currentShapes) => { - const shapesModified = [...currentShapes]; - shapesModified[currentMoveNumber] = shapes; - - return shapesModified; - }); - }, - }, - }); - }, [api, currentMoveNumber, setShapes]); - - //Load Shapes - useEffect(() => { - if (currentMoveShapes) api?.setShapes(currentMoveShapes); - }, [api, currentMoveNumber, currentMoveShapes, setShapes]); + if (shapes) { + api?.setShapes([...shapes]); + } + }, [api, shapes]); return (
diff --git a/src/components/react/CommentSection/index.tsx b/src/components/react/CommentSection/index.tsx index 057a635..acfdb08 100644 --- a/src/components/react/CommentSection/index.tsx +++ b/src/components/react/CommentSection/index.tsx @@ -4,21 +4,17 @@ import * as React from 'react'; import { useEffect } from 'react'; interface CommentSectionProps { - currentMove: number; currentComment: JSONContent | null; - setComments: React.Dispatch>; + setComments: (comment: JSONContent) => void; } export const CommentSection = React.memo( - ({ currentMove, currentComment, setComments }: CommentSectionProps) => { + ({ currentComment, setComments }: CommentSectionProps) => { const editor = useEditor({ extensions: [StarterKit], onUpdate: (state) => { - setComments((currentComments) => { - const currentCommentModified = [...currentComments]; - currentCommentModified[currentMove] = state.editor.getJSON(); - return currentCommentModified; - }); + const comment = state.editor.getJSON(); + if (comment) setComments(comment); }, }); diff --git a/src/components/react/PgnViewer/index.tsx b/src/components/react/PgnViewer/index.tsx index fefac1f..d4066dd 100644 --- a/src/components/react/PgnViewer/index.tsx +++ b/src/components/react/PgnViewer/index.tsx @@ -1,7 +1,7 @@ -import { Move } from 'chess.js'; import { ArrowLeft, ArrowRight, Save } from 'lucide-react'; import * as React from 'react'; -import { useEffect, useMemo, useRef } from 'react'; +import { ReactNode, useEffect, useMemo, useRef } from 'react'; +import { ChessStudyMove } from 'src/lib/storage'; const chunkArray = (array: T[], chunkSize: number) => { return array.reduce((resultArray, item, index) => { @@ -54,20 +54,70 @@ const MoveItem = ({ ); }; +const VariantMoveItem = ({ + isCurrentMove, + san, + onMoveItemClick, + moveIndicator = null, +}: { + isCurrentMove: boolean; + san: string; + onMoveItemClick: () => void; + moveIndicator?: string | null; +}) => { + const ref = useRef(null); + + useEffect(() => { + if (ref.current && isCurrentMove) { + ref.current?.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'end', + }); + } + }, [isCurrentMove]); + + return ( +
{ + e.stopPropagation(); + onMoveItemClick(); + }} + ref={ref} + > + {moveIndicator} + {san} +
+ ); +}; + +const VariantMoveItemContainer = ({ children }: { children: ReactNode }) => { + return
{children}
; +}; + +const VariantContainer = ({ children }: { children: ReactNode }) => { + return
{children}
; +}; + +const VariantsContainer = ({ children }: { children: ReactNode }) => { + return
{children}
; +}; + export const PgnViewer = React.memo( ({ history, - currentMove, + currentMoveId, onBackButtonClick, onForwardButtonClick, onMoveItemClick, onSaveButtonClick, }: { - history: Move[]; - currentMove: number; + history: ChessStudyMove[]; + currentMoveId: string; onBackButtonClick: () => void; onForwardButtonClick: () => void; - onMoveItemClick: (moveIndex: number) => void; + onMoveItemClick: (moveId: string) => void; onSaveButtonClick: () => void; }) => { const movePairs = useMemo(() => chunkArray(history, 2), [history]); @@ -76,23 +126,142 @@ export const PgnViewer = React.memo(
- {movePairs.map((pair, i) => { + {movePairs.map((pair, currentMoveIndex) => { const [wMove, bMove] = pair; + return ( - -

{i + 1}

+ +

+ {currentMoveIndex + 1} +

onMoveItemClick(i * 2)} + isCurrentMove={wMove.moveId === currentMoveId} + onMoveItemClick={() => onMoveItemClick(wMove.moveId)} /> {bMove && ( onMoveItemClick(i * 2 + 1)} + isCurrentMove={bMove.moveId === currentMoveId} + onMoveItemClick={() => onMoveItemClick(bMove.moveId)} /> )} + {!!wMove.variants.concat(bMove?.variants || []).length && ( + + {!!wMove.variants.length && ( + + {wMove.variants.map((variant) => { + return ( + + {chunkArray(variant.moves, 2).map( + (pair, wMoveVarianti) => { + const [bMove, wMove] = pair; + + return ( + + + onMoveItemClick(bMove.moveId) + } + moveIndicator={ + (wMoveVarianti === 0 && + `${ + currentMoveIndex + + 1 + + wMoveVarianti + }... `) || + null + } + /> + {wMove && ( + + onMoveItemClick(wMove.moveId) + } + moveIndicator={ + (wMoveVarianti % 2 === 0 && + null) || + `${ + currentMoveIndex + + 2 + + wMoveVarianti + }. ` + } + /> + )} + + ); + } + )} + + ); + })} + + )} + {!!bMove?.variants.length && ( + + {bMove.variants.map((variant) => { + return ( + + {chunkArray(variant.moves, 2).map( + (pair, bMoveVarianti) => { + const [wMove, bMove] = pair; + return ( + + + onMoveItemClick(wMove.moveId) + } + moveIndicator={`${ + currentMoveIndex + 2 + bMoveVarianti + }. `} + /> + {bMove && ( + + onMoveItemClick(bMove.moveId) + } + /> + )} + + ); + } + )} + + ); + })} + + )} + + )}
); })} diff --git a/src/lib/chess-logic/index.ts b/src/lib/chess-logic/index.ts new file mode 100644 index 0000000..7ee820b --- /dev/null +++ b/src/lib/chess-logic/index.ts @@ -0,0 +1,47 @@ +import { Chess, QUEEN, SQUARES, Square } from 'chess.js'; +import { Api } from 'chessground/api'; +import { Config } from 'chessground/config'; + +export function toColor(chess: Chess) { + return chess.turn() === 'w' ? 'white' : 'black'; +} + +export function toDests(chess: Chess): Map { + const dests = new Map(); + SQUARES.forEach((s) => { + const ms = chess.moves({ square: s, verbose: true }); + if (ms.length) + dests.set( + s, + ms.map((m) => m.to) + ); + }); + return dests; +} + +export function playOtherSide(cg: Api, chess: Chess) { + return (orig: string, dest: string) => { + const move = chess.move({ from: orig, to: dest, promotion: QUEEN }); + + const commonTurnProperties: Partial = { + turnColor: toColor(chess), + movable: { + color: toColor(chess), + dests: toDests(chess), + }, + check: chess.isCheck(), + }; + + if (move.flags === 'e' || move.promotion) { + //Handle En Passant && Promote to Queen by default + cg.set({ + fen: chess.fen(), + ...commonTurnProperties, + }); + } else { + cg.set(commonTurnProperties); + } + + return move; + }; +} diff --git a/src/lib/obsidian/index.ts b/src/lib/obsidian/index.ts new file mode 100644 index 0000000..545abd9 --- /dev/null +++ b/src/lib/obsidian/index.ts @@ -0,0 +1,25 @@ +import { parseYaml } from 'obsidian'; +import { ChessStudyPluginSettings } from 'src/components/obsidian/SettingsTab'; + +type ChessStudyAppConfig = ChessStudyPluginSettings & { + chessStudyId: string; +}; + +export const parseUserConfig = ( + settings: ChessStudyPluginSettings, + content: string +): ChessStudyAppConfig => { + const chessStudyConfig: ChessStudyAppConfig = { + ...settings, + chessStudyId: '', + }; + + try { + return { + ...chessStudyConfig, + ...parseYaml(content), + }; + } catch (e) { + throw Error('Something went wrong during parsing. :('); + } +}; diff --git a/src/lib/storage/index.ts b/src/lib/storage/index.ts new file mode 100644 index 0000000..1abe140 --- /dev/null +++ b/src/lib/storage/index.ts @@ -0,0 +1,68 @@ +import { JSONContent } from '@tiptap/react'; +import { Move } from 'chess.js'; +import { DrawShape } from 'chessground/draw'; +import { nanoid } from 'nanoid'; +import { DataAdapter, normalizePath } from 'obsidian'; + +export const CURRENT_STORAGE_VERSION = '0.0.1'; + +export interface VariantMove extends Move { + moveId: string; + shapes: DrawShape[]; + comment: JSONContent | null; +} + +export interface Variant { + variantId: string; + parentMoveId: string; + moves: VariantMove[]; +} + +export interface ChessStudyMove extends Move { + moveId: string; + variants: Variant[]; + shapes: DrawShape[]; + comment: JSONContent | null; +} + +export interface ChessStudyFileData { + version: string; + header: { title: string | null }; + moves: ChessStudyMove[]; +} + +export class ChessStudyDataAdapter { + adapter: DataAdapter; + storagePath: string; + + constructor(adapter: DataAdapter, storagePath: string) { + this.adapter = adapter; + this.storagePath = storagePath; + } + + async saveFile(data: ChessStudyFileData, id?: string) { + const chessStudyId = id || nanoid(); + await this.adapter.write( + normalizePath(`${this.storagePath}/${chessStudyId}.json`), + JSON.stringify(data, null, 2), + {} + ); + + return chessStudyId; + } + + async loadFile(id: string): Promise { + const data = await this.adapter.read( + normalizePath(`${this.storagePath}/${id}.json`) + ); + return JSON.parse(data); + } + + async createStorageFolderIfNotExists() { + const folderExists = await this.adapter.exists(this.storagePath); + + if (!folderExists) { + this.adapter.mkdir(this.storagePath); + } + } +} diff --git a/src/lib/ui-state/index.ts b/src/lib/ui-state/index.ts new file mode 100644 index 0000000..0a9667a --- /dev/null +++ b/src/lib/ui-state/index.ts @@ -0,0 +1,111 @@ +import { Chess } from 'chess.js'; +import { Api as ChessgroundApi } from 'chessground/api'; +import { Draft } from 'immer'; +import { GameState } from 'src/components/react/ChessStudy'; +import { toColor, toDests } from '../chess-logic'; +import { ChessStudyMove, VariantMove } from '../storage'; + +interface MovePosition { + variant: { parentMoveIndex: number; variantIndex: number } | null; + moveIndex: number; +} + +export const findMoveIndex = ( + moves: ChessStudyMove[], + moveId: string +): MovePosition => { + for (const [iMainLine, move] of moves.entries()) { + if (move.moveId === moveId) return { variant: null, moveIndex: iMainLine }; + + for (const [iVariant, variant] of move.variants.entries()) { + const moveIndex = variant.moves.findIndex( + (move) => move.moveId === moveId + ); + + if (moveIndex >= 0) + return { + variant: { parentMoveIndex: iMainLine, variantIndex: iVariant }, + moveIndex: moveIndex, + }; + } + } + + return { variant: null, moveIndex: -1 }; +}; + +export const displayMoveInHistory = ( + draft: Draft, + chessView: ChessgroundApi, + setChessLogic: React.Dispatch>, + options: { offset: number; selectedMoveId: string | null } = { + offset: 0, + selectedMoveId: null, + } +): Draft => { + let moveToDisplay: ChessStudyMove | VariantMove | null = null; + + const { offset, selectedMoveId } = options; + + //Figure out where we are + const currentMove = draft.currentMove; + const currentMoveId = currentMove.moveId; + + const moves = draft.study.moves; + + //If we pass a moveId, find out where that is and offset from there, otherwise take current moveId + const baseMoveId = selectedMoveId || currentMoveId; + + const { variant, moveIndex } = findMoveIndex(moves, baseMoveId); + //Are we in a variant? Are we not? Decide which move to display + + if (variant) { + const variantMoves = + moves[variant.parentMoveIndex].variants[variant.variantIndex].moves; + + if (typeof variantMoves[moveIndex + offset] !== 'undefined') { + moveToDisplay = variantMoves[moveIndex + offset]; + } + } else { + if (typeof moves[moveIndex + offset] !== 'undefined') { + moveToDisplay = moves[moveIndex + offset]; + } + } + + if (!moveToDisplay) return draft; + + const chess = new Chess(moveToDisplay.after); + + chessView.set({ + fen: moveToDisplay.after, + check: chess.isCheck(), + movable: { + free: false, + color: toColor(chess), + dests: toDests(chess), + }, + turnColor: toColor(chess), + }); + + draft.currentMove = moveToDisplay; + + setChessLogic(chess); + + return draft; +}; + +export const getCurrentMove = ( + draft: Draft +): Draft | Draft => { + const currentMoveId = draft.currentMove?.moveId; + const moves = draft.study.moves; + + const { variant, moveIndex } = findMoveIndex(moves, currentMoveId); + + if (variant) { + return moves[variant.parentMoveIndex].variants[variant.variantIndex].moves[ + moveIndex + ]; + } else { + return moves[moveIndex]; + } +}; diff --git a/src/main.css b/src/main.css index 434fe0f..322cb71 100644 --- a/src/main.css +++ b/src/main.css @@ -1,18 +1,8 @@ /* General */ - .chess-study button:active { transform: translateY(1px); } -.chess-study .border { - border: 1px var(--color-base-10) solid; - border-radius: 5px; -} - -.chess-study .border-top { - border: 1px var(--color-base-10) solid; -} - .chess-study .vertical-align { display: flex; align-items: center; @@ -34,12 +24,13 @@ } .chess-study { + height: 100%; + max-width: 750px; line-height: 1.5; -webkit-font-smoothing: antialiased; -} - -.chess-study { - height: 100%; + background-color: var(--background-secondary); + border: 1px solid var(--background-secondary-alt); + border-radius: 5px; } .chess-study *, @@ -85,6 +76,7 @@ } /* PGN Viewer */ + .chess-study .move-item-section { height: 415px; } @@ -98,10 +90,12 @@ height: 35px; } +/* Move Items */ + .chess-study .move-item-container { display: grid; grid-template-columns: 0.15fr 0.425fr 0.425fr; - grid-auto-rows: 30px; + grid-auto-rows: minmax(30px, auto); width: 100%; max-height: 415px; overflow-y: scroll; @@ -132,6 +126,21 @@ font-weight: bold; } +/* Variant Move Items */ + +.chess-study .variants-container { + grid-column: span 3 / auto; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--color-base-50); +} + +.chess-study .variant-container { + grid-column: span 3 / auto; + display: flex; + flex-direction: column; +} + .chess-study .move-indicator { color: var(--color-base-60); text-align: center; @@ -140,6 +149,28 @@ cursor: default; } +.chess-study .variant-move-item { + padding-left: 6px; + user-select: none; + cursor: pointer; +} + +.chess-study .variant-move-item-container { + display: flex; + flex-wrap: wrap; + padding: 4px 0; + border-top: 1px solid var(--color-base-50); +} + +.chess-study .variant-move-item.active, +.chess-study .variant-move-item:hover { + color: hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) - 10%)); +} + +.chess-study .variant-move-indicator { + font-weight: bold; +} + /* Comment Section */ .chess-study .editor-input { @@ -158,10 +189,11 @@ height: 250px; padding-top: 5px; overflow: scroll; + border-top: 1px solid var(--background-secondary-alt); } -.chess-study.ProseMirror, -.chess-study.CommentSection > div { +.chess-study .ProseMirror, +.chess-study .CommentSection > div { width: 100%; height: 100%; } diff --git a/src/main.tsx b/src/main.tsx index 1261628..c4a4a18 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,10 @@ import { Chess } from 'chess.js'; import { Editor, Notice, Plugin, normalizePath } from 'obsidian'; +import { + CURRENT_STORAGE_VERSION, + ChessStudyDataAdapter, + ChessStudyFileData, +} from 'src/lib/storage'; import { ReactView } from './components/ReactView'; import { PgnModal } from './components/obsidian/PgnModal'; import { @@ -7,17 +12,14 @@ import { DEFAULT_SETTINGS, SettingsTab, } from './components/obsidian/SettingsTab'; -import { - ChessStudyDataAdapter, - ChessStudyFileData, - parseUserConfig, -} from './utils'; // these styles must be imported somewhere import 'assets/board/green.css'; import 'chessground/assets/chessground.base.css'; import 'chessground/assets/chessground.brown.css'; import 'chessground/assets/chessground.cburnett.css'; +import { nanoid } from 'nanoid'; +import { parseUserConfig } from './lib/obsidian'; import './main.css'; export default class ChessStudyPlugin extends Plugin { @@ -61,11 +63,13 @@ export default class ChessStudyPlugin extends Plugin { } const chessStudyFileData: ChessStudyFileData = { + version: CURRENT_STORAGE_VERSION, header: { title: chess.header()['opening'] || null, }, moves: chess.history({ verbose: true }).map((move) => ({ ...move, + moveId: nanoid(), variants: [], shapes: [], comment: null, @@ -75,7 +79,7 @@ export default class ChessStudyPlugin extends Plugin { const id = await this.dataAdapter.saveFile(chessStudyFileData); editor.replaceRange( - `\`\`\`chessStudy\nchessStudyId: ${id}\nboardOrientation: white\nboardColor: green\n\`\`\``, + `\`\`\`chessStudy\nchessStudyId: ${id}\n\`\`\``, cursorPosition ); } catch (e) { diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 3c01f9d..0000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { JSONContent } from '@tiptap/react'; -import { Chess, Move, QUEEN, SQUARES, Square } from 'chess.js'; -import { Api } from 'chessground/api'; -import { Config } from 'chessground/config'; -import { DrawShape } from 'chessground/draw'; -import { nanoid } from 'nanoid'; -import { DataAdapter, normalizePath, parseYaml } from 'obsidian'; -import { ChessStudyPluginSettings } from 'src/components/obsidian/SettingsTab'; - -//Chess Logic -type ChessStudyAppConfig = ChessStudyPluginSettings & { - chessStudyId: string; -}; - -export const parseUserConfig = ( - settings: ChessStudyPluginSettings, - content: string -): ChessStudyAppConfig => { - const chessStudyConfig: ChessStudyAppConfig = { - ...settings, - chessStudyId: '', - }; - - try { - return { - ...chessStudyConfig, - ...parseYaml(content), - }; - } catch (e) { - throw Error('Something went wrong during parsing. :('); - } -}; - -export function toColor(chess: Chess) { - return chess.turn() === 'w' ? 'white' : 'black'; -} - -export function toDests(chess: Chess): Map { - const dests = new Map(); - SQUARES.forEach((s) => { - const ms = chess.moves({ square: s, verbose: true }); - if (ms.length) - dests.set( - s, - ms.map((m) => m.to) - ); - }); - return dests; -} - -export function playOtherSide(cg: Api, chess: Chess) { - return (orig: string, dest: string) => { - const move = chess.move({ from: orig, to: dest, promotion: QUEEN }); - - const commonTurnProperties: Partial = { - turnColor: toColor(chess), - movable: { - color: toColor(chess), - dests: toDests(chess), - }, - check: chess.isCheck(), - }; - - if (move.flags === 'e' || move.promotion) { - //Handle En Passant && Promote to Queen by default - cg.set({ - fen: chess.fen(), - ...commonTurnProperties, - }); - } else { - cg.set(commonTurnProperties); - } - - return chess.history({ verbose: true }); - }; -} - -//Storage Logic - -interface ChessStudyMove extends Move { - variants: Move[][]; - shapes: DrawShape[]; - comment: JSONContent | null; -} -export interface ChessStudyFileData { - header: { title: string | null }; - moves: ChessStudyMove[]; -} - -export class ChessStudyDataAdapter { - adapter: DataAdapter; - storagePath: string; - - constructor(adapter: DataAdapter, storagePath: string) { - this.adapter = adapter; - this.storagePath = storagePath; - } - - async saveFile(data: ChessStudyFileData, id?: string) { - const chessStudyId = id || nanoid(); - await this.adapter.write( - normalizePath(`${this.storagePath}/${chessStudyId}.json`), - JSON.stringify(data, null, 2), - {} - ); - - return chessStudyId; - } - - async loadFile(id: string): Promise { - const data = await this.adapter.read( - normalizePath(`${this.storagePath}/${id}.json`) - ); - return JSON.parse(data); - } - - async createStorageFolderIfNotExists() { - const folderExists = await this.adapter.exists(this.storagePath); - - if (!folderExists) { - this.adapter.mkdir(this.storagePath); - } - } -} diff --git a/styles.css b/styles.css index 8c088aa..0656493 100644 --- a/styles.css +++ b/styles.css @@ -265,13 +265,6 @@ cg-board square.current-premove { .chess-study button:active { transform: translateY(1px); } -.chess-study .border { - border: 1px var(--color-base-10) solid; - border-radius: 5px; -} -.chess-study .border-top { - border: 1px var(--color-base-10) solid; -} .chess-study .vertical-align { display: flex; align-items: center; @@ -289,11 +282,13 @@ cg-board square.current-premove { display: table; } .chess-study { + height: 100%; + max-width: 750px; line-height: 1.5; -webkit-font-smoothing: antialiased; -} -.chess-study { - height: 100%; + background-color: var(--background-secondary); + border: 1px solid var(--background-secondary-alt); + border-radius: 5px; } .chess-study *, .chess-study *::before, @@ -344,7 +339,7 @@ cg-board square.current-premove { .chess-study .move-item-container { display: grid; grid-template-columns: 0.15fr 0.425fr 0.425fr; - grid-auto-rows: 30px; + grid-auto-rows: minmax(30px, auto); width: 100%; max-height: 415px; overflow-y: scroll; @@ -371,6 +366,17 @@ cg-board square.current-premove { color: var(--text-on-accent); font-weight: bold; } +.chess-study .variants-container { + grid-column: span 3 / auto; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--color-base-50); +} +.chess-study .variant-container { + grid-column: span 3 / auto; + display: flex; + flex-direction: column; +} .chess-study .move-indicator { color: var(--color-base-60); text-align: center; @@ -378,6 +384,24 @@ cg-board square.current-premove { user-select: none; cursor: default; } +.chess-study .variant-move-item { + padding-left: 6px; + user-select: none; + cursor: pointer; +} +.chess-study .variant-move-item-container { + display: flex; + flex-wrap: wrap; + padding: 4px 0; + border-top: 1px solid var(--color-base-50); +} +.chess-study .variant-move-item.active, +.chess-study .variant-move-item:hover { + color: hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) - 10%)); +} +.chess-study .variant-move-indicator { + font-weight: bold; +} .chess-study .editor-input { min-height: 150px; resize: none; @@ -393,10 +417,10 @@ cg-board square.current-premove { height: 250px; padding-top: 5px; overflow: scroll; + border-top: 1px solid var(--background-secondary-alt); } -.chess-study.ProseMirror, -.chess-study.CommentSection > div { +.chess-study .ProseMirror, +.chess-study .CommentSection > div { width: 100%; height: 100%; } -/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiYXNzZXRzL2JvYXJkL2dyZWVuLmNzcyIsICJub2RlX21vZHVsZXMvY2hlc3Nncm91bmQvYXNzZXRzL2NoZXNzZ3JvdW5kLmJhc2UuY3NzIiwgIm5vZGVfbW9kdWxlcy9jaGVzc2dyb3VuZC9hc3NldHMvY2hlc3Nncm91bmQuYnJvd24uY3NzIiwgIm5vZGVfbW9kdWxlcy9jaGVzc2dyb3VuZC9hc3NldHMvY2hlc3Nncm91bmQuY2J1cm5ldHQuY3NzIiwgInNyYy9tYWluLmNzcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLmdyZWVuLWJvYXJkIGNnLWJvYXJkIHtcblx0YmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBEOTRiV3dnZG1WeWMybHZiajBpTVM0d0lpQmxibU52WkdsdVp6MGlWVlJHTFRnaUlITjBZVzVrWVd4dmJtVTlJbTV2SWo4K0NqeHpkbWNnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JaUI0Yld4dWN6cDRQU0pvZEhSd09pOHZkM2QzTG5jekxtOXlaeTh4T1RrNUwzaHNhVzVySWdvZ0lDQWdJSFpwWlhkQ2IzZzlJakFnTUNBNElEZ2lJSE5vWVhCbExYSmxibVJsY21sdVp6MGlZM0pwYzNCRlpHZGxjeUkrQ2p4bklHbGtQU0poSWo0S0lDQThaeUJwWkQwaVlpSStDaUFnSUNBOFp5QnBaRDBpWXlJK0NpQWdJQ0FnSUR4bklHbGtQU0prSWo0S0lDQWdJQ0FnSUNBOGNtVmpkQ0IzYVdSMGFEMGlNU0lnYUdWcFoyaDBQU0l4SWlCbWFXeHNQU0lqWm1abVptUmtJaUJwWkQwaVpTSXZQZ29nSUNBZ0lDQWdJRHgxYzJVZ2VEMGlNU0lnZVQwaU1TSWdhSEpsWmowaUkyVWlJSGc2YUhKbFpqMGlJMlVpTHo0S0lDQWdJQ0FnSUNBOGNtVmpkQ0I1UFNJeElpQjNhV1IwYUQwaU1TSWdhR1ZwWjJoMFBTSXhJaUJtYVd4c1BTSWpPRFpoTmpZMklpQnBaRDBpWmlJdlBnb2dJQ0FnSUNBZ0lEeDFjMlVnZUQwaU1TSWdlVDBpTFRFaUlHaHlaV1k5SWlObUlpQjRPbWh5WldZOUlpTm1JaTgrQ2lBZ0lDQWdJRHd2Wno0S0lDQWdJQ0FnUEhWelpTQjRQU0l5SWlCb2NtVm1QU0lqWkNJZ2VEcG9jbVZtUFNJalpDSXZQZ29nSUNBZ1BDOW5QZ29nSUNBZ1BIVnpaU0I0UFNJMElpQm9jbVZtUFNJall5SWdlRHBvY21WbVBTSWpZeUl2UGdvZ0lEd3ZaejRLSUNBOGRYTmxJSGs5SWpJaUlHaHlaV1k5SWlOaUlpQjRPbWh5WldZOUlpTmlJaTgrQ2p3dlp6NEtQSFZ6WlNCNVBTSTBJaUJvY21WbVBTSWpZU0lnZURwb2NtVm1QU0lqWVNJdlBnbzhMM04yWno0SycpO1xufVxuIiwgIi5jZy13cmFwIHtcbiAgYm94LXNpemluZzogY29udGVudC1ib3g7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgZGlzcGxheTogYmxvY2s7XG59XG5cbmNnLWNvbnRhaW5lciB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgd2lkdGg6IDEwMCU7XG4gIGhlaWdodDogMTAwJTtcbiAgZGlzcGxheTogYmxvY2s7XG4gIHRvcDogMDtcbn1cblxuY2ctYm9hcmQge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgd2lkdGg6IDEwMCU7XG4gIGhlaWdodDogMTAwJTtcbiAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTtcbiAgLW1vei11c2VyLXNlbGVjdDogbm9uZTtcbiAgLW1zLXVzZXItc2VsZWN0OiBub25lO1xuICB1c2VyLXNlbGVjdDogbm9uZTtcbiAgbGluZS1oZWlnaHQ6IDA7XG4gIGJhY2tncm91bmQtc2l6ZTogY292ZXI7XG59XG5cbi5jZy13cmFwLm1hbmlwdWxhYmxlIGNnLWJvYXJkIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xufVxuXG5jZy1ib2FyZCBzcXVhcmUge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgd2lkdGg6IDEyLjUlO1xuICBoZWlnaHQ6IDEyLjUlO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbn1cblxuY2ctYm9hcmQgc3F1YXJlLm1vdmUtZGVzdCB7XG4gIHBvaW50ZXItZXZlbnRzOiBhdXRvO1xufVxuXG5jZy1ib2FyZCBzcXVhcmUubGFzdC1tb3ZlIHtcbiAgd2lsbC1jaGFuZ2U6IHRyYW5zZm9ybTtcbn1cblxuLmNnLXdyYXAgcGllY2Uge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgd2lkdGg6IDEyLjUlO1xuICBoZWlnaHQ6IDEyLjUlO1xuICBiYWNrZ3JvdW5kLXNpemU6IGNvdmVyO1xuICB6LWluZGV4OiAyO1xuICB3aWxsLWNoYW5nZTogdHJhbnNmb3JtO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbn1cblxuY2ctYm9hcmQgcGllY2UuZHJhZ2dpbmcge1xuICBjdXJzb3I6IG1vdmU7XG4gIC8qICFpbXBvcnRhbnQgdG8gb3ZlcnJpZGUgei1pbmRleCBmcm9tIDNEIHBpZWNlIGlubGluZSBzdHlsZSAqL1xuICB6LWluZGV4OiAxMSAhaW1wb3J0YW50O1xufVxuXG5waWVjZS5hbmltIHtcbiAgei1pbmRleDogODtcbn1cblxucGllY2UuZmFkaW5nIHtcbiAgei1pbmRleDogMTtcbiAgb3BhY2l0eTogMC41O1xufVxuXG4uY2ctd3JhcCBwaWVjZS5naG9zdCB7XG4gIG9wYWNpdHk6IDAuMztcbn1cblxuLmNnLXdyYXAgcGllY2Ugc3ZnIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB0b3A6IDBweDtcbiAgbGVmdDogMHB4O1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbiAgei1pbmRleDogMjtcbiAgb3BhY2l0eTogMC42O1xufVxuXG4uY2ctd3JhcCBjZy1hdXRvLXBpZWNlcyxcbi5jZy13cmFwIC5jZy1zaGFwZXMsXG4uY2ctd3JhcCAuY2ctY3VzdG9tLXN2Z3Mge1xuICBvdmVyZmxvdzogdmlzaWJsZTtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDBweDtcbiAgbGVmdDogMHB4O1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbn1cblxuLmNnLXdyYXAgY2ctYXV0by1waWVjZXMge1xuICB6LWluZGV4OiAyO1xufVxuXG4uY2ctd3JhcCBjZy1hdXRvLXBpZWNlcyBwaWVjZSB7XG4gIG9wYWNpdHk6IDAuMztcbn1cblxuLmNnLXdyYXAgLmNnLXNoYXBlcyB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIG9wYWNpdHk6IDAuNjtcbiAgei1pbmRleDogMjtcbn1cblxuLmNnLXdyYXAgLmNnLWN1c3RvbS1zdmdzIHtcbiAgLyogb3ZlciBwaWVjZS5hbmltID0gOCwgYnV0IHVuZGVyIHBpZWNlLmRyYWdnaW5nID0gMTEgKi9cbiAgei1pbmRleDogOTtcbn1cblxuLmNnLXdyYXAgLmNnLWN1c3RvbS1zdmdzIHN2ZyB7XG4gIG92ZXJmbG93OiB2aXNpYmxlO1xufVxuXG4uY2ctd3JhcCBjb29yZHMge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIHBvaW50ZXItZXZlbnRzOiBub25lO1xuICBvcGFjaXR5OiAwLjg7XG4gIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmO1xuICBmb250LXNpemU6IDlweDtcbn1cblxuLmNnLXdyYXAgY29vcmRzLnJhbmtzIHtcbiAgbGVmdDogNHB4O1xuICB0b3A6IC0yMHB4O1xuICBmbGV4LWZsb3c6IGNvbHVtbi1yZXZlcnNlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIHdpZHRoOiAxMnB4O1xufVxuXG4uY2ctd3JhcCBjb29yZHMucmFua3MuYmxhY2sge1xuICBmbGV4LWZsb3c6IGNvbHVtbjtcbn1cblxuLmNnLXdyYXAgY29vcmRzLnJhbmtzLmxlZnQge1xuICBsZWZ0OiAtMTVweDtcbiAgYWxpZ24taXRlbXM6IGZsZXgtZW5kO1xufVxuXG4uY2ctd3JhcCBjb29yZHMuZmlsZXMge1xuICBib3R0b206IC00cHg7XG4gIGxlZnQ6IDI0cHg7XG4gIGZsZXgtZmxvdzogcm93O1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxNnB4O1xuICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG59XG5cbi5jZy13cmFwIGNvb3Jkcy5maWxlcy5ibGFjayB7XG4gIGZsZXgtZmxvdzogcm93LXJldmVyc2U7XG59XG5cbi5jZy13cmFwIGNvb3JkcyBjb29yZCB7XG4gIGZsZXg6IDEgMSBhdXRvO1xufVxuXG4uY2ctd3JhcCBjb29yZHMucmFua3MgY29vcmQge1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMzklKTtcbn1cbiIsICIvKiogQ29sb3JlZCBib2FyZCBzcXVhcmVzIGFzIGFuIGVtYmVkZGVkIFNWRyAqL1xuY2ctYm9hcmQge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBkOWI1O1xuICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoJ2RhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEQ5NGJXd2dkbVZ5YzJsdmJqMGlNUzR3SWlCbGJtTnZaR2x1WnowaVZWUkdMVGdpSUhOMFlXNWtZV3h2Ym1VOUltNXZJajgrQ2p4emRtY2dlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklpQjRiV3h1Y3pwNFBTSm9kSFJ3T2k4dmQzZDNMbmN6TG05eVp5OHhPVGs1TDNoc2FXNXJJZ29nSUNBZ0lIWnBaWGRDYjNnOUlqQWdNQ0E0SURnaUlITm9ZWEJsTFhKbGJtUmxjbWx1WnowaVkzSnBjM0JGWkdkbGN5SStDanhuSUdsa1BTSmhJajRLSUNBOFp5QnBaRDBpWWlJK0NpQWdJQ0E4WnlCcFpEMGlZeUkrQ2lBZ0lDQWdJRHhuSUdsa1BTSmtJajRLSUNBZ0lDQWdJQ0E4Y21WamRDQjNhV1IwYUQwaU1TSWdhR1ZwWjJoMFBTSXhJaUJwWkQwaVpTSWdiM0JoWTJsMGVUMGlNQ0l2UGdvZ0lDQWdJQ0FnSUR4MWMyVWdlRDBpTVNJZ2VUMGlNU0lnYUhKbFpqMGlJMlVpSUhnNmFISmxaajBpSTJVaUx6NEtJQ0FnSUNBZ0lDQThjbVZqZENCNVBTSXhJaUIzYVdSMGFEMGlNU0lnYUdWcFoyaDBQU0l4SWlCcFpEMGlaaUlnYjNCaFkybDBlVDBpTUM0eUlpOCtDaUFnSUNBZ0lDQWdQSFZ6WlNCNFBTSXhJaUI1UFNJdE1TSWdhSEpsWmowaUkyWWlJSGc2YUhKbFpqMGlJMllpTHo0S0lDQWdJQ0FnUEM5blBnb2dJQ0FnSUNBOGRYTmxJSGc5SWpJaUlHaHlaV1k5SWlOa0lpQjRPbWh5WldZOUlpTmtJaTgrQ2lBZ0lDQThMMmMrQ2lBZ0lDQThkWE5sSUhnOUlqUWlJR2h5WldZOUlpTmpJaUI0T21oeVpXWTlJaU5qSWk4K0NpQWdQQzluUGdvZ0lEeDFjMlVnZVQwaU1pSWdhSEpsWmowaUkySWlJSGc2YUhKbFpqMGlJMklpTHo0S1BDOW5QZ284ZFhObElIazlJalFpSUdoeVpXWTlJaU5oSWlCNE9taHlaV1k5SWlOaElpOCtDand2YzNablBnPT0nKTtcbn1cblxuLyoqIEludGVyYWN0aXZlIGJvYXJkIHNxdWFyZSBjb2xvcnMgKi9cbmNnLWJvYXJkIHNxdWFyZS5tb3ZlLWRlc3Qge1xuICBiYWNrZ3JvdW5kOiByYWRpYWwtZ3JhZGllbnQocmdiYSgyMCwgODUsIDMwLCAwLjUpIDIyJSwgIzIwODUzMCAwLCByZ2JhKDAsIDAsIDAsIDAuMykgMCwgcmdiYSgwLCAwLCAwLCAwKSAwKTtcbn1cbmNnLWJvYXJkIHNxdWFyZS5wcmVtb3ZlLWRlc3Qge1xuICBiYWNrZ3JvdW5kOiByYWRpYWwtZ3JhZGllbnQocmdiYSgyMCwgMzAsIDg1LCAwLjUpIDIyJSwgIzIwMzA4NSAwLCByZ2JhKDAsIDAsIDAsIDAuMykgMCwgcmdiYSgwLCAwLCAwLCAwKSAwKTtcbn1cbmNnLWJvYXJkIHNxdWFyZS5vYy5tb3ZlLWRlc3Qge1xuICBiYWNrZ3JvdW5kOiByYWRpYWwtZ3JhZGllbnQodHJhbnNwYXJlbnQgMCUsIHRyYW5zcGFyZW50IDgwJSwgcmdiYSgyMCwgODUsIDAsIDAuMykgODAlKTtcbn1cbmNnLWJvYXJkIHNxdWFyZS5vYy5wcmVtb3ZlLWRlc3Qge1xuICBiYWNrZ3JvdW5kOiByYWRpYWwtZ3JhZGllbnQodHJhbnNwYXJlbnQgMCUsIHRyYW5zcGFyZW50IDgwJSwgcmdiYSgyMCwgMzAsIDg1LCAwLjIpIDgwJSk7XG59XG5jZy1ib2FyZCBzcXVhcmUubW92ZS1kZXN0OmhvdmVyIHtcbiAgYmFja2dyb3VuZDogcmdiYSgyMCwgODUsIDMwLCAwLjMpO1xufVxuY2ctYm9hcmQgc3F1YXJlLnByZW1vdmUtZGVzdDpob3ZlciB7XG4gIGJhY2tncm91bmQ6IHJnYmEoMjAsIDMwLCA4NSwgMC4yKTtcbn1cbmNnLWJvYXJkIHNxdWFyZS5sYXN0LW1vdmUge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDE1NSwgMTk5LCAwLCAwLjQxKTtcbn1cbmNnLWJvYXJkIHNxdWFyZS5zZWxlY3RlZCB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoMjAsIDg1LCAzMCwgMC41KTtcbn1cbmNnLWJvYXJkIHNxdWFyZS5jaGVjayB7XG4gIGJhY2tncm91bmQ6IHJhZGlhbC1ncmFkaWVudChcbiAgICBlbGxpcHNlIGF0IGNlbnRlcixcbiAgICByZ2JhKDI1NSwgMCwgMCwgMSkgMCUsXG4gICAgcmdiYSgyMzEsIDAsIDAsIDEpIDI1JSxcbiAgICByZ2JhKDE2OSwgMCwgMCwgMCkgODklLFxuICAgIHJnYmEoMTU4LCAwLCAwLCAwKSAxMDAlXG4gICk7XG59XG5jZy1ib2FyZCBzcXVhcmUuY3VycmVudC1wcmVtb3ZlIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyMCwgMzAsIDg1LCAwLjUpO1xufVxuXG4vKiogQWx0ZXJuYXRpbmcgY29sb3JzIGluIHJhbmsvZmlsZSBsYWJlbHMgKi9cbi5jZy13cmFwLm9yaWVudGF0aW9uLXdoaXRlIGNvb3Jkcy5yYW5rcyBjb29yZDpudGgtY2hpbGQoMm4pLFxuLmNnLXdyYXAub3JpZW50YXRpb24td2hpdGUgY29vcmRzLmZpbGVzIGNvb3JkOm50aC1jaGlsZCgybiksXG4uY2ctd3JhcC5vcmllbnRhdGlvbi1ibGFjayBjb29yZHMucmFua3MgY29vcmQ6bnRoLWNoaWxkKDJuICsgMSksXG4uY2ctd3JhcC5vcmllbnRhdGlvbi1ibGFjayBjb29yZHMuZmlsZXMgY29vcmQ6bnRoLWNoaWxkKDJuICsgMSkge1xuICBjb2xvcjogcmdiYSg3MiwgNzIsIDcyLCAwLjgpO1xufVxuXG4uY2ctd3JhcC5vcmllbnRhdGlvbi1ibGFjayBjb29yZHMucmFua3MgY29vcmQ6bnRoLWNoaWxkKDJuKSxcbi5jZy13cmFwLm9yaWVudGF0aW9uLWJsYWNrIGNvb3Jkcy5maWxlcyBjb29yZDpudGgtY2hpbGQoMm4pLFxuLmNnLXdyYXAub3JpZW50YXRpb24td2hpdGUgY29vcmRzLnJhbmtzIGNvb3JkOm50aC1jaGlsZCgybiArIDEpLFxuLmNnLXdyYXAub3JpZW50YXRpb24td2hpdGUgY29vcmRzLmZpbGVzIGNvb3JkOm50aC1jaGlsZCgybiArIDEpIHtcbiAgY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC44KTtcbn1cbiIsICIvKiogRW1iZWRkZWQgU1ZHcyBmb3IgYWxsIGNoZXNzIHBpZWNlcyAqL1xuLmNnLXdyYXAgcGllY2UucGF3bi53aGl0ZSB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kwTlNJZ2FHVnBaMmgwUFNJME5TSStQSEJoZEdnZ1pEMGlUVEl5TGpVZ09XTXRNaTR5TVNBd0xUUWdNUzQzT1MwMElEUWdNQ0F1T0RrdU1qa2dNUzQzTVM0M09DQXlMak00UXpFM0xqTXpJREUyTGpVZ01UWWdNVGd1TlRrZ01UWWdNakZqTUNBeUxqQXpMamswSURNdU9EUWdNaTQwTVNBMUxqQXpMVE1nTVM0d05pMDNMalF4SURVdU5UVXROeTQwTVNBeE15NDBOMmd5TTJNd0xUY3VPVEl0TkM0ME1TMHhNaTQwTVMwM0xqUXhMVEV6TGpRM0lERXVORGN0TVM0eE9TQXlMalF4TFRNZ01pNDBNUzAxTGpBeklEQXRNaTQwTVMweExqTXpMVFF1TlMwekxqSTRMVFV1TmpJdU5Ea3RMalkzTGpjNExURXVORGt1TnpndE1pNHpPQ0F3TFRJdU1qRXRNUzQzT1MwMExUUXROSG9pSUdacGJHdzlJaU5tWm1ZaUlITjBjbTlyWlQwaUl6QXdNQ0lnYzNSeWIydGxMWGRwWkhSb1BTSXhMalVpSUhOMGNtOXJaUzFzYVc1bFkyRndQU0p5YjNWdVpDSXZQand2YzNablBnPT0nKTtcbn1cbi5jZy13cmFwIHBpZWNlLmJpc2hvcC53aGl0ZSB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kwTlNJZ2FHVnBaMmgwUFNJME5TSStQR2NnWm1sc2JEMGlibTl1WlNJZ1ptbHNiQzF5ZFd4bFBTSmxkbVZ1YjJSa0lpQnpkSEp2YTJVOUlpTXdNREFpSUhOMGNtOXJaUzEzYVdSMGFEMGlNUzQxSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaVBqeG5JR1pwYkd3OUlpTm1abVlpSUhOMGNtOXJaUzFzYVc1bFkyRndQU0ppZFhSMElqNDhjR0YwYUNCa1BTSk5PU0F6Tm1NekxqTTVMUzQ1TnlBeE1DNHhNUzQwTXlBeE15NDFMVElnTXk0ek9TQXlMalF6SURFd0xqRXhJREV1TURNZ01UTXVOU0F5SURBZ01DQXhMalkxTGpVMElETWdNaTB1TmpndU9UY3RNUzQyTlM0NU9TMHpJQzQxTFRNdU16a3RMamszTFRFd0xqRXhMalEyTFRFekxqVXRNUzB6TGpNNUlERXVORFl0TVRBdU1URXVNRE10TVRNdU5TQXhMVEV1TXpVMExqUTVMVEl1TXpJekxqUTNMVE10TGpVZ01TNHpOVFF0TVM0NU5DQXpMVElnTXkweWVpSXZQanh3WVhSb0lHUTlJazB4TlNBek1tTXlMalVnTWk0MUlERXlMalVnTWk0MUlERTFJREFnTGpVdE1TNDFJREF0TWlBd0xUSWdNQzB5TGpVdE1pNDFMVFF0TWk0MUxUUWdOUzQxTFRFdU5TQTJMVEV4TGpVdE5TMHhOUzQxTFRFeElEUXRNVEF1TlNBeE5DMDFJREUxTGpVZ01DQXdMVEl1TlNBeExqVXRNaTQxSURRZ01DQXdMUzQxTGpVZ01DQXllaUl2UGp4d1lYUm9JR1E5SWsweU5TQTRZVEl1TlNBeUxqVWdNQ0F4SURFdE5TQXdJREl1TlNBeUxqVWdNQ0F4SURFZ05TQXdlaUl2UGp3dlp6NDhjR0YwYUNCa1BTSk5NVGN1TlNBeU5tZ3hNRTB4TlNBek1HZ3hOVzB0Tnk0MUxURTBMalYyTlUweU1DQXhPR2cxSWlCemRISnZhMlV0YkdsdVpXcHZhVzQ5SW0xcGRHVnlJaTgrUEM5blBqd3ZjM1puUGc9PScpO1xufVxuLmNnLXdyYXAgcGllY2Uua25pZ2h0LndoaXRlIHtcbiAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSTBOU0lnYUdWcFoyaDBQU0kwTlNJK1BHY2dabWxzYkQwaWJtOXVaU0lnWm1sc2JDMXlkV3hsUFNKbGRtVnViMlJrSWlCemRISnZhMlU5SWlNd01EQWlJSE4wY205clpTMTNhV1IwYUQwaU1TNDFJaUJ6ZEhKdmEyVXRiR2x1WldOaGNEMGljbTkxYm1RaUlITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpUGp4d1lYUm9JR1E5SWsweU1pQXhNR014TUM0MUlERWdNVFl1TlNBNElERTJJREk1U0RFMVl6QXRPU0F4TUMwMkxqVWdPQzB5TVNJZ1ptbHNiRDBpSTJabVppSXZQanh3WVhSb0lHUTlJazB5TkNBeE9HTXVNemdnTWk0NU1TMDFMalUxSURjdU16Y3RPQ0E1TFRNZ01pMHlMamd5SURRdU16UXROU0EwTFRFdU1EUXlMUzQ1TkNBeExqUXhMVE11TURRZ01DMHpMVEVnTUNBdU1Ua2dNUzR5TXkweElESXRNU0F3TFRRdU1EQXpJREV0TkMwMElEQXRNaUEyTFRFeUlEWXRNVEp6TVM0NE9TMHhMamtnTWkwekxqVmpMUzQzTXkwdU9UazBMUzQxTFRJdExqVXRNeUF4TFRFZ015QXlMalVnTXlBeUxqVm9Nbk11TnpndE1TNDVPVElnTWk0MUxUTmpNU0F3SURFZ015QXhJRE1pSUdacGJHdzlJaU5tWm1ZaUx6NDhjR0YwYUNCa1BTSk5PUzQxSURJMUxqVmhMalV1TlNBd0lERWdNUzB4SURBZ0xqVXVOU0F3SURFZ01TQXhJREI2YlRVdU5ETXpMVGt1TnpWaExqVWdNUzQxSURNd0lERWdNUzB1T0RZMkxTNDFMalVnTVM0MUlETXdJREVnTVNBdU9EWTJMalY2SWlCbWFXeHNQU0lqTURBd0lpOCtQQzluUGp3dmMzWm5QZz09Jyk7XG59XG4uY2ctd3JhcCBwaWVjZS5yb29rLndoaXRlIHtcbiAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSTBOU0lnYUdWcFoyaDBQU0kwTlNJK1BHY2dabWxzYkQwaUkyWm1aaUlnWm1sc2JDMXlkV3hsUFNKbGRtVnViMlJrSWlCemRISnZhMlU5SWlNd01EQWlJSE4wY205clpTMTNhV1IwYUQwaU1TNDFJaUJ6ZEhKdmEyVXRiR2x1WldOaGNEMGljbTkxYm1RaUlITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpUGp4d1lYUm9JR1E5SWswNUlETTVhREkzZGkwelNEbDJNM3B0TXkwemRpMDBhREl4ZGpSSU1USjZiUzB4TFRJeVZqbG9OSFl5YURWV09XZzFkakpvTlZZNWFEUjJOU0lnYzNSeWIydGxMV3hwYm1WallYQTlJbUoxZEhRaUx6NDhjR0YwYUNCa1BTSk5NelFnTVRSc0xUTWdNMGd4Tkd3dE15MHpJaTgrUEhCaGRHZ2daRDBpVFRNeElERTNkakV5TGpWSU1UUldNVGNpSUhOMGNtOXJaUzFzYVc1bFkyRndQU0ppZFhSMElpQnpkSEp2YTJVdGJHbHVaV3B2YVc0OUltMXBkR1Z5SWk4K1BIQmhkR2dnWkQwaVRUTXhJREk1TGpWc01TNDFJREl1TldndE1qQnNNUzQxTFRJdU5TSXZQanh3WVhSb0lHUTlJazB4TVNBeE5HZ3lNeUlnWm1sc2JEMGlibTl1WlNJZ2MzUnliMnRsTFd4cGJtVnFiMmx1UFNKdGFYUmxjaUl2UGp3dlp6NDhMM04yWno0PScpO1xufVxuLmNnLXdyYXAgcGllY2UucXVlZW4ud2hpdGUge1xuICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoJ2RhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJME5TSWdhR1ZwWjJoMFBTSTBOU0krUEdjZ1ptbHNiRDBpSTJabVppSWdabWxzYkMxeWRXeGxQU0psZG1WdWIyUmtJaUJ6ZEhKdmEyVTlJaU13TURBaUlITjBjbTlyWlMxM2FXUjBhRDBpTVM0MUlpQnpkSEp2YTJVdGJHbHVaV05oY0QwaWNtOTFibVFpSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlQanh3WVhSb0lHUTlJazA0SURFeVlUSWdNaUF3SURFZ01TMDBJREFnTWlBeUlEQWdNU0F4SURRZ01IcHRNVFl1TlMwMExqVmhNaUF5SURBZ01TQXhMVFFnTUNBeUlESWdNQ0F4SURFZ05DQXdlazAwTVNBeE1tRXlJRElnTUNBeElERXROQ0F3SURJZ01pQXdJREVnTVNBMElEQjZUVEUySURndU5XRXlJRElnTUNBeElERXROQ0F3SURJZ01pQXdJREVnTVNBMElEQjZUVE16SURsaE1pQXlJREFnTVNBeExUUWdNQ0F5SURJZ01DQXhJREVnTkNBd2VpSXZQanh3WVhSb0lHUTlJazA1SURJMll6Z3VOUzB4TGpVZ01qRXRNUzQxSURJM0lEQnNNaTB4TWkwM0lERXhWakV4YkMwMUxqVWdNVE11TlMwekxURTFMVE1nTVRVdE5TNDFMVEUwVmpJMVREY2dNVFJzTWlBeE1ub2lJSE4wY205clpTMXNhVzVsWTJGd1BTSmlkWFIwSWk4K1BIQmhkR2dnWkQwaVRUa2dNalpqTUNBeUlERXVOU0F5SURJdU5TQTBJREVnTVM0MUlERWdNU0F1TlNBekxqVXRNUzQxSURFdE1TNDFJREl1TlMweExqVWdNaTQxTFRFdU5TQXhMalV1TlNBeUxqVXVOU0F5TGpVZ05pNDFJREVnTVRZdU5TQXhJREl6SURBZ01DQXdJREV1TlMweElEQXRNaTQxSURBZ01DQXVOUzB4TGpVdE1TMHlMalV0TGpVdE1pNDFMUzQxTFRJZ0xqVXRNeTQxSURFdE1pQXlMalV0TWlBeUxqVXROQzA0TGpVdE1TNDFMVEU0TGpVdE1TNDFMVEkzSURCNklpQnpkSEp2YTJVdGJHbHVaV05oY0QwaVluVjBkQ0l2UGp4d1lYUm9JR1E5SWsweE1TNDFJRE13WXpNdU5TMHhJREU0TGpVdE1TQXlNaUF3VFRFeUlETXpMalZqTmkweElERTFMVEVnTWpFZ01DSWdabWxzYkQwaWJtOXVaU0l2UGp3dlp6NDhMM04yWno0PScpO1xufVxuLmNnLXdyYXAgcGllY2Uua2luZy53aGl0ZSB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kwTlNJZ2FHVnBaMmgwUFNJME5TSStQR2NnWm1sc2JEMGlibTl1WlNJZ1ptbHNiQzF5ZFd4bFBTSmxkbVZ1YjJSa0lpQnpkSEp2YTJVOUlpTXdNREFpSUhOMGNtOXJaUzEzYVdSMGFEMGlNUzQxSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaVBqeHdZWFJvSUdROUlrMHlNaTQxSURFeExqWXpWalpOTWpBZ09HZzFJaUJ6ZEhKdmEyVXRiR2x1WldwdmFXNDlJbTFwZEdWeUlpOCtQSEJoZEdnZ1pEMGlUVEl5TGpVZ01qVnpOQzQxTFRjdU5TQXpMVEV3TGpWak1DQXdMVEV0TWk0MUxUTXRNaTQxY3kweklESXVOUzB6SURJdU5XTXRNUzQxSURNZ015QXhNQzQxSURNZ01UQXVOU0lnWm1sc2JEMGlJMlptWmlJZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW1KMWRIUWlJSE4wY205clpTMXNhVzVsYW05cGJqMGliV2wwWlhJaUx6NDhjR0YwYUNCa1BTSk5NVEV1TlNBek4yTTFMalVnTXk0MUlERTFMalVnTXk0MUlESXhJREIyTFRkek9TMDBMalVnTmkweE1DNDFZeTAwTFRZdU5TMHhNeTQxTFRNdU5TMHhOaUEwVmpJM2RpMHpMalZqTFRNdU5TMDNMalV0TVRNdE1UQXVOUzB4TmkwMExUTWdOaUExSURFd0lEVWdNVEJXTXpkNklpQm1hV3hzUFNJalptWm1JaTgrUEhCaGRHZ2daRDBpVFRFeExqVWdNekJqTlM0MUxUTWdNVFV1TlMweklESXhJREJ0TFRJeElETXVOV00xTGpVdE15QXhOUzQxTFRNZ01qRWdNRzB0TWpFZ015NDFZelV1TlMweklERTFMalV0TXlBeU1TQXdJaTgrUEM5blBqd3ZjM1puUGc9PScpO1xufVxuLmNnLXdyYXAgcGllY2UucGF3bi5ibGFjayB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kwTlNJZ2FHVnBaMmgwUFNJME5TSStQSEJoZEdnZ1pEMGlUVEl5TGpVZ09XTXRNaTR5TVNBd0xUUWdNUzQzT1MwMElEUWdNQ0F1T0RrdU1qa2dNUzQzTVM0M09DQXlMak00UXpFM0xqTXpJREUyTGpVZ01UWWdNVGd1TlRrZ01UWWdNakZqTUNBeUxqQXpMamswSURNdU9EUWdNaTQwTVNBMUxqQXpMVE1nTVM0d05pMDNMalF4SURVdU5UVXROeTQwTVNBeE15NDBOMmd5TTJNd0xUY3VPVEl0TkM0ME1TMHhNaTQwTVMwM0xqUXhMVEV6TGpRM0lERXVORGN0TVM0eE9TQXlMalF4TFRNZ01pNDBNUzAxTGpBeklEQXRNaTQwTVMweExqTXpMVFF1TlMwekxqSTRMVFV1TmpJdU5Ea3RMalkzTGpjNExURXVORGt1TnpndE1pNHpPQ0F3TFRJdU1qRXRNUzQzT1MwMExUUXROSG9pSUhOMGNtOXJaVDBpSXpBd01DSWdjM1J5YjJ0bExYZHBaSFJvUFNJeExqVWlJSE4wY205clpTMXNhVzVsWTJGd1BTSnliM1Z1WkNJdlBqd3ZjM1puUGc9PScpO1xufVxuLmNnLXdyYXAgcGllY2UuYmlzaG9wLmJsYWNrIHtcbiAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSTBOU0lnYUdWcFoyaDBQU0kwTlNJK1BHY2dabWxzYkQwaWJtOXVaU0lnWm1sc2JDMXlkV3hsUFNKbGRtVnViMlJrSWlCemRISnZhMlU5SWlNd01EQWlJSE4wY205clpTMTNhV1IwYUQwaU1TNDFJaUJ6ZEhKdmEyVXRiR2x1WldOaGNEMGljbTkxYm1RaUlITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpUGp4bklHWnBiR3c5SWlNd01EQWlJSE4wY205clpTMXNhVzVsWTJGd1BTSmlkWFIwSWo0OGNHRjBhQ0JrUFNKTk9TQXpObU16TGpNNUxTNDVOeUF4TUM0eE1TNDBNeUF4TXk0MUxUSWdNeTR6T1NBeUxqUXpJREV3TGpFeElERXVNRE1nTVRNdU5TQXlJREFnTUNBeExqWTFMalUwSURNZ01pMHVOamd1T1RjdE1TNDJOUzQ1T1MweklDNDFMVE11TXprdExqazNMVEV3TGpFeExqUTJMVEV6TGpVdE1TMHpMak01SURFdU5EWXRNVEF1TVRFdU1ETXRNVE11TlNBeExURXVNelUwTGpRNUxUSXVNekl6TGpRM0xUTXRMalVnTVM0ek5UUXRNUzQ1TkNBekxUSWdNeTB5ZWlJdlBqeHdZWFJvSUdROUlrMHhOU0F6TW1NeUxqVWdNaTQxSURFeUxqVWdNaTQxSURFMUlEQWdMalV0TVM0MUlEQXRNaUF3TFRJZ01DMHlMalV0TWk0MUxUUXRNaTQxTFRRZ05TNDFMVEV1TlNBMkxURXhMalV0TlMweE5TNDFMVEV4SURRdE1UQXVOU0F4TkMwMUlERTFMalVnTUNBd0xUSXVOU0F4TGpVdE1pNDFJRFFnTUNBd0xTNDFMalVnTUNBeWVpSXZQanh3WVhSb0lHUTlJazB5TlNBNFlUSXVOU0F5TGpVZ01DQXhJREV0TlNBd0lESXVOU0F5TGpVZ01DQXhJREVnTlNBd2VpSXZQand2Wno0OGNHRjBhQ0JrUFNKTk1UY3VOU0F5Tm1neE1FMHhOU0F6TUdneE5XMHROeTQxTFRFMExqVjJOVTB5TUNBeE9HZzFJaUJ6ZEhKdmEyVTlJaU5sWTJWalpXTWlJSE4wY205clpTMXNhVzVsYW05cGJqMGliV2wwWlhJaUx6NDhMMmMrUEM5emRtYysnKTtcbn1cbi5jZy13cmFwIHBpZWNlLmtuaWdodC5ibGFjayB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kwTlNJZ2FHVnBaMmgwUFNJME5TSStQR2NnWm1sc2JEMGlibTl1WlNJZ1ptbHNiQzF5ZFd4bFBTSmxkbVZ1YjJSa0lpQnpkSEp2YTJVOUlpTXdNREFpSUhOMGNtOXJaUzEzYVdSMGFEMGlNUzQxSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaVBqeHdZWFJvSUdROUlrMHlNaUF4TUdNeE1DNDFJREVnTVRZdU5TQTRJREUySURJNVNERTFZekF0T1NBeE1DMDJMalVnT0MweU1TSWdabWxzYkQwaUl6QXdNQ0l2UGp4d1lYUm9JR1E5SWsweU5DQXhPR011TXpnZ01pNDVNUzAxTGpVMUlEY3VNemN0T0NBNUxUTWdNaTB5TGpneUlEUXVNelF0TlNBMExURXVNRFF5TFM0NU5DQXhMalF4TFRNdU1EUWdNQzB6TFRFZ01DQXVNVGtnTVM0eU15MHhJREl0TVNBd0xUUXVNREF6SURFdE5DMDBJREF0TWlBMkxURXlJRFl0TVRKek1TNDRPUzB4TGprZ01pMHpMalZqTFM0M015MHVPVGswTFM0MUxUSXRMalV0TXlBeExURWdNeUF5TGpVZ015QXlMalZvTW5NdU56Z3RNUzQ1T1RJZ01pNDFMVE5qTVNBd0lERWdNeUF4SURNaUlHWnBiR3c5SWlNd01EQWlMejQ4Y0dGMGFDQmtQU0pOT1M0MUlESTFMalZoTGpVdU5TQXdJREVnTVMweElEQWdMalV1TlNBd0lERWdNU0F4SURCNmJUVXVORE16TFRrdU56VmhMalVnTVM0MUlETXdJREVnTVMwdU9EWTJMUzQxTGpVZ01TNDFJRE13SURFZ01TQXVPRFkyTGpWNklpQm1hV3hzUFNJalpXTmxZMlZqSWlCemRISnZhMlU5SWlObFkyVmpaV01pTHo0OGNHRjBhQ0JrUFNKTk1qUXVOVFVnTVRBdU5Hd3RMalExSURFdU5EVXVOUzR4TldNekxqRTFJREVnTlM0Mk5TQXlMalE1SURjdU9TQTJMamMxVXpNMUxqYzFJREk1TGpBMklETTFMakkxSURNNWJDMHVNRFV1TldneUxqSTFiQzR3TlMwdU5XTXVOUzB4TUM0d05pMHVPRGd0TVRZdU9EVXRNeTR5TlMweU1TNHpOQzB5TGpNM0xUUXVORGt0TlM0M09TMDJMalkwTFRrdU1Ua3ROeTR4Tm13dExqVXhMUzR4ZWlJZ1ptbHNiRDBpSTJWalpXTmxZeUlnYzNSeWIydGxQU0p1YjI1bElpOCtQQzluUGp3dmMzWm5QZz09Jyk7XG59XG4uY2ctd3JhcCBwaWVjZS5yb29rLmJsYWNrIHtcbiAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSTBOU0lnYUdWcFoyaDBQU0kwTlNJK1BHY2dabWxzYkMxeWRXeGxQU0psZG1WdWIyUmtJaUJ6ZEhKdmEyVTlJaU13TURBaUlITjBjbTlyWlMxM2FXUjBhRDBpTVM0MUlpQnpkSEp2YTJVdGJHbHVaV05oY0QwaWNtOTFibVFpSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlQanh3WVhSb0lHUTlJazA1SURNNWFESTNkaTB6U0RsMk0zcHRNeTQxTFRkc01TNDFMVEl1TldneE4yd3hMalVnTWk0MWFDMHlNSHB0TFM0MUlEUjJMVFJvTWpGMk5FZ3hNbm9pSUhOMGNtOXJaUzFzYVc1bFkyRndQU0ppZFhSMElpOCtQSEJoZEdnZ1pEMGlUVEUwSURJNUxqVjJMVEV6YURFM2RqRXpTREUwZWlJZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW1KMWRIUWlJSE4wY205clpTMXNhVzVsYW05cGJqMGliV2wwWlhJaUx6NDhjR0YwYUNCa1BTSk5NVFFnTVRZdU5Vd3hNU0F4TkdneU0yd3RNeUF5TGpWSU1UUjZUVEV4SURFMFZqbG9OSFl5YURWV09XZzFkakpvTlZZNWFEUjJOVWd4TVhvaUlITjBjbTlyWlMxc2FXNWxZMkZ3UFNKaWRYUjBJaTgrUEhCaGRHZ2daRDBpVFRFeUlETTFMalZvTWpGdExUSXdMVFJvTVRsdExURTRMVEpvTVRkdExURTNMVEV6YURFM1RURXhJREUwYURJeklpQm1hV3hzUFNKdWIyNWxJaUJ6ZEhKdmEyVTlJaU5sWTJWalpXTWlJSE4wY205clpTMTNhV1IwYUQwaU1TSWdjM1J5YjJ0bExXeHBibVZxYjJsdVBTSnRhWFJsY2lJdlBqd3ZaejQ4TDNOMlp6ND0nKTtcbn1cbi5jZy13cmFwIHBpZWNlLnF1ZWVuLmJsYWNrIHtcbiAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSTBOU0lnYUdWcFoyaDBQU0kwTlNJK1BHY2dabWxzYkMxeWRXeGxQU0psZG1WdWIyUmtJaUJ6ZEhKdmEyVTlJaU13TURBaUlITjBjbTlyWlMxM2FXUjBhRDBpTVM0MUlpQnpkSEp2YTJVdGJHbHVaV05oY0QwaWNtOTFibVFpSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlQanhuSUhOMGNtOXJaVDBpYm05dVpTSStQR05wY21Oc1pTQmplRDBpTmlJZ1kzazlJakV5SWlCeVBTSXlMamMxSWk4K1BHTnBjbU5zWlNCamVEMGlNVFFpSUdONVBTSTVJaUJ5UFNJeUxqYzFJaTgrUEdOcGNtTnNaU0JqZUQwaU1qSXVOU0lnWTNrOUlqZ2lJSEk5SWpJdU56VWlMejQ4WTJseVkyeGxJR040UFNJek1TSWdZM2s5SWpraUlISTlJakl1TnpVaUx6NDhZMmx5WTJ4bElHTjRQU0l6T1NJZ1kzazlJakV5SWlCeVBTSXlMamMxSWk4K1BDOW5Qanh3WVhSb0lHUTlJazA1SURJMll6Z3VOUzB4TGpVZ01qRXRNUzQxSURJM0lEQnNNaTQxTFRFeUxqVk1NekVnTWpWc0xTNHpMVEUwTGpFdE5TNHlJREV6TGpZdE15MHhOQzQxTFRNZ01UUXVOUzAxTGpJdE1UTXVOa3d4TkNBeU5TQTJMalVnTVRNdU5TQTVJREkyZWlJZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW1KMWRIUWlMejQ4Y0dGMGFDQmtQU0pOT1NBeU5tTXdJRElnTVM0MUlESWdNaTQxSURRZ01TQXhMalVnTVNBeElDNDFJRE11TlMweExqVWdNUzB4TGpVZ01pNDFMVEV1TlNBeUxqVXRNUzQxSURFdU5TNDFJREl1TlM0MUlESXVOU0EyTGpVZ01TQXhOaTQxSURFZ01qTWdNQ0F3SURBZ01TNDFMVEVnTUMweUxqVWdNQ0F3SUM0MUxURXVOUzB4TFRJdU5TMHVOUzB5TGpVdExqVXRNaUF1TlMwekxqVWdNUzB5SURJdU5TMHlJREl1TlMwMExUZ3VOUzB4TGpVdE1UZ3VOUzB4TGpVdE1qY2dNSG9pSUhOMGNtOXJaUzFzYVc1bFkyRndQU0ppZFhSMElpOCtQSEJoZEdnZ1pEMGlUVEV4SURNNExqVmhNelVnTXpVZ01TQXdJREFnTWpNZ01DSWdabWxzYkQwaWJtOXVaU0lnYzNSeWIydGxMV3hwYm1WallYQTlJbUoxZEhRaUx6NDhjR0YwYUNCa1BTSk5NVEVnTWpsaE16VWdNelVnTVNBd0lERWdNak1nTUcwdE1qRXVOU0F5TGpWb01qQnRMVEl4SUROaE16VWdNelVnTVNBd0lEQWdNaklnTUcwdE1qTWdNMkV6TlNBek5TQXhJREFnTUNBeU5DQXdJaUJtYVd4c1BTSnViMjVsSWlCemRISnZhMlU5SWlObFkyVmpaV01pTHo0OEwyYytQQzl6ZG1jKycpO1xufVxuLmNnLXdyYXAgcGllY2Uua2luZy5ibGFjayB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kwTlNJZ2FHVnBaMmgwUFNJME5TSStQR2NnWm1sc2JEMGlibTl1WlNJZ1ptbHNiQzF5ZFd4bFBTSmxkbVZ1YjJSa0lpQnpkSEp2YTJVOUlpTXdNREFpSUhOMGNtOXJaUzEzYVdSMGFEMGlNUzQxSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaVBqeHdZWFJvSUdROUlrMHlNaTQxSURFeExqWXpWallpSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpYldsMFpYSWlMejQ4Y0dGMGFDQmtQU0pOTWpJdU5TQXlOWE0wTGpVdE55NDFJRE10TVRBdU5XTXdJREF0TVMweUxqVXRNeTB5TGpWekxUTWdNaTQxTFRNZ01pNDFZeTB4TGpVZ015QXpJREV3TGpVZ015QXhNQzQxSWlCbWFXeHNQU0lqTURBd0lpQnpkSEp2YTJVdGJHbHVaV05oY0QwaVluVjBkQ0lnYzNSeWIydGxMV3hwYm1WcWIybHVQU0p0YVhSbGNpSXZQanh3WVhSb0lHUTlJazB4TVM0MUlETTNZelV1TlNBekxqVWdNVFV1TlNBekxqVWdNakVnTUhZdE4zTTVMVFF1TlNBMkxURXdMalZqTFRRdE5pNDFMVEV6TGpVdE15NDFMVEUySURSV01qZDJMVE11TldNdE15NDFMVGN1TlMweE15MHhNQzQxTFRFMkxUUXRNeUEySURVZ01UQWdOU0F4TUZZek4zb2lJR1pwYkd3OUlpTXdNREFpTHo0OGNHRjBhQ0JrUFNKTk1qQWdPR2cxSWlCemRISnZhMlV0YkdsdVpXcHZhVzQ5SW0xcGRHVnlJaTgrUEhCaGRHZ2daRDBpVFRNeUlESTVMalZ6T0M0MUxUUWdOaTR3TXkwNUxqWTFRek0wTGpFMUlERTBJREkxSURFNElESXlMalVnTWpRdU5Xd3VNREVnTWk0eExTNHdNUzB5TGpGRE1qQWdNVGdnT1M0NU1EWWdNVFFnTmk0NU9UY2dNVGt1T0RWakxUSXVORGszSURVdU5qVWdOQzQ0TlRNZ09TQTBMamcxTXlBNUlpQnpkSEp2YTJVOUlpTmxZMlZqWldNaUx6NDhjR0YwYUNCa1BTSk5NVEV1TlNBek1HTTFMalV0TXlBeE5TNDFMVE1nTWpFZ01HMHRNakVnTXk0MVl6VXVOUzB6SURFMUxqVXRNeUF5TVNBd2JTMHlNU0F6TGpWak5TNDFMVE1nTVRVdU5TMHpJREl4SURBaUlITjBjbTlyWlQwaUkyVmpaV05sWXlJdlBqd3ZaejQ4TDNOMlp6ND0nKTtcbn1cbiIsICIvKiBHZW5lcmFsICovXG5cbi5jaGVzcy1zdHVkeSBidXR0b246YWN0aXZlIHtcblx0dHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7XG59XG5cbi5jaGVzcy1zdHVkeSAuYm9yZGVyIHtcblx0Ym9yZGVyOiAxcHggdmFyKC0tY29sb3ItYmFzZS0xMCkgc29saWQ7XG5cdGJvcmRlci1yYWRpdXM6IDVweDtcbn1cblxuLmNoZXNzLXN0dWR5IC5ib3JkZXItdG9wIHtcblx0Ym9yZGVyOiAxcHggdmFyKC0tY29sb3ItYmFzZS0xMCkgc29saWQ7XG59XG5cbi5jaGVzcy1zdHVkeSAudmVydGljYWwtYWxpZ24ge1xuXHRkaXNwbGF5OiBmbGV4O1xuXHRhbGlnbi1pdGVtczogY2VudGVyO1xufVxuXG4uY2hlc3Mtc3R1ZHkgLmNlbnRlciB7XG5cdGRpc3BsYXk6IGZsZXg7XG5cdGFsaWduLWl0ZW1zOiBjZW50ZXI7XG5cdGp1c3RpZnktY29udGVudDogY2VudGVyO1xufVxuXG4uY2hlc3Mtc3R1ZHkgLmhlaWdodC13aWR0aC0xMDAge1xuXHR3aWR0aDogMTAwJTtcblx0aGVpZ2h0OiAxMDAlO1xufVxuXG4uY2hlc3Mtc3R1ZHkgLnRhYmxlIHtcblx0ZGlzcGxheTogdGFibGU7XG59XG5cbi5jaGVzcy1zdHVkeSB7XG5cdGxpbmUtaGVpZ2h0OiAxLjU7XG5cdC13ZWJraXQtZm9udC1zbW9vdGhpbmc6IGFudGlhbGlhc2VkO1xufVxuXG4uY2hlc3Mtc3R1ZHkge1xuXHRoZWlnaHQ6IDEwMCU7XG59XG5cbi5jaGVzcy1zdHVkeSAqLFxuLmNoZXNzLXN0dWR5ICo6OmJlZm9yZSxcbi5jaGVzcy1zdHVkeSAqOjphZnRlciB7XG5cdGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG59XG4uY2hlc3Mtc3R1ZHkgKiB7XG5cdG1hcmdpbjogMDtcbn1cblxuLmNoZXNzLXN0dWR5IGlucHV0LFxuLmNoZXNzLXN0dWR5IGJ1dHRvbixcbi5jaGVzcy1zdHVkeSB0ZXh0YXJlYSxcbi5jaGVzcy1zdHVkeSBzZWxlY3Qge1xuXHRmb250OiBpbmhlcml0O1xufVxuXG4uY2hlc3Mtc3R1ZHkgcCxcbi5jaGVzcy1zdHVkeSBoMSxcbi5jaGVzcy1zdHVkeSBoMixcbi5jaGVzcy1zdHVkeSBoMyxcbi5jaGVzcy1zdHVkeSBoNCxcbi5jaGVzcy1zdHVkeSBoNSxcbi5jaGVzcy1zdHVkeSBoNiB7XG5cdG92ZXJmbG93LXdyYXA6IGJyZWFrLXdvcmQ7XG59XG5cbi8qIENvbnRhaW5lciAqL1xuLmNoZXNzLXN0dWR5IC5jaGVzc2dyb3VuZC1wZ24tY29udGFpbmVyIHtcblx0ZGlzcGxheTogZmxleDtcblx0aGVpZ2h0OiA0NTBweDtcbn1cblxuLmNoZXNzLXN0dWR5IC5jaGVzc2dyb3VuZC1wZ24tY29udGFpbmVyIC5jaGVzc2dyb3VuZC1jb250YWluZXIge1xuXHRmbGV4OiAwIDAgNDUwcHg7XG5cdGhlaWdodDogMTAwJTtcbn1cblxuLmNoZXNzLXN0dWR5IC5jaGVzc2dyb3VuZC1wZ24tY29udGFpbmVyIC5wZ24tY29udGFpbmVyIHtcblx0ZmxleDogMTtcblx0aGVpZ2h0OiAxMDAlO1xufVxuXG4vKiBQR04gVmlld2VyICovXG4uY2hlc3Mtc3R1ZHkgLm1vdmUtaXRlbS1zZWN0aW9uIHtcblx0aGVpZ2h0OiA0MTVweDtcbn1cblxuLmNoZXNzLXN0dWR5IC5idXR0b24tc2VjdGlvbiB7XG5cdGRpc3BsYXk6IGZsZXg7XG5cdGp1c3RpZnktY29udGVudDogY2VudGVyO1xuXHRhbGlnbi1pdGVtczogY2VudGVyO1xuXHR3aWR0aDogMTAwJTtcblx0Z2FwOiA0cHg7XG5cdGhlaWdodDogMzVweDtcbn1cblxuLmNoZXNzLXN0dWR5IC5tb3ZlLWl0ZW0tY29udGFpbmVyIHtcblx0ZGlzcGxheTogZ3JpZDtcblx0Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOiAwLjE1ZnIgMC40MjVmciAwLjQyNWZyO1xuXHRncmlkLWF1dG8tcm93czogMzBweDtcblx0d2lkdGg6IDEwMCU7XG5cdG1heC1oZWlnaHQ6IDQxNXB4O1xuXHRvdmVyZmxvdy15OiBzY3JvbGw7XG59XG5cbi5jaGVzcy1zdHVkeSAubW92ZS1pdGVtIHtcblx0cGFkZGluZy1sZWZ0OiA4cHg7XG5cdHVzZXItc2VsZWN0OiBub25lO1xufVxuXG4uY2hlc3Mtc3R1ZHkgLm1vdmUtaXRlbTpob3ZlciB7XG5cdGJhY2tncm91bmQtY29sb3I6IGhzbChcblx0XHR2YXIoLS1hY2NlbnQtaCksXG5cdFx0dmFyKC0tYWNjZW50LXMpLFxuXHRcdGNhbGModmFyKC0tYWNjZW50LWwpKVxuXHQpO1xuXHRjb2xvcjogdmFyKC0tdGV4dC1vbi1hY2NlbnQpO1xuXHRjdXJzb3I6IHBvaW50ZXI7XG59XG5cbi5jaGVzcy1zdHVkeSAubW92ZS1pdGVtLmFjdGl2ZSB7XG5cdGJhY2tncm91bmQtY29sb3I6IGhzbChcblx0XHR2YXIoLS1hY2NlbnQtaCksXG5cdFx0dmFyKC0tYWNjZW50LXMpLFxuXHRcdGNhbGModmFyKC0tYWNjZW50LWwpIC0gMTAlKVxuXHQpO1xuXHRjb2xvcjogdmFyKC0tdGV4dC1vbi1hY2NlbnQpO1xuXHRmb250LXdlaWdodDogYm9sZDtcbn1cblxuLmNoZXNzLXN0dWR5IC5tb3ZlLWluZGljYXRvciB7XG5cdGNvbG9yOiB2YXIoLS1jb2xvci1iYXNlLTYwKTtcblx0dGV4dC1hbGlnbjogY2VudGVyO1xuXHRiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1jb2xvci1iYXNlLTEwKTtcblx0dXNlci1zZWxlY3Q6IG5vbmU7XG5cdGN1cnNvcjogZGVmYXVsdDtcbn1cblxuLyogQ29tbWVudCBTZWN0aW9uICovXG5cbi5jaGVzcy1zdHVkeSAuZWRpdG9yLWlucHV0IHtcblx0bWluLWhlaWdodDogMTUwcHg7XG5cdHJlc2l6ZTogbm9uZTtcblx0Zm9udC1zaXplOiAxNXB4O1xuXHRjYXJldC1jb2xvcjogcmdiKDUsIDUsIDUpO1xuXHRwb3NpdGlvbjogcmVsYXRpdmU7XG5cdHRhYi1zaXplOiAxO1xuXHRvdXRsaW5lOiAwO1xuXHRwYWRkaW5nOiAxNXB4IDEwcHg7XG5cdGNhcmV0LWNvbG9yOiAjNDQ0O1xufVxuXG4uY2hlc3Mtc3R1ZHkgLkNvbW1lbnRTZWN0aW9uIHtcblx0aGVpZ2h0OiAyNTBweDtcblx0cGFkZGluZy10b3A6IDVweDtcblx0b3ZlcmZsb3c6IHNjcm9sbDtcbn1cblxuLmNoZXNzLXN0dWR5LlByb3NlTWlycm9yLFxuLmNoZXNzLXN0dWR5LkNvbW1lbnRTZWN0aW9uID4gZGl2IHtcblx0d2lkdGg6IDEwMCU7XG5cdGhlaWdodDogMTAwJTtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBQTtBQUNDO0FBQUE7OztBQ0REO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBRUE7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQUE7QUFBQTtBQUdFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFFRTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQUE7OztBQzNLRjtBQUNFO0FBQ0E7QUFBQTtBQUlGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQVFGO0FBQ0U7QUFBQTtBQUlGO0FBQUE7QUFBQTtBQUFBO0FBSUU7QUFBQTtBQUdGO0FBQUE7QUFBQTtBQUFBO0FBSUU7QUFBQTs7O0FDdkRGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTtBQUVGO0FBQ0U7QUFBQTs7O0FDakNGO0FBQ0M7QUFBQTtBQUdEO0FBQ0M7QUFDQTtBQUFBO0FBR0Q7QUFDQztBQUFBO0FBR0Q7QUFDQztBQUNBO0FBQUE7QUFHRDtBQUNDO0FBQ0E7QUFDQTtBQUFBO0FBR0Q7QUFDQztBQUNBO0FBQUE7QUFHRDtBQUNDO0FBQUE7QUFHRDtBQUNDO0FBQ0E7QUFBQTtBQUdEO0FBQ0M7QUFBQTtBQUdEO0FBQUE7QUFBQTtBQUdDO0FBQUE7QUFFRDtBQUNDO0FBQUE7QUFHRDtBQUFBO0FBQUE7QUFBQTtBQUlDO0FBQUE7QUFHRDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQU9DO0FBQUE7QUFJRDtBQUNDO0FBQ0E7QUFBQTtBQUdEO0FBQ0M7QUFDQTtBQUFBO0FBR0Q7QUFDQztBQUNBO0FBQUE7QUFJRDtBQUNDO0FBQUE7QUFHRDtBQUNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Q7QUFDQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdEO0FBQ0M7QUFDQTtBQUFBO0FBR0Q7QUFDQztBQUtBO0FBQ0E7QUFBQTtBQUdEO0FBQ0M7QUFLQTtBQUNBO0FBQUE7QUFHRDtBQUNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUtEO0FBQ0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRDtBQUNDO0FBQ0E7QUFDQTtBQUFBO0FBR0Q7QUFBQTtBQUVDO0FBQ0E7QUFBQTsiLAogICJuYW1lcyI6IFtdCn0K */ diff --git a/versions.json b/versions.json index 26382a1..189c17e 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,4 @@ { - "1.0.0": "0.15.0" + "1.0.0": "0.15.0", + "1.0.1": "0.15.0" }