diff --git a/src/workbench/parts/workspace/workspace.ts b/src/workbench/parts/workspace/workspace.ts index 67ba5c062..0ef076087 100644 --- a/src/workbench/parts/workspace/workspace.ts +++ b/src/workbench/parts/workspace/workspace.ts @@ -6,6 +6,9 @@ import { IInstantiationService } from "src/platform/instantiation/common/instant import { IEditorService } from "src/workbench/parts/workspace/editor/editorService"; import { OPERATING_SYSTEM, Platform } from 'src/base/common/platform'; import { Orientation } from 'src/base/browser/basic/dom'; +import { Priority } from 'src/base/common/event'; +import { DashboardView } from 'src/workbench/services/dashboard/dashboardView'; +import { Type1SubView } from 'src/workbench/services/dashboard/type1SubView'; export const IWorkspaceService = createService('workspace-service'); @@ -52,8 +55,22 @@ export class WorkspaceComponent extends Component implements IWorkspaceService { }); } + // layout.push({ + // component: this.editorService, + // initSize: null, + // maximumSize: null, + // minimumSize: null, + // }); + const dashboardView = this.instantiationService.createInstance(DashboardView, { + subViews: [ + { id: 'type1', title: 'Hello,' }, + { id: 'type2', title: 'Pinned', content: ["Note 1", "Note 2"] }, + { id: 'type2', title: 'Recent', content: ["Item 1", "Item 2"] }, + ], + }); + layout.push({ - component: this.editorService, + component: dashboardView, initSize: null, maximumSize: null, minimumSize: null, diff --git a/src/workbench/services/dashboard/dashboardSlider.ts b/src/workbench/services/dashboard/dashboardSlider.ts new file mode 100644 index 000000000..29800fcd2 --- /dev/null +++ b/src/workbench/services/dashboard/dashboardSlider.ts @@ -0,0 +1,37 @@ +import { IInstantiationService } from "src/platform/instantiation/common/instantiation"; +import { Component } from "src/workbench/services/component/component"; + +export class DashboardSlider extends Component { + + private items: HTMLElement[]; + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + items: HTMLElement[], + ) { + super('dashboard-slider', null, instantiationService); + this.items = items; + } + + public createView(): HTMLElement { + const sliderContainer = document.createElement('div'); + sliderContainer.classList.add('slider'); + + this.items.forEach(item => { + const itemContainer = document.createElement('div'); + itemContainer.classList.add('slider-item'); + itemContainer.appendChild(item); + sliderContainer.appendChild(itemContainer); + }); + return sliderContainer; + } + + protected override _createContent(): void { + const viewElement = this.createView(); + this.element.appendChild(viewElement); + } + + protected override _registerListeners(): void { + + } +} diff --git a/src/workbench/services/dashboard/dashboardSubView.ts b/src/workbench/services/dashboard/dashboardSubView.ts new file mode 100644 index 000000000..0ee6facae --- /dev/null +++ b/src/workbench/services/dashboard/dashboardSubView.ts @@ -0,0 +1,51 @@ +import { Priority } from "src/base/common/event"; + +export interface IDashboardSubView { + /** + * The unique identifier of the subview. + */ + readonly id: string; + + /** + * The priority of the subview when determining its display order. + * Subviews with higher priority are displayed before others. + * @default Priority.Low + */ + priority?: Priority; + + /** + * Renders the subview and appends it to the provided parent element. + * @param parentElement - The DOM element to append the rendered subview to. + * @returns The rendered subview element. + */ + render(parentElement: HTMLElement): HTMLElement; + + /** + * Cleans up resources and removes the subview when it is no longer needed. + */ + dispose(): void; +} + +export interface IDashboardSubViewOpts { + /** + * The unique identifier of the dashboard view. + */ + id: string; + + /** + * When adding/removing subview, the view with higher priority will show at top + * first. + * @default Priority.Low + */ + priority?: Priority; + + /** + * The title of the subview, typically displayed as a header. + */ + title?: string; + + /** + * The content of the subview, which can include an array of strings. + */ + content?: string[]; +} diff --git a/src/workbench/services/dashboard/dashboardView.ts b/src/workbench/services/dashboard/dashboardView.ts new file mode 100644 index 000000000..fb03f5ba0 --- /dev/null +++ b/src/workbench/services/dashboard/dashboardView.ts @@ -0,0 +1,57 @@ +import "src/workbench/services/dashboard/media/dashboard.scss"; +import { IInstantiationService } from "src/platform/instantiation/common/instantiation"; +import { Component } from "src/workbench/services/component/component"; +import { Type1SubView } from "src/workbench/services/dashboard/type1SubView"; +import { Type2SubView } from "src/workbench/services/dashboard/type2SubView"; +import { IDashboardSubView, IDashboardSubViewOpts } from "src/workbench/services/dashboard/dashboardSubView"; +import { Priority } from "src/base/common/event"; +import { panic } from "src/base/common/utilities/panic"; + +export interface IDashboardViewOpts { + subViews?: IDashboardSubViewOpts[]; +} + +export class DashboardView extends Component { + + constructor( + private opts: IDashboardViewOpts, + @IInstantiationService instantiationService: IInstantiationService + ) { + super("dashboard-view", null, instantiationService); + } + + public createView(): HTMLElement { + const container = document.createElement("div"); + container.classList.add("dashboard-view"); + + this.createSubViews() + .sort((a, b) => (a.priority || Priority.Low) - (b.priority || Priority.Low)) + .forEach((subView) => { + container.appendChild(subView.render(container)); + this.__register(subView); + }); + + return container; + } + + protected override _createContent(): void { + const viewElement = this.createView(); + this.element.appendChild(viewElement); + } + + protected override _registerListeners(): void {} + + private createSubViews(): IDashboardSubView[] { + const subViewOptions = this.opts.subViews || []; + return subViewOptions.map((opts) => { + switch (opts.id) { + case 'type1': + return new Type1SubView(opts); + case 'type2': + return new Type2SubView(this.instantiationService, opts); + default: + panic(`Unsupported subview type: ${opts.id}`); + } + }); + } +} diff --git a/src/workbench/services/dashboard/media/dashboard.scss b/src/workbench/services/dashboard/media/dashboard.scss new file mode 100644 index 000000000..ce78bf5be --- /dev/null +++ b/src/workbench/services/dashboard/media/dashboard.scss @@ -0,0 +1,126 @@ +.dashboard-view { + display: flex; + flex-direction: column; + padding: 100px 65px; + height: 100vh; + overflow-y: auto; + + // Targeting type1-subview for the welcome section + .type1-subview { + display: flex; + flex-direction: column; + align-items: flex-start; + + h2 { + font-size: 28px; + font-weight: bolder; + color: #211F27; + margin-bottom: 24px; + } + + .new-note-btn { + background-color: #2aa882; + color: #ffffff; + border: none; + border-radius: 2px; + padding: 8px 10px; + margin-bottom: 8px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s ease; + + &:hover { + background-color: #249372; + } + } + } + + // Updated for all subviews + .type2-subview { + padding: 1rem 0; + + .section-header { + display: flex; + align-items: center; + margin-bottom: 16px; + + h2 { + font-size: 1.2rem; + font-weight: 900; + color: #211F27; + margin: 0; + } + + .sort-dropdown { + display: flex; + align-items: center; + font-size: 12px; + color: #5A564D; + cursor: pointer; + background-color: #F0F8F5; + border: 2px; + margin-left: 8px; + padding: 4px 8px; + border-radius: 2px; + + .triangle-icon { + width: 4px; + height: 6px; + background-color: #2aa882; + clip-path: polygon(0% 0%, 100% 50%, 0% 100%); + margin-right: 6px; + } + + .dropdown-text { + font-weight: 500; + color: #5A564D; + } + } + } + + .slider { + display: flex; + overflow-x: auto; + gap: 1rem; + padding-bottom: 1rem; // Add padding for smooth scrolling + + scrollbar-color: #D9D9D9 transparent; + + &::-webkit-scrollbar { + height: 3.2px; + } + + &::-webkit-scrollbar-thumb { + background: #D9D9D9; + } + + .slider-item { + flex: 0 0 calc((100% - 3rem) / 5); + background-color: transparent; + border: 1px solid #e6e6e6; + padding: 10px; + text-align: left; + font-size: 10px; + font-weight: 500; + color: #495057; + transition: transform 0.2s ease; + scroll-behavior: smooth; + + &:hover { + cursor: pointer; + } + + .item-title { + font-weight: bold; + color: #211F27; + margin-bottom: 0.5rem; + } + + .item-meta { + font-size: 0.8rem; + color: #5A564D; + } + } + } + } +} diff --git a/src/workbench/services/dashboard/type1SubView.ts b/src/workbench/services/dashboard/type1SubView.ts new file mode 100644 index 000000000..d74ccfbbc --- /dev/null +++ b/src/workbench/services/dashboard/type1SubView.ts @@ -0,0 +1,65 @@ +import { IDashboardSubView, IDashboardSubViewOpts } from "src/workbench/services/dashboard/dashboardSubView"; +import { Disposable } from "src/base/common/dispose"; +import { addDisposableListener } from "src/base/browser/basic/dom"; + +export class Type1SubView extends Disposable implements IDashboardSubView { + + // [fields] + + public id: string = 'type1'; + private _subViewContainer: HTMLElement; + + // [constructor] + + constructor( + private opts: IDashboardSubViewOpts + ) { + super(); + this._subViewContainer = document.createElement("div"); + this._subViewContainer.classList.add("type1-subview"); + this._subViewContainer.setAttribute("data-id", this.opts.id); + } + + // [public methods] + + public render(): HTMLElement { + + this._subViewContainer.innerHTML = ""; + + // Create section header + const header = document.createElement("div"); + header.classList.add("section-header"); + + // Title for the section + const title = document.createElement("h2"); + title.textContent = this.opts.title || "Welcome to the Dashboard"; + header.appendChild(title); + + this._subViewContainer.appendChild(header); + + // Add the New Note button + const newNoteButton = this.__createNewNoteButton(); + this._subViewContainer.appendChild(newNoteButton); + + return this._subViewContainer; + } + + // [protected methods] + + public override dispose(): void { + super.dispose(); + this._subViewContainer.remove(); + } + + // [private methods] + + private __createNewNoteButton(): HTMLElement { + const button = document.createElement("button"); + button.classList.add("new-note-btn"); + button.textContent = "+ New Note"; + this.__register(addDisposableListener(button, "click", () => { + console.log("New Note button clicked"); + })); + return button; + } +} diff --git a/src/workbench/services/dashboard/type2SubView.ts b/src/workbench/services/dashboard/type2SubView.ts new file mode 100644 index 000000000..c00b468ab --- /dev/null +++ b/src/workbench/services/dashboard/type2SubView.ts @@ -0,0 +1,103 @@ +import { IDashboardSubView, IDashboardSubViewOpts } from "src/workbench/services/dashboard/dashboardSubView"; +import { Disposable } from "src/base/common/dispose"; +import { addDisposableListener } from "src/base/browser/basic/dom"; +import { DashboardSlider } from "src/workbench/services/dashboard/dashboardSlider"; +import { IInstantiationService } from "src/platform/instantiation/common/instantiation"; + +export class Type2SubView extends Disposable implements IDashboardSubView { + + // [fields] + + public id: string = 'type2'; + private _subViewContainer: HTMLElement; + private _slider: DashboardSlider; + + // [constructor] + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + private opts: IDashboardSubViewOpts + ) { + super(); + this.opts = opts; + this._subViewContainer = document.createElement("div"); + this._subViewContainer.classList.add("type2-subview"); + this._subViewContainer.setAttribute("data-id", this.opts.id); + this._slider = new DashboardSlider( + instantiationService, + this.__createSliderItems(this.opts.content || []) + ); + } + + // [public methods] + + public render(): HTMLElement { + this._subViewContainer.innerHTML = ""; + + const header = document.createElement("div"); + header.classList.add("section-header"); + + // Title for the section + const title = document.createElement("h2"); + title.textContent = this.opts.title || "Default Title"; + header.appendChild(title); + + // Add dropdown to the header + const sortDropdown = this.__createSortDropdown(); + header.appendChild(sortDropdown); + + this._subViewContainer.appendChild(header); + + // Add slider to the subview container + const sliderElement = this._slider.createView(); + this._subViewContainer.appendChild(sliderElement); + + return this._subViewContainer; + } + + public registerListeners(): void { + if (this.isDisposed()) { + return; + } + } + + // [protected methods] + + public override dispose(): void { + super.dispose(); + this._subViewContainer.remove(); + } + + // [private methods] + + private __createSortDropdown(): HTMLElement { + const sortDropdown = document.createElement("div"); + sortDropdown.classList.add("sort-dropdown"); + + // Add triangle icon + const triangleIcon = document.createElement("div"); + triangleIcon.classList.add("triangle-icon"); + sortDropdown.appendChild(triangleIcon); + + // Add text + const dropdownText = document.createElement("div"); + dropdownText.classList.add("dropdown-text"); + dropdownText.textContent = "Last modified"; + sortDropdown.appendChild(dropdownText); + + this.__register(addDisposableListener(sortDropdown, "click", () => { + console.log("Sort dropdown clicked"); + })); + + return sortDropdown; + } + + private __createSliderItems(content: string[]): HTMLElement[] { + return content.map((itemText) => { + const itemElement = document.createElement("div"); + itemElement.textContent = itemText; + itemElement.classList.add("slider-item"); + return itemElement; + }); + } +}