diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5c42b..8653ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## version 2.6.1 + +- 新增题目自定义分类 + ## version 2.5.1 - 今日提交的分类,方便设置下次回顾时间 diff --git a/README.md b/README.md index cecb408..4631d6d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ - [新增搬砖功能(重复练习?)](#搬砖功能的说明) - [状态栏增加简易计时器](#状态栏增加简易计时器) - [新增一个 remark 功能](#新增在工作目录存放数据) +- [新增题目自定义分类](#新增在工作目录存放数据) # 关于本项目 @@ -105,6 +106,8 @@ > > > remark 备注数据 > > > > > > > qid 备注 remark 数据 + > > + > > > group.json ### bricks.json 存放格式 @@ -121,6 +124,24 @@ ``` +### group.json 存放格式 + +``` +{ + "version": 1, + "all_group": [ + { + "name": "www", // 分类名称 + "time": 1669791273308, // 分类编号 + "qid_list": [ // 该分类的题目qid + "1000229", + "1000231" + ] + } + ] +} +``` + ## 运行条件 - [VS Code 1.57.0+](https://code.visualstudio.com/) diff --git a/package.json b/package.json index 2ba01b9..677ed1c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-leetcode-problem-rating", "displayName": "LeetCode", "description": "LeetCode 官方插件增强, 代码开源, 增加 LeetCode 题目难度分, 给个star吧, 球球了", - "version": "2.5.1", + "version": "2.6.1", "author": "ccagml", "publisher": "ccagml", "license": "MIT", @@ -69,28 +69,28 @@ }, { "command": "lcpr.refreshExplorer", - "title": "Refresh", + "title": "刷新", "category": "LeetCode", "icon": "$(refresh)" }, { "command": "lcpr.pickOne", - "title": "Pick One", + "title": "手气一下", "category": "LeetCode" }, { "command": "lcpr.deleteAllCache", - "title": "deleteAllCache", + "title": "删除题目缓存", "category": "LeetCode" }, { "command": "lcpr.showProblem", - "title": "Show Problem", + "title": "做题", "category": "LeetCode" }, { "command": "lcpr.previewProblem", - "title": "Preview Problem", + "title": "预览题目", "category": "LeetCode" }, { @@ -222,6 +222,26 @@ { "command": "lcpr.remarkDispose", "title": "Remove All Notes" + }, + { + "command": "lcpr.newBrickGroup", + "title": "新建一个自定义分类", + "icon": "$(file-directory-create)" + }, + { + "command": "lcpr.addQidToGroup", + "title": "添加题目到自定义分类", + "icon": "$(add)" + }, + { + "command": "lcpr.removeBrickGroup", + "title": "移除这个自定义分类", + "icon": "$(remove)" + }, + { + "command": "lcpr.removeQidFromGroup", + "title": "从分类中移除这个题目", + "icon": "$(remove)" } ], "viewsContainers": { @@ -333,6 +353,11 @@ "command": "lcpr.deleteAllCache", "when": "view == QuestionExplorer", "group": "overflow@2" + }, + { + "command": "lcpr.newBrickGroup", + "when": "view == BricksExplorer", + "group": "navigation@1" } ], "view/item/context": [ @@ -356,6 +381,11 @@ "when": "view == QuestionExplorer && viewItem =~ /problem*/", "group": "leetcode@4" }, + { + "command": "lcpr.addQidToGroup", + "when": "view == QuestionExplorer && viewItem =~ /problem*/", + "group": "leetcode@5" + }, { "command": "lcpr.addFavorite", "when": "view == QuestionExplorer && viewItem == problem", @@ -368,23 +398,33 @@ }, { "command": "lcpr.previewProblem", - "when": "view == BricksExplorer && viewItem == bricks", + "when": "view == BricksExplorer && viewItem =~ /nodebricks*/", "group": "leetcode@1" }, { "command": "lcpr.showProblem", - "when": "view == BricksExplorer && viewItem == bricks", + "when": "view == BricksExplorer && viewItem =~ /nodebricks*/", "group": "leetcode@2" }, { "command": "lcpr.showSolution", - "when": "view == BricksExplorer && viewItem == bricks", + "when": "view == BricksExplorer && viewItem =~ /nodebricks*/", "group": "leetcode@3" }, { "submenu": "lcpr.setBricksType_sub1", - "when": "view == BricksExplorer && viewItem == bricks", + "when": "view == BricksExplorer && viewItem =~ /nodebricks*/ ", "group": "leetcode@4" + }, + { + "command": "lcpr.removeQidFromGroup", + "when": "view == BricksExplorer && viewItem == nodebricksdiy", + "group": "leetcode@5" + }, + { + "command": "lcpr.removeBrickGroup", + "when": "view == BricksExplorer && viewItem == bricksdiy", + "group": "leetcode@1" } ], "commandPalette": [ diff --git a/src/controller/BricksViewController.ts b/src/controller/BricksViewController.ts index 41d1488..ffdda32 100644 --- a/src/controller/BricksViewController.ts +++ b/src/controller/BricksViewController.ts @@ -7,12 +7,14 @@ * Copyright (c) 2022 ccagml . All rights reserved. */ -import { Disposable, TreeItemCollapsibleState } from "vscode"; +import { Disposable, TreeItemCollapsibleState, window } from "vscode"; import { bricksDao } from "../dao/bricksDao"; -import { BricksNormalId, defaultProblem } from "../model/Model"; +import { groupDao } from "../dao/groupDao"; +import { BricksNormalId, defaultProblem, IQuickItemEx } from "../model/Model"; import { BricksNode } from "../model/NodeModel"; import { bricksDataService } from "../service/BricksDataService"; +import { eventService } from "../service/EventService"; import { treeViewController } from "./TreeViewController"; // 视图控制器 @@ -46,6 +48,29 @@ class BricksViewController implements Disposable { return baseNode; } + public async getDiyNode(element: BricksNode) { + let time = element.groupTime; + if (time == undefined) { + return []; + } + let all_qid: string[] = await groupDao.getQidByTime(time); + const baseNode: BricksNode[] = []; + all_qid.forEach((qid) => { + let node = treeViewController.getNodeByQid(qid); + if (node) { + let new_obj = new BricksNode( + Object.assign({}, node.data, {}), + true, + node.user_score, + TreeItemCollapsibleState.None, + time + ); + baseNode.push(new_obj); + } + }); + return baseNode; + } + public async getRootNodes(): Promise { let all_qid: string[] = await bricksDao.getTodayBricks(); let all_submit_qid: string[] = await bricksDao.getTodayBricksSubmit(); @@ -53,7 +78,7 @@ class BricksViewController implements Disposable { let has_qid = all_qid.length > 0; let has_submit = all_submit_qid.length > 0; const baseNode: BricksNode[] = []; - + // 监工 baseNode.push( new BricksNode( Object.assign({}, defaultProblem, { @@ -66,6 +91,7 @@ class BricksViewController implements Disposable { ) ); + // 今日提交 if (has_submit) { let temp_score = 0; all_submit_qid.forEach((qid) => { @@ -89,6 +115,22 @@ class BricksViewController implements Disposable { ) ); } + // 分类 + let all_group = await groupDao.getAllGroup(); + all_group.forEach((element) => { + baseNode.push( + new BricksNode( + Object.assign({}, defaultProblem, { + id: BricksNormalId.DIY, + name: element.name, + }), + false, + 0, + TreeItemCollapsibleState.Collapsed, + element.time + ) + ); + }); return baseNode; } @@ -97,6 +139,60 @@ class BricksViewController implements Disposable { await bricksDataService.setBricksType(node, type); } public dispose(): void {} + + public async newBrickGroup() { + let name = await window.showInputBox({ + title: "创建新的分类", + validateInput: (s: string): string | undefined => (s && s.trim() ? undefined : "分类名称不能为空"), + placeHolder: "输入新分类名称", + ignoreFocusOut: true, + }); + if (name && name.trim()) { + bricksDataService.newBrickGroup(name); + eventService.emit("groupUpdate"); + } + } + + public async removeBrickGroup(node) { + let time = node.groupTime; + bricksDataService.removeBrickGroup(time); + eventService.emit("groupUpdate"); + } + + public async addQidToGroup(node: BricksNode) { + const picks: Array> = []; + + let all_group = await bricksDataService.getAllGroup(); + all_group.forEach((element) => { + picks.push({ + label: element.name, + detail: "", + value: element.time, + }); + }); + + const choice: Array> | undefined = await window.showQuickPick(picks, { + title: "正在添加题目到分类中", + matchOnDescription: false, + matchOnDetail: false, + placeHolder: "选择要添加的分类", + canPickMany: true, + }); + if (!choice) { + return; + } + let time_list: Array = []; + choice.forEach((element) => { + time_list.push(element.value); + }); + groupDao.addQidToTimeList(node.qid, time_list); + eventService.emit("groupUpdate"); + } + + public async removeQidFromGroup(node) { + groupDao.removeQidFromTime(node.qid, node.groupTime); + eventService.emit("groupUpdate"); + } } export const bricksViewController: BricksViewController = new BricksViewController(); diff --git a/src/controller/TreeViewController.ts b/src/controller/TreeViewController.ts index bc817e5..99130b4 100644 --- a/src/controller/TreeViewController.ts +++ b/src/controller/TreeViewController.ts @@ -1388,7 +1388,8 @@ class TreeViewController implements Disposable { } public getNodeByQid(qid: string): NodeModel | undefined { - return this.getNodeById(this.qidToFid.get(qid) || ""); + let new_qid = qid.toString(); + return this.getNodeById(this.qidToFid.get(new_qid) || ""); } public getQidByFid(id: string) { diff --git a/src/dao/bricksDao.ts b/src/dao/bricksDao.ts index d4461f6..58db8aa 100644 --- a/src/dao/bricksDao.ts +++ b/src/dao/bricksDao.ts @@ -1,11 +1,11 @@ -// > workspace/ 工作目录 -// > -// > > .lcpr_data/ 存数据 -// > > -// > > > remake/ 备注 -// > > > -// > > > > 题目内部编号.json 根据 qid 备注的信息 -// > > +/* + * Filename: /home/cc/vscode-leetcode-problem-rating/src/dao/bricksDao.ts + * Path: /home/cc/vscode-leetcode-problem-rating + * Created Date: Wednesday, November 23rd 2022, 4:36:38 pm + * Author: ccagml + * + * Copyright (c) 2022 ccagml . All rights reserved. + */ import { fetchProblemLanguage, selectWorkspaceFolder } from "../utils/ConfigUtils"; import { useWsl, toWinPath, getDayStart, getDayNow } from "../utils/SystemUtils"; diff --git a/src/dao/groupDao.ts b/src/dao/groupDao.ts new file mode 100644 index 0000000..913c457 --- /dev/null +++ b/src/dao/groupDao.ts @@ -0,0 +1,139 @@ +/* + * Filename: /home/cc/vscode-leetcode-problem-rating/src/dao/groupDao.ts + * Path: /home/cc/vscode-leetcode-problem-rating + * Created Date: Wednesday, November 30th 2022, 9:47:36 am + * Author: ccagml + * + * Copyright (c) 2022 ccagml . All rights reserved. + */ + +import { fetchProblemLanguage, selectWorkspaceFolder } from "../utils/ConfigUtils"; +import { useWsl, toWinPath, getDayNowM } from "../utils/SystemUtils"; +import * as path from "path"; +import * as fse from "fs-extra"; + +// let group_json = { +// version: 1, +// all_group: [{ name: "aaa", time: "qqq", qid_list: [] }, {}, {}], +// }; + +class GroupDao { + version = 1; + public async group_data_path() { + const language: string | undefined = await fetchProblemLanguage(); + if (!language) { + return; + } + const workspaceFolder: string = await selectWorkspaceFolder(false); + if (!workspaceFolder) { + return; + } + let lcpr_data_path: string = path.join(workspaceFolder, ".lcpr_data"); + await fse.ensureDir(lcpr_data_path); + + let finalPath = path.join(lcpr_data_path, "group.json"); + finalPath = useWsl() ? await toWinPath(finalPath) : finalPath; + return finalPath; + } + public async init() { + let lcpr_data_path = await this.group_data_path(); + if (!lcpr_data_path) { + return; + } + if (!(await fse.pathExists(lcpr_data_path))) { + await fse.createFile(lcpr_data_path); + await fse.writeFile(lcpr_data_path, JSON.stringify({ version: this.version })); + } + } + + private async _write_data(data: object) { + let lcpr_data_path = await this.group_data_path(); + if (!lcpr_data_path) { + return; + } + return await fse.writeFile(lcpr_data_path, JSON.stringify(data)); + } + + private async _read_data() { + let lcpr_data_path = await this.group_data_path(); + if (!lcpr_data_path) { + return; + } + let temp_data = await fse.readFile(lcpr_data_path, "utf8"); + return JSON.parse(temp_data) || {}; + } + + // 获取所有分组 + public async getAllGroup() { + let old_data = await this._read_data(); + let all_group = old_data.all_group || []; + return all_group; + } + + // 新的分组 + public async newBrickGroup(name: string) { + let old_data = await this._read_data(); + let all_group = old_data.all_group || []; + let newGroup = {}; + newGroup["name"] = name; + newGroup["time"] = getDayNowM(); + newGroup["qid_list"] = []; + all_group.push(newGroup); + old_data.all_group = all_group; + this._write_data(old_data); + } + + public async removeBrickGroupByTime(time) { + let old_data = await this._read_data(); + let all_group = old_data.all_group || []; + old_data.all_group = all_group.filter((gob) => gob.time !== time); + this._write_data(old_data); + } + + public async getQidByTime(time) { + let old_data = await this._read_data(); + let all_group = old_data.all_group || []; + let result = []; + all_group.forEach((element) => { + if (element.time == time) { + result = element.qid_list || []; + return; + } + }); + return result; + } + + public async addQidToTimeList(qid, time_list) { + let new_qid = qid.toString(); + let time_map: Map = new Map(); + time_list.forEach((element) => { + time_map.set(element, 1); + }); + + let old_data = await this._read_data(); + let all_group = old_data.all_group || []; + all_group.forEach((element) => { + if (time_map.get(element.time)) { + element.qid_list = element.qid_list.filter((eqid) => eqid !== new_qid); + element.qid_list.push(new_qid); + } + }); + old_data.all_group = all_group; + this._write_data(old_data); + } + + public async removeQidFromTime(qid, time) { + let new_qid = qid.toString(); + let old_data = await this._read_data(); + let all_group = old_data.all_group || []; + all_group.forEach((element) => { + if (element.time == time) { + element.qid_list = element.qid_list.filter((eqid) => eqid !== new_qid); + } + }); + old_data.all_group = all_group; + this._write_data(old_data); + } +} + +export const groupDao: GroupDao = new GroupDao(); diff --git a/src/extension.ts b/src/extension.ts index 0d543e3..33499fb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -105,6 +105,10 @@ export async function activate(context: ExtensionContext): Promise { commands.registerCommand("lcpr.setBricksType6", (node: NodeModel) => bricksViewController.setBricksType(node, BricksType.TYPE_6) ), + commands.registerCommand("lcpr.newBrickGroup", () => bricksViewController.newBrickGroup()), + commands.registerCommand("lcpr.addQidToGroup", (a) => bricksViewController.addQidToGroup(a)), + commands.registerCommand("lcpr.removeBrickGroup", (a) => bricksViewController.removeBrickGroup(a)), + commands.registerCommand("lcpr.removeQidFromGroup", (node) => bricksViewController.removeQidFromGroup(node)), commands.registerCommand("lcpr.remarkCreateNote", (reply: CommentReply) => { remarkController.remarkCreateNote(reply); diff --git a/src/model/Model.ts b/src/model/Model.ts index 8f7738d..9462606 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -156,6 +156,7 @@ export enum BricksNormalId { No = "bricksNo", // 没活 NoDesc = "工头让你去上面那个工地,过几天再回来", Today = "bricksToday", + DIY = "bricksDiy", } export enum BricksType { diff --git a/src/model/NodeModel.ts b/src/model/NodeModel.ts index 8f122fc..c85a1c2 100644 --- a/src/model/NodeModel.ts +++ b/src/model/NodeModel.ts @@ -142,9 +142,11 @@ export class NodeModel { export class BricksNode extends NodeModel { public collapsibleState?; - constructor(data: IProblem, ipn: boolean = true, userscore: number = 0, collapsibleState = 0) { + public groupTime?; + constructor(data: IProblem, ipn: boolean = true, userscore: number = 0, collapsibleState = 0, groupTime?: number) { super(data, ipn, userscore); this.isProblemNode = ipn; this.collapsibleState = collapsibleState; + this.groupTime = groupTime; } } diff --git a/src/service/BricksDataService.ts b/src/service/BricksDataService.ts index 0524089..6f3d19b 100644 --- a/src/service/BricksDataService.ts +++ b/src/service/BricksDataService.ts @@ -13,6 +13,7 @@ import { bricksViewController } from "../controller/BricksViewController"; import { BricksNode } from "../model/NodeModel"; import { statusBarService } from "./StatusBarService"; import { bricksDao } from "../dao/bricksDao"; +import { groupDao } from "../dao/groupDao"; export class BricksDataService implements TreeDataProvider { private onDidChangeTreeDataEvent: EventEmitter = new EventEmitter< @@ -27,6 +28,7 @@ export class BricksDataService implements TreeDataProvider { public async initialize() { await bricksDao.init(); + await groupDao.init(); } // 节点的内容 @@ -43,7 +45,7 @@ export class BricksDataService implements TreeDataProvider { } let contextValue: string; if (element.isProblem) { - contextValue = "bricks"; + contextValue = element.groupTime ? "nodebricksdiy" : "nodebricks"; } else { contextValue = element.id.toLowerCase(); } @@ -54,8 +56,6 @@ export class BricksDataService implements TreeDataProvider { : element.name, tooltip: this.getSubCategoryTooltip(element), collapsibleState: element.collapsibleState || TreeItemCollapsibleState.None, - // ? vscode.TreeItemCollapsibleState.None // 问题没有子节点 - // : vscode.TreeItemCollapsibleState.Collapsed, // 折叠 iconPath: this.parseIconPathFromProblemState(element), command: element.isProblem ? element.previewCommand : undefined, resourceUri: element.uri, @@ -89,6 +89,9 @@ export class BricksDataService implements TreeDataProvider { case BricksNormalId.Have: return await bricksViewController.getHaveNodes(); break; + case BricksNormalId.DIY: + return await bricksViewController.getDiyNode(element); + break; default: return []; break; @@ -124,6 +127,19 @@ export class BricksDataService implements TreeDataProvider { } return ""; } + + // 创建一个新的分类 + public async newBrickGroup(name) { + await groupDao.newBrickGroup(name); + } + // 删除一个分类 + public async removeBrickGroup(time) { + await groupDao.removeBrickGroupByTime(time); + } + + public async getAllGroup() { + return await groupDao.getAllGroup(); + } } export const bricksDataService: BricksDataService = new BricksDataService(); diff --git a/src/service/EventService.ts b/src/service/EventService.ts index 0565c03..e1975f0 100644 --- a/src/service/EventService.ts +++ b/src/service/EventService.ts @@ -52,6 +52,10 @@ class EventService extends EventEmitter { this.on("showProblemFinish", (node: IProblem) => { statusBarTimeService.showProblemFinish(node); }); + + this.on("groupUpdate", () => { + bricksDataService.refresh(); + }); } }