-
Notifications
You must be signed in to change notification settings - Fork 684
Enhanced Tree view #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
(from direct import of leetcode-cli company plugin)
Add unknown label to companies and tags
|
@Vigilans Thank you for contribution. I will look it later. Also noticed that there are some lint errors according to the CI output, would you mind to fix them first? |
|
The lint errors have been fixed. Note: A default object is used to define // tslint:disable-next-line:typedef
export const IProblemDefault = {
favorite: false,
locked: false,
state: ProblemState.Unknown,
id: "",
name: "",
difficulty: "",
passRate: "",
companies: [] as string[],
tags: [] as string[],
};
export type IProblem = typeof IProblemDefault;The default object is then used to create a new new LeetCodeNode(Object.assign({}, list.IProblemDefault, {
id: "Root", // parent node name
name: "Tag", // current node name
}), false), |
|
Hi @Vigilans, Is there any reason to add accepted/not accepted categories into the explorer? |
|
The Not-AC category shows problems which are submitted but not accepted. If you think them not necessary, I will try to revert this commit. |
|
Hmm, I think it's a little bit redundant somehow. I better approach might be: having an extension setting that specifies if the explorer will only show unresolved problems or all the problems, then this setting will apply to all the categories. To retrieving all accepted codes, this can be another action in the explorer context menu, which is another story. |
This reverts commit 7e00062.
|
I see. Btw, there are some other changes I plan to contribute, maybe I should open another issue to query you for your opinions_(눈_ 눈」∠)_ |
|
@taoweicn I will work on this feature later. |
👍 very thankful. |
|
@Vigilans Contributions are welcome! 👍 Yes Let's open issues and discuss the implementation there first before we create a PR. 😄 |
|
@Vigilans Could you please send another PR about hiding solved problems? A PR contains several issue fixes will make it too large to review |
|
@jdneo The |
src/commands/list.ts
Outdated
| tags: [] as string[], | ||
| }; | ||
|
|
||
| export type IProblem = typeof IProblemDefault; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to put the IProblem definition and IProblemDefault to other places instead of in this file.
Let's put them into shared.ts, I'll refactor this part later.
src/leetCodeExecutor.ts
Outdated
| // Licensed under the MIT license. | ||
|
|
||
| import * as cp from "child_process"; | ||
| import * as fs from "fs"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use fs-extra
src/leetCodeExecutor.ts
Outdated
| public async getCompaniesAndTags(): Promise<{ companies: { [key: string]: string[] }, tags: { [key: string]: string[] } }> { | ||
| // preprocess the plugin source | ||
| const componiesTagsPath: string = path.join(await leetCodeExecutor.getLeetCodeRootPath(), "lib", "plugins", "company.js"); | ||
| const componiesTagsSrc: string = (await fs.readFileSync(componiesTagsPath, "utf8")).replace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use readFile from fs-extra.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something went wrong with my usage. fs-extra is correct.
src/leetCodeExecutor.ts
Outdated
| "module.exports = { COMPONIES, TAGS }", | ||
| ); | ||
| // require plugin from modified string | ||
| const requireFromString: (src: string, path: string) => any = require("require-from-string"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we import the module?
For example, npm i --save-dev @types/require-from-string
Then import it instead of using require
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the definitelyTyped indeed provides type for require-from-string.
import * as requireFromString from "require-from-string"; successfully worked.
src/leetCodeExplorer.ts
Outdated
| import { leetCodeManager } from "./leetCodeManager"; | ||
| import { ProblemState } from "./shared"; | ||
|
|
||
| export type Category = "Difficulty" | "Tag" | "Company"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use enum instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LeetCodeTreeDataProvider.treeData needs the type semantic from Category to preserve the type hint, whereas enum just provides the value semantic.
Without the string union type, this line won't get proper type hint:
const problems: list.IProblem[] | undefined = this.treeData[parent].get(subCategory);this.treeData[parent] will return any type.
If we change Category to enum type, then the above line should be rewritten to:
this.treeData[parent.toString() as "Difficulty" | "Tag" | "Company"]which falls back to where it starts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tried this:
export enum Category {
Difficulty = "Difficulty",
Tag = "Tag",
Company = "Company",
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After trying, Category.Tag.toString() only returns type string.
There is no one-line way to get the type hint here, reference:
https://stackoverflow.com/questions/52370544/parse-string-as-typescript-enum/
https://stackoverflow.com/questions/52393730/typescript-string-literal-union-type-from-enum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to call .toString() here.
Whenever you want to use the Category value as a string, just using Category.xxxx.
By doing this, you will get the value as a string meanwhile keep the typedef.
I tried using enum here and everything works fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make the Category enum type:
export enum Category {
Difficulty = "Difficulty",
Tag = "Tag",
Company = "Company",
}Then rewrite line 147 this way:
const categories: Map<Category, string[]> = new Map<Category, string[]>([
[Category.Difficulty, [problem.difficulty]],
[Category.Tag, problem.tags],
[Category.Company, problem.companies],
]);Follows the iteration:
for (const [parent, children] of categories) {
for (let subCategory of children) {Here, typeof parent is Category:

Continues the line 156:
const problems: list.IProblem[] | undefined = this.treeData[parent].get(subCategory);Since typeof parent is Category and this.treeData's key type is string, get's type could not be deduced from the usage, and there's no way to use Category.xxxx here:

Meanwhile, we couldn't change this.treeData type to Map<Category, Map<string, list.IProblem[]>> since there is a field Favorite which doesn't processed as Category and its value type is list.IProblem[].
If you insist to use enum here, a workaround could be:
private treeData: {
[Category.Difficulty]: Map<string, list.IProblem[]>,
[Category.Tag]: Map<string, list.IProblem[]>,
[Category.Company]: Map<string, list.IProblem[]>,
Favorite: list.IProblem[],
};Now get's type could be deduced properly:

Yet I think it complicates what could have been an easy and elegant solution in Typescript.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to write more hard-coded strings if using string union here while using enum can get rid of this. That's the reason I prefer using enum here: make the code easier to maintain in the future.
There are many ways to get rid of the special field Favorite. One simple way to do a simple check before getting the map. TS will help us to infer the type during compilation.
src/leetCodeExplorer.ts
Outdated
| Company: new Map(), | ||
| Favorite: [], | ||
| }; | ||
| for (const problem of await list.listProblems()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking that we can filter the accepted problems in the method list.listProblems(). If hideSolved === true then only return unaccepted problems.
By doing this, we decouple the this.leetCodeConfig with the explorer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, Favorite group should show all starred problems no matter whether it's accepted or not.
Maybe it could be checked this way in list.listProblems():
!problem.favorite && problem.state === ProblemState.AC && hideSolved -> continue
The flaw is that the favorite checking logic is duplicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, ok, just keep it there first.
src/leetCodeExplorer.ts
Outdated
| continue; | ||
| } | ||
| // Add problems according to category | ||
| const categories: Array<[Category, string[]]> = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Map here?
use index to represent the parent-children relationship is confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use Map here this way:
const categories: Map<Category, string[]> = new Map<Category, string[]>([
["Difficulty", [problem.difficulty]],
["Tag", problem.tags],
["Company", problem.companies],
]);Btw, a better way would be to use Object.entries here, but it is not supported by es6, which is specified by tsconfig.json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use Map here.
src/leetCodeExplorer.ts
Outdated
| for (const [parent, children] of categories) { | ||
| for (let subCategory of children) { | ||
| // map 'first-second' to 'First Second' | ||
| subCategory = subCategory.split("-").map((c: string) => c[0].toUpperCase() + c.slice(1)).join(" "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first-second is not that bad.
Cuz current logic could handle a-b but not a-b-c
Just leave first-second as it is for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does a-b-c refer to? The code here could transform breadth-first-search to Breadth First Search.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, it's fine.
src/leetCodeExplorer.ts
Outdated
|
|
||
| const idPrefix: number = Date.now(); | ||
| return { | ||
| return { // element.id -> parent node name, element.name -> node name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we save parent node name into element.id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be some field to store the parent node name when it is not problem node...since in previous commits you simply set node.id and node.name the same value, I decided to reuse the id field to represent some other value.
The reasons for the necessity of parent node name are:
- We should be able to retrieve data from the
LeetCodeTreeDataProvider.treeData. - One problem will now be added as
LeetCodeNodeat least 3 times(inDifficulty,TagandCompany), we should be able to distinguish them, hence the following naming rule:
id: `${idPrefix}.${element.id}.${element.name}`,(idPrefix = Date.now() just returns same value throughout initialization process)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could add a filed in the LeetCodeNode called 'parentName' to store the parent node info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will review later
|
Merged with some refactoring. @Vigilans Thank you for your contribution. The idea is very inspiring! |
Introduction
This PR restructures the left side panel's tree view to support following functions:
Favoriteview)Details
leetcode-clicompany plugin.Unknown.leetcode.hideSolvedsetting, which defaults to false.Demonstration
Difficulty, tag, company or favorite:

Problems by tag:

Problems by company:

Favorite problems:
