diff --git a/CHANGELOG.md b/CHANGELOG.md index 98370f4..8c4ec50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 2.2.0 [Offline Mode] + +- Your theme's wallpaper is now available offline! +- Small adjustments to the look and feel of the light Emilia theme. + + ## 2.1.1 [Better Update Experience] - The plugin will actually tell you if it could not install your specified sticker. diff --git a/package.json b/package.json index 218517c..66f1c31 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "The Doki Theme", "description": "Themes based on various anime and visual novel characters.", "publisher": "unthrottled", - "version": "2.1.1", + "version": "2.2.0", "license": "MIT", "icon": "Doki-Theme.png", "galleryBanner": { diff --git a/src/ENV.ts b/src/ENV.ts index 0901041..8c3af29 100644 --- a/src/ENV.ts +++ b/src/ENV.ts @@ -1,4 +1,24 @@ +import path from 'path'; +import fs from "fs"; + export const ASSETS_URL = `https://doki.assets.unthrottled.io`; export const VSCODE_ASSETS_URL = `${ASSETS_URL}/stickers/vscode`; export const BACKGROUND_ASSETS_URL = `${ASSETS_URL}/backgrounds`; export const SCREENSHOT_ASSETS_URL = `${ASSETS_URL}/screenshots`; + + +const main = require.main || { filename: 'yeet' }; +export const workbenchDirectory = path.join(path.dirname(main.filename), 'vs', 'workbench'); + +const CODE_SERVER_FILE = 'web.api'; +const getFileName = () => { + return fs.existsSync(path.join(workbenchDirectory, `workbench.desktop.main.css`)) ? + 'desktop.main' : CODE_SERVER_FILE; +}; + +const fileName = getFileName(); + +export const editorCss = path.join(workbenchDirectory, `workbench.${fileName}.css`); +export const editorCssCopy = path.join(workbenchDirectory, `workbench.${fileName}.css.copy`); + +export const isCodeServer = () => fileName === CODE_SERVER_FILE; diff --git a/src/NotificationService.ts b/src/NotificationService.ts index 0c0ee51..41f745b 100644 --- a/src/NotificationService.ts +++ b/src/NotificationService.ts @@ -3,7 +3,7 @@ import { VSCodeGlobals } from './VSCodeGlobals'; import { attemptToGreetUser } from './WelcomeService'; const SAVED_VERSION = 'doki.theme.version'; -const DOKI_THEME_VERSION = 'v2.1.1'; +const DOKI_THEME_VERSION = 'v2.2.0'; export function attemptToNotifyUpdates(context: vscode.ExtensionContext) { const savedVersion = VSCodeGlobals.globalState.get(SAVED_VERSION); diff --git a/src/RESTClient.ts b/src/RESTClient.ts index 52e6ae1..59d8bb7 100644 --- a/src/RESTClient.ts +++ b/src/RESTClient.ts @@ -3,7 +3,9 @@ import { Transform as Stream } from 'stream'; export const performGet = (url: string): Promise => { return new Promise((resolve, reject) => { - https.get(url, (res) => { + https.get(url, { + timeout: 10000, + }, (res) => { const inputStream = new Stream(); res.on('data', (d) => { inputStream.push(d); diff --git a/src/StickerService.ts b/src/StickerService.ts index c8693a6..3999fb7 100644 --- a/src/StickerService.ts +++ b/src/StickerService.ts @@ -1,35 +1,18 @@ import * as vscode from "vscode"; import { DokiTheme } from "./DokiTheme"; -import path from 'path'; import fs from "fs"; -import { resolveLocalStickerPath, isStickerNotCurrent, StickerUpdateStatus, stickerPathToUrl, cleanPathToUrl } from "./StickerUpdateService"; -import { performGet } from "./RESTClient"; -import { ASSETS_URL, BACKGROUND_ASSETS_URL, VSCODE_ASSETS_URL } from "./ENV"; +import { ASSETS_URL, editorCss, editorCssCopy } from "./ENV"; +import { attemptToUpdateSticker } from "./StickerUpdateService"; export enum InstallStatus { - INSTALLED, NOT_INSTALLED, FAILURE + INSTALLED, + NOT_INSTALLED, + FAILURE, } -const main = require.main || { filename: 'yeet' }; -export const workbenchDirectory = path.join(path.dirname(main.filename), 'vs', 'workbench'); - -const CODE_SERVER_FILE = 'web.api'; -const getFileName = () => { - return fs.existsSync(path.join(workbenchDirectory, `workbench.desktop.main.css`)) ? - 'desktop.main' : CODE_SERVER_FILE; -}; - -const fileName = getFileName(); - -const editorCss = path.join(workbenchDirectory, `workbench.${fileName}.css`); -const editorCssCopy = path.join(workbenchDirectory, `workbench.${fileName}.css.copy`); - - -const isCodeServer = () => fileName === CODE_SERVER_FILE; - // Was VS Code upgraded when stickers where installed? function isCssPrestine() { - const currentCss = fs.readFileSync(editorCss, 'utf-8'); + const currentCss = fs.readFileSync(editorCss, "utf-8"); return currentCss.indexOf(ASSETS_URL) < 0; } @@ -41,25 +24,26 @@ function ensureRightCssCopy() { function getVsCodeCss() { ensureRightCssCopy(); - return fs.readFileSync(editorCssCopy, 'utf-8'); + return fs.readFileSync(editorCssCopy, "utf-8"); } function buildStickerCss({ stickerDataURL: stickerUrl, - backgroundImageURL: backgroundImage + backgroundImageURL: wallpaperUrl, }: DokiStickers): string { - const style = 'content:\'\';pointer-events:none;position:absolute;z-index:99999;width:100%;height:100%;background-position:100% 100%;background-repeat:no-repeat;opacity:1;'; + const style = + "content:'';pointer-events:none;position:absolute;z-index:9001;width:100%;height:100%;background-position:100% 100%;background-repeat:no-repeat;opacity:1;"; return ` /* Stickers */ .split-view-view .editor-container .editor-instance>.monaco-editor .overflow-guard>.monaco-scrollable-element::after{background-image: url('${stickerUrl}');${style}} /* Background Image */ .monaco-workbench .part.editor > .content { - background-image: url('${BACKGROUND_ASSETS_URL}/${backgroundImage}') !important; + background-image: url('${wallpaperUrl}') !important; background-position: center; background-size: cover; content:''; - z-index:99999; + z-index:9001; width:100%; height:100%; background-repeat:no-repeat; @@ -70,10 +54,10 @@ function buildStickerCss({ function buildStyles(dokiStickers: DokiStickers): string { return `${getVsCodeCss()}${buildStickerCss(dokiStickers)}`; - } + function installEditorStyles(styles: string) { - fs.writeFileSync(editorCss, styles, 'utf-8'); + fs.writeFileSync(editorCss, styles, "utf-8"); } function canWrite(): boolean { @@ -90,67 +74,21 @@ export interface DokiStickers { backgroundImageURL: string; } -const downloadSticker = async (stickerPath: string, localDestination: string) => { - const parentDirectory = path.dirname(localDestination); - if (!fs.existsSync(parentDirectory)) { - fs.mkdirSync(parentDirectory, { recursive: true }); - } - - const stickerUrl = `${VSCODE_ASSETS_URL}${stickerPath}`; - console.log(`Downloading image: ${stickerUrl}`); - const stickerInputStream = await performGet(stickerUrl); - console.log('Image Downloaded!'); - fs.writeFileSync(localDestination, stickerInputStream.read()); -}; - -const readFileToDataURL = (localStickerPath: string): string => { - if(isCodeServer()) { - const base64ImageString = fs.readFileSync(localStickerPath, {encoding: 'base64'}); - return `data:image/png;base64,${base64ImageString}`; - } - - return `file://${cleanPathToUrl(localStickerPath)}`; -}; - -export async function getLatestStickerAndBackground( - dokiTheme: DokiTheme, - context: vscode.ExtensionContext, - stickerStatus: StickerUpdateStatus -): Promise { - const localStickerPath = resolveLocalStickerPath( - dokiTheme, context - ); - if (stickerStatus === StickerUpdateStatus.STALE || - !fs.existsSync(localStickerPath) || - await isStickerNotCurrent(dokiTheme, localStickerPath)) { - await downloadSticker(stickerPathToUrl(dokiTheme), localStickerPath); - } - - const stickerDataURL = readFileToDataURL(localStickerPath); - - return { - stickerDataURL, - backgroundImageURL: dokiTheme.sticker.name - }; -} - -export async function installSticker( +export async function installStickersAndWallPaper( dokiTheme: DokiTheme, - context: vscode.ExtensionContext, - stickerStatus: StickerUpdateStatus = StickerUpdateStatus.NOT_CHECKED + context: vscode.ExtensionContext ): Promise { if (canWrite()) { try { - const stickersAndBackground = await getLatestStickerAndBackground( - dokiTheme, + const stickersAndWallpaper = await attemptToUpdateSticker( context, - stickerStatus + dokiTheme ); - const stickerStyles = buildStyles(stickersAndBackground); + const stickerStyles = buildStyles(stickersAndWallpaper); installEditorStyles(stickerStyles); return true; } catch (e) { - console.error('Unable to install sticker!', e); + console.error("Unable to install sticker!", e); } } @@ -170,4 +108,4 @@ export function removeStickers(): InstallStatus { } return InstallStatus.FAILURE; -} \ No newline at end of file +} diff --git a/src/StickerUpdateService.ts b/src/StickerUpdateService.ts index 121cc1c..835430b 100644 --- a/src/StickerUpdateService.ts +++ b/src/StickerUpdateService.ts @@ -1,41 +1,94 @@ -import * as vscode from 'vscode'; -import { getCurrentTheme } from './ThemeManager'; -import { performGet } from './RESTClient'; -import { installSticker } from './StickerService'; -import { DokiTheme } from './DokiTheme'; -import path from 'path'; -import fs from 'fs'; -import crypto from 'crypto'; -import { VSCODE_ASSETS_URL } from './ENV'; - -const fetchRemoteChecksum = async (currentTheme: DokiTheme) => { - const checksumUrl = `${VSCODE_ASSETS_URL}${stickerPathToUrl(currentTheme)}.checksum.txt`; - console.log(`Fetching checksum: ${checksumUrl}`); +import * as vscode from "vscode"; +import { performGet } from "./RESTClient"; +import { DokiTheme } from "./DokiTheme"; +import path from "path"; +import fs from "fs"; +import crypto from "crypto"; +import { VSCODE_ASSETS_URL, isCodeServer, BACKGROUND_ASSETS_URL } from "./ENV"; +import { DokiStickers } from "./StickerService"; + +export const attemptToUpdateSticker = async ( + context: vscode.ExtensionContext, + currentTheme: DokiTheme +): Promise => { + const remoteStickerUrl = `${VSCODE_ASSETS_URL}${stickerPathToUrl( + currentTheme + )}`; + const remoteWallpaperUrl = `${BACKGROUND_ASSETS_URL}${wallpaperPathToUrl( + currentTheme + )}`; + if (isCodeServer()) { + return { + stickerDataURL: remoteStickerUrl, + backgroundImageURL: remoteWallpaperUrl, + }; + } + + const localStickerPath = resolveLocalStickerPath(currentTheme, context); + const localWallpaperPath = resolveLocalWallpaperPath(currentTheme, context); + await Promise.all([ + attemptToUpdateAsset(remoteStickerUrl, localStickerPath), + attemptToUpdateAsset(remoteWallpaperUrl, localWallpaperPath), + ]); + + return { + stickerDataURL: createCssDokiAssetUrl(localStickerPath), + backgroundImageURL: createCssDokiAssetUrl(localWallpaperPath), + }; +}; + +async function attemptToUpdateAsset( + remoteStickerUrl: string, + localStickerPath: string +) { + if (await shouldDownloadNewAsset(remoteStickerUrl, localStickerPath)) { + await installAsset(remoteStickerUrl, localStickerPath); + } +} + +const fetchRemoteChecksum = async (remoteAssetUrl: string) => { + const checksumUrl = `${remoteAssetUrl}.checksum.txt`; + console.log(`Fetching resource checksum: ${checksumUrl}`); const checkSumInputStream = await performGet(checksumUrl); - return checkSumInputStream.setEncoding('utf8').read(); + return checkSumInputStream.setEncoding("utf8").read(); }; -export const resolveLocalStickerPath = ( +const resolveLocalStickerPath = ( currentTheme: DokiTheme, - context: vscode.ExtensionContext, + context: vscode.ExtensionContext ): string => { const safeStickerPath = stickerPathToUrl(currentTheme); - return path.join(context.globalStoragePath, 'stickers', safeStickerPath); + return path.join(context.globalStoragePath, "stickers", safeStickerPath); }; -export function cleanPathToUrl(stickerPath: string) { - return stickerPath.replace(/\\/g, '/'); +const resolveLocalWallpaperPath = ( + currentTheme: DokiTheme, + context: vscode.ExtensionContext +): string => { + const safeStickerPath = wallpaperPathToUrl(currentTheme); + return path.join(context.globalStoragePath, "wallpapers", safeStickerPath); +}; + +const createCssDokiAssetUrl = (localAssetPath: string): string => { + return `file://${cleanPathToUrl(localAssetPath)}`; +}; + +function cleanPathToUrl(stickerPath: string) { + return stickerPath.replace(/\\/g, "/"); } -export function stickerPathToUrl(currentTheme: DokiTheme) { +function stickerPathToUrl(currentTheme: DokiTheme) { const stickerPath = currentTheme.sticker.path; return cleanPathToUrl(stickerPath); } -export function createChecksum(data: Buffer | string): string { - return crypto.createHash('md5') - .update(data) - .digest('hex'); +function wallpaperPathToUrl(currentTheme: DokiTheme) { + const stickerPath = `/${currentTheme.sticker.name}`; + return cleanPathToUrl(stickerPath); +} + +function createChecksum(data: Buffer | string): string { + return crypto.createHash("md5").update(data).digest("hex"); } const calculateFileChecksum = (filePath: string): string => { @@ -44,31 +97,48 @@ const calculateFileChecksum = (filePath: string): string => { }; const fetchLocalChecksum = async (localSticker: string) => { - return fs.existsSync(localSticker) ? - calculateFileChecksum(localSticker) : 'File not downloaded, bruv.'; + return fs.existsSync(localSticker) + ? calculateFileChecksum(localSticker) + : "File not downloaded, bruv."; }; -export const isStickerNotCurrent = async ( - dokiTheme: DokiTheme, +const shouldDownloadNewAsset = async ( + remoteAssetUrl: string, localStickerPath: string ): Promise => { try { - const remoteChecksum = await fetchRemoteChecksum(dokiTheme); + const remoteChecksum = await fetchRemoteChecksum(remoteAssetUrl); const localChecksum = await fetchLocalChecksum(localStickerPath); return remoteChecksum !== localChecksum; } catch (e) { - console.error('Unable to check for updates', e); + console.error("Unable to check for updates", e); return false; } }; -export enum StickerUpdateStatus { - CURRENT, STALE, NOT_CHECKED, -} -export const attemptToUpdateSticker = async (context: vscode.ExtensionContext) => { - const currentTheme = getCurrentTheme(); - const localStickerPath = resolveLocalStickerPath(currentTheme, context); - if (await isStickerNotCurrent(currentTheme, localStickerPath)) { - await installSticker(currentTheme, context, StickerUpdateStatus.STALE); +const downloadRemoteAsset = async ( + remoteAssetUrl: string, + localDestination: string +) => { + const parentDirectory = path.dirname(localDestination); + if (!fs.existsSync(parentDirectory)) { + fs.mkdirSync(parentDirectory, { recursive: true }); } + console.log(`Downloading remote asset: ${remoteAssetUrl}`); + const stickerInputStream = await performGet(remoteAssetUrl); + console.log("Remote asset Downloaded!"); + fs.writeFileSync(localDestination, stickerInputStream.read()); }; + +async function installAsset( + remoteAssetUrl: string, + localAssetPath: string +): Promise { + try { + await downloadRemoteAsset(remoteAssetUrl, localAssetPath); + return true; + } catch (e) { + console.error(`Unable to install asset ${remoteAssetUrl}!`, e); + } + return false; +} diff --git a/src/SupportService.ts b/src/SupportService.ts index 9b6ecc0..ab46439 100644 --- a/src/SupportService.ts +++ b/src/SupportService.ts @@ -1,7 +1,6 @@ import * as vscode from 'vscode'; import { getWebviewIcon, buildWebviewHtml } from "./ChangelogService"; -import { workbenchDirectory } from './StickerService'; - +import { workbenchDirectory } from './ENV'; export function showStickerInstallationSupportWindow(context: vscode.ExtensionContext) { const verbs = { diff --git a/src/ThemeManager.ts b/src/ThemeManager.ts index 92fc64e..5569e9c 100644 --- a/src/ThemeManager.ts +++ b/src/ThemeManager.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import {DokiTheme} from "./DokiTheme"; -import {InstallStatus, installSticker, removeStickers} from "./StickerService"; +import {InstallStatus, removeStickers, installStickersAndWallPaper} from "./StickerService"; import {VSCodeGlobals} from "./VSCodeGlobals"; import {StatusBarComponent} from "./StatusBar"; import {showStickerInstallationSupportWindow, showStickerRemovalSupportWindow} from "./SupportService"; @@ -8,7 +8,6 @@ import DokiThemeDefinitions from "./DokiThemeDefinitions"; export const ACTIVE_THEME = 'doki.theme.active'; - const FIRST_TIME_STICKER_INSTALL = 'doki.sticker.first.install'; function isFirstTimeInstalling(context: vscode.ExtensionContext) { return !context.globalState.get(FIRST_TIME_STICKER_INSTALL); @@ -41,7 +40,7 @@ async function performStickerInstall( dokiTheme: DokiTheme, context: vscode.ExtensionContext ): Promise { - const installResult = await installSticker(dokiTheme, context); + const installResult = await installStickersAndWallPaper(dokiTheme, context); return installResult ? InstallStatus.INSTALLED : InstallStatus.FAILURE; } @@ -62,7 +61,6 @@ export function activateTheme( }); } - // :( export function uninstallImages( context: vscode.ExtensionContext diff --git a/src/extension.ts b/src/extension.ts index 7bc32e4..45fc324 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,63 +1,57 @@ -import * as vscode from 'vscode'; -import { activateTheme, ACTIVE_THEME, uninstallImages } from "./ThemeManager"; +import * as vscode from "vscode"; +import { + activateTheme, + uninstallImages, + getCurrentTheme, +} from "./ThemeManager"; import { DokiTheme } from "./DokiTheme"; -import DokiThemeDefinitions from './DokiThemeDefinitions'; -import { StatusBarComponent } from './StatusBar'; -import { VSCodeGlobals } from './VSCodeGlobals'; -import { attemptToNotifyUpdates } from './NotificationService'; -import { showChanglog } from './ChangelogService'; -import { attemptToUpdateSticker } from './StickerUpdateService'; +import DokiThemeDefinitions from "./DokiThemeDefinitions"; +import { StatusBarComponent } from "./StatusBar"; +import { VSCodeGlobals } from "./VSCodeGlobals"; +import { attemptToNotifyUpdates } from "./NotificationService"; +import { showChanglog } from "./ChangelogService"; +import { attemptToUpdateSticker } from "./StickerUpdateService"; export interface DokiThemeDefinition { - sticker: { - path: string; - name: string; - }; - information: any; + sticker: { + path: string; + name: string; + }; + information: any; } export interface VSCodeDokiThemeDefinition { - extensionName: string; - themeDefinition: DokiThemeDefinition; + extensionName: string; + themeDefinition: DokiThemeDefinition; } - export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.commands.registerCommand( - 'extension.remove.sticker', - () => uninstallImages(context) - ) - ); - - context.subscriptions.push( - vscode.commands.registerCommand( - 'extension.doki.changelog', - () => showChanglog(context) - ) - ); - - VSCodeGlobals.globalState = context.globalState; - - StatusBarComponent.initialize(); - context.subscriptions.push(StatusBarComponent); - - attemptToNotifyUpdates(context); - - attemptToUpdateSticker(context); - - DokiThemeDefinitions - .map((dokiThemeDefinition: VSCodeDokiThemeDefinition) => - vscode.commands.registerCommand( - dokiThemeDefinition.extensionName, - () => activateTheme( - new DokiTheme(dokiThemeDefinition.themeDefinition), - context - )) - ).forEach(disposableHero => - context.subscriptions.push(disposableHero) - ); -} + context.subscriptions.push( + vscode.commands.registerCommand("extension.remove.sticker", () => + uninstallImages(context) + ) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("extension.doki.changelog", () => + showChanglog(context) + ) + ); + + VSCodeGlobals.globalState = context.globalState; + StatusBarComponent.initialize(); + context.subscriptions.push(StatusBarComponent); + + attemptToNotifyUpdates(context); + + attemptToUpdateSticker(context, getCurrentTheme()); + + DokiThemeDefinitions.map((dokiThemeDefinition: VSCodeDokiThemeDefinition) => + vscode.commands.registerCommand(dokiThemeDefinition.extensionName, () => + activateTheme(new DokiTheme(dokiThemeDefinition.themeDefinition), context) + ) + ).forEach((disposableHero) => context.subscriptions.push(disposableHero)); +} -export function deactivate() { } +export function deactivate() {}