Compare commits

...

3 commits

Author SHA1 Message Date
alexweininger 15370fa493 Merge branch 'main' into alex/projectsView 2021-04-30 00:58:18 -07:00
alexweininger 260af5138e Add multi project gif to readme 2021-04-30 00:53:39 -07:00
alexweininger 640635cc42 Basic projects view 2021-04-30 00:52:12 -07:00
15 changed files with 242 additions and 25 deletions

View file

@ -14,6 +14,10 @@ From [appwrite.io](https://appwrite.io)
## Features ## Features
### Connect to multiple Appwrite projects
![Mutliple projects](media/features/projects/projectsView1.gif)
### View database documents right inside VS Code. ### View database documents right inside VS Code.
![Database feature](media/features/database/scr2.png) ![Database feature](media/features/database/scr2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View file

@ -169,6 +169,25 @@
"command": "vscode-appwrite.openStorageDocumentation", "command": "vscode-appwrite.openStorageDocumentation",
"title": "Open storage documentation", "title": "Open storage documentation",
"icon": "$(book)" "icon": "$(book)"
},
{
"command": "vscode-appwrite.addProject",
"title": "Add Appwrite project",
"icon": "$(plus)"
},
{
"command": "vscode-appwrite.setActiveProject",
"title": "Set as active"
},
{
"command": "vscode-appwrite.refreshProjects",
"title": "Refresh projects",
"icon": "$(refresh)"
},
{
"command": "vscode-appwrite.removeProject",
"title": "Remove project",
"icon": "$(trash)"
} }
], ],
"views": { "views": {
@ -188,6 +207,10 @@
{ {
"id": "Health", "id": "Health",
"name": "Health" "name": "Health"
},
{
"id": "Projects",
"name": "Projects"
} }
] ]
}, },
@ -195,6 +218,10 @@
{ {
"view": "Users", "view": "Users",
"contents": "Connect to Appwrite to get started.\n[Connect to Appwrite](command:vscode-appwrite.Connect)" "contents": "Connect to Appwrite to get started.\n[Connect to Appwrite](command:vscode-appwrite.Connect)"
},
{
"view": "Projects",
"contents": "Add an Appwrite project to get started.\n[Connect to Appwrite](command:vscode-appwrite.Connect)"
} }
], ],
"menus": { "menus": {
@ -248,6 +275,16 @@
"command": "vscode-appwrite.openStorageDocumentation", "command": "vscode-appwrite.openStorageDocumentation",
"when": "view == Storage", "when": "view == Storage",
"group": "navigation" "group": "navigation"
},
{
"command": "vscode-appwrite.refreshProjects",
"when": "view == Projects",
"group": "navigation"
},
{
"command": "vscode-appwrite.addProject",
"when": "view == Projects",
"group": "navigation"
} }
], ],
"view/item/context": [ "view/item/context": [
@ -337,6 +374,15 @@
"command": "vscode-appwrite.deletePermission", "command": "vscode-appwrite.deletePermission",
"when": "viewItem =~ /^(permission)$/", "when": "viewItem =~ /^(permission)$/",
"group": "inline" "group": "inline"
},
{
"command": "vscode-appwrite.setActiveProject",
"when": "viewItem =~ /^(appwriteProject)$/",
"group": "inline"
},
{
"command": "vscode-appwrite.removeProject",
"when": "viewItem =~ /(appwriteProject)/"
} }
], ],
"commandPalette": [ "commandPalette": [
@ -428,6 +474,11 @@
"type": "array", "type": "array",
"default": [], "default": [],
"markdownDescription": "List of Appwrite project configurations. You can use the Connect command to set this up, or see [docs](https://github.com/streamlux/vscode-appwrite/) for more information." "markdownDescription": "List of Appwrite project configurations. You can use the Connect command to set this up, or see [docs](https://github.com/streamlux/vscode-appwrite/) for more information."
},
"appwrite.activeProjectId": {
"type": "string",
"default": "",
"markdownDescription": "Project id of the active project, see [docs](https://github.com/streamlux/vscode-appwrite/) for more information."
} }
} }
} }

View file

@ -0,0 +1,14 @@
import { window } from "vscode";
import { initAppwriteClient } from "../../client";
import { removeProjectConfig } from '../../settings';
import { ProjectTreeItem } from '../../tree/projects/ProjectTreeItem';
import { addProjectWizard } from "../../ui/AddProjectWizard";
export async function removeProject(project: ProjectTreeItem | string) {
if (typeof project === 'string') {
await removeProjectConfig(project);
return;
}
await removeProjectConfig(project.project.projectId);
}

View file

@ -0,0 +1,16 @@
import { window } from "vscode";
import { initAppwriteClient } from "../../client";
import { setActiveProjectId } from '../../settings';
import { ProjectTreeItem } from "../../tree/projects/ProjectTreeItem";
export async function setActiveProject(treeItem: ProjectTreeItem) {
if (treeItem === undefined) {
return;
}
if (!(treeItem instanceof ProjectTreeItem)) {
return;
}
await setActiveProjectId(treeItem.project.projectId);
}

View file

@ -1,6 +1,6 @@
import { commands, ExtensionContext } from "vscode"; import { commands, ExtensionContext } from "vscode";
import { AppwriteTree, ext } from "../extensionVariables"; import { AppwriteTree, ext } from "../extensionVariables";
import { refreshTree } from "../utils/refreshTree"; import { refreshAllViews, refreshTree } from "../utils/refreshTree";
import { connectAppwrite } from "./connectAppwrite"; import { connectAppwrite } from "./connectAppwrite";
import { createCollection } from "./database/createCollection"; import { createCollection } from "./database/createCollection";
import { createPermission } from "./database/permissions/createPermission"; import { createPermission } from "./database/permissions/createPermission";
@ -14,7 +14,6 @@ import { refreshCollectionsList } from "./database/refreshCollectionsList";
import { removeRule } from "./database/removeRule"; import { removeRule } from "./database/removeRule";
import { viewCollectionAsJson } from "./database/viewCollectionAsJson"; import { viewCollectionAsJson } from "./database/viewCollectionAsJson";
import { openDocumentation } from "./openDocumentation"; import { openDocumentation } from "./openDocumentation";
import { addProject } from "./project/addProject";
import { copyUserEmail } from "./users/copyUserEmail"; import { copyUserEmail } from "./users/copyUserEmail";
import { copyUserId } from "./users/copyUserId"; import { copyUserId } from "./users/copyUserId";
import { createUser } from "./users/createUser"; import { createUser } from "./users/createUser";
@ -24,6 +23,8 @@ import { openUserInConsole } from "./users/openUserInConsole";
import { refreshUsersList } from "./users/refreshUsersList"; import { refreshUsersList } from "./users/refreshUsersList";
import { viewUserPrefs } from "./users/viewUserPrefs"; import { viewUserPrefs } from "./users/viewUserPrefs";
import { editPermission } from "./database/permissions/editPermission"; import { editPermission } from "./database/permissions/editPermission";
import { setActiveProject } from "./project/setActiveProject";
import { removeProject } from './project/removeProject';
class CommandRegistrar { class CommandRegistrar {
constructor(private readonly context: ExtensionContext) {} constructor(private readonly context: ExtensionContext) {}
@ -36,11 +37,21 @@ class CommandRegistrar {
export function registerCommands(context: ExtensionContext): void { export function registerCommands(context: ExtensionContext): void {
const registrar = new CommandRegistrar(context); const registrar = new CommandRegistrar(context);
const registerCommand = (commandId: string, callback: (...args: any[]) => any, refresh?: keyof AppwriteTree) => { const registerCommand = (
commandId: string,
callback?: (...args: any[]) => any,
refresh?: keyof AppwriteTree | (keyof AppwriteTree)[] | "all"
) => {
registrar.registerCommand(`vscode-appwrite.${commandId}`, async (...args: any[]) => { registrar.registerCommand(`vscode-appwrite.${commandId}`, async (...args: any[]) => {
await callback(...args); await callback?.(...args);
if (refresh) { if (refresh !== undefined) {
if (refresh === "all") {
refreshAllViews();
} else if (typeof refresh === "string") {
refreshTree(refresh); refreshTree(refresh);
} else {
refreshTree(...refresh);
}
} }
}); });
}; };
@ -81,4 +92,10 @@ export function registerCommands(context: ExtensionContext): void {
/** Storage **/ /** Storage **/
registerCommand("refreshStorage", () => {}, "storage"); registerCommand("refreshStorage", () => {}, "storage");
registerCommand("openStorageDocumentation", () => openDocumentation("storage")); registerCommand("openStorageDocumentation", () => openDocumentation("storage"));
/** Projects **/
registerCommand("addProject", connectAppwrite, "all");
registerCommand("setActiveProject", setActiveProject, "all");
registerCommand("refreshProjects", undefined, "projects");
registerCommand("removeProject", removeProject, "all");
} }

View file

@ -1,10 +1,12 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { workspace } from 'vscode';
import { initAppwriteClient } from "./client"; import { initAppwriteClient } from "./client";
import { registerCommands } from "./commands/registerCommands"; import { registerCommands } from "./commands/registerCommands";
import { ext } from "./extensionVariables"; import { ext } from "./extensionVariables";
import { getDefaultProject } from "./settings"; import { getActiveProjectConfiguration, getActiveProjectId, getDefaultProject } from "./settings";
import { DatabaseTreeItemProvider } from "./tree/database/DatabaseTreeItemProvider"; import { DatabaseTreeItemProvider } from "./tree/database/DatabaseTreeItemProvider";
import { HealthTreeItemProvider } from "./tree/health/HealthTreeItemProvider"; import { HealthTreeItemProvider } from "./tree/health/HealthTreeItemProvider";
import { ProjectsTreeItemProvider } from './tree/projects/ProjectsTreeItemProvider';
import { StorageTreeItemProvider } from "./tree/storage/StorageTreeItemProvider"; import { StorageTreeItemProvider } from "./tree/storage/StorageTreeItemProvider";
import { UserTreeItemProvider } from "./tree/users/UserTreeItemProvider"; import { UserTreeItemProvider } from "./tree/users/UserTreeItemProvider";
import { createAppwriteOutputChannel } from "./ui/AppwriteOutputChannel"; import { createAppwriteOutputChannel } from "./ui/AppwriteOutputChannel";
@ -14,15 +16,17 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
const healthTreeItemProvider = new HealthTreeItemProvider(); const healthTreeItemProvider = new HealthTreeItemProvider();
const databaseTreeItemProvider = new DatabaseTreeItemProvider(); const databaseTreeItemProvider = new DatabaseTreeItemProvider();
const storageTreeItemProvider = new StorageTreeItemProvider(); const storageTreeItemProvider = new StorageTreeItemProvider();
const projectsTreeItemProvider = new ProjectsTreeItemProvider();
vscode.window.registerTreeDataProvider("Users", userTreeItemProvider); vscode.window.registerTreeDataProvider("Users", userTreeItemProvider);
vscode.window.registerTreeDataProvider("Health", healthTreeItemProvider); vscode.window.registerTreeDataProvider("Health", healthTreeItemProvider);
vscode.window.registerTreeDataProvider("Database", databaseTreeItemProvider); vscode.window.registerTreeDataProvider("Database", databaseTreeItemProvider);
vscode.window.registerTreeDataProvider("Storage", storageTreeItemProvider); vscode.window.registerTreeDataProvider("Storage", storageTreeItemProvider);
vscode.window.registerTreeDataProvider("Projects", projectsTreeItemProvider);
const defaultProject = await getDefaultProject(); const activeProject = await getActiveProjectConfiguration();
if (defaultProject) { if (activeProject) {
initAppwriteClient(defaultProject); initAppwriteClient(activeProject);
} }
ext.context = context; ext.context = context;
@ -33,6 +37,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
health: healthTreeItemProvider, health: healthTreeItemProvider,
database: databaseTreeItemProvider, database: databaseTreeItemProvider,
storage: storageTreeItemProvider, storage: storageTreeItemProvider,
projects: projectsTreeItemProvider
}; };
registerCommands(context); registerCommands(context);

View file

@ -1,6 +1,7 @@
import { ExtensionContext, OutputChannel } from "vscode"; import { ExtensionContext, OutputChannel } from "vscode";
import { DatabaseTreeItemProvider } from './tree/database/DatabaseTreeItemProvider'; import { DatabaseTreeItemProvider } from './tree/database/DatabaseTreeItemProvider';
import { HealthTreeItemProvider } from './tree/health/HealthTreeItemProvider'; import { HealthTreeItemProvider } from './tree/health/HealthTreeItemProvider';
import { ProjectsTreeItemProvider } from './tree/projects/ProjectsTreeItemProvider';
import { StorageTreeItemProvider } from './tree/storage/StorageTreeItemProvider'; import { StorageTreeItemProvider } from './tree/storage/StorageTreeItemProvider';
import { UserTreeItemProvider } from './tree/users/UserTreeItemProvider'; import { UserTreeItemProvider } from './tree/users/UserTreeItemProvider';
import { AppwriteOutputChannel } from './ui/AppwriteOutputChannel'; import { AppwriteOutputChannel } from './ui/AppwriteOutputChannel';
@ -10,6 +11,7 @@ export type AppwriteTree = {
health?: HealthTreeItemProvider; health?: HealthTreeItemProvider;
database?: DatabaseTreeItemProvider; database?: DatabaseTreeItemProvider;
storage?: StorageTreeItemProvider; storage?: StorageTreeItemProvider;
projects?: ProjectsTreeItemProvider;
}; };
export type Ext = { export type Ext = {

View file

@ -1,4 +1,5 @@
import { workspace } from 'vscode'; import { workspace } from "vscode";
import { initAppwriteClient } from "./client";
export type AppwriteProjectConfiguration = { export type AppwriteProjectConfiguration = {
nickname?: string; nickname?: string;
@ -14,17 +15,68 @@ export async function getDefaultProject(): Promise<AppwriteProjectConfiguration
} }
export async function getAppwriteProjects(): Promise<AppwriteProjectConfiguration[]> { export async function getAppwriteProjects(): Promise<AppwriteProjectConfiguration[]> {
const configuration = workspace.getConfiguration('appwrite'); const configuration = workspace.getConfiguration("appwrite");
const projects = configuration.get('projects'); const projects = configuration.get("projects");
if (projects === undefined) { if (projects === undefined) {
configuration.update('projects', []); configuration.update("projects", []);
return []; return [];
} }
return projects as AppwriteProjectConfiguration[]; return projects as AppwriteProjectConfiguration[];
} }
export async function addProjectConfiguration(projectConfig: AppwriteProjectConfiguration): Promise<void> { export async function addProjectConfiguration(projectConfig: AppwriteProjectConfiguration): Promise<void> {
const configuration = workspace.getConfiguration('appwrite'); const configuration = workspace.getConfiguration("appwrite");
const projects = await getAppwriteProjects(); const projects = await getAppwriteProjects();
await configuration.update('projects', [...projects, projectConfig], true);
await configuration.update("projects", [...projects, projectConfig], true);
await setActiveProjectId(projectConfig.projectId);
}
export async function getActiveProjectId(): Promise<string> {
const configuration = workspace.getConfiguration("appwrite");
const projectId = configuration.get<string>("activeProjectId");
return projectId ?? "";
}
export async function getActiveProjectConfiguration(): Promise<AppwriteProjectConfiguration> {
const configurations = await getAppwriteProjects();
const activeConfigId = await getActiveProjectId();
let activeConfig;
configurations.forEach((config) => {
if (config.projectId === activeConfigId) {
activeConfig = config;
}
});
if (activeConfig === undefined) {
activeConfig = configurations[0];
setActiveProjectId(configurations[0].projectId);
}
return activeConfig;
}
export async function setActiveProjectId(projectId: string): Promise<void> {
const configuration = workspace.getConfiguration("appwrite");
await configuration.update("activeProjectId", projectId, true);
initAppwriteClient(await getActiveProjectConfiguration());
}
export async function updateActiveProjectId(): Promise<void> {
const projects = await getAppwriteProjects();
if (projects.length > 0) {
const configuration = workspace.getConfiguration("appwrite");
await configuration.update("activeProjectId", projects[0].projectId, true);
initAppwriteClient(await getActiveProjectConfiguration());
}
}
export async function removeProjectConfig(projectId: string): Promise<void> {
const projects = await getAppwriteProjects();
const activeProjectId = await getActiveProjectId();
const updatedProjects = projects.filter((project) => project.projectId !== projectId);
const configuration = workspace.getConfiguration("appwrite");
await configuration.update("projects", updatedProjects, true);
await updateActiveProjectId();
} }

View file

@ -0,0 +1,15 @@
import { ThemeIcon, TreeItem } from "vscode";
import { AppwriteProjectConfiguration } from "../../settings";
export class ProjectTreeItem extends TreeItem {
constructor(public readonly project: AppwriteProjectConfiguration, active: boolean) {
super("Project");
this.iconPath = new ThemeIcon("rocket");
const name = project.nickname ?? "Project";
this.label = `${name} ${active ? "(Active)" : ""}`;
this.contextValue = `appwriteProject${active ? "_active" : ""}`;
if (!active) {
this.command = { command: "vscode-appwrite.setActiveProject", title: "Set active", arguments: [this] };
}
}
}

View file

@ -0,0 +1,30 @@
import * as vscode from "vscode";
import { getActiveProjectId, getAppwriteProjects } from '../../settings';
import { ProjectTreeItem } from './ProjectTreeItem';
export class ProjectsTreeItemProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
private _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | undefined | void> = new vscode.EventEmitter<
vscode.TreeItem | undefined | void
>();
readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | undefined | void> = this._onDidChangeTreeData.event;
constructor() {}
refresh(): void {
this._onDidChangeTreeData.fire();
}
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
async getChildren(element?: vscode.TreeItem): Promise<vscode.TreeItem[]> {
const configs = await getAppwriteProjects();
if (configs === undefined || configs.length === 0) {
return [];
}
const activeProjectId = await getActiveProjectId();
return configs.map((config) => new ProjectTreeItem(config, config.projectId === activeProjectId));
}
}

View file

@ -21,8 +21,9 @@ export class StorageTreeItemProvider implements vscode.TreeDataProvider<vscode.T
async getChildren(element?: vscode.TreeItem): Promise<vscode.TreeItem[]> { async getChildren(element?: vscode.TreeItem): Promise<vscode.TreeItem[]> {
const files = await storageClient.listFiles(); const files = await storageClient.listFiles();
if (files === undefined) { if (files === undefined || files?.files.length === 0) {
return []; const noStorage = new vscode.TreeItem('No files found');
return [noStorage];
} }
return files.files.map((file) => new FileTreeItem(file)); return files.files.map((file) => new FileTreeItem(file));
} }

View file

@ -7,18 +7,27 @@ export async function addProjectWizard(): Promise<AppwriteProjectConfiguration |
prompt: "Enter your Appwrite API endping", prompt: "Enter your Appwrite API endping",
ignoreFocusOut: true ignoreFocusOut: true
}); });
if (endpoint === undefined) {
return;
}
const projectId = await window.showInputBox({ const projectId = await window.showInputBox({
placeHolder: "Project Id", placeHolder: "Project Id",
prompt: "Enter your Appwrite project id", prompt: "Enter your Appwrite project id",
ignoreFocusOut: true ignoreFocusOut: true
}); });
if (projectId === undefined) {
return;
}
const secret = await window.showInputBox({ const secret = await window.showInputBox({
placeHolder: "API key secret", placeHolder: "API key secret",
prompt: "Enter your Appwrite API key secret", prompt: "Enter your Appwrite API key secret",
ignoreFocusOut: true ignoreFocusOut: true
}); });
if (secret === undefined) {
return;
}
const nickname = await window.showInputBox({ const nickname = await window.showInputBox({
prompt: "(Optional) Project nickname", prompt: "(Optional) Project name",
ignoreFocusOut: true ignoreFocusOut: true
}); });

View file

@ -1,14 +1,15 @@
import { AppwriteTree, ext } from "../extensionVariables"; import { AppwriteTree, ext } from "../extensionVariables";
export function refreshTree(tree?: keyof AppwriteTree): void { export function refreshTree(...trees: (keyof AppwriteTree)[]): void {
if (tree !== undefined) { trees.forEach((tree) => {
ext.tree?.[tree]?.refresh(); ext.tree?.[tree]?.refresh();
return; });
} }
export function refreshAllViews(): void {
if (ext.tree) { if (ext.tree) {
Object.values(ext.tree).forEach((treeView) => { Object.keys(ext.tree).forEach((tree) => {
treeView?.refresh(); refreshTree(tree as keyof AppwriteTree);
}); });
} }
} }