mirror of
https://github.com/ivabus/gui
synced 2025-04-23 14:07:14 +03:00
parent
d300efd805
commit
6c3be19da2
166 changed files with 7717 additions and 7703 deletions
|
@ -91,7 +91,7 @@ pnpm dev
|
|||
## Prettier
|
||||
|
||||
```sh
|
||||
pnpm run -r format
|
||||
pnpm run --reporter append-only -r format
|
||||
```
|
||||
|
||||
## Dist
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
globals: {
|
||||
NodeJS: true
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
plugins: ["svelte3", "@typescript-eslint"],
|
||||
ignorePatterns: ["*.cjs"],
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.svelte"],
|
||||
processor: "svelte3/svelte3"
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
"svelte3/typescript": () => require("typescript")
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }]
|
||||
}
|
||||
root: true,
|
||||
globals: {
|
||||
NodeJS: true
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
plugins: ["svelte3", "@typescript-eslint"],
|
||||
ignorePatterns: ["*.cjs"],
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.svelte"],
|
||||
processor: "svelte3/svelte3"
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
"svelte3/typescript": () => require("typescript")
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"pluginSearchDirs": ["../../node_modules"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
|
|
@ -5,59 +5,59 @@ const otaClient = require("@crowdin/ota-client");
|
|||
const _ = require("lodash");
|
||||
|
||||
module.exports = {
|
||||
appId: "xyz.tea.gui",
|
||||
productName: "tea",
|
||||
asar: false,
|
||||
directories: { output: "dist" },
|
||||
files: ["electron/dist/electron.cjs", { from: "build", to: "" }],
|
||||
linux: {
|
||||
icon: "./icon.png"
|
||||
},
|
||||
mac: {
|
||||
icon: "./electron/icon.icns",
|
||||
target: {
|
||||
target: "default",
|
||||
arch: ["x64", "arm64"]
|
||||
}
|
||||
},
|
||||
afterSign: async (params) => {
|
||||
if (process.platform !== "darwin" || process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") {
|
||||
console.log("not notarizing app");
|
||||
return;
|
||||
}
|
||||
appId: "xyz.tea.gui",
|
||||
productName: "tea",
|
||||
asar: false,
|
||||
directories: { output: "dist" },
|
||||
files: ["electron/dist/electron.cjs", { from: "build", to: "" }],
|
||||
linux: {
|
||||
icon: "./icon.png"
|
||||
},
|
||||
mac: {
|
||||
icon: "./electron/icon.icns",
|
||||
target: {
|
||||
target: "default",
|
||||
arch: ["x64", "arm64"]
|
||||
}
|
||||
},
|
||||
afterSign: async (params) => {
|
||||
if (process.platform !== "darwin" || process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") {
|
||||
console.log("not notarizing app");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("afterSign hook triggered");
|
||||
console.log("afterSign hook triggered");
|
||||
|
||||
const appBundleId = "xyz.tea.gui";
|
||||
const appBundleId = "xyz.tea.gui";
|
||||
|
||||
let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
|
||||
if (!fs.existsSync(appPath)) {
|
||||
console.log("skip");
|
||||
return;
|
||||
}
|
||||
let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
|
||||
if (!fs.existsSync(appPath)) {
|
||||
console.log("skip");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Notarizing ${appBundleId} found at ${appPath} with Apple ID ${process.env.APPLE_ID}`
|
||||
);
|
||||
console.log(
|
||||
`Notarizing ${appBundleId} found at ${appPath} with Apple ID ${process.env.APPLE_ID}`
|
||||
);
|
||||
|
||||
try {
|
||||
await notarize({
|
||||
appBundleId,
|
||||
appPath,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
try {
|
||||
await notarize({
|
||||
appBundleId,
|
||||
appPath,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
console.log(`Done notarizing`);
|
||||
},
|
||||
// this determines the configuration of the auto-update feature
|
||||
publish: {
|
||||
provider: "generic",
|
||||
// TODO: replace this with tea branded domain: gui-dist.tea.xyz
|
||||
// url: "https://d2ovumu63qzbn6.cloudfront.net/"
|
||||
url: "https://s3.amazonaws.com/preview.gui.tea.xyz/release"
|
||||
}
|
||||
console.log(`Done notarizing`);
|
||||
},
|
||||
// this determines the configuration of the auto-update feature
|
||||
publish: {
|
||||
provider: "generic",
|
||||
// TODO: replace this with tea branded domain: gui-dist.tea.xyz
|
||||
// url: "https://d2ovumu63qzbn6.cloudfront.net/"
|
||||
url: "https://s3.amazonaws.com/preview.gui.tea.xyz/release"
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { checkUpdater } from "./libs/auto-updater";
|
|||
|
||||
import initializeHandlers, { setProtocolPath } from "./libs/ipc";
|
||||
import initializePushNotification, {
|
||||
syncPackageTopicSubscriptions
|
||||
syncPackageTopicSubscriptions
|
||||
} from "./libs/push-notification";
|
||||
|
||||
import init from "./libs/initialize";
|
||||
|
@ -19,26 +19,26 @@ import { readSessionData } from "./libs/auth";
|
|||
|
||||
log.info("App starting...");
|
||||
if (app.isPackaged) {
|
||||
Sentry.init({
|
||||
dsn: "https://5ff29bb5b3b64cd4bd4f4960ef1db2e3@o4504750197899264.ingest.sentry.io/4504750206746624",
|
||||
debug: true,
|
||||
transportOptions: {
|
||||
maxQueueAgeDays: 30,
|
||||
maxQueueCount: 30,
|
||||
beforeSend: async () => {
|
||||
const ol = await net.isOnline();
|
||||
return ol ? "send" : "queue";
|
||||
}
|
||||
}
|
||||
});
|
||||
Sentry.configureScope(async (scope) => {
|
||||
const session = await readSessionData();
|
||||
scope.setUser({
|
||||
id: session.device_id, // device_id this should exist in our pg db: developer_id is to many device_id
|
||||
username: session?.user?.login || "" // github username or handler
|
||||
});
|
||||
});
|
||||
setSentryLogging(Sentry);
|
||||
Sentry.init({
|
||||
dsn: "https://5ff29bb5b3b64cd4bd4f4960ef1db2e3@o4504750197899264.ingest.sentry.io/4504750206746624",
|
||||
debug: true,
|
||||
transportOptions: {
|
||||
maxQueueAgeDays: 30,
|
||||
maxQueueCount: 30,
|
||||
beforeSend: async () => {
|
||||
const ol = await net.isOnline();
|
||||
return ol ? "send" : "queue";
|
||||
}
|
||||
}
|
||||
});
|
||||
Sentry.configureScope(async (scope) => {
|
||||
const session = await readSessionData();
|
||||
scope.setUser({
|
||||
id: session.device_id, // device_id this should exist in our pg db: developer_id is to many device_id
|
||||
username: session?.user?.login || "" // github username or handler
|
||||
});
|
||||
});
|
||||
setSentryLogging(Sentry);
|
||||
}
|
||||
|
||||
init();
|
||||
|
@ -56,148 +56,148 @@ setupTitlebar();
|
|||
initializeHandlers({ notifyMainWindow });
|
||||
|
||||
function createWindow() {
|
||||
const windowState = windowStateManager({
|
||||
defaultWidth: 1000,
|
||||
defaultHeight: 600
|
||||
});
|
||||
const windowState = windowStateManager({
|
||||
defaultWidth: 1000,
|
||||
defaultHeight: 600
|
||||
});
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
titleBarStyle: "hidden",
|
||||
backgroundColor: "black",
|
||||
autoHideMenuBar: true,
|
||||
trafficLightPosition: {
|
||||
x: 14,
|
||||
y: 15
|
||||
},
|
||||
minHeight: 600,
|
||||
minWidth: 1000,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false,
|
||||
webSecurity: false,
|
||||
devTools: allowDebug,
|
||||
preload: path.join(app.getAppPath(), "preload.cjs")
|
||||
},
|
||||
x: windowState.x,
|
||||
y: windowState.y,
|
||||
width: windowState.width,
|
||||
height: windowState.height
|
||||
});
|
||||
const mainWindow = new BrowserWindow({
|
||||
titleBarStyle: "hidden",
|
||||
backgroundColor: "black",
|
||||
autoHideMenuBar: true,
|
||||
trafficLightPosition: {
|
||||
x: 14,
|
||||
y: 15
|
||||
},
|
||||
minHeight: 600,
|
||||
minWidth: 1000,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false,
|
||||
webSecurity: false,
|
||||
devTools: allowDebug,
|
||||
preload: path.join(app.getAppPath(), "preload.cjs")
|
||||
},
|
||||
x: windowState.x,
|
||||
y: windowState.y,
|
||||
width: windowState.width,
|
||||
height: windowState.height
|
||||
});
|
||||
|
||||
windowState.manage(mainWindow);
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
});
|
||||
windowState.manage(mainWindow);
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
});
|
||||
|
||||
mainWindow.on("close", () => {
|
||||
windowState.saveState(mainWindow);
|
||||
});
|
||||
mainWindow.on("close", () => {
|
||||
windowState.saveState(mainWindow);
|
||||
});
|
||||
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
initializePushNotification(mainWindow);
|
||||
});
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
initializePushNotification(mainWindow);
|
||||
});
|
||||
|
||||
attachTitlebarToWindow(mainWindow);
|
||||
return mainWindow;
|
||||
attachTitlebarToWindow(mainWindow);
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
contextMenu({
|
||||
showLookUpSelection: false,
|
||||
showSearchWithGoogle: false,
|
||||
showCopyImage: false
|
||||
showLookUpSelection: false,
|
||||
showSearchWithGoogle: false,
|
||||
showCopyImage: false
|
||||
});
|
||||
|
||||
function loadVite(port) {
|
||||
mainWindow?.loadURL(`http://localhost:${port}?is-vite=true`).catch((e) => {
|
||||
console.log("Error loading URL, retrying", e);
|
||||
setTimeout(() => {
|
||||
loadVite(port);
|
||||
}, 200);
|
||||
});
|
||||
mainWindow?.loadURL(`http://localhost:${port}?is-vite=true`).catch((e) => {
|
||||
console.log("Error loading URL, retrying", e);
|
||||
setTimeout(() => {
|
||||
loadVite(port);
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
} else {
|
||||
mainWindow = createWindow();
|
||||
}
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
} else {
|
||||
mainWindow = createWindow();
|
||||
}
|
||||
|
||||
checkUpdater(mainWindow);
|
||||
checkUpdater(mainWindow);
|
||||
|
||||
mainWindow.once("close", () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
mainWindow.once("close", () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
if (!app.isPackaged) {
|
||||
// dev
|
||||
loadVite(port);
|
||||
} else {
|
||||
serveURL(mainWindow);
|
||||
}
|
||||
if (!app.isPackaged) {
|
||||
// dev
|
||||
loadVite(port);
|
||||
} else {
|
||||
serveURL(mainWindow);
|
||||
}
|
||||
|
||||
syncPackageTopicSubscriptions();
|
||||
syncPackageTopicSubscriptions();
|
||||
}
|
||||
|
||||
if (process.defaultApp) {
|
||||
app.setAsDefaultProtocolClient("tea", process.execPath, [path.resolve(process.argv[1])]);
|
||||
app.setAsDefaultProtocolClient("tea", process.execPath, [path.resolve(process.argv[1])]);
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient("tea");
|
||||
app.setAsDefaultProtocolClient("tea");
|
||||
}
|
||||
|
||||
app.once("ready", createMainWindow);
|
||||
|
||||
app.on("activate", () => {
|
||||
if (!mainWindow) {
|
||||
createMainWindow();
|
||||
}
|
||||
if (!mainWindow) {
|
||||
createMainWindow();
|
||||
}
|
||||
});
|
||||
app.on("window-all-closed", () => {
|
||||
// mac ux is just minimize them when closed unless forced quite CMD+Q
|
||||
macWindowClosed = true;
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
// mac ux is just minimize them when closed unless forced quite CMD+Q
|
||||
macWindowClosed = true;
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE: this doesnt work in linux
|
||||
// you have to loop through process.argv to figure out which url launched the app
|
||||
app.on("open-url", (event, url) => {
|
||||
// ie url: tea://packages/slug
|
||||
event.preventDefault();
|
||||
// ie url: tea://packages/slug
|
||||
event.preventDefault();
|
||||
|
||||
const packagesPrefix = "/packages/";
|
||||
const packagesPrefix = "/packages/";
|
||||
|
||||
let rawPath = url.replace("tea:/", "");
|
||||
let rawPath = url.replace("tea:/", "");
|
||||
|
||||
const isPackage = url.includes(packagesPrefix);
|
||||
if (isPackage) {
|
||||
// /packages/github.com/pypa/twine -> /packages/github_com_pypa_twine
|
||||
const packageSlug = nameToSlug(rawPath.replace(packagesPrefix, ""));
|
||||
rawPath = [packagesPrefix, packageSlug].join("");
|
||||
}
|
||||
const isPackage = url.includes(packagesPrefix);
|
||||
if (isPackage) {
|
||||
// /packages/github.com/pypa/twine -> /packages/github_com_pypa_twine
|
||||
const packageSlug = nameToSlug(rawPath.replace(packagesPrefix, ""));
|
||||
rawPath = [packagesPrefix, packageSlug].join("");
|
||||
}
|
||||
|
||||
setProtocolPath(rawPath);
|
||||
setProtocolPath(rawPath);
|
||||
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
log.info("restored");
|
||||
}
|
||||
mainWindow.webContents.send("sync-path");
|
||||
log.info("synced path", rawPath);
|
||||
} else if (macWindowClosed) {
|
||||
log.info("open new window");
|
||||
createMainWindow();
|
||||
}
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
log.info("restored");
|
||||
}
|
||||
mainWindow.webContents.send("sync-path");
|
||||
log.info("synced path", rawPath);
|
||||
} else if (macWindowClosed) {
|
||||
log.info("open new window");
|
||||
createMainWindow();
|
||||
}
|
||||
});
|
||||
|
||||
function notifyMainWindow(channel: string, data: unknown) {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send(channel, data);
|
||||
}
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send(channel, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,145 +10,145 @@ const sessionFilePath = path.join(getTeaPath(), "tea.xyz/gui/tmp.dat");
|
|||
const sessionFolder = path.join(getTeaPath(), "tea.xyz/gui");
|
||||
|
||||
export interface Session {
|
||||
device_id?: string;
|
||||
key?: string;
|
||||
user?: any;
|
||||
locale?: string;
|
||||
device_id?: string;
|
||||
key?: string;
|
||||
user?: any;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
let sessionMemory: Session = { device_id: "", locale: "en" };
|
||||
const initialized: Promise<Session> = new Promise((resolve, reject) => {
|
||||
try {
|
||||
log.info("initializing GUI session folder");
|
||||
createInitialSessionFile().then((newSession) => {
|
||||
resolve(newSession);
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
reject(error);
|
||||
}
|
||||
try {
|
||||
log.info("initializing GUI session folder");
|
||||
createInitialSessionFile().then((newSession) => {
|
||||
resolve(newSession);
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
async function addEmptySessionFile(): Promise<Session> {
|
||||
const locale = app.getLocale();
|
||||
await mkdirp(sessionFolder);
|
||||
const data = {
|
||||
device_id: await getDeviceId(),
|
||||
locale
|
||||
};
|
||||
await writeSessionData(data, true);
|
||||
log.info("new session file created");
|
||||
return data;
|
||||
const locale = app.getLocale();
|
||||
await mkdirp(sessionFolder);
|
||||
const data = {
|
||||
device_id: await getDeviceId(),
|
||||
locale
|
||||
};
|
||||
await writeSessionData(data, true);
|
||||
log.info("new session file created");
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function createInitialSessionFile(): Promise<Session> {
|
||||
// TODO: this looks nasty, refactor this
|
||||
// the app is too dependent that this function succeeds
|
||||
let session = {
|
||||
...sessionMemory
|
||||
};
|
||||
const locale = app.getLocale();
|
||||
// TODO: this looks nasty, refactor this
|
||||
// the app is too dependent that this function succeeds
|
||||
let session = {
|
||||
...sessionMemory
|
||||
};
|
||||
const locale = app.getLocale();
|
||||
|
||||
try {
|
||||
if (fs.existsSync(sessionFilePath)) {
|
||||
log.info("session file exists!");
|
||||
const sessionBuffer = await fs.readFileSync(sessionFilePath);
|
||||
const sessionData = JSON.parse(sessionBuffer.toString()) as Session;
|
||||
try {
|
||||
if (fs.existsSync(sessionFilePath)) {
|
||||
log.info("session file exists!");
|
||||
const sessionBuffer = await fs.readFileSync(sessionFilePath);
|
||||
const sessionData = JSON.parse(sessionBuffer.toString()) as Session;
|
||||
|
||||
if (!sessionData?.device_id) {
|
||||
throw new Error("device_id is empty!");
|
||||
} else {
|
||||
session = sessionData;
|
||||
session.locale = locale;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
if (!sessionData?.device_id) {
|
||||
throw new Error("device_id is empty!");
|
||||
} else {
|
||||
session = sessionData;
|
||||
session.locale = locale;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
if (!session?.device_id) {
|
||||
try {
|
||||
const newSession = await addEmptySessionFile();
|
||||
if (newSession) {
|
||||
session = newSession;
|
||||
session.locale = locale;
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
if (!session?.device_id) {
|
||||
try {
|
||||
const newSession = await addEmptySessionFile();
|
||||
if (newSession) {
|
||||
session = newSession;
|
||||
session.locale = locale;
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
sessionMemory = session;
|
||||
sessionMemory = session;
|
||||
|
||||
return session;
|
||||
return session;
|
||||
}
|
||||
|
||||
let deviceIdRetryCount = 0;
|
||||
async function getDeviceId() {
|
||||
let deviceId = "";
|
||||
try {
|
||||
const req = await axios.get<{ deviceId: string }>("https://api.tea.xyz/v1/auth/registerDevice");
|
||||
deviceId = req.data.deviceId;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
let deviceId = "";
|
||||
try {
|
||||
const req = await axios.get<{ deviceId: string }>("https://api.tea.xyz/v1/auth/registerDevice");
|
||||
deviceId = req.data.deviceId;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
if (deviceIdRetryCount < 3 && !deviceId) {
|
||||
deviceIdRetryCount++;
|
||||
deviceId = await getDeviceId();
|
||||
}
|
||||
if (deviceIdRetryCount < 3 && !deviceId) {
|
||||
deviceIdRetryCount++;
|
||||
deviceId = await getDeviceId();
|
||||
}
|
||||
|
||||
return deviceId;
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
export async function readSessionData(): Promise<Session> {
|
||||
log.info("read session data.");
|
||||
const data = await initialized;
|
||||
log.info(
|
||||
"initialized session device_id:",
|
||||
data?.device_id,
|
||||
"developer_id:",
|
||||
data?.user?.developer_id
|
||||
);
|
||||
if (sessionMemory?.device_id) {
|
||||
log.debug("use session cache");
|
||||
return sessionMemory;
|
||||
}
|
||||
log.info("read session data.");
|
||||
const data = await initialized;
|
||||
log.info(
|
||||
"initialized session device_id:",
|
||||
data?.device_id,
|
||||
"developer_id:",
|
||||
data?.user?.developer_id
|
||||
);
|
||||
if (sessionMemory?.device_id) {
|
||||
log.debug("use session cache");
|
||||
return sessionMemory;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("re-reading session data");
|
||||
const locale = app.getLocale();
|
||||
const sessionBuffer = await fs.readFileSync(sessionFilePath);
|
||||
const session = JSON.parse(sessionBuffer.toString()) as Session;
|
||||
if (!session?.device_id) throw new Error("device_id is empty!");
|
||||
try {
|
||||
log.info("re-reading session data");
|
||||
const locale = app.getLocale();
|
||||
const sessionBuffer = await fs.readFileSync(sessionFilePath);
|
||||
const session = JSON.parse(sessionBuffer.toString()) as Session;
|
||||
if (!session?.device_id) throw new Error("device_id is empty!");
|
||||
|
||||
session.locale = locale;
|
||||
sessionMemory = session;
|
||||
log.info("re-read session data done");
|
||||
} catch (error) {
|
||||
sessionMemory = await createInitialSessionFile();
|
||||
log.error(error);
|
||||
}
|
||||
return sessionMemory;
|
||||
session.locale = locale;
|
||||
sessionMemory = session;
|
||||
log.info("re-read session data done");
|
||||
} catch (error) {
|
||||
sessionMemory = await createInitialSessionFile();
|
||||
log.error(error);
|
||||
}
|
||||
return sessionMemory;
|
||||
}
|
||||
|
||||
export async function writeSessionData(data: Session, force?: boolean) {
|
||||
try {
|
||||
const existingData = force ? sessionMemory : await readSessionData();
|
||||
sessionMemory = {
|
||||
...existingData,
|
||||
...data
|
||||
};
|
||||
try {
|
||||
const existingData = force ? sessionMemory : await readSessionData();
|
||||
sessionMemory = {
|
||||
...existingData,
|
||||
...data
|
||||
};
|
||||
|
||||
if (!sessionMemory.device_id) throw new Error("writing without device_id is not allowed!");
|
||||
if (!sessionMemory.device_id) throw new Error("writing without device_id is not allowed!");
|
||||
|
||||
log.info("creating:", sessionFolder);
|
||||
await mkdirp(sessionFolder);
|
||||
log.info("writing session data:", sessionMemory); // rm this
|
||||
await fs.writeFileSync(sessionFilePath, JSON.stringify(sessionMemory), {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
log.info("creating:", sessionFolder);
|
||||
await mkdirp(sessionFolder);
|
||||
log.info("writing session data:", sessionMemory); // rm this
|
||||
await fs.writeFileSync(sessionFilePath, JSON.stringify(sessionMemory), {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import log from "./logger";
|
|||
import { BrowserWindow } from "electron";
|
||||
|
||||
type AutoUpdateStatus = {
|
||||
status: "up-to-date" | "available" | "ready";
|
||||
version?: string;
|
||||
status: "up-to-date" | "available" | "ready";
|
||||
version?: string;
|
||||
};
|
||||
|
||||
autoUpdater.logger = log;
|
||||
|
@ -18,59 +18,59 @@ let lastStatus: AutoUpdateStatus = { status: "up-to-date" };
|
|||
export const getUpdater = () => autoUpdater;
|
||||
|
||||
export function checkUpdater(mainWindow: BrowserWindow): AppUpdater {
|
||||
try {
|
||||
window = mainWindow;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
try {
|
||||
window = mainWindow;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
|
||||
if (!initalized) {
|
||||
initalized = true;
|
||||
if (!initalized) {
|
||||
initalized = true;
|
||||
|
||||
setInterval(() => {
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
}, 1000 * 60 * 30); // check for updates every 30 minutes
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
setInterval(() => {
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
}, 1000 * 60 * 30); // check for updates every 30 minutes
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
return autoUpdater;
|
||||
return autoUpdater;
|
||||
}
|
||||
|
||||
// The auto update runs in the background so the window might not be open when the status changes
|
||||
// When the update store gets created as part of the window it will request the latest status.
|
||||
export function getAutoUpdateStatus() {
|
||||
return lastStatus;
|
||||
return lastStatus;
|
||||
}
|
||||
|
||||
function sendStatusToWindow(status: AutoUpdateStatus) {
|
||||
lastStatus = status;
|
||||
window?.webContents.send("app-update-status", status);
|
||||
lastStatus = status;
|
||||
window?.webContents.send("app-update-status", status);
|
||||
}
|
||||
|
||||
autoUpdater.on("checking-for-update", () => {
|
||||
log.info("checking for tea gui update");
|
||||
log.info("checking for tea gui update");
|
||||
});
|
||||
|
||||
autoUpdater.on("update-available", (info) => {
|
||||
sendStatusToWindow({ status: "available" });
|
||||
sendStatusToWindow({ status: "available" });
|
||||
});
|
||||
|
||||
autoUpdater.on("update-not-available", () => {
|
||||
log.info("no update for tea gui");
|
||||
sendStatusToWindow({ status: "up-to-date" });
|
||||
log.info("no update for tea gui");
|
||||
sendStatusToWindow({ status: "up-to-date" });
|
||||
});
|
||||
|
||||
autoUpdater.on("error", (err) => {
|
||||
log.error("auto update:", err);
|
||||
log.error("auto update:", err);
|
||||
});
|
||||
|
||||
autoUpdater.on("download-progress", (progressObj) => {
|
||||
let log_message = "Download speed: " + progressObj.bytesPerSecond;
|
||||
log_message = log_message + " - Downloaded " + progressObj.percent + "%";
|
||||
log_message = log_message + " (" + progressObj.transferred + "/" + progressObj.total + ")";
|
||||
log.info("tea gui:", log_message);
|
||||
let log_message = "Download speed: " + progressObj.bytesPerSecond;
|
||||
log_message = log_message + " - Downloaded " + progressObj.percent + "%";
|
||||
log_message = log_message + " (" + progressObj.transferred + "/" + progressObj.total + ")";
|
||||
log.info("tea gui:", log_message);
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", (info) => {
|
||||
sendStatusToWindow({ status: "ready", version: info.version });
|
||||
sendStatusToWindow({ status: "ready", version: info.version });
|
||||
});
|
||||
|
|
|
@ -13,188 +13,188 @@ const destinationDirectory = getGuiPath();
|
|||
export const cliBinPath = path.join(destinationDirectory, "tea");
|
||||
|
||||
export async function installPackage(
|
||||
full_name: string,
|
||||
version: string,
|
||||
notifyMainWindow: MainWindowNotifier
|
||||
full_name: string,
|
||||
version: string,
|
||||
notifyMainWindow: MainWindowNotifier
|
||||
) {
|
||||
const teaVersion = await initializeTeaCli();
|
||||
const progressNotifier = newInstallProgressNotifier(full_name, notifyMainWindow);
|
||||
const teaVersion = await initializeTeaCli();
|
||||
const progressNotifier = newInstallProgressNotifier(full_name, notifyMainWindow);
|
||||
|
||||
if (!teaVersion) throw new Error("no tea");
|
||||
if (!teaVersion) throw new Error("no tea");
|
||||
|
||||
const qualifedPackage = `${full_name}@${version}`;
|
||||
const qualifedPackage = `${full_name}@${version}`;
|
||||
|
||||
log.info(`installing package ${qualifedPackage}`);
|
||||
log.info(`installing package ${qualifedPackage}`);
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
// tea requires HOME to be set.
|
||||
const opts = { env: { HOME: app.getPath("home"), NO_COLOR: "1" } };
|
||||
await new Promise((resolve, reject) => {
|
||||
// tea requires HOME to be set.
|
||||
const opts = { env: { HOME: app.getPath("home"), NO_COLOR: "1" } };
|
||||
|
||||
const child = spawn(
|
||||
`${destinationDirectory}/tea`,
|
||||
["--env=false", "--sync", "--json", `+${qualifedPackage}`],
|
||||
opts
|
||||
);
|
||||
const child = spawn(
|
||||
`${destinationDirectory}/tea`,
|
||||
["--env=false", "--sync", "--json", `+${qualifedPackage}`],
|
||||
opts
|
||||
);
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
stdout += data.toString().trim();
|
||||
});
|
||||
child.stdout.on("data", (data) => {
|
||||
stdout += data.toString().trim();
|
||||
});
|
||||
|
||||
child.stderr.on("data", (data) => {
|
||||
try {
|
||||
const msg = JSON.parse(data.toString().trim());
|
||||
progressNotifier(msg);
|
||||
} catch (err) {
|
||||
//swallow it
|
||||
}
|
||||
child.stderr.on("data", (data) => {
|
||||
try {
|
||||
const msg = JSON.parse(data.toString().trim());
|
||||
progressNotifier(msg);
|
||||
} catch (err) {
|
||||
//swallow it
|
||||
}
|
||||
|
||||
stderr += data.toString().trim();
|
||||
});
|
||||
stderr += data.toString().trim();
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
console.log("stdout:", stdout);
|
||||
if (code !== 0) {
|
||||
reject(new Error("tea exited with non-zero code: " + code));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
child.on("exit", (code) => {
|
||||
console.log("stdout:", stdout);
|
||||
if (code !== 0) {
|
||||
reject(new Error("tea exited with non-zero code: " + code));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", () => {
|
||||
reject(new Error(stderr));
|
||||
});
|
||||
});
|
||||
child.on("error", () => {
|
||||
reject(new Error(stderr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// This is hacky and kind of complex because of the output we get from the CLI. When the CLI
|
||||
// gives better output this definitely should get looked at.
|
||||
function newInstallProgressNotifier(full_name: string, notifyMainWindow: MainWindowNotifier) {
|
||||
// the install progress is super spammy, only send every 10th update
|
||||
let counter = 0;
|
||||
// the install progress is super spammy, only send every 10th update
|
||||
let counter = 0;
|
||||
|
||||
// the totall number of packages to install - this is set by the "resolved" message
|
||||
let numberOfPackages = 1;
|
||||
// the totall number of packages to install - this is set by the "resolved" message
|
||||
let numberOfPackages = 1;
|
||||
|
||||
// the current package number - this is incremented by the "installed" or "downloaded" message
|
||||
let currentPackageNumber = 0;
|
||||
// the current package number - this is incremented by the "installed" or "downloaded" message
|
||||
let currentPackageNumber = 0;
|
||||
|
||||
return function (msg: any) {
|
||||
if (msg.status !== "downloading") {
|
||||
log.info("cli:", msg);
|
||||
}
|
||||
return function (msg: any) {
|
||||
if (msg.status !== "downloading") {
|
||||
log.info("cli:", msg);
|
||||
}
|
||||
|
||||
if (msg.status === "resolved") {
|
||||
numberOfPackages = msg.pkgs?.length ?? 1;
|
||||
log.info(`installing ${numberOfPackages} packages`);
|
||||
} else if (msg.status === "downloading") {
|
||||
counter++;
|
||||
if (counter % 10 !== 0) return;
|
||||
if (msg.status === "resolved") {
|
||||
numberOfPackages = msg.pkgs?.length ?? 1;
|
||||
log.info(`installing ${numberOfPackages} packages`);
|
||||
} else if (msg.status === "downloading") {
|
||||
counter++;
|
||||
if (counter % 10 !== 0) return;
|
||||
|
||||
const { received = 0, "content-size": contentSize = 0 } = msg;
|
||||
if (contentSize > 0) {
|
||||
// how many total pacakges are completed
|
||||
const overallProgress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
// how much of the current package is completed
|
||||
const packageProgress = (received / contentSize) * 100;
|
||||
// progress is the total packages completed plus the percentage of the current package
|
||||
const progress = overallProgress + packageProgress / numberOfPackages;
|
||||
notifyMainWindow("install-progress", { full_name, progress });
|
||||
}
|
||||
} else if (msg.status === "installed") {
|
||||
currentPackageNumber++;
|
||||
const progress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
notifyMainWindow("install-progress", { full_name, progress });
|
||||
notifyPackageInstalled(msg.pkg, notifyMainWindow);
|
||||
}
|
||||
};
|
||||
const { received = 0, "content-size": contentSize = 0 } = msg;
|
||||
if (contentSize > 0) {
|
||||
// how many total pacakges are completed
|
||||
const overallProgress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
// how much of the current package is completed
|
||||
const packageProgress = (received / contentSize) * 100;
|
||||
// progress is the total packages completed plus the percentage of the current package
|
||||
const progress = overallProgress + packageProgress / numberOfPackages;
|
||||
notifyMainWindow("install-progress", { full_name, progress });
|
||||
}
|
||||
} else if (msg.status === "installed") {
|
||||
currentPackageNumber++;
|
||||
const progress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
notifyMainWindow("install-progress", { full_name, progress });
|
||||
notifyPackageInstalled(msg.pkg, notifyMainWindow);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const notifyPackageInstalled = (rawPkg: string, notifyMainWindow: MainWindowNotifier) => {
|
||||
try {
|
||||
const [full_name, version] = rawPkg.split("=");
|
||||
notifyMainWindow("pkg-installed", { full_name, version });
|
||||
} catch (err) {
|
||||
log.error("failed to notify package installed", err);
|
||||
}
|
||||
try {
|
||||
const [full_name, version] = rawPkg.split("=");
|
||||
notifyMainWindow("pkg-installed", { full_name, version });
|
||||
} catch (err) {
|
||||
log.error("failed to notify package installed", err);
|
||||
}
|
||||
};
|
||||
|
||||
export async function openPackageEntrypointInTerminal(pkg: string) {
|
||||
let sh = `${cliBinPath} --sync --env=false +${pkg} `;
|
||||
if (pkg == "github.com/AUTOMATIC1111/stable-diffusion-webui") {
|
||||
sh += `~/.tea/${pkg}/v*/entrypoint.sh`;
|
||||
} else {
|
||||
sh += "sh";
|
||||
}
|
||||
let sh = `${cliBinPath} --sync --env=false +${pkg} `;
|
||||
if (pkg == "github.com/AUTOMATIC1111/stable-diffusion-webui") {
|
||||
sh += `~/.tea/${pkg}/v*/entrypoint.sh`;
|
||||
} else {
|
||||
sh += "sh";
|
||||
}
|
||||
|
||||
const scriptPath = await createCommandScriptFile(sh);
|
||||
const scriptPath = await createCommandScriptFile(sh);
|
||||
|
||||
try {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
try {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("/usr/bin/osascript", [scriptPath]);
|
||||
child.stdout.on("data", (data) => {
|
||||
stdout += data.toString().trim();
|
||||
});
|
||||
child.stderr.on("data", (data) => {
|
||||
stderr += data.toString().trim();
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("/usr/bin/osascript", [scriptPath]);
|
||||
child.stdout.on("data", (data) => {
|
||||
stdout += data.toString().trim();
|
||||
});
|
||||
child.stderr.on("data", (data) => {
|
||||
stderr += data.toString().trim();
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
log.info("exit:", code, `\`${stdout}\``);
|
||||
if (code == 0) {
|
||||
resolve(stdout);
|
||||
} else {
|
||||
reject(new Error("failed to open terminal and run tea sh"));
|
||||
}
|
||||
});
|
||||
child.on("exit", (code) => {
|
||||
log.info("exit:", code, `\`${stdout}\``);
|
||||
if (code == 0) {
|
||||
resolve(stdout);
|
||||
} else {
|
||||
reject(new Error("failed to open terminal and run tea sh"));
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", () => {
|
||||
reject(new Error(stderr));
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
if (scriptPath) await fs.unlinkSync(scriptPath);
|
||||
}
|
||||
child.on("error", () => {
|
||||
reject(new Error(stderr));
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
if (scriptPath) await fs.unlinkSync(scriptPath);
|
||||
}
|
||||
}
|
||||
|
||||
const createCommandScriptFile = async (cmd: string): Promise<string> => {
|
||||
const guiFolder = getGuiPath();
|
||||
const tmpFilePath = path.join(guiFolder, `${+new Date()}.scpt`);
|
||||
const command = `${cmd.replace(/"/g, '\\"')}`;
|
||||
const script = `
|
||||
const guiFolder = getGuiPath();
|
||||
const tmpFilePath = path.join(guiFolder, `${+new Date()}.scpt`);
|
||||
const command = `${cmd.replace(/"/g, '\\"')}`;
|
||||
const script = `
|
||||
tell application "Terminal"
|
||||
activate
|
||||
do script "${command}"
|
||||
end tell
|
||||
`.trim();
|
||||
|
||||
await fs.writeFileSync(tmpFilePath, script, "utf-8");
|
||||
return tmpFilePath;
|
||||
await fs.writeFileSync(tmpFilePath, script, "utf-8");
|
||||
return tmpFilePath;
|
||||
};
|
||||
|
||||
export async function asyncExec(cmd: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (err, stdout) => {
|
||||
if (err) {
|
||||
console.log("err:", err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
console.log("stdout:", stdout);
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (err, stdout) => {
|
||||
if (err) {
|
||||
console.log("err:", err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
console.log("stdout:", stdout);
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncPantry() {
|
||||
const teaVersion = await initializeTeaCli();
|
||||
const teaVersion = await initializeTeaCli();
|
||||
|
||||
if (!teaVersion) throw new Error("no tea");
|
||||
log.info("Syncing pantry");
|
||||
await asyncExec(`cd '${destinationDirectory}' && ./tea -S`);
|
||||
if (!teaVersion) throw new Error("no tea");
|
||||
log.info("Syncing pantry");
|
||||
await asyncExec(`cd '${destinationDirectory}' && ./tea -S`);
|
||||
}
|
||||
|
|
|
@ -19,73 +19,73 @@ const binaryUrl = "https://tea.xyz/$(uname)/$(uname -m)";
|
|||
let initializePromise: Promise<string> | null = null;
|
||||
|
||||
export async function initializeTeaCli(): Promise<string> {
|
||||
if (initializePromise) {
|
||||
return initializePromise;
|
||||
}
|
||||
if (initializePromise) {
|
||||
return initializePromise;
|
||||
}
|
||||
|
||||
log.info("Initializing tea cli");
|
||||
initializePromise = initializeTeaCliInternal();
|
||||
log.info("Initializing tea cli");
|
||||
initializePromise = initializeTeaCliInternal();
|
||||
|
||||
initializePromise.catch((error) => {
|
||||
log.info("Error initializing tea cli, resetting promise:", error);
|
||||
initializePromise = null;
|
||||
});
|
||||
initializePromise.catch((error) => {
|
||||
log.info("Error initializing tea cli, resetting promise:", error);
|
||||
initializePromise = null;
|
||||
});
|
||||
|
||||
return initializePromise;
|
||||
return initializePromise;
|
||||
}
|
||||
|
||||
async function initializeTeaCliInternal(): Promise<string> {
|
||||
try {
|
||||
let binCheck = "";
|
||||
let needsUpdate = false;
|
||||
try {
|
||||
let binCheck = "";
|
||||
let needsUpdate = false;
|
||||
|
||||
// Create the destination directory if it doesn't exist
|
||||
if (!fs.existsSync(destinationDirectory)) {
|
||||
fs.mkdirSync(destinationDirectory, { recursive: true });
|
||||
}
|
||||
// Create the destination directory if it doesn't exist
|
||||
if (!fs.existsSync(destinationDirectory)) {
|
||||
fs.mkdirSync(destinationDirectory, { recursive: true });
|
||||
}
|
||||
|
||||
const curlCommand = `curl -L -o "${cliBinPath}" "${binaryUrl}"`;
|
||||
const curlCommand = `curl -L -o "${cliBinPath}" "${binaryUrl}"`;
|
||||
|
||||
const exists = fs.existsSync(cliBinPath);
|
||||
if (exists) {
|
||||
log.info("binary tea already exists at", cliBinPath);
|
||||
try {
|
||||
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
|
||||
const teaVersion = binCheck.toString().split(" ")[1];
|
||||
if (semverCompare(teaVersion, MINIMUM_TEA_VERSION) < 0) {
|
||||
log.info("binary tea version is too old, updating");
|
||||
needsUpdate = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// probably binary is not executable or no permission
|
||||
log.error("Error checking tea binary version:", error);
|
||||
needsUpdate = true;
|
||||
await asyncExec(`cd ${destinationDirectory} && rm tea`);
|
||||
}
|
||||
}
|
||||
const exists = fs.existsSync(cliBinPath);
|
||||
if (exists) {
|
||||
log.info("binary tea already exists at", cliBinPath);
|
||||
try {
|
||||
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
|
||||
const teaVersion = binCheck.toString().split(" ")[1];
|
||||
if (semverCompare(teaVersion, MINIMUM_TEA_VERSION) < 0) {
|
||||
log.info("binary tea version is too old, updating");
|
||||
needsUpdate = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// probably binary is not executable or no permission
|
||||
log.error("Error checking tea binary version:", error);
|
||||
needsUpdate = true;
|
||||
await asyncExec(`cd ${destinationDirectory} && rm tea`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists || needsUpdate) {
|
||||
try {
|
||||
await asyncExec(curlCommand);
|
||||
log.info("Binary downloaded and saved to", cliBinPath);
|
||||
await asyncExec("chmod u+x " + cliBinPath);
|
||||
log.info("Binary is now ready for use at", cliBinPath);
|
||||
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
|
||||
} catch (error) {
|
||||
log.error("Error setting-up tea binary:", error);
|
||||
}
|
||||
}
|
||||
if (!exists || needsUpdate) {
|
||||
try {
|
||||
await asyncExec(curlCommand);
|
||||
log.info("Binary downloaded and saved to", cliBinPath);
|
||||
await asyncExec("chmod u+x " + cliBinPath);
|
||||
log.info("Binary is now ready for use at", cliBinPath);
|
||||
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
|
||||
} catch (error) {
|
||||
log.error("Error setting-up tea binary:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const version = binCheck.toString().split(" ")[1];
|
||||
log.info("binary tea version:", version);
|
||||
return semver.valid(version.trim()) ? version : "";
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return "";
|
||||
}
|
||||
const version = binCheck.toString().split(" ")[1];
|
||||
log.info("binary tea version:", version);
|
||||
return semver.valid(version.trim()) ? version : "";
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export default async function initialize(): Promise<string> {
|
||||
const [version] = await Promise.all([initializeTeaCli(), createInitialSessionFile()]);
|
||||
return version;
|
||||
const [version] = await Promise.all([initializeTeaCli(), createInitialSessionFile()]);
|
||||
return version;
|
||||
}
|
||||
|
|
|
@ -15,194 +15,194 @@ import { nanoid } from "nanoid";
|
|||
import { MainWindowNotifier } from "./types";
|
||||
|
||||
export type HandlerOptions = {
|
||||
// A function to call back to the current main
|
||||
notifyMainWindow: MainWindowNotifier;
|
||||
// A function to call back to the current main
|
||||
notifyMainWindow: MainWindowNotifier;
|
||||
};
|
||||
|
||||
let teaProtocolPath = ""; // this should be empty string
|
||||
export const setProtocolPath = (path: string) => {
|
||||
teaProtocolPath = path;
|
||||
teaProtocolPath = path;
|
||||
};
|
||||
|
||||
export default function initializeHandlers({ notifyMainWindow }: HandlerOptions) {
|
||||
ipcMain.handle("get-installed-packages", async () => {
|
||||
try {
|
||||
log.info("getting installed packages");
|
||||
const pkgs = await getInstalledPackages();
|
||||
log.info(`got installed packages: ${pkgs.length}`);
|
||||
return pkgs;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
ipcMain.handle("get-installed-packages", async () => {
|
||||
try {
|
||||
log.info("getting installed packages");
|
||||
const pkgs = await getInstalledPackages();
|
||||
log.info(`got installed packages: ${pkgs.length}`);
|
||||
return pkgs;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-session", async () => {
|
||||
try {
|
||||
log.info("getting session");
|
||||
const session = await readSessionData();
|
||||
log.debug(session ? "found session data" : "no session data found");
|
||||
return session;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
ipcMain.handle("get-session", async () => {
|
||||
try {
|
||||
log.info("getting session");
|
||||
const session = await readSessionData();
|
||||
log.debug(session ? "found session data" : "no session data found");
|
||||
return session;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("update-session", async (_, data) => {
|
||||
try {
|
||||
log.info("updating session data with", data); // rm this
|
||||
await writeSessionData(data as Session);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
ipcMain.handle("update-session", async (_, data) => {
|
||||
try {
|
||||
log.info("updating session data with", data); // rm this
|
||||
await writeSessionData(data as Session);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("install-package", async (_, { full_name, version }) => {
|
||||
try {
|
||||
return await installPackage(full_name, version, notifyMainWindow);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error;
|
||||
}
|
||||
});
|
||||
ipcMain.handle("install-package", async (_, { full_name, version }) => {
|
||||
try {
|
||||
return await installPackage(full_name, version, notifyMainWindow);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("sync-pantry", async () => {
|
||||
try {
|
||||
return await syncPantry();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error;
|
||||
}
|
||||
});
|
||||
ipcMain.handle("sync-pantry", async () => {
|
||||
try {
|
||||
return await syncPantry();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("open-terminal", async (_, data) => {
|
||||
const { pkg } = data as { pkg: string };
|
||||
try {
|
||||
// TODO: detect if mac or linux
|
||||
// current openTerminal is only design for Mac
|
||||
log.info("open tea entrypoint in terminal for pkg:", pkg);
|
||||
await openPackageEntrypointInTerminal(pkg);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
ipcMain.handle("open-terminal", async (_, data) => {
|
||||
const { pkg } = data as { pkg: string };
|
||||
try {
|
||||
// TODO: detect if mac or linux
|
||||
// current openTerminal is only design for Mac
|
||||
log.info("open tea entrypoint in terminal for pkg:", pkg);
|
||||
await openPackageEntrypointInTerminal(pkg);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("relaunch", async () => {
|
||||
try {
|
||||
log.info("relaunching app");
|
||||
const autoUpdater = getUpdater();
|
||||
await autoUpdater.quitAndInstall();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
ipcMain.handle("relaunch", async () => {
|
||||
try {
|
||||
log.info("relaunching app");
|
||||
const autoUpdater = getUpdater();
|
||||
await autoUpdater.quitAndInstall();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-protocol-path", async () => {
|
||||
const path = teaProtocolPath;
|
||||
teaProtocolPath = "";
|
||||
return path;
|
||||
});
|
||||
ipcMain.handle("get-protocol-path", async () => {
|
||||
const path = teaProtocolPath;
|
||||
teaProtocolPath = "";
|
||||
return path;
|
||||
});
|
||||
|
||||
ipcMain.handle("submit-logs", async () => {
|
||||
try {
|
||||
log.info("syncing logs");
|
||||
const { device_id } = await readSessionData();
|
||||
const logId = [device_id, nanoid()].join("---");
|
||||
ipcMain.handle("submit-logs", async () => {
|
||||
try {
|
||||
log.info("syncing logs");
|
||||
const { device_id } = await readSessionData();
|
||||
const logId = [device_id, nanoid()].join("---");
|
||||
|
||||
// sync in background
|
||||
syncLogsAt(logId)
|
||||
.then(() => {
|
||||
log.info("logs synced:", logId);
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error(error);
|
||||
});
|
||||
// sync in background
|
||||
syncLogsAt(logId)
|
||||
.then(() => {
|
||||
log.info("logs synced:", logId);
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error(error);
|
||||
});
|
||||
|
||||
return logId;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error.message;
|
||||
}
|
||||
});
|
||||
return logId;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error.message;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("set-badge-count", async (_, { count }) => {
|
||||
if (count) {
|
||||
app.dock.setBadge(count.toString());
|
||||
} else {
|
||||
app.dock.setBadge("");
|
||||
}
|
||||
});
|
||||
ipcMain.handle("set-badge-count", async (_, { count }) => {
|
||||
if (count) {
|
||||
app.dock.setBadge(count.toString());
|
||||
} else {
|
||||
app.dock.setBadge("");
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
"delete-package",
|
||||
async (_, { fullName, version }: { fullName: string; version: string }) => {
|
||||
try {
|
||||
log.info("deleting package:", fullName);
|
||||
await deletePackageFolder(fullName, version);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
);
|
||||
ipcMain.handle(
|
||||
"delete-package",
|
||||
async (_, { fullName, version }: { fullName: string; version: string }) => {
|
||||
try {
|
||||
log.info("deleting package:", fullName);
|
||||
await deletePackageFolder(fullName, version);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle("write-package-cache", async (_, data) => {
|
||||
try {
|
||||
await writePackageCache(data as Packages);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
ipcMain.handle("write-package-cache", async (_, data) => {
|
||||
try {
|
||||
await writePackageCache(data as Packages);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("load-package-cache", async () => {
|
||||
try {
|
||||
return await loadPackageCache();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { version: "1", packages: {} };
|
||||
}
|
||||
});
|
||||
ipcMain.handle("load-package-cache", async () => {
|
||||
try {
|
||||
return await loadPackageCache();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { version: "1", packages: {} };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-tea-version", async () => {
|
||||
try {
|
||||
log.info("installing tea cli");
|
||||
const version = await initializeTeaCli();
|
||||
if (!version) {
|
||||
throw new Error("failed to install tea cli");
|
||||
}
|
||||
ipcMain.handle("get-tea-version", async () => {
|
||||
try {
|
||||
log.info("installing tea cli");
|
||||
const version = await initializeTeaCli();
|
||||
if (!version) {
|
||||
throw new Error("failed to install tea cli");
|
||||
}
|
||||
|
||||
return { version, message: "" };
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { version: "", message: error.message };
|
||||
}
|
||||
});
|
||||
return { version, message: "" };
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { version: "", message: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("topbar-double-click", async (event: Electron.IpcMainInvokeEvent) => {
|
||||
const mainWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
if (mainWindow) {
|
||||
mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
ipcMain.handle("topbar-double-click", async (event: Electron.IpcMainInvokeEvent) => {
|
||||
const mainWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
if (mainWindow) {
|
||||
mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("cache-image", async (_event, url) => {
|
||||
try {
|
||||
log.info("caching:", url);
|
||||
const cachedImagePath = await cacheImage(url);
|
||||
return cachedImagePath;
|
||||
} catch (error) {
|
||||
log.error("Failed to cache image:", error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
ipcMain.handle("cache-image", async (_event, url) => {
|
||||
try {
|
||||
log.info("caching:", url);
|
||||
const cachedImagePath = await cacheImage(url);
|
||||
return cachedImagePath;
|
||||
} catch (error) {
|
||||
log.error("Failed to cache image:", error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-auto-update-status", async () => {
|
||||
try {
|
||||
log.info("getting auto update status");
|
||||
return getAutoUpdateStatus();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
ipcMain.handle("get-auto-update-status", async () => {
|
||||
try {
|
||||
log.info("getting auto update status");
|
||||
return getAutoUpdateStatus();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import log from "electron-log";
|
||||
|
||||
export const setSentryLogging = (sentry: any) => {
|
||||
const oldError = log.error;
|
||||
const oldError = log.error;
|
||||
|
||||
log.error = (...params: any[]) => {
|
||||
oldError(params);
|
||||
sentry.captureException(params[0].message);
|
||||
};
|
||||
log.error = (...params: any[]) => {
|
||||
oldError(params);
|
||||
sentry.captureException(params[0].message);
|
||||
};
|
||||
};
|
||||
|
||||
// Export the log object to use it throughout the app
|
||||
|
|
|
@ -9,37 +9,37 @@ const pkgsFilePath = path.join(getTeaPath(), "tea.xyz/gui/pkgs.json");
|
|||
const pkgsFolder = path.join(getTeaPath(), "tea.xyz/gui");
|
||||
|
||||
export async function writePackageCache(pkgs: Packages) {
|
||||
try {
|
||||
if (!pkgs || !Object.keys(pkgs.packages).length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!pkgs || !Object.keys(pkgs.packages).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`writing data for ${Object.keys(pkgs.packages).length} packages to ${pkgsFilePath}`);
|
||||
await mkdirp(pkgsFolder);
|
||||
fs.writeFileSync(pkgsFilePath, JSON.stringify(pkgs), {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
log.info(`writing data for ${Object.keys(pkgs.packages).length} packages to ${pkgsFilePath}`);
|
||||
await mkdirp(pkgsFolder);
|
||||
fs.writeFileSync(pkgsFilePath, JSON.stringify(pkgs), {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPackageCache(): Promise<Packages> {
|
||||
try {
|
||||
log.info(`loading package cache from ${pkgsFilePath}`);
|
||||
const pkgData = fs.readFileSync(pkgsFilePath);
|
||||
return JSON.parse(pkgData.toString()) as Packages;
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
log.error(err);
|
||||
}
|
||||
return { version: "1", packages: {} };
|
||||
}
|
||||
try {
|
||||
log.info(`loading package cache from ${pkgsFilePath}`);
|
||||
const pkgData = fs.readFileSync(pkgsFilePath);
|
||||
return JSON.parse(pkgData.toString()) as Packages;
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
log.error(err);
|
||||
}
|
||||
return { version: "1", packages: {} };
|
||||
}
|
||||
}
|
||||
|
||||
export const nameToSlug = (name: string) => {
|
||||
// github.com/Pypa/twine -> github_com_pypa_twine
|
||||
const [nameOnly] = name.split("@");
|
||||
const slug = nameOnly.replace(/[^\w\s]/gi, "_").toLocaleLowerCase();
|
||||
return slug;
|
||||
// github.com/Pypa/twine -> github_com_pypa_twine
|
||||
const [nameOnly] = name.split("@");
|
||||
const slug = nameOnly.replace(/[^\w\s]/gi, "_").toLocaleLowerCase();
|
||||
return slug;
|
||||
};
|
||||
|
|
|
@ -5,10 +5,10 @@ import log from "./logger";
|
|||
import { Notification, BrowserWindow } from "electron";
|
||||
import { nameToSlug } from "./package";
|
||||
import {
|
||||
getInstalledPackages,
|
||||
getPackagesInstalledList,
|
||||
updatePackageInstalledList,
|
||||
getGuiPath
|
||||
getInstalledPackages,
|
||||
getPackagesInstalledList,
|
||||
updatePackageInstalledList,
|
||||
getGuiPath
|
||||
} from "./tea-dir";
|
||||
import { app } from "electron";
|
||||
import { promisify } from "util";
|
||||
|
@ -21,162 +21,162 @@ const readFile = promisify(fs.readFile);
|
|||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
export default function initialize(mainWindow: BrowserWindow) {
|
||||
if (config.PUSHY_APP_ID) {
|
||||
Pushy.listen();
|
||||
// Register device for push notifications
|
||||
Pushy.register({ appId: config.PUSHY_APP_ID })
|
||||
.then(async (push_token) => {
|
||||
const { device_id } = await readSessionData();
|
||||
log.info(
|
||||
`Registering device ${device_id} for push notifications with token: ${push_token}`
|
||||
);
|
||||
if (device_id) await post(`/auth/device/${device_id}/register-push-token`, { push_token });
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error(err);
|
||||
// Display error dialog
|
||||
// Pushy.alert(mainWindow, 'Pushy registration error: ' + err.message);
|
||||
});
|
||||
if (config.PUSHY_APP_ID) {
|
||||
Pushy.listen();
|
||||
// Register device for push notifications
|
||||
Pushy.register({ appId: config.PUSHY_APP_ID })
|
||||
.then(async (push_token) => {
|
||||
const { device_id } = await readSessionData();
|
||||
log.info(
|
||||
`Registering device ${device_id} for push notifications with token: ${push_token}`
|
||||
);
|
||||
if (device_id) await post(`/auth/device/${device_id}/register-push-token`, { push_token });
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error(err);
|
||||
// Display error dialog
|
||||
// Pushy.alert(mainWindow, 'Pushy registration error: ' + err.message);
|
||||
});
|
||||
|
||||
// Listen for incoming notifications
|
||||
Pushy.setNotificationListener(async (data: any) => {
|
||||
try {
|
||||
log.info("new notification received", data);
|
||||
// Listen for incoming notifications
|
||||
Pushy.setNotificationListener(async (data: any) => {
|
||||
try {
|
||||
log.info("new notification received", data);
|
||||
|
||||
const isDup = await wasReceivedBefore(data);
|
||||
if (!isDup) {
|
||||
new Notification({
|
||||
title: "tea",
|
||||
body: data?.message as string
|
||||
}).show();
|
||||
const isDup = await wasReceivedBefore(data);
|
||||
if (!isDup) {
|
||||
new Notification({
|
||||
title: "tea",
|
||||
body: data?.message as string
|
||||
}).show();
|
||||
|
||||
const v = app.dock.getBadge();
|
||||
if (!v) {
|
||||
app.dock.setBadge("1");
|
||||
} else {
|
||||
app.dock.setBadge((parseInt(v) + 1).toString());
|
||||
}
|
||||
} else {
|
||||
log.info("notification was already received before", data);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("notification listener", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
const v = app.dock.getBadge();
|
||||
if (!v) {
|
||||
app.dock.setBadge("1");
|
||||
} else {
|
||||
app.dock.setBadge((parseInt(v) + 1).toString());
|
||||
}
|
||||
} else {
|
||||
log.info("notification was already received before", data);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("notification listener", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscribeToPackageTopic(pkgFullname: string) {
|
||||
try {
|
||||
if (Pushy.isRegistered()) {
|
||||
const slug = nameToSlug(pkgFullname);
|
||||
try {
|
||||
if (Pushy.isRegistered()) {
|
||||
const slug = nameToSlug(pkgFullname);
|
||||
|
||||
// override rules for brewkit_mnt
|
||||
if (slug.includes("brewkit_mnt")) return;
|
||||
// override rules for brewkit_mnt
|
||||
if (slug.includes("brewkit_mnt")) return;
|
||||
|
||||
const platformArch = getTopicArch();
|
||||
const topic = `packages-${slug}_${platformArch}`;
|
||||
const platformArch = getTopicArch();
|
||||
const topic = `packages-${slug}_${platformArch}`;
|
||||
|
||||
await Pushy.subscribe(topic);
|
||||
log.info("push: registered to pkg-topic: ", topic);
|
||||
} else {
|
||||
log.info("pushy is not registered");
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
await Pushy.subscribe(topic);
|
||||
log.info("push: registered to pkg-topic: ", topic);
|
||||
} else {
|
||||
log.info("pushy is not registered");
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function unsubscribeToPackageTopic(pkgFullname: string) {
|
||||
try {
|
||||
if (Pushy.isRegistered()) {
|
||||
const slug = nameToSlug(pkgFullname);
|
||||
const topic = `packages-${slug}`;
|
||||
await Pushy.unsubscribe(topic);
|
||||
log.info("push: unregistered from pkg-topic: ", topic);
|
||||
} else {
|
||||
log.info("pushy is not registered");
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
if (Pushy.isRegistered()) {
|
||||
const slug = nameToSlug(pkgFullname);
|
||||
const topic = `packages-${slug}`;
|
||||
await Pushy.unsubscribe(topic);
|
||||
log.info("push: unregistered from pkg-topic: ", topic);
|
||||
} else {
|
||||
log.info("pushy is not registered");
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncPackageTopicSubscriptions() {
|
||||
try {
|
||||
log.info("syncing package topic subscriptions");
|
||||
const [installedPackages, lastInstalledList] = await Promise.all([
|
||||
getInstalledPackages(),
|
||||
getPackagesInstalledList()
|
||||
]);
|
||||
try {
|
||||
log.info("syncing package topic subscriptions");
|
||||
const [installedPackages, lastInstalledList] = await Promise.all([
|
||||
getInstalledPackages(),
|
||||
getPackagesInstalledList()
|
||||
]);
|
||||
|
||||
const previouslyInstalledNames = lastInstalledList.map((pkg) => pkg.full_name);
|
||||
const currentlyInstalledNames = installedPackages.map((pkg) => pkg.full_name);
|
||||
const previouslyInstalledNames = lastInstalledList.map((pkg) => pkg.full_name);
|
||||
const currentlyInstalledNames = installedPackages.map((pkg) => pkg.full_name);
|
||||
|
||||
const subscribedTo = currentlyInstalledNames.filter(
|
||||
(pkg) => !previouslyInstalledNames.includes(pkg)
|
||||
);
|
||||
const unsubscribedFrom = previouslyInstalledNames.filter(
|
||||
(pkg) => !currentlyInstalledNames.includes(pkg)
|
||||
);
|
||||
const subscribedTo = currentlyInstalledNames.filter(
|
||||
(pkg) => !previouslyInstalledNames.includes(pkg)
|
||||
);
|
||||
const unsubscribedFrom = previouslyInstalledNames.filter(
|
||||
(pkg) => !currentlyInstalledNames.includes(pkg)
|
||||
);
|
||||
|
||||
for (const subscribe of subscribedTo) {
|
||||
await subscribeToPackageTopic(subscribe);
|
||||
}
|
||||
for (const unsubscribe of unsubscribedFrom) {
|
||||
await unsubscribeToPackageTopic(unsubscribe);
|
||||
}
|
||||
for (const subscribe of subscribedTo) {
|
||||
await subscribeToPackageTopic(subscribe);
|
||||
}
|
||||
for (const unsubscribe of unsubscribedFrom) {
|
||||
await unsubscribeToPackageTopic(unsubscribe);
|
||||
}
|
||||
|
||||
await updatePackageInstalledList(installedPackages);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
await updatePackageInstalledList(installedPackages);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
enum PlatformArch {
|
||||
DarwinAarch64 = "darwin_aarch64",
|
||||
DarwinX86_64 = "darwin_x86-64",
|
||||
LinuxAarch64 = "linux_aarch64",
|
||||
LinuxX86_64 = "linux_x86-64"
|
||||
DarwinAarch64 = "darwin_aarch64",
|
||||
DarwinX86_64 = "darwin_x86-64",
|
||||
LinuxAarch64 = "linux_aarch64",
|
||||
LinuxX86_64 = "linux_x86-64"
|
||||
}
|
||||
|
||||
export function getTopicArch() {
|
||||
const arch = (process.arch as string) === "arm64" ? "aarch64" : "x86-64";
|
||||
const platform = process.platform === "darwin" ? "darwin" : "linux";
|
||||
return `${platform}_${arch}` as PlatformArch;
|
||||
const arch = (process.arch as string) === "arm64" ? "aarch64" : "x86-64";
|
||||
const platform = process.platform === "darwin" ? "darwin" : "linux";
|
||||
return `${platform}_${arch}` as PlatformArch;
|
||||
}
|
||||
|
||||
async function wasReceivedBefore({
|
||||
url,
|
||||
version
|
||||
url,
|
||||
version
|
||||
}: {
|
||||
url: string;
|
||||
version: string;
|
||||
url: string;
|
||||
version: string;
|
||||
}): Promise<boolean> {
|
||||
if (!url || !version) return false;
|
||||
if (!url || !version) return false;
|
||||
|
||||
let received = false;
|
||||
const pkg = url.replace("tea://packages/", "");
|
||||
const searchString = `${pkg}:::${version}`;
|
||||
const notificationPath = path.join(getGuiPath(), "notifications");
|
||||
try {
|
||||
const fileContent = await readFile(notificationPath, "utf-8");
|
||||
let received = false;
|
||||
const pkg = url.replace("tea://packages/", "");
|
||||
const searchString = `${pkg}:::${version}`;
|
||||
const notificationPath = path.join(getGuiPath(), "notifications");
|
||||
try {
|
||||
const fileContent = await readFile(notificationPath, "utf-8");
|
||||
|
||||
if (fileContent.includes(searchString)) {
|
||||
log.info("user has already been notified before of ", searchString);
|
||||
received = true;
|
||||
} else {
|
||||
const appendString = fileContent ? `\n${searchString}` : searchString;
|
||||
await writeFile(notificationPath, fileContent + appendString, "utf-8");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT") {
|
||||
// If the file does not exist, create the file and write the string
|
||||
await writeFile(notificationPath, searchString, "utf-8");
|
||||
log.info("notification file created with the ", searchString);
|
||||
} else {
|
||||
log.error("Error processing the file:", error);
|
||||
}
|
||||
}
|
||||
return received;
|
||||
if (fileContent.includes(searchString)) {
|
||||
log.info("user has already been notified before of ", searchString);
|
||||
received = true;
|
||||
} else {
|
||||
const appendString = fileContent ? `\n${searchString}` : searchString;
|
||||
await writeFile(notificationPath, fileContent + appendString, "utf-8");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT") {
|
||||
// If the file does not exist, create the file and write the string
|
||||
await writeFile(notificationPath, searchString, "utf-8");
|
||||
log.info("notification file created with the ", searchString);
|
||||
} else {
|
||||
log.error("Error processing the file:", error);
|
||||
}
|
||||
}
|
||||
return received;
|
||||
}
|
||||
|
|
|
@ -10,195 +10,195 @@ import { mkdirp } from "mkdirp";
|
|||
import fetch from "node-fetch";
|
||||
|
||||
type Dir = {
|
||||
name: string;
|
||||
path: string;
|
||||
children?: Dir[];
|
||||
name: string;
|
||||
path: string;
|
||||
children?: Dir[];
|
||||
};
|
||||
|
||||
type ParsedVersion = { full_name: string; semVer: SemVer };
|
||||
|
||||
export const getTeaPath = () => {
|
||||
const homePath = app.getPath("home");
|
||||
const teaPath = path.join(homePath, "./.tea");
|
||||
return teaPath;
|
||||
const homePath = app.getPath("home");
|
||||
const teaPath = path.join(homePath, "./.tea");
|
||||
return teaPath;
|
||||
};
|
||||
|
||||
const guiFolder = path.join(getTeaPath(), "tea.xyz/gui");
|
||||
|
||||
export const getGuiPath = () => {
|
||||
return path.join(getTeaPath(), "tea.xyz/gui");
|
||||
return path.join(getTeaPath(), "tea.xyz/gui");
|
||||
};
|
||||
|
||||
export async function getInstalledPackages(): Promise<InstalledPackage[]> {
|
||||
const pkgsPath = getTeaPath();
|
||||
log.info("recursively reading:", pkgsPath);
|
||||
const folders = await deepReadDir({
|
||||
dir: pkgsPath,
|
||||
continueDeeper: (name: string) => !semver.valid(name) && name !== ".tea",
|
||||
filter: (name: string) => !!semver.valid(name) && name !== ".tea"
|
||||
});
|
||||
const pkgsPath = getTeaPath();
|
||||
log.info("recursively reading:", pkgsPath);
|
||||
const folders = await deepReadDir({
|
||||
dir: pkgsPath,
|
||||
continueDeeper: (name: string) => !semver.valid(name) && name !== ".tea",
|
||||
filter: (name: string) => !!semver.valid(name) && name !== ".tea"
|
||||
});
|
||||
|
||||
const bottles = folders
|
||||
.map((p: string) => p.split(".tea/")[1])
|
||||
.map(parseVersionFromPath)
|
||||
.filter((v): v is ParsedVersion => !!v)
|
||||
.sort((a, b) => semverCompare(b.semVer, a.semVer));
|
||||
const bottles = folders
|
||||
.map((p: string) => p.split(".tea/")[1])
|
||||
.map(parseVersionFromPath)
|
||||
.filter((v): v is ParsedVersion => !!v)
|
||||
.sort((a, b) => semverCompare(b.semVer, a.semVer));
|
||||
|
||||
log.info("installed bottles:", bottles.length);
|
||||
log.info("installed bottles:", bottles.length);
|
||||
|
||||
return bottles.reduce<InstalledPackage[]>((pkgs, bottle) => {
|
||||
const pkg = pkgs.find((v) => v.full_name === bottle.full_name);
|
||||
if (pkg) {
|
||||
pkg.installed_versions.push(bottle.semVer.version);
|
||||
} else {
|
||||
pkgs.push({
|
||||
full_name: bottle.full_name,
|
||||
installed_versions: [bottle.semVer.version]
|
||||
});
|
||||
}
|
||||
return pkgs;
|
||||
}, []);
|
||||
return bottles.reduce<InstalledPackage[]>((pkgs, bottle) => {
|
||||
const pkg = pkgs.find((v) => v.full_name === bottle.full_name);
|
||||
if (pkg) {
|
||||
pkg.installed_versions.push(bottle.semVer.version);
|
||||
} else {
|
||||
pkgs.push({
|
||||
full_name: bottle.full_name,
|
||||
installed_versions: [bottle.semVer.version]
|
||||
});
|
||||
}
|
||||
return pkgs;
|
||||
}, []);
|
||||
}
|
||||
|
||||
const parseVersionFromPath = (versionPath: string): ParsedVersion | null => {
|
||||
try {
|
||||
const path = versionPath.trim().split("/");
|
||||
const version = path.pop();
|
||||
return {
|
||||
semVer: new SemVer(semver.clean(version || "") || ""),
|
||||
full_name: path.join("/")
|
||||
};
|
||||
} catch (e) {
|
||||
log.error("error parsing version from path: ", versionPath);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const path = versionPath.trim().split("/");
|
||||
const version = path.pop();
|
||||
return {
|
||||
semVer: new SemVer(semver.clean(version || "") || ""),
|
||||
full_name: path.join("/")
|
||||
};
|
||||
} catch (e) {
|
||||
log.error("error parsing version from path: ", versionPath);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const semverTest =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g;
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g;
|
||||
|
||||
export const getPkgBottles = (packageDir: Dir): string[] => {
|
||||
log.info("getting installed bottle for ", packageDir);
|
||||
const bottles: string[] = [];
|
||||
log.info("getting installed bottle for ", packageDir);
|
||||
const bottles: string[] = [];
|
||||
|
||||
const pkg = packageDir.path.split(".tea/")[1];
|
||||
const version = pkg.split("/v")[1];
|
||||
const pkg = packageDir.path.split(".tea/")[1];
|
||||
const version = pkg.split("/v")[1];
|
||||
|
||||
const isVersion = semverTest.test(version) || !isNaN(+version) || version === "*";
|
||||
const isVersion = semverTest.test(version) || !isNaN(+version) || version === "*";
|
||||
|
||||
if (version && isVersion) {
|
||||
bottles.push(pkg);
|
||||
} else if (packageDir?.children?.length) {
|
||||
const childBottles = packageDir.children
|
||||
.map(getPkgBottles)
|
||||
.reduce((arr, bottles) => [...arr, ...bottles], []);
|
||||
bottles.push(...childBottles);
|
||||
}
|
||||
if (version && isVersion) {
|
||||
bottles.push(pkg);
|
||||
} else if (packageDir?.children?.length) {
|
||||
const childBottles = packageDir.children
|
||||
.map(getPkgBottles)
|
||||
.reduce((arr, bottles) => [...arr, ...bottles], []);
|
||||
bottles.push(...childBottles);
|
||||
}
|
||||
|
||||
const foundBottles = bottles.filter((b) => b !== undefined).sort(); // ie: ["gohugo.io/v*", "gohugo.io/v0", "gohugo.io/v0.108", "gohugo.io/v0.108.0"]
|
||||
const foundBottles = bottles.filter((b) => b !== undefined).sort(); // ie: ["gohugo.io/v*", "gohugo.io/v0", "gohugo.io/v0.108", "gohugo.io/v0.108.0"]
|
||||
|
||||
log.info(`Found ${foundBottles.length} bottles from `, packageDir);
|
||||
return foundBottles;
|
||||
log.info(`Found ${foundBottles.length} bottles from `, packageDir);
|
||||
return foundBottles;
|
||||
};
|
||||
|
||||
export const deepReadDir = async ({
|
||||
dir,
|
||||
continueDeeper,
|
||||
filter
|
||||
dir,
|
||||
continueDeeper,
|
||||
filter
|
||||
}: {
|
||||
dir: string;
|
||||
continueDeeper?: (name: string) => boolean;
|
||||
filter?: (name: string) => boolean;
|
||||
dir: string;
|
||||
continueDeeper?: (name: string) => boolean;
|
||||
filter?: (name: string) => boolean;
|
||||
}) => {
|
||||
const arrayOfFiles: string[] = [];
|
||||
try {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const f of files) {
|
||||
const nextPath = path.join(dir, f.name);
|
||||
const deeper = continueDeeper ? continueDeeper(f.name) : true;
|
||||
if (f.isDirectory() && deeper) {
|
||||
const nextFiles = await deepReadDir({ dir: nextPath, continueDeeper, filter });
|
||||
arrayOfFiles.push(...nextFiles);
|
||||
} else if (filter && filter(f.name)) {
|
||||
arrayOfFiles.push(nextPath);
|
||||
} else if (!filter) {
|
||||
arrayOfFiles.push(nextPath);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
return arrayOfFiles;
|
||||
const arrayOfFiles: string[] = [];
|
||||
try {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const f of files) {
|
||||
const nextPath = path.join(dir, f.name);
|
||||
const deeper = continueDeeper ? continueDeeper(f.name) : true;
|
||||
if (f.isDirectory() && deeper) {
|
||||
const nextFiles = await deepReadDir({ dir: nextPath, continueDeeper, filter });
|
||||
arrayOfFiles.push(...nextFiles);
|
||||
} else if (filter && filter(f.name)) {
|
||||
arrayOfFiles.push(nextPath);
|
||||
} else if (!filter) {
|
||||
arrayOfFiles.push(nextPath);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
return arrayOfFiles;
|
||||
};
|
||||
|
||||
const listFilePath = path.join(getGuiPath(), "installed.json");
|
||||
export const getPackagesInstalledList = async (): Promise<InstalledPackage[]> => {
|
||||
let list: InstalledPackage[] = [];
|
||||
try {
|
||||
if (fs.existsSync(listFilePath)) {
|
||||
log.info("gui/installed.json file exists!");
|
||||
const listBuffer = await fs.readFileSync(listFilePath);
|
||||
list = JSON.parse(listBuffer.toString()) as InstalledPackage[];
|
||||
} else {
|
||||
log.info("gui/installed.json does not exists!");
|
||||
await mkdirp(guiFolder);
|
||||
await updatePackageInstalledList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
return list;
|
||||
let list: InstalledPackage[] = [];
|
||||
try {
|
||||
if (fs.existsSync(listFilePath)) {
|
||||
log.info("gui/installed.json file exists!");
|
||||
const listBuffer = await fs.readFileSync(listFilePath);
|
||||
list = JSON.parse(listBuffer.toString()) as InstalledPackage[];
|
||||
} else {
|
||||
log.info("gui/installed.json does not exists!");
|
||||
await mkdirp(guiFolder);
|
||||
await updatePackageInstalledList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
export async function updatePackageInstalledList(list: InstalledPackage[]) {
|
||||
try {
|
||||
log.info("creating:", listFilePath);
|
||||
await mkdirp(guiFolder);
|
||||
await fs.writeFileSync(listFilePath, JSON.stringify(list), {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
log.info("creating:", listFilePath);
|
||||
await mkdirp(guiFolder);
|
||||
await fs.writeFileSync(listFilePath, JSON.stringify(list), {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deletePackageFolder(fullName, version) {
|
||||
try {
|
||||
const foldPath = path.join(getTeaPath(), fullName, `v${version}`);
|
||||
log.info("rm:", foldPath);
|
||||
await fs.rmdirSync(foldPath, { recursive: true });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
const foldPath = path.join(getTeaPath(), fullName, `v${version}`);
|
||||
log.info("rm:", foldPath);
|
||||
await fs.rmdirSync(foldPath, { recursive: true });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadImage(url: string, imagePath: string): Promise<void> {
|
||||
const response = await fetch(url);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(imagePath);
|
||||
response.body.pipe(fileStream);
|
||||
fileStream.on("finish", () => resolve());
|
||||
fileStream.on("error", (error) => reject(error));
|
||||
});
|
||||
const response = await fetch(url);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(imagePath);
|
||||
response.body.pipe(fileStream);
|
||||
fileStream.on("finish", () => resolve());
|
||||
fileStream.on("error", (error) => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
export async function cacheImage(url: string): Promise<string> {
|
||||
const imageFolder = path.join(getGuiPath(), "cached_images");
|
||||
const imageName = path.basename(url);
|
||||
const imagePath = path.join(imageFolder, imageName);
|
||||
const imageFolder = path.join(getGuiPath(), "cached_images");
|
||||
const imageName = path.basename(url);
|
||||
const imagePath = path.join(imageFolder, imageName);
|
||||
|
||||
await mkdirp(imageFolder);
|
||||
await mkdirp(imageFolder);
|
||||
|
||||
if (!fs.existsSync(imagePath)) {
|
||||
try {
|
||||
await downloadImage(url, imagePath);
|
||||
console.log("Image downloaded and cached:", imagePath);
|
||||
} catch (error) {
|
||||
console.error("Failed to download image:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Image already cached:", imagePath);
|
||||
}
|
||||
if (!fs.existsSync(imagePath)) {
|
||||
try {
|
||||
await downloadImage(url, imagePath);
|
||||
console.log("Image downloaded and cached:", imagePath);
|
||||
} catch (error) {
|
||||
console.error("Failed to download image:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Image already cached:", imagePath);
|
||||
}
|
||||
|
||||
return `file://${imagePath}`;
|
||||
return `file://${imagePath}`;
|
||||
}
|
||||
|
|
|
@ -12,104 +12,104 @@ import { readSessionData, type Session } from "./auth";
|
|||
const base = "https://api.tea.xyz";
|
||||
const publicHeader = { Authorization: "public" };
|
||||
export async function get<T>(urlPath: string) {
|
||||
try {
|
||||
log.info(`GET /v1/${urlPath}`);
|
||||
try {
|
||||
log.info(`GET /v1/${urlPath}`);
|
||||
|
||||
const session = await readSessionData();
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: publicHeader;
|
||||
const session = await readSessionData();
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: publicHeader;
|
||||
|
||||
const url = new URL(path.join("v1", urlPath), base).toString();
|
||||
// TODO: add headers
|
||||
const req = await axios.request<T>({
|
||||
method: "GET",
|
||||
url,
|
||||
headers
|
||||
});
|
||||
const url = new URL(path.join("v1", urlPath), base).toString();
|
||||
// TODO: add headers
|
||||
const req = await axios.request<T>({
|
||||
method: "GET",
|
||||
url,
|
||||
headers
|
||||
});
|
||||
|
||||
log.info("REQUEST:", urlPath, req.status);
|
||||
log.info("REQUEST:", urlPath, req.status);
|
||||
|
||||
return req.data;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return null;
|
||||
}
|
||||
return req.data;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function post<T>(urlPath: string, data: { [key: string]: any }) {
|
||||
try {
|
||||
log.info(`POST /v1/${urlPath}`);
|
||||
try {
|
||||
log.info(`POST /v1/${urlPath}`);
|
||||
|
||||
const session = await readSessionData();
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: publicHeader;
|
||||
const session = await readSessionData();
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: publicHeader;
|
||||
|
||||
const url = new URL(path.join("v1", urlPath), base).toString();
|
||||
const req = await axios.request<T>({
|
||||
method: "POST",
|
||||
url,
|
||||
headers,
|
||||
data
|
||||
});
|
||||
const url = new URL(path.join("v1", urlPath), base).toString();
|
||||
const req = await axios.request<T>({
|
||||
method: "POST",
|
||||
url,
|
||||
headers,
|
||||
data
|
||||
});
|
||||
|
||||
log.info("REQUEST:", urlPath, req.status);
|
||||
log.info("REQUEST:", urlPath, req.status);
|
||||
|
||||
return req.data;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return null;
|
||||
}
|
||||
return req.data;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getHeaders(path: string, session: Session) {
|
||||
const unixMs = new Date().getTime();
|
||||
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
|
||||
const deviceId = session.device_id?.split("-")[0];
|
||||
const preHash = [unixHexSecs, session.key, deviceId, path].join("");
|
||||
const unixMs = new Date().getTime();
|
||||
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
|
||||
const deviceId = session.device_id?.split("-")[0];
|
||||
const preHash = [unixHexSecs, session.key, deviceId, path].join("");
|
||||
|
||||
const Authorization = bcrypt.hashSync(preHash, 10);
|
||||
const Authorization = bcrypt.hashSync(preHash, 10);
|
||||
|
||||
return {
|
||||
Authorization,
|
||||
["tea-ts"]: unixMs.toString(),
|
||||
["tea-uid"]: session.user?.developer_id,
|
||||
["tea-gui_id"]: session.device_id
|
||||
};
|
||||
return {
|
||||
Authorization,
|
||||
["tea-ts"]: unixMs.toString(),
|
||||
["tea-uid"]: session.user?.developer_id,
|
||||
["tea-gui_id"]: session.device_id
|
||||
};
|
||||
}
|
||||
|
||||
export async function syncLogsAt(prefix: string) {
|
||||
const logDir = path.join(app.getPath("home"), "Library/Logs/tea");
|
||||
// ['/Users/neil/Library/Logs/tea/main.log']
|
||||
const logFiles = await deepReadDir({ dir: logDir });
|
||||
const files = logFiles.map((p) => {
|
||||
const paths = p.split("/");
|
||||
return paths.pop();
|
||||
});
|
||||
const logDir = path.join(app.getPath("home"), "Library/Logs/tea");
|
||||
// ['/Users/neil/Library/Logs/tea/main.log']
|
||||
const logFiles = await deepReadDir({ dir: logDir });
|
||||
const files = logFiles.map((p) => {
|
||||
const paths = p.split("/");
|
||||
return paths.pop();
|
||||
});
|
||||
|
||||
const signedUrls = await post<{ [key: string]: string }>(`/gui/${prefix}/sync-log-files`, {
|
||||
files
|
||||
});
|
||||
if (signedUrls) {
|
||||
for (const key in signedUrls) {
|
||||
const fileIndex = files.indexOf(key);
|
||||
const filePath = logFiles[fileIndex];
|
||||
if (filePath) {
|
||||
const payload = createReadStream(filePath);
|
||||
const response = await fetch(signedUrls[key], {
|
||||
method: "PUT",
|
||||
body: payload,
|
||||
headers: {
|
||||
"Content-Length": statSync(filePath).size.toString()
|
||||
}
|
||||
});
|
||||
log.info("uploading log:", key, response.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
const signedUrls = await post<{ [key: string]: string }>(`/gui/${prefix}/sync-log-files`, {
|
||||
files
|
||||
});
|
||||
if (signedUrls) {
|
||||
for (const key in signedUrls) {
|
||||
const fileIndex = files.indexOf(key);
|
||||
const filePath = logFiles[fileIndex];
|
||||
if (filePath) {
|
||||
const payload = createReadStream(filePath);
|
||||
const response = await fetch(signedUrls[key], {
|
||||
method: "PUT",
|
||||
body: payload,
|
||||
headers: {
|
||||
"Content-Length": statSync(filePath).size.toString()
|
||||
}
|
||||
});
|
||||
log.info("uploading log:", key, response.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default get;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
const isVite = () => {
|
||||
try {
|
||||
return window.location.href.includes("is-vite");
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return window.location.href.includes("is-vite");
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!isVite()) {
|
||||
const { init } = window.require("@sentry/electron/renderer");
|
||||
const SvelteSentry = window.require("@sentry/svelte");
|
||||
init(
|
||||
{
|
||||
dsn: "https://5ff29bb5b3b64cd4bd4f4960ef1db2e3@o4504750197899264.ingest.sentry.io/4504750206746624",
|
||||
debug: true
|
||||
},
|
||||
SvelteSentry.init
|
||||
);
|
||||
const { init } = window.require("@sentry/electron/renderer");
|
||||
const SvelteSentry = window.require("@sentry/svelte");
|
||||
init(
|
||||
{
|
||||
dsn: "https://5ff29bb5b3b64cd4bd4f4960ef1db2e3@o4504750197899264.ingest.sentry.io/4504750206746624",
|
||||
debug: true
|
||||
},
|
||||
SvelteSentry.init
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,41 +9,41 @@ const PROJECT_ROOT = join(PACKAGE_ROOT, "../..");
|
|||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
const config = {
|
||||
root: PACKAGE_ROOT,
|
||||
envDir: PROJECT_ROOT,
|
||||
resolve: {
|
||||
alias: {
|
||||
"/@/": join(PACKAGE_ROOT, "src") + "/"
|
||||
}
|
||||
},
|
||||
build: {
|
||||
ssr: true,
|
||||
sourcemap: "inline",
|
||||
outDir: "dist",
|
||||
assetsDir: ".",
|
||||
minify: process.env.MODE !== "development",
|
||||
lib: {
|
||||
entry: "electron.ts",
|
||||
formats: ["cjs"]
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: "[name].cjs"
|
||||
}
|
||||
},
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: false
|
||||
},
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: "./preload.cjs",
|
||||
dest: "."
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
root: PACKAGE_ROOT,
|
||||
envDir: PROJECT_ROOT,
|
||||
resolve: {
|
||||
alias: {
|
||||
"/@/": join(PACKAGE_ROOT, "src") + "/"
|
||||
}
|
||||
},
|
||||
build: {
|
||||
ssr: true,
|
||||
sourcemap: "inline",
|
||||
outDir: "dist",
|
||||
assetsDir: ".",
|
||||
minify: process.env.MODE !== "development",
|
||||
lib: {
|
||||
entry: "electron.ts",
|
||||
formats: ["cjs"]
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: "[name].cjs"
|
||||
}
|
||||
},
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: false
|
||||
},
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: "./preload.cjs",
|
||||
dest: "."
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -1,121 +1,121 @@
|
|||
{
|
||||
"name": "tea",
|
||||
"version": "0.0.45",
|
||||
"private": true,
|
||||
"description": "tea gui app",
|
||||
"author": "tea.xyz",
|
||||
"main": "electron/dist/electron.cjs",
|
||||
"scripts": {
|
||||
"prepare": "svelte-kit sync",
|
||||
"dev": "cross-env NODE_ENV=dev npm run dev:all",
|
||||
"dev:all": "concurrently -n=svelte,electron -c='#ff3e00',blue \"pnpm dev:main\" \"pnpm dev:svelte\" \"pnpm dev:electron\"",
|
||||
"dev:svelte": "vite dev",
|
||||
"dev:electron": "electron electron/dist/electron.cjs",
|
||||
"dev:main": "cd ./electron && vite build --config ./vite.config.js --watch",
|
||||
"build:main": "cp package.json electron && cd ./electron && vite --config ./vite.config.js build --base . && rm package.json",
|
||||
"pack": "electron-builder --dir --config electron-builder.config.cjs",
|
||||
"predist": "node ./scripts/predist.cjs",
|
||||
"dist": "pnpm build && electron-builder --config electron-builder.config.cjs",
|
||||
"package": "pnpm build && electron-builder --config electron-builder.config.cjs",
|
||||
"dev:package": "pnpm build && electron-builder --config electron-builder.config.cjs --dir",
|
||||
"electron": "concurrently --kill-others \"vite dev\" \"electron electron/dist/electron.cjs\"",
|
||||
"olddev": "vite dev",
|
||||
"build": "pnpm build:main && vite build && cp build/app.html build/index.html",
|
||||
"preview": "vite preview",
|
||||
"unit:test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"test": "playwright test",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --compiler-warnings \"css-unused-selector:ignore\"",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --compiler-warnings \"css-unused-selector:ignore\" --watch",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"@playwright/experimental-ct-svelte": "^1.29.2",
|
||||
"@playwright/test": "1.25.0",
|
||||
"@sveltejs/adapter-auto": "^1.0.0",
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.101",
|
||||
"@sveltejs/adapter-static": "^1.0.0-next.48",
|
||||
"@sveltejs/kit": "^1.0.0-next.562",
|
||||
"@tea/ui": "workspace:*",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/svelte": "^3.2.2",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/mixpanel-browser": "^2.38.1",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"concurrently": "^7.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "22.1.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-reloader": "^1.2.3",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"jsdom": "^21.0.0",
|
||||
"postcss": "^8.4.19",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.0",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"svelte2tsx": "^0.5.20",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.3"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@crowdin/ota-client": "^0.7.0",
|
||||
"@electron/asar": "^3.2.3",
|
||||
"@sentry/browser": "^7.49.0",
|
||||
"@sentry/electron": "^4.4.0",
|
||||
"@sentry/svelte": "^7.47.0",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@vitest/coverage-c8": "^0.27.1",
|
||||
"axios": "^1.3.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"buffer": "^6.0.3",
|
||||
"custom-electron-titlebar": "4.2.0-beta.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"electron-context-menu": "^3.6.1",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-serve": "^1.1.0",
|
||||
"electron-updater": "^5.3.0",
|
||||
"electron-vite": "^1.0.18",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"fuse.js": "^6.6.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lorem-ipsum": "^2.0.8",
|
||||
"mixpanel-browser": "^2.45.0",
|
||||
"mkdirp": "^2.1.3",
|
||||
"moment": "^2.29.4",
|
||||
"mousetrap": "^1.6.5",
|
||||
"pushy-electron": "^1.0.11",
|
||||
"renderer": "link:@types/electron/renderer",
|
||||
"semver": "^7.3.8",
|
||||
"svelte-infinite-scroll": "^2.0.1",
|
||||
"svelte-markdown": "^0.2.3",
|
||||
"svelte-watch-resize": "^1.0.3",
|
||||
"sveltekit-i18n": "^2.2.2",
|
||||
"upath": "^2.0.1",
|
||||
"vite-plugin-static-copy": "^0.13.1",
|
||||
"yaml": "^2.2.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@tea/ui"
|
||||
]
|
||||
},
|
||||
"homepage": "https://tea.xyz",
|
||||
"repository": "https://github.com/teaxyz/gui.git"
|
||||
"name": "tea",
|
||||
"version": "0.0.45",
|
||||
"private": true,
|
||||
"description": "tea gui app",
|
||||
"author": "tea.xyz",
|
||||
"main": "electron/dist/electron.cjs",
|
||||
"scripts": {
|
||||
"prepare": "svelte-kit sync",
|
||||
"dev": "cross-env NODE_ENV=dev npm run dev:all",
|
||||
"dev:all": "concurrently -n=svelte,electron -c='#ff3e00',blue \"pnpm dev:main\" \"pnpm dev:svelte\" \"pnpm dev:electron\"",
|
||||
"dev:svelte": "vite dev",
|
||||
"dev:electron": "electron electron/dist/electron.cjs",
|
||||
"dev:main": "cd ./electron && vite build --config ./vite.config.js --watch",
|
||||
"build:main": "cp package.json electron && cd ./electron && vite --config ./vite.config.js build --base . && rm package.json",
|
||||
"pack": "electron-builder --dir --config electron-builder.config.cjs",
|
||||
"predist": "node ./scripts/predist.cjs",
|
||||
"dist": "pnpm build && electron-builder --config electron-builder.config.cjs",
|
||||
"package": "pnpm build && electron-builder --config electron-builder.config.cjs",
|
||||
"dev:package": "pnpm build && electron-builder --config electron-builder.config.cjs --dir",
|
||||
"electron": "concurrently --kill-others \"vite dev\" \"electron electron/dist/electron.cjs\"",
|
||||
"olddev": "vite dev",
|
||||
"build": "pnpm build:main && vite build && cp build/app.html build/index.html",
|
||||
"preview": "vite preview",
|
||||
"unit:test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"test": "playwright test",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --compiler-warnings \"css-unused-selector:ignore\"",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --compiler-warnings \"css-unused-selector:ignore\" --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"@playwright/experimental-ct-svelte": "^1.29.2",
|
||||
"@playwright/test": "1.25.0",
|
||||
"@sveltejs/adapter-auto": "^1.0.0",
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.101",
|
||||
"@sveltejs/adapter-static": "^1.0.0-next.48",
|
||||
"@sveltejs/kit": "^1.0.0-next.562",
|
||||
"@tea/ui": "workspace:*",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/svelte": "^3.2.2",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/mixpanel-browser": "^2.38.1",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"concurrently": "^7.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "22.1.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-reloader": "^1.2.3",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"jsdom": "^21.0.0",
|
||||
"postcss": "^8.4.19",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.0",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"svelte2tsx": "^0.5.20",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.3"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@crowdin/ota-client": "^0.7.0",
|
||||
"@electron/asar": "^3.2.3",
|
||||
"@sentry/browser": "^7.49.0",
|
||||
"@sentry/electron": "^4.4.0",
|
||||
"@sentry/svelte": "^7.47.0",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@vitest/coverage-c8": "^0.27.1",
|
||||
"axios": "^1.3.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"buffer": "^6.0.3",
|
||||
"custom-electron-titlebar": "4.2.0-beta.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"electron-context-menu": "^3.6.1",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-serve": "^1.1.0",
|
||||
"electron-updater": "^5.3.0",
|
||||
"electron-vite": "^1.0.18",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"fuse.js": "^6.6.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lorem-ipsum": "^2.0.8",
|
||||
"mixpanel-browser": "^2.45.0",
|
||||
"mkdirp": "^2.1.3",
|
||||
"moment": "^2.29.4",
|
||||
"mousetrap": "^1.6.5",
|
||||
"pushy-electron": "^1.0.11",
|
||||
"renderer": "link:@types/electron/renderer",
|
||||
"semver": "^7.3.8",
|
||||
"svelte-infinite-scroll": "^2.0.1",
|
||||
"svelte-markdown": "^0.2.3",
|
||||
"svelte-watch-resize": "^1.0.3",
|
||||
"sveltekit-i18n": "^2.2.2",
|
||||
"upath": "^2.0.1",
|
||||
"vite-plugin-static-copy": "^0.13.1",
|
||||
"yaml": "^2.2.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@tea/ui"
|
||||
]
|
||||
},
|
||||
"homepage": "https://tea.xyz",
|
||||
"repository": "https://github.com/teaxyz/gui.git"
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { PlaywrightTestConfig } from "@playwright/test";
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: "npm run build && npm run preview",
|
||||
port: 4173
|
||||
}
|
||||
webServer: {
|
||||
command: "npm run build && npm run preview",
|
||||
port: 4173
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
const { theme, plugins } = require("@tea/ui/tailwind.config.cjs");
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
content: ["./src/**/*.{html,svelte,ts,js}", "../ui/src/**/*.{html,svelte,ts,js}"],
|
||||
theme,
|
||||
plugins: [...plugins]
|
||||
},
|
||||
autoprefixer: {}
|
||||
}
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
content: ["./src/**/*.{html,svelte,ts,js}", "../ui/src/**/*.{html,svelte,ts,js}"],
|
||||
theme,
|
||||
plugins: [...plugins]
|
||||
},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,52 +10,52 @@ const hash = "cf849610ca66250f0954379ct4t";
|
|||
const client = new otaClient.default(hash);
|
||||
|
||||
async function main() {
|
||||
const configPath = path.join(__dirname, "../electron/config.json");
|
||||
const config = {
|
||||
PUSHY_APP_ID: process.env.PUSHY_APP_ID || ""
|
||||
};
|
||||
await fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
||||
const configPath = path.join(__dirname, "../electron/config.json");
|
||||
const config = {
|
||||
PUSHY_APP_ID: process.env.PUSHY_APP_ID || ""
|
||||
};
|
||||
await fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
||||
|
||||
console.log("getting latest translation!");
|
||||
if (!process.env.SYNC_I18N) return;
|
||||
console.log("getting latest translation!");
|
||||
if (!process.env.SYNC_I18N) return;
|
||||
|
||||
const [languagesList, translationsRaw] = await Promise.all([
|
||||
client.getLanguageObjects(),
|
||||
client.getStrings()
|
||||
]);
|
||||
const [languagesList, translationsRaw] = await Promise.all([
|
||||
client.getLanguageObjects(),
|
||||
client.getStrings()
|
||||
]);
|
||||
|
||||
const lang = languagesList.reduce((map, lang) => {
|
||||
map[lang.id] = lang.name;
|
||||
return map;
|
||||
}, {});
|
||||
const lang = languagesList.reduce((map, lang) => {
|
||||
map[lang.id] = lang.name;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
const translations = languagesList.reduce((map, langRaw) => {
|
||||
map[langRaw.id] = {
|
||||
lang
|
||||
};
|
||||
const translation = translationsRaw[langRaw.id];
|
||||
for (const k in translation) {
|
||||
const key = [langRaw.id, k].join(".");
|
||||
_.set(map, key, translation[k]);
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
const translations = languagesList.reduce((map, langRaw) => {
|
||||
map[langRaw.id] = {
|
||||
lang
|
||||
};
|
||||
const translation = translationsRaw[langRaw.id];
|
||||
for (const k in translation) {
|
||||
const key = [langRaw.id, k].join(".");
|
||||
_.set(map, key, translation[k]);
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
const translationsPath = path.join(__dirname, "../src/libs/translations/translations.json");
|
||||
const translationsPath = path.join(__dirname, "../src/libs/translations/translations.json");
|
||||
|
||||
defaultEnTranslation.en.lang = translations.en.lang;
|
||||
await fs.writeFileSync(
|
||||
translationsPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
...translations,
|
||||
...defaultEnTranslation
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
"utf-8"
|
||||
);
|
||||
defaultEnTranslation.en.lang = translations.en.lang;
|
||||
await fs.writeFileSync(
|
||||
translationsPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
...translations,
|
||||
...defaultEnTranslation
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
"utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
|
@ -7,8 +7,8 @@ const token = process.env.CROWDIN_API_TOKEN;
|
|||
const projectId = 570715;
|
||||
const fileId = 7;
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json"
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
|
||||
const englishRaw = translations["en"];
|
||||
|
@ -16,85 +16,85 @@ const englishRaw = translations["en"];
|
|||
delete englishRaw.lang;
|
||||
|
||||
function flattenObject(o, prefix = "", result = {}, keepNull = true) {
|
||||
if (_.isString(o) || _.isNumber(o) || _.isBoolean(o) || (keepNull && _.isNull(o))) {
|
||||
result[prefix] = o;
|
||||
return result;
|
||||
}
|
||||
if (_.isString(o) || _.isNumber(o) || _.isBoolean(o) || (keepNull && _.isNull(o))) {
|
||||
result[prefix] = o;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_.isArray(o) || _.isPlainObject(o)) {
|
||||
for (let i in o) {
|
||||
let pref = prefix;
|
||||
if (_.isArray(o)) {
|
||||
pref = pref + `[${i}]`;
|
||||
} else {
|
||||
if (_.isEmpty(prefix)) {
|
||||
pref = i;
|
||||
} else {
|
||||
pref = prefix + "." + i;
|
||||
}
|
||||
}
|
||||
flattenObject(o[i], pref, result, keepNull);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
if (_.isArray(o) || _.isPlainObject(o)) {
|
||||
for (let i in o) {
|
||||
let pref = prefix;
|
||||
if (_.isArray(o)) {
|
||||
pref = pref + `[${i}]`;
|
||||
} else {
|
||||
if (_.isEmpty(prefix)) {
|
||||
pref = i;
|
||||
} else {
|
||||
pref = prefix + "." + i;
|
||||
}
|
||||
}
|
||||
flattenObject(o[i], pref, result, keepNull);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const flattenedEnglish = flattenObject(englishRaw);
|
||||
|
||||
const getStrings = async () => {
|
||||
const { data } = await axios({
|
||||
method: "GET",
|
||||
url: `https://api.crowdin.com/api/v2/projects/${projectId}/strings?limit=500`,
|
||||
headers
|
||||
});
|
||||
return data.data;
|
||||
const { data } = await axios({
|
||||
method: "GET",
|
||||
url: `https://api.crowdin.com/api/v2/projects/${projectId}/strings?limit=500`,
|
||||
headers
|
||||
});
|
||||
return data.data;
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const data = await getStrings();
|
||||
for (const key in flattenedEnglish) {
|
||||
const found = data.find((data) => data.data.identifier === `"${key}"`);
|
||||
if (found && found.data.text !== flattenedEnglish[key]) {
|
||||
console.log("update!", key, found.data.text, flattenedEnglish[key]);
|
||||
await updateString(found.data.id, flattenedEnglish[key]);
|
||||
} else if (!found) {
|
||||
// insert add
|
||||
await createString(key, flattenedEnglish[key]);
|
||||
}
|
||||
}
|
||||
const data = await getStrings();
|
||||
for (const key in flattenedEnglish) {
|
||||
const found = data.find((data) => data.data.identifier === `"${key}"`);
|
||||
if (found && found.data.text !== flattenedEnglish[key]) {
|
||||
console.log("update!", key, found.data.text, flattenedEnglish[key]);
|
||||
await updateString(found.data.id, flattenedEnglish[key]);
|
||||
} else if (!found) {
|
||||
// insert add
|
||||
await createString(key, flattenedEnglish[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createString(key, text) {
|
||||
await axios({
|
||||
method: "POST",
|
||||
url: `https://api.crowdin.com/api/v2/projects/${projectId}/strings`,
|
||||
headers,
|
||||
data: {
|
||||
text,
|
||||
identifier: `"${key}"`,
|
||||
fileId,
|
||||
context: ` -> ${key}`,
|
||||
isHidden: false,
|
||||
maxLength: 0,
|
||||
labelIds: []
|
||||
}
|
||||
});
|
||||
await axios({
|
||||
method: "POST",
|
||||
url: `https://api.crowdin.com/api/v2/projects/${projectId}/strings`,
|
||||
headers,
|
||||
data: {
|
||||
text,
|
||||
identifier: `"${key}"`,
|
||||
fileId,
|
||||
context: ` -> ${key}`,
|
||||
isHidden: false,
|
||||
maxLength: 0,
|
||||
labelIds: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function updateString(stringId, value) {
|
||||
const d = await axios({
|
||||
method: "PATCH",
|
||||
url: `https://api.crowdin.com/api/v2/projects/${projectId}/strings/${stringId}`,
|
||||
headers,
|
||||
data: [
|
||||
{
|
||||
value,
|
||||
op: "replace",
|
||||
path: "/text"
|
||||
}
|
||||
]
|
||||
});
|
||||
const d = await axios({
|
||||
method: "PATCH",
|
||||
url: `https://api.crowdin.com/api/v2/projects/${projectId}/strings/${stringId}`,
|
||||
headers,
|
||||
data: [
|
||||
{
|
||||
value,
|
||||
op: "replace",
|
||||
path: "/text"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
|
@ -3,26 +3,26 @@
|
|||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: "mona-sans";
|
||||
src: url("/fonts/mona-sans-bold.woff2");
|
||||
font-family: "mona-sans";
|
||||
src: url("/fonts/mona-sans-bold.woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "inter";
|
||||
src: url("/fonts/inter-regular.woff2");
|
||||
font-family: "inter";
|
||||
src: url("/fonts/inter-regular.woff2");
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #1a1a1a;
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
background-color: #1a1a1a;
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-family: sono, sans-serif;
|
||||
}
|
||||
html {
|
||||
font-family: sono, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.text-primary,
|
||||
|
@ -33,9 +33,9 @@ h4,
|
|||
h5,
|
||||
h6,
|
||||
.click-copy {
|
||||
font-family: "mona-sans";
|
||||
font-family: "mona-sans";
|
||||
}
|
||||
|
||||
.pk-version {
|
||||
font-family: "inter";
|
||||
font-family: "inter";
|
||||
}
|
||||
|
|
18
modules/desktop/src/app.d.ts
vendored
18
modules/desktop/src/app.d.ts
vendored
|
@ -2,17 +2,17 @@
|
|||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
||||
// Declare custom event handlers here to make typscript happy.
|
||||
declare namespace svelte.JSX {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface HTMLAttributes<T> {
|
||||
onclick_outside?: () => void;
|
||||
onleave_delay?: () => void;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface HTMLAttributes<T> {
|
||||
onclick_outside?: () => void;
|
||||
onleave_delay?: () => void;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
<style>
|
||||
html {
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
<style>
|
||||
html {
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import Placeholder from '$components/placeholder/placeholder.svelte';
|
||||
import "$appcss";
|
||||
import Placeholder from "$components/placeholder/placeholder.svelte";
|
||||
</script>
|
||||
|
||||
<Placeholder label="Badges" />
|
||||
<Placeholder label="Badges" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import "$appcss";
|
||||
</script>
|
||||
|
||||
<section class="border-gray h-56 border bg-black" />
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
// import { t } from '$libs/translations';
|
||||
import { SideMenuOptions } from "$libs/types";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import Package from "$components/packages/package.svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import "$appcss";
|
||||
// import { t } from '$libs/translations';
|
||||
import { SideMenuOptions } from "$libs/types";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import Package from "$components/packages/package.svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
|
||||
const { packageList: allPackages } = packagesStore;
|
||||
export let packageFilter: SideMenuOptions = SideMenuOptions.discover;
|
||||
const { packageList: allPackages } = packagesStore;
|
||||
export let packageFilter: SideMenuOptions = SideMenuOptions.discover;
|
||||
|
||||
export let scrollY = 0;
|
||||
export let scrollY = 0;
|
||||
|
||||
const onScroll = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
scrollY = target.scrollTop || 0;
|
||||
};
|
||||
const onScroll = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
scrollY = target.scrollTop || 0;
|
||||
};
|
||||
|
||||
$: packages = $allPackages
|
||||
.filter((p) => p.categories.includes(SideMenuOptions.discover))
|
||||
.sort((a, b) => {
|
||||
return a.manual_sorting - b.manual_sorting;
|
||||
});
|
||||
console.log("test", packages);
|
||||
$: packages = $allPackages
|
||||
.filter((p) => p.categories.includes(SideMenuOptions.discover))
|
||||
.sort((a, b) => {
|
||||
return a.manual_sorting - b.manual_sorting;
|
||||
});
|
||||
console.log("test", packages);
|
||||
</script>
|
||||
|
||||
<div class="relative h-full w-full">
|
||||
<ul class="flex flex-col items-stretch" on:scroll={onScroll}>
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg, idx}
|
||||
<div class="z-1 p-1">
|
||||
<Package tab={packageFilter} {pkg} layout={idx % 2 === 0 ? "left" : "right"} />
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Array(9) as _}
|
||||
<section class="card p-1 h-{238}">
|
||||
<div class="border-gray h-full w-full border">
|
||||
<Preloader />
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
<ul class="flex flex-col items-stretch" on:scroll={onScroll}>
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg, idx}
|
||||
<div class="z-1 p-1">
|
||||
<Package tab={packageFilter} {pkg} layout={idx % 2 === 0 ? "left" : "right"} />
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Array(9) as _}
|
||||
<section class="card p-1 h-{238}">
|
||||
<div class="border-gray h-full w-full border">
|
||||
<Preloader />
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
margin-top: 0px;
|
||||
padding-top: 80px;
|
||||
padding-bottom: 8px;
|
||||
height: calc(100vh - 49px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
}
|
||||
ul {
|
||||
margin-top: 0px;
|
||||
padding-top: 80px;
|
||||
padding-bottom: 8px;
|
||||
height: calc(100vh - 49px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { t } from '$libs/translations';
|
||||
import type { AirtablePost } from '@tea/ui/types';
|
||||
import Posts from '@tea/ui/posts/posts.svelte';
|
||||
import PanelHeader from '@tea/ui/panel-header/panel-header.svelte';
|
||||
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
|
||||
import { postsStore } from '$libs/stores';
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
import type { AirtablePost } from "@tea/ui/types";
|
||||
import Posts from "@tea/ui/posts/posts.svelte";
|
||||
import PanelHeader from "@tea/ui/panel-header/panel-header.svelte";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import { postsStore } from "$libs/stores";
|
||||
|
||||
export let title = 'Workshops';
|
||||
export let ctaLabel = 'View all';
|
||||
export let title = "Workshops";
|
||||
export let ctaLabel = "View all";
|
||||
|
||||
let courses: AirtablePost[] = [];
|
||||
let courses: AirtablePost[] = [];
|
||||
|
||||
postsStore.subscribeByTag('course', (posts) => (courses = posts));
|
||||
postsStore.subscribeByTag("course", (posts) => (courses = posts));
|
||||
</script>
|
||||
|
||||
<PanelHeader {title} {ctaLabel} ctaLink="/" />
|
||||
{#if courses.length}
|
||||
<Posts posts={courses} linkTarget="_blank" />
|
||||
<Posts posts={courses} linkTarget="_blank" />
|
||||
{:else}
|
||||
<section class="border-gray h-64 border bg-black p-4">
|
||||
<Preloader />
|
||||
</section>
|
||||
<section class="border-gray h-64 border bg-black p-4">
|
||||
<Preloader />
|
||||
</section>
|
||||
{/if}
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { t } from '$libs/translations';
|
||||
import { postsStore } from '$libs/stores';
|
||||
import type { Course } from '$libs/types';
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
import { postsStore } from "$libs/stores";
|
||||
import type { Course } from "$libs/types";
|
||||
|
||||
import Gallery from '@tea/ui/gallery/gallery.svelte';
|
||||
import Gallery from "@tea/ui/gallery/gallery.svelte";
|
||||
|
||||
let courses: Course[] = [];
|
||||
let courses: Course[] = [];
|
||||
|
||||
postsStore.subscribeByTag('featured_course', (posts) => {
|
||||
courses = posts.map((post) => {
|
||||
return {
|
||||
title: post.title,
|
||||
sub_title: post.sub_title,
|
||||
banner_image_url: post.thumb_image_url,
|
||||
link: post.link
|
||||
} as Course;
|
||||
});
|
||||
});
|
||||
postsStore.subscribeByTag("featured_course", (posts) => {
|
||||
courses = posts.map((post) => {
|
||||
return {
|
||||
title: post.title,
|
||||
sub_title: post.sub_title,
|
||||
banner_image_url: post.thumb_image_url,
|
||||
link: post.link
|
||||
} as Course;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<Gallery
|
||||
title={$t("documentation.featured-courses-title").toUpperCase()}
|
||||
items={courses.map((course) => ({
|
||||
title: course.title,
|
||||
subTitle: course.sub_title,
|
||||
imageUrl: course.banner_image_url,
|
||||
link: course.link
|
||||
}))}
|
||||
linkTarget="_blank"
|
||||
title={$t("documentation.featured-courses-title").toUpperCase()}
|
||||
items={courses.map((course) => ({
|
||||
title: course.title,
|
||||
subTitle: course.sub_title,
|
||||
imageUrl: course.banner_image_url,
|
||||
link: course.link
|
||||
}))}
|
||||
linkTarget="_blank"
|
||||
/>
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Package } from '@tea/ui/types';
|
||||
import "$appcss";
|
||||
import { onMount } from "svelte";
|
||||
import type { Package } from "@tea/ui/types";
|
||||
|
||||
import Gallery from '@tea/ui/gallery/gallery.svelte';
|
||||
import {
|
||||
featuredPackages as featuredPackagesStore,
|
||||
initializeFeaturedPackages
|
||||
} from '$libs/stores';
|
||||
import Gallery from "@tea/ui/gallery/gallery.svelte";
|
||||
import {
|
||||
featuredPackages as featuredPackagesStore,
|
||||
initializeFeaturedPackages
|
||||
} from "$libs/stores";
|
||||
|
||||
let featuredPackages: Package[] = [];
|
||||
let featuredPackages: Package[] = [];
|
||||
|
||||
featuredPackagesStore.subscribe((v) => {
|
||||
featuredPackages = v;
|
||||
});
|
||||
featuredPackagesStore.subscribe((v) => {
|
||||
featuredPackages = v;
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if (!featuredPackages.length) {
|
||||
initializeFeaturedPackages();
|
||||
}
|
||||
});
|
||||
onMount(() => {
|
||||
if (!featuredPackages.length) {
|
||||
initializeFeaturedPackages();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Gallery
|
||||
title="FEATURED PACKAGES"
|
||||
items={featuredPackages.map((pkg) => ({
|
||||
title: pkg.full_name,
|
||||
subTitle: pkg.maintainer || '',
|
||||
imageUrl: pkg.thumb_image_url,
|
||||
link: `/packages/${pkg.slug}`
|
||||
}))}
|
||||
title="FEATURED PACKAGES"
|
||||
items={featuredPackages.map((pkg) => ({
|
||||
title: pkg.full_name,
|
||||
subTitle: pkg.maintainer || "",
|
||||
imageUrl: pkg.thumb_image_url,
|
||||
link: `/packages/${pkg.slug}`
|
||||
}))}
|
||||
/>
|
||||
|
|
|
@ -1,75 +1,75 @@
|
|||
<script lang="ts">
|
||||
import { t } from '$libs/translations';
|
||||
import Button from '@tea/ui/button/button.svelte';
|
||||
import * as pub from '$env/static/public';
|
||||
import { t } from "$libs/translations";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import * as pub from "$env/static/public";
|
||||
</script>
|
||||
|
||||
<footer class="relative h-auto w-full bg-black">
|
||||
<section class="p-4 px-16 py-16">
|
||||
<h3 class="text-primary mb-5 text-2xl">{$t("footer.quick-links-title").toUpperCase()}</h3>
|
||||
<menu class="flex gap-4">
|
||||
<div class="border-gray flex-grow border border-l-0 border-r-0">
|
||||
<a href="/">
|
||||
<Button>
|
||||
<div class="text-primary flex justify-between hover:text-black">
|
||||
<div class="uppercase">{$t("footer.about-tea-store").toUpperCase()}</div>
|
||||
<div>→</div>
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-gray flex-grow border border-l-0 border-r-0">
|
||||
<a href="/">
|
||||
<Button>
|
||||
<div class="text-primary flex justify-between hover:text-black">
|
||||
<div class="uppercase">{$t("footer.report-a-problem").toUpperCase()}</div>
|
||||
<div>→</div>
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-gray flex-grow border border-l-0 border-r-0">
|
||||
<a href="https://tea.xyz" target="_blank" rel="noreferrer">
|
||||
<Button>
|
||||
<div class="text-primary flex justify-between hover:text-black">
|
||||
<div class="uppercase">{$t("footer.visit-website").toUpperCase()}</div>
|
||||
<div>→</div>
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</menu>
|
||||
</section>
|
||||
<section class="p-4 px-16 py-16">
|
||||
<h3 class="text-primary mb-5 text-2xl">{$t("footer.quick-links-title").toUpperCase()}</h3>
|
||||
<menu class="flex gap-4">
|
||||
<div class="border-gray flex-grow border border-l-0 border-r-0">
|
||||
<a href="/">
|
||||
<Button>
|
||||
<div class="text-primary flex justify-between hover:text-black">
|
||||
<div class="uppercase">{$t("footer.about-tea-store").toUpperCase()}</div>
|
||||
<div>→</div>
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-gray flex-grow border border-l-0 border-r-0">
|
||||
<a href="/">
|
||||
<Button>
|
||||
<div class="text-primary flex justify-between hover:text-black">
|
||||
<div class="uppercase">{$t("footer.report-a-problem").toUpperCase()}</div>
|
||||
<div>→</div>
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-gray flex-grow border border-l-0 border-r-0">
|
||||
<a href="https://tea.xyz" target="_blank" rel="noreferrer">
|
||||
<Button>
|
||||
<div class="text-primary flex justify-between hover:text-black">
|
||||
<div class="uppercase">{$t("footer.visit-website").toUpperCase()}</div>
|
||||
<div>→</div>
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</menu>
|
||||
</section>
|
||||
|
||||
<section class="border-gray h-16 border border-r-0 p-4 px-16 flex justify-between">
|
||||
<div class="text-gray flex gap-4 text-xs">
|
||||
<a
|
||||
href="https://tea.xyz/terms-of-use/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="hover:text-white"
|
||||
>
|
||||
{$t("footer.terms-services").toUpperCase()}
|
||||
</a>
|
||||
<a
|
||||
href="https://tea.xyz/privacy-policy/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="hover:text-white"
|
||||
>
|
||||
{$t("footer.privacy-policy").toUpperCase()}
|
||||
</a>
|
||||
</div>
|
||||
{#if pub.PUBLIC_VERSION}
|
||||
<div class="text-gray flex gap-4 text-xs">
|
||||
<span>v{pub.PUBLIC_VERSION}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
<section class="border-gray flex h-16 justify-between border border-r-0 p-4 px-16">
|
||||
<div class="text-gray flex gap-4 text-xs">
|
||||
<a
|
||||
href="https://tea.xyz/terms-of-use/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="hover:text-white"
|
||||
>
|
||||
{$t("footer.terms-services").toUpperCase()}
|
||||
</a>
|
||||
<a
|
||||
href="https://tea.xyz/privacy-policy/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="hover:text-white"
|
||||
>
|
||||
{$t("footer.privacy-policy").toUpperCase()}
|
||||
</a>
|
||||
</div>
|
||||
{#if pub.PUBLIC_VERSION}
|
||||
<div class="text-gray flex gap-4 text-xs">
|
||||
<span>v{pub.PUBLIC_VERSION}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
color: #00ffd0;
|
||||
}
|
||||
h3 {
|
||||
color: #00ffd0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import ArticleCard from '@tea/ui/article-card/article-card.svelte';
|
||||
import "$appcss";
|
||||
import ArticleCard from "@tea/ui/article-card/article-card.svelte";
|
||||
|
||||
const doStuff = () => {
|
||||
console.log('do stuff!');
|
||||
};
|
||||
const doStuff = () => {
|
||||
console.log("do stuff!");
|
||||
};
|
||||
</script>
|
||||
|
||||
<header class="border-gray text-primary border bg-black p-4">GETTING STARTED WITH TEA</header>
|
||||
<section class="grid grid-cols-3 bg-black">
|
||||
<div class="border-gray border p-4">
|
||||
<ArticleCard
|
||||
content={{
|
||||
title: 'installing tea',
|
||||
copy: "It's time to take your first sip! Click below to visit our tea-cli documentation page.",
|
||||
img_url: '/images/bored-ape.png',
|
||||
cta_label: 'Get Started',
|
||||
link: '/cli'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="border-gray border p-4">
|
||||
<ArticleCard
|
||||
content={{
|
||||
title: 'authenticating',
|
||||
copy: 'Using tea without authenticating is like playing a video game without the DLC. Join us today!',
|
||||
img_url: '/images/bored-ape.png',
|
||||
cta_label: 'Get Started',
|
||||
link: ''
|
||||
}}
|
||||
onClick={doStuff}
|
||||
/>
|
||||
</div>
|
||||
<div class="border-gray border p-4">
|
||||
<ArticleCard
|
||||
content={{
|
||||
title: 'give us a star',
|
||||
copy: 'Revolutions are built on the will of the people. Show your support for a more equitable internet.',
|
||||
img_url: '/images/bored-ape.png',
|
||||
cta_label: 'Get Started'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="border-gray border p-4">
|
||||
<ArticleCard
|
||||
content={{
|
||||
title: "installing tea",
|
||||
copy: "It's time to take your first sip! Click below to visit our tea-cli documentation page.",
|
||||
img_url: "/images/bored-ape.png",
|
||||
cta_label: "Get Started",
|
||||
link: "/cli"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="border-gray border p-4">
|
||||
<ArticleCard
|
||||
content={{
|
||||
title: "authenticating",
|
||||
copy: "Using tea without authenticating is like playing a video game without the DLC. Join us today!",
|
||||
img_url: "/images/bored-ape.png",
|
||||
cta_label: "Get Started",
|
||||
link: ""
|
||||
}}
|
||||
onClick={doStuff}
|
||||
/>
|
||||
</div>
|
||||
<div class="border-gray border p-4">
|
||||
<ArticleCard
|
||||
content={{
|
||||
title: "give us a star",
|
||||
copy: "Revolutions are built on the will of the people. Show your support for a more equitable internet.",
|
||||
img_url: "/images/bored-ape.png",
|
||||
cta_label: "Get Started"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { t } from '$libs/translations';
|
||||
import { postsStore } from '$libs/stores';
|
||||
import type { AirtablePost } from '@tea/ui/types';
|
||||
import Posts from '@tea/ui/posts/posts.svelte';
|
||||
import PanelHeader from '@tea/ui/panel-header/panel-header.svelte';
|
||||
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
import { postsStore } from "$libs/stores";
|
||||
import type { AirtablePost } from "@tea/ui/types";
|
||||
import Posts from "@tea/ui/posts/posts.svelte";
|
||||
import PanelHeader from "@tea/ui/panel-header/panel-header.svelte";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
|
||||
let news: AirtablePost[] = [];
|
||||
let news: AirtablePost[] = [];
|
||||
|
||||
postsStore.subscribeByTag('news', (posts) => (news = posts));
|
||||
postsStore.subscribeByTag("news", (posts) => (news = posts));
|
||||
</script>
|
||||
|
||||
<PanelHeader title="OPEN-SOURCE NEWS" ctaLabel="Read more articles" ctaLink="/" />
|
||||
{#if news.length}
|
||||
<Posts posts={news} linkTarget="_blank" />
|
||||
<Posts posts={news} linkTarget="_blank" />
|
||||
{:else}
|
||||
<section class="border-gray h-64 border bg-black p-4">
|
||||
<Preloader />
|
||||
</section>
|
||||
<section class="border-gray h-64 border bg-black p-4">
|
||||
<Preloader />
|
||||
</section>
|
||||
{/if}
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { t } from "$libs/translations";
|
||||
import { notificationStore } from '$libs/stores';
|
||||
import Notification from "@tea/ui/notification/notification.svelte";
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
import { notificationStore } from "$libs/stores";
|
||||
import Notification from "@tea/ui/notification/notification.svelte";
|
||||
</script>
|
||||
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<div class="flex w-full flex-col gap-1">
|
||||
{#each $notificationStore as notification}
|
||||
<Notification
|
||||
notification={{
|
||||
...notification,
|
||||
// TODO this looks nasty but cleanup later.
|
||||
message: notification.i18n_key ? $t(notification.i18n_key, notification.params) : notification.message
|
||||
message: notification.i18n_key
|
||||
? $t(notification.i18n_key, notification.params)
|
||||
: notification.message
|
||||
}}
|
||||
onClose={() => {
|
||||
onClose={() => {
|
||||
notificationStore.remove(notification.id);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,175 +1,174 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import "@tea/ui/icons/icons.css";
|
||||
import { t } from "$libs/translations";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import ButtonIcon from "@tea/ui/button-icon/button-icon.svelte";
|
||||
import ToolTip from "@tea/ui/tool-tip/tool-tip.svelte";
|
||||
import semverCompare from "semver/functions/compare";
|
||||
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
|
||||
import "$appcss";
|
||||
import "@tea/ui/icons/icons.css";
|
||||
import { t } from "$libs/translations";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import ButtonIcon from "@tea/ui/button-icon/button-icon.svelte";
|
||||
import ToolTip from "@tea/ui/tool-tip/tool-tip.svelte";
|
||||
import semverCompare from "semver/functions/compare";
|
||||
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
|
||||
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { openPackageEntrypointInTerminal, shellOpenExternal } from "@native";
|
||||
import { findAvailableVersions, findRecentInstalledVersion } from "$libs/packages/pkg-utils";
|
||||
import { trimGithubSlug } from "$libs/github";
|
||||
import PackageImage from "../package-card/bg-image.svelte";
|
||||
import PackageVersionSelector from "$components/package-install-button/package-version-selector.svelte";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { openPackageEntrypointInTerminal, shellOpenExternal } from "@native";
|
||||
import { findAvailableVersions, findRecentInstalledVersion } from "$libs/packages/pkg-utils";
|
||||
import { trimGithubSlug } from "$libs/github";
|
||||
import PackageImage from "../package-card/bg-image.svelte";
|
||||
import PackageVersionSelector from "$components/package-install-button/package-version-selector.svelte";
|
||||
import { isPackageInstalled } from "$libs/native-mock";
|
||||
|
||||
export let pkg: GUIPackage;
|
||||
let installing = false;
|
||||
let pruning = false;
|
||||
|
||||
export let pkg: GUIPackage;
|
||||
let installing = false;
|
||||
let pruning = false;
|
||||
const install = async (version: string) => {
|
||||
installing = true;
|
||||
await packagesStore.installPkg(pkg, version);
|
||||
installing = false;
|
||||
};
|
||||
|
||||
const install = async (version: string) => {
|
||||
installing = true;
|
||||
await packagesStore.installPkg(pkg, version);
|
||||
installing = false;
|
||||
};
|
||||
const prune = async () => {
|
||||
pruning = true;
|
||||
const versions = (pkg?.installed_versions || []).sort((a, b) => semverCompare(b, a));
|
||||
for (const [i, v] of versions.entries()) {
|
||||
if (i) {
|
||||
// skip the latest version = 0
|
||||
try {
|
||||
await packagesStore.deletePkg(pkg, v);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
pruning = false;
|
||||
};
|
||||
|
||||
const prune = async () => {
|
||||
pruning = true;
|
||||
const versions = (pkg?.installed_versions || []).sort((a, b) => semverCompare(b, a));
|
||||
for (const [i, v] of versions.entries()) {
|
||||
if (i) {
|
||||
// skip the latest version = 0
|
||||
try {
|
||||
await packagesStore.deletePkg(pkg, v);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
pruning = false;
|
||||
};
|
||||
|
||||
let copied = false;
|
||||
const copyPackagePantryLink = async () => {
|
||||
const pantryLink = `https://tea.xyz/+${pkg.full_name}`.toLowerCase();
|
||||
await navigator.clipboard.writeText(pantryLink);
|
||||
copied = true;
|
||||
};
|
||||
let copied = false;
|
||||
const copyPackagePantryLink = async () => {
|
||||
const pantryLink = `https://tea.xyz/+${pkg.full_name}`.toLowerCase();
|
||||
await navigator.clipboard.writeText(pantryLink);
|
||||
copied = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="mt-4 bg-black">
|
||||
<header class="flex">
|
||||
<figure class="grow-1 relative w-1/3">
|
||||
<PackageImage class="min-h-[300px] w-full overflow-hidden" {pkg} layout="none" />
|
||||
{#if pkg.install_progress_percentage && pkg.install_progress_percentage < 100}
|
||||
<div class="absolute left-0 top-0 z-40 h-full w-full bg-black bg-opacity-50">
|
||||
<div class="absolute left-0 right-0 top-1/2 m-auto -mt-12 h-24 w-24">
|
||||
<ProgressCircle value={pkg.install_progress_percentage} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</figure>
|
||||
<article class="w-2/3 p-4 pt-8">
|
||||
<div class="align-center flex items-center gap-2">
|
||||
<h3 class="text-primary text-3xl">{pkg.full_name}</h3>
|
||||
<ButtonIcon
|
||||
icon="pencil"
|
||||
helpText="edit package"
|
||||
on:click={() =>
|
||||
shellOpenExternal(
|
||||
`https://github.com/teaxyz/pantry/blob/main/projects/${pkg.full_name}/package.yml`
|
||||
)}
|
||||
/>
|
||||
<ButtonIcon icon="link" helpText="share package" on:click={copyPackagePantryLink} />
|
||||
{#if copied}
|
||||
<p class="text-green">copied!</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if pkg.homepage}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="hover:text-primary cursor-pointer"
|
||||
on:click={() => shellOpenExternal(pkg.homepage)}>{pkg.homepage}</span
|
||||
>
|
||||
{/if}
|
||||
<p class="mt-4 text-sm">{pkg.desc}</p>
|
||||
<menu class="mt-4 flex h-10 gap-4 text-xs">
|
||||
<div class="min-w-[150px]">
|
||||
<PackageVersionSelector
|
||||
buttonSize="large"
|
||||
{pkg}
|
||||
availableVersions={findAvailableVersions(pkg)}
|
||||
onClick={install}
|
||||
/>
|
||||
</div>
|
||||
{#if (pkg?.installed_versions?.length || 0) > 0}
|
||||
<ToolTip class="ml-[-80px]">
|
||||
<Button
|
||||
slot="target"
|
||||
class="h-10"
|
||||
type="plain"
|
||||
color="blue"
|
||||
onClick={async () => {
|
||||
packagesStore.uninstallPkg(pkg);
|
||||
}}
|
||||
loading={pruning}
|
||||
>
|
||||
<div class="version-item flex w-full items-center justify-center gap-x-1 text-xs">
|
||||
<div class="icon-trash" />
|
||||
<div>{$t("package.cta-UNINSTALL")}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<div slot="tooltip-content" class="flex flex-col items-center">
|
||||
<div>Removes all the versions of the package</div>
|
||||
</div>
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if (pkg?.installed_versions?.length || 0) > 1}
|
||||
<ToolTip>
|
||||
<Button
|
||||
slot="target"
|
||||
class="h-10"
|
||||
type="plain"
|
||||
color="blue"
|
||||
onClick={prune}
|
||||
loading={pruning}
|
||||
>
|
||||
<div class="version-item flex w-full items-center justify-center gap-x-1 text-xs">
|
||||
<div class="icon-scissors" />
|
||||
<div>{$t("package.cta-PRUNE")}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<div slot="tooltip-content" class="flex flex-col items-center">
|
||||
<div>Removes {pkg.installed_versions?.length ?? 0 - 1} old versions</div>
|
||||
<div>Keeps latest (v{findRecentInstalledVersion(pkg)})</div>
|
||||
</div>
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if pkg.github}
|
||||
<button
|
||||
class="border-gray group flex h-[40px] w-[40px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1] shrink-0"
|
||||
on:click={() => {
|
||||
if (pkg.github) {
|
||||
const slug = trimGithubSlug(pkg.github);
|
||||
shellOpenExternal(`https://github.com/${slug}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="icon-github text-gray flex text-xl group-hover:text-black" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if pkg.installed_versions?.length}
|
||||
<Button
|
||||
class="h-10"
|
||||
type="plain"
|
||||
color="black"
|
||||
onClick={() => {
|
||||
openPackageEntrypointInTerminal(pkg.full_name);
|
||||
}}>
|
||||
{#if pkg.full_name == "github.com/AUTOMATIC1111/stable-diffusion-webui"}
|
||||
OPEN
|
||||
{:else}
|
||||
OPEN IN TERMINAL
|
||||
{/if}
|
||||
</Button
|
||||
>
|
||||
<header class="flex">
|
||||
<figure class="grow-1 relative w-1/3">
|
||||
<PackageImage class="min-h-[300px] w-full overflow-hidden" {pkg} layout="none" />
|
||||
{#if pkg.install_progress_percentage && pkg.install_progress_percentage < 100}
|
||||
<div class="absolute left-0 top-0 z-40 h-full w-full bg-black bg-opacity-50">
|
||||
<div class="absolute left-0 right-0 top-1/2 m-auto -mt-12 h-24 w-24">
|
||||
<ProgressCircle value={pkg.install_progress_percentage} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</figure>
|
||||
<article class="w-2/3 p-4 pt-8">
|
||||
<div class="align-center flex items-center gap-2">
|
||||
<h3 class="text-primary text-3xl">{pkg.full_name}</h3>
|
||||
<ButtonIcon
|
||||
icon="pencil"
|
||||
helpText="edit package"
|
||||
on:click={() =>
|
||||
shellOpenExternal(
|
||||
`https://github.com/teaxyz/pantry/blob/main/projects/${pkg.full_name}/package.yml`
|
||||
)}
|
||||
/>
|
||||
<ButtonIcon icon="link" helpText="share package" on:click={copyPackagePantryLink} />
|
||||
{#if copied}
|
||||
<p class="text-green">copied!</p>
|
||||
{/if}
|
||||
</menu>
|
||||
</article>
|
||||
</header>
|
||||
</div>
|
||||
{#if pkg.homepage}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="hover:text-primary cursor-pointer"
|
||||
on:click={() => shellOpenExternal(pkg.homepage)}>{pkg.homepage}</span
|
||||
>
|
||||
{/if}
|
||||
<p class="mt-4 text-sm">{pkg.desc}</p>
|
||||
<menu class="mt-4 flex h-10 gap-4 text-xs">
|
||||
<div class="min-w-[150px]">
|
||||
<PackageVersionSelector
|
||||
buttonSize="large"
|
||||
{pkg}
|
||||
availableVersions={findAvailableVersions(pkg)}
|
||||
onClick={install}
|
||||
/>
|
||||
</div>
|
||||
{#if (pkg?.installed_versions?.length || 0) > 0}
|
||||
<ToolTip class="ml-[-80px]">
|
||||
<Button
|
||||
slot="target"
|
||||
class="h-10"
|
||||
type="plain"
|
||||
color="blue"
|
||||
onClick={async () => {
|
||||
packagesStore.uninstallPkg(pkg);
|
||||
}}
|
||||
loading={pruning}
|
||||
>
|
||||
<div class="version-item flex w-full items-center justify-center gap-x-1 text-xs">
|
||||
<div class="icon-trash" />
|
||||
<div>{$t("package.cta-UNINSTALL")}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<div slot="tooltip-content" class="flex flex-col items-center">
|
||||
<div>Removes all the versions of the package</div>
|
||||
</div>
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if (pkg?.installed_versions?.length || 0) > 1}
|
||||
<ToolTip>
|
||||
<Button
|
||||
slot="target"
|
||||
class="h-10"
|
||||
type="plain"
|
||||
color="blue"
|
||||
onClick={prune}
|
||||
loading={pruning}
|
||||
>
|
||||
<div class="version-item flex w-full items-center justify-center gap-x-1 text-xs">
|
||||
<div class="icon-scissors" />
|
||||
<div>{$t("package.cta-PRUNE")}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<div slot="tooltip-content" class="flex flex-col items-center">
|
||||
<div>Removes {pkg.installed_versions?.length ?? 0 - 1} old versions</div>
|
||||
<div>Keeps latest (v{findRecentInstalledVersion(pkg)})</div>
|
||||
</div>
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if pkg.github}
|
||||
<button
|
||||
class="border-gray group flex h-[40px] w-[40px] shrink-0 items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
||||
on:click={() => {
|
||||
if (pkg.github) {
|
||||
const slug = trimGithubSlug(pkg.github);
|
||||
shellOpenExternal(`https://github.com/${slug}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="icon-github text-gray flex text-xl group-hover:text-black" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if pkg.installed_versions?.length}
|
||||
<Button
|
||||
class="h-10"
|
||||
type="plain"
|
||||
color="black"
|
||||
onClick={() => {
|
||||
openPackageEntrypointInTerminal(pkg.full_name);
|
||||
}}
|
||||
>
|
||||
{#if pkg.full_name == "github.com/AUTOMATIC1111/stable-diffusion-webui"}
|
||||
OPEN
|
||||
{:else}
|
||||
OPEN IN TERMINAL
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</menu>
|
||||
</article>
|
||||
</header>
|
||||
</section>
|
||||
|
|
|
@ -1,172 +1,172 @@
|
|||
<script lang="ts">
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import { onMount } from "svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import { onMount } from "svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
|
||||
let clazz = "";
|
||||
export { clazz as class };
|
||||
let clazz = "";
|
||||
export { clazz as class };
|
||||
|
||||
export let layout: "bottom" | "right" | "left" | "none" = "bottom";
|
||||
export let layout: "bottom" | "right" | "left" | "none" = "bottom";
|
||||
|
||||
export let pkg: GUIPackage;
|
||||
export let pkg: GUIPackage;
|
||||
|
||||
const defaultImgUrl = "/images/default-thumb.jpg";
|
||||
$: loadedImg = "";
|
||||
let loaded = false;
|
||||
let lastProcessedPkg: GUIPackage | null = null;
|
||||
const defaultImgUrl = "/images/default-thumb.jpg";
|
||||
$: loadedImg = "";
|
||||
let loaded = false;
|
||||
let lastProcessedPkg: GUIPackage | null = null;
|
||||
|
||||
const loadImage = async (url: string): Promise<string> => {
|
||||
const image = new Image();
|
||||
image.src = url;
|
||||
return new Promise((resolve, reject) => {
|
||||
image.onload = () => {
|
||||
loadedImg = url;
|
||||
setTimeout(() => {
|
||||
loaded = true;
|
||||
}, 300);
|
||||
resolve(url);
|
||||
};
|
||||
image.onerror = () => {
|
||||
reject(new Error(`file/url does not exist ${url}`));
|
||||
};
|
||||
});
|
||||
};
|
||||
const loadImage = async (url: string): Promise<string> => {
|
||||
const image = new Image();
|
||||
image.src = url;
|
||||
return new Promise((resolve, reject) => {
|
||||
image.onload = () => {
|
||||
loadedImg = url;
|
||||
setTimeout(() => {
|
||||
loaded = true;
|
||||
}, 300);
|
||||
resolve(url);
|
||||
};
|
||||
image.onerror = () => {
|
||||
reject(new Error(`file/url does not exist ${url}`));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const recachePkg = async () => {
|
||||
const url = await packagesStore.cachePkgImage(pkg);
|
||||
loadImage(url);
|
||||
};
|
||||
const recachePkg = async () => {
|
||||
const url = await packagesStore.cachePkgImage(pkg);
|
||||
loadImage(url);
|
||||
};
|
||||
|
||||
const getCache = async () => {
|
||||
if (pkg.cached_image_url) {
|
||||
loadImage(pkg.cached_image_url).catch(() => {
|
||||
if (pkg.thumb_image_url) {
|
||||
loadImage(pkg.thumb_image_url);
|
||||
recachePkg();
|
||||
}
|
||||
});
|
||||
} else if (pkg.thumb_image_url) {
|
||||
recachePkg();
|
||||
}
|
||||
}
|
||||
const getCache = async () => {
|
||||
if (pkg.cached_image_url) {
|
||||
loadImage(pkg.cached_image_url).catch(() => {
|
||||
if (pkg.thumb_image_url) {
|
||||
loadImage(pkg.thumb_image_url);
|
||||
recachePkg();
|
||||
}
|
||||
});
|
||||
} else if (pkg.thumb_image_url) {
|
||||
recachePkg();
|
||||
}
|
||||
};
|
||||
|
||||
$: {
|
||||
if (pkg && pkg?.slug !== lastProcessedPkg?.slug) {
|
||||
loaded = false;
|
||||
loadedImg = "";
|
||||
lastProcessedPkg = pkg;
|
||||
getCache();
|
||||
}
|
||||
}
|
||||
$: {
|
||||
if (pkg && pkg?.slug !== lastProcessedPkg?.slug) {
|
||||
loaded = false;
|
||||
loadedImg = "";
|
||||
lastProcessedPkg = pkg;
|
||||
getCache();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="bg-black {clazz} {layout}">
|
||||
<i class="logo icon-tea-logo-iconasset-1 text-gray animate-pulse text-3xl {layout}" />
|
||||
<div
|
||||
class="bg-center opacity-0 transition-all duration-500"
|
||||
class:opacity-100={loaded}
|
||||
style="background-image: url({loadedImg})"
|
||||
>
|
||||
<!-- dup image: save processing power instead of computing the blur across all the HTML layers -->
|
||||
{#if layout !== "none"}
|
||||
<aside
|
||||
class="blur-sm {layout} opacity-0 transition-all duration-500"
|
||||
class:opacity-100={loaded}
|
||||
>
|
||||
<figure class="bg-center" style="background-image: url({loadedImg})" />
|
||||
</aside>
|
||||
{/if}
|
||||
</div>
|
||||
<i class="logo icon-tea-logo-iconasset-1 text-gray animate-pulse text-3xl {layout}" />
|
||||
<div
|
||||
class="bg-center opacity-0 transition-all duration-500"
|
||||
class:opacity-100={loaded}
|
||||
style="background-image: url({loadedImg})"
|
||||
>
|
||||
<!-- dup image: save processing power instead of computing the blur across all the HTML layers -->
|
||||
{#if layout !== "none"}
|
||||
<aside
|
||||
class="blur-sm {layout} opacity-0 transition-all duration-500"
|
||||
class:opacity-100={loaded}
|
||||
>
|
||||
<figure class="bg-center" style="background-image: url({loadedImg})" />
|
||||
</aside>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
section {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
.logo.none {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
.logo.bottom {
|
||||
left: 50%;
|
||||
top: 30%;
|
||||
}
|
||||
.logo.right {
|
||||
left: 22%;
|
||||
top: 50%;
|
||||
margin-top: -15px;
|
||||
}
|
||||
.logo.left {
|
||||
left: 70%;
|
||||
top: 50%;
|
||||
margin-top: -15px;
|
||||
}
|
||||
.logo {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
.logo.none {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
.logo.bottom {
|
||||
left: 50%;
|
||||
top: 30%;
|
||||
}
|
||||
.logo.right {
|
||||
left: 22%;
|
||||
top: 50%;
|
||||
margin-top: -15px;
|
||||
}
|
||||
.logo.left {
|
||||
left: 70%;
|
||||
top: 50%;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
box-sizing: border-box;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
aside {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
aside.bottom {
|
||||
left: 0px;
|
||||
height: 50%;
|
||||
}
|
||||
div {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
box-sizing: border-box;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
aside {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
aside.bottom {
|
||||
left: 0px;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
aside.left {
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
}
|
||||
aside.left {
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
aside.right {
|
||||
height: 100%;
|
||||
right: 0px;
|
||||
width: 60%;
|
||||
}
|
||||
aside.right {
|
||||
height: 100%;
|
||||
right: 0px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
figure {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 338px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
figure {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 338px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
aside.bottom figure {
|
||||
left: 0px;
|
||||
}
|
||||
aside.bottom figure {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
aside.right figure {
|
||||
height: 100%;
|
||||
/* the overlay is 60% of the image, so we need to oversize the background image back to 100% */
|
||||
width: 166.6666666%;
|
||||
right: 0px;
|
||||
}
|
||||
aside.right figure {
|
||||
height: 100%;
|
||||
/* the overlay is 60% of the image, so we need to oversize the background image back to 100% */
|
||||
width: 166.6666666%;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
aside.left figure {
|
||||
height: 100%;
|
||||
/* the overlay is 60% of the image, so we need to oversize the background image back to 100% */
|
||||
width: 166.66666666%;
|
||||
left: 0px;
|
||||
}
|
||||
aside.left figure {
|
||||
height: 100%;
|
||||
/* the overlay is 60% of the image, so we need to oversize the background image back to 100% */
|
||||
width: 166.66666666%;
|
||||
left: 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,219 +1,219 @@
|
|||
<script lang="ts">
|
||||
import "../../app.css";
|
||||
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import { findRecentInstalledVersion } from "$libs/packages/pkg-utils";
|
||||
import BgImage from "./bg-image.svelte";
|
||||
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
|
||||
import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte";
|
||||
import "../../app.css";
|
||||
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import { findRecentInstalledVersion } from "$libs/packages/pkg-utils";
|
||||
import BgImage from "./bg-image.svelte";
|
||||
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
|
||||
import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte";
|
||||
|
||||
export let pkg: GUIPackage;
|
||||
export let link: string;
|
||||
export let progessLoading = 0;
|
||||
export let pkg: GUIPackage;
|
||||
export let link: string;
|
||||
export let progessLoading = 0;
|
||||
|
||||
export let layout: "bottom" | "right" | "left" = "bottom";
|
||||
export let layout: "bottom" | "right" | "left" = "bottom";
|
||||
|
||||
export let onClickCTA = async () => {
|
||||
console.log("do nothing");
|
||||
};
|
||||
export let onClickCTA = async () => {
|
||||
console.log("do nothing");
|
||||
};
|
||||
|
||||
const fixPackageName = (title: string) => {
|
||||
return title.replace("-", "\u2011");
|
||||
};
|
||||
const fixPackageName = (title: string) => {
|
||||
return title.replace("-", "\u2011");
|
||||
};
|
||||
|
||||
// Using this instead of css :active because there is a button inside of a button
|
||||
let isActive = false;
|
||||
const activate = () => (isActive = true);
|
||||
const deactivate = () => (isActive = false);
|
||||
// Using this instead of css :active because there is a button inside of a button
|
||||
let isActive = false;
|
||||
const activate = () => (isActive = true);
|
||||
const deactivate = () => (isActive = false);
|
||||
|
||||
const preventPropagation = (evt: MouseEvent) => evt.stopPropagation();
|
||||
const preventPropagation = (evt: MouseEvent) => evt.stopPropagation();
|
||||
</script>
|
||||
|
||||
<section class="package-card border-gray relative h-auto border {layout}" class:active={isActive}>
|
||||
<BgImage class="absolute top-0 left-0 h-full w-full" {layout} {pkg} />
|
||||
<BgImage class="absolute top-0 left-0 h-full w-full" {layout} {pkg} />
|
||||
|
||||
<a href={link} on:mousedown={activate} on:mouseup={deactivate} on:mouseleave={deactivate}>
|
||||
<div class="package-card-content absolute h-full w-full flex-col justify-between">
|
||||
<div class="hint-container">
|
||||
<div class="hint">
|
||||
<div class="line-clamp-1 text-xs">view more details</div>
|
||||
<div class="hint-icon"><i class="icon-upward-arrow" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-container absolute bottom-0 w-full {layout}">
|
||||
<article class="card-thumb-label relative">
|
||||
{#if layout === "bottom"}
|
||||
<h3 class="text-bold font-mona line-clamp-1 text-2xl font-bold text-white">
|
||||
{fixPackageName(pkg.name)}
|
||||
</h3>
|
||||
<p class="line-clamp-2 h-[32px] text-xs font-thin lowercase">{pkg.desc ?? ""}</p>
|
||||
{:else}
|
||||
<h3 class="text-bold font-mona line-clamp-1 mb-4 text-3xl font-bold text-white">
|
||||
{fixPackageName(pkg.name)}
|
||||
</h3>
|
||||
<p class="line-clamp-10 h-[160px] text-xs font-thin lowercase">{pkg.desc ?? ""}</p>
|
||||
{/if}
|
||||
</article>
|
||||
<div class="relative mt-3.5 w-full">
|
||||
<div class="install-button {layout}" on:mousedown={preventPropagation}>
|
||||
{#if pkg.state === PackageStates.INSTALLED}
|
||||
<PackageInstalledBadge version={pkg.version} />
|
||||
{:else}
|
||||
<PackageInstallButton
|
||||
{pkg}
|
||||
onClick={(evt) => {
|
||||
// prevent default to prevent the link that this button is inside of from being followed
|
||||
evt?.preventDefault();
|
||||
onClickCTA();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative mt-1.5 h-[10px] leading-[10px]">
|
||||
{#if pkg.state === "NEEDS_UPDATE"}
|
||||
<span class="text-[10px]">
|
||||
<span class="opacity-70">you have</span>
|
||||
v{findRecentInstalledVersion(pkg)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href={link} on:mousedown={activate} on:mouseup={deactivate} on:mouseleave={deactivate}>
|
||||
<div class="package-card-content absolute h-full w-full flex-col justify-between">
|
||||
<div class="hint-container">
|
||||
<div class="hint">
|
||||
<div class="line-clamp-1 text-xs">view more details</div>
|
||||
<div class="hint-icon"><i class="icon-upward-arrow" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-container absolute bottom-0 w-full {layout}">
|
||||
<article class="card-thumb-label relative">
|
||||
{#if layout === "bottom"}
|
||||
<h3 class="text-bold font-mona line-clamp-1 text-2xl font-bold text-white">
|
||||
{fixPackageName(pkg.name)}
|
||||
</h3>
|
||||
<p class="line-clamp-2 h-[32px] text-xs font-thin lowercase">{pkg.desc ?? ""}</p>
|
||||
{:else}
|
||||
<h3 class="text-bold font-mona line-clamp-1 mb-4 text-3xl font-bold text-white">
|
||||
{fixPackageName(pkg.name)}
|
||||
</h3>
|
||||
<p class="line-clamp-10 h-[160px] text-xs font-thin lowercase">{pkg.desc ?? ""}</p>
|
||||
{/if}
|
||||
</article>
|
||||
<div class="relative mt-3.5 w-full">
|
||||
<div class="install-button {layout}" on:mousedown={preventPropagation}>
|
||||
{#if pkg.state === PackageStates.INSTALLED}
|
||||
<PackageInstalledBadge version={pkg.version} />
|
||||
{:else}
|
||||
<PackageInstallButton
|
||||
{pkg}
|
||||
onClick={(evt) => {
|
||||
// prevent default to prevent the link that this button is inside of from being followed
|
||||
evt?.preventDefault();
|
||||
onClickCTA();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative mt-1.5 h-[10px] leading-[10px]">
|
||||
{#if pkg.state === "NEEDS_UPDATE"}
|
||||
<span class="text-[10px]">
|
||||
<span class="opacity-70">you have</span>
|
||||
v{findRecentInstalledVersion(pkg)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{#if progessLoading > 0 && progessLoading < 100}
|
||||
<div class="absolute left-0 top-0 z-40 h-full w-full bg-black bg-opacity-50">
|
||||
<div class="absolute left-0 right-0 top-1/2 m-auto -mt-12 h-24 w-24">
|
||||
<ProgressCircle value={progessLoading} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if progessLoading > 0 && progessLoading < 100}
|
||||
<div class="absolute left-0 top-0 z-40 h-full w-full bg-black bg-opacity-50">
|
||||
<div class="absolute left-0 right-0 top-1/2 m-auto -mt-12 h-24 w-24">
|
||||
<ProgressCircle value={progessLoading} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
transition: all 0.3s;
|
||||
width: 100%;
|
||||
height: 340px;
|
||||
background-size: cover;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
section {
|
||||
transition: all 0.3s;
|
||||
width: 100%;
|
||||
height: 340px;
|
||||
background-size: cover;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
section.active::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(26, 26, 26, 0.7);
|
||||
z-index: 2;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
section.active::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(26, 26, 26, 0.7);
|
||||
z-index: 2;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
section.package-card:active {
|
||||
border-color: #8000ff;
|
||||
box-shadow: 0px 0px 0px 2px rgba(128, 0, 255, 0.5);
|
||||
}
|
||||
section.package-card:active {
|
||||
border-color: #8000ff;
|
||||
box-shadow: 0px 0px 0px 2px rgba(128, 0, 255, 0.5);
|
||||
}
|
||||
|
||||
.content-container {
|
||||
height: 50%;
|
||||
background: linear-gradient(180deg, rgba(26, 26, 26, 0.3) 0%, rgba(26, 26, 26, 0.75) 72.92%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 28px 14px;
|
||||
justify-content: center;
|
||||
}
|
||||
.content-container {
|
||||
height: 50%;
|
||||
background: linear-gradient(180deg, rgba(26, 26, 26, 0.3) 0%, rgba(26, 26, 26, 0.75) 72.92%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 28px 14px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-container.bottom {
|
||||
left: 0px;
|
||||
}
|
||||
.content-container.bottom {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.content-container.left {
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
left: 0px;
|
||||
padding: 28px 28px;
|
||||
}
|
||||
.content-container.left {
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
left: 0px;
|
||||
padding: 28px 28px;
|
||||
}
|
||||
|
||||
.content-container.right {
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
right: 0px;
|
||||
padding: 28px 28px;
|
||||
}
|
||||
.content-container.right {
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
right: 0px;
|
||||
padding: 28px 28px;
|
||||
}
|
||||
|
||||
.hint-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
z-index: 1;
|
||||
}
|
||||
.hint-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hint {
|
||||
min-width: 240px;
|
||||
padding-left: 30%;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 0.5rem;
|
||||
background: linear-gradient(270deg, #e1e1e1 66.29%, rgba(225, 225, 225, 0) 100%);
|
||||
color: #1a1a1a;
|
||||
visibility: hidden;
|
||||
}
|
||||
.hint {
|
||||
min-width: 240px;
|
||||
padding-left: 30%;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 0.5rem;
|
||||
background: linear-gradient(270deg, #e1e1e1 66.29%, rgba(225, 225, 225, 0) 100%);
|
||||
color: #1a1a1a;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.hint-icon {
|
||||
background: #8000ff;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #e1e1e1;
|
||||
}
|
||||
.hint-icon {
|
||||
background: #8000ff;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #e1e1e1;
|
||||
}
|
||||
|
||||
.package-card.right {
|
||||
min-width: 550px;
|
||||
}
|
||||
.package-card.right {
|
||||
min-width: 550px;
|
||||
}
|
||||
|
||||
.package-card.left {
|
||||
min-width: 550px;
|
||||
}
|
||||
.package-card.left {
|
||||
min-width: 550px;
|
||||
}
|
||||
|
||||
.package-card:hover .hint {
|
||||
visibility: visible;
|
||||
}
|
||||
.package-card:hover .hint {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.card-thumb-label {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
.card-thumb-label {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-thumb-label p {
|
||||
color: white;
|
||||
}
|
||||
.card-thumb-label p {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.install-button {
|
||||
width: 160px;
|
||||
}
|
||||
.install-button {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.install-button.bottom {
|
||||
min-width: 100%;
|
||||
}
|
||||
.install-button.bottom {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 650px) {
|
||||
.install-button.bottom {
|
||||
min-width: 60%;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 650px) {
|
||||
.install-button.bottom {
|
||||
min-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
.install-button.bottom {
|
||||
min-width: 50%;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 1000px) {
|
||||
.install-button.bottom {
|
||||
min-width: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { Package } from '@tea/ui/types';
|
||||
export let pkg: Package;
|
||||
import type { Package } from "@tea/ui/types";
|
||||
export let pkg: Package;
|
||||
</script>
|
||||
|
||||
<section class="h-64 w-full">
|
||||
<h1>{pkg.full_name}</h1>
|
||||
<h1>{pkg.full_name}</h1>
|
||||
</section>
|
||||
|
|
|
@ -1,105 +1,105 @@
|
|||
<script lang="ts">
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import { t } from "$libs/translations";
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import { t } from "$libs/translations";
|
||||
|
||||
export let buttonSize: "small" | "large" = "small";
|
||||
export let buttonSize: "small" | "large" = "small";
|
||||
|
||||
export let pkg: GUIPackage;
|
||||
export let onClick = (evt?: MouseEvent) => {
|
||||
console.log("do nothing");
|
||||
};
|
||||
export let pkg: GUIPackage;
|
||||
export let onClick = (evt?: MouseEvent) => {
|
||||
console.log("do nothing");
|
||||
};
|
||||
|
||||
const getColor = (state: PackageStates): "primary" | "secondary" | "black" => {
|
||||
if (state === PackageStates.INSTALLED) {
|
||||
return "black";
|
||||
}
|
||||
if (state === PackageStates.AVAILABLE || state === PackageStates.INSTALLING) {
|
||||
return "secondary";
|
||||
}
|
||||
return "primary";
|
||||
};
|
||||
const getColor = (state: PackageStates): "primary" | "secondary" | "black" => {
|
||||
if (state === PackageStates.INSTALLED) {
|
||||
return "black";
|
||||
}
|
||||
if (state === PackageStates.AVAILABLE || state === PackageStates.INSTALLING) {
|
||||
return "secondary";
|
||||
}
|
||||
return "primary";
|
||||
};
|
||||
|
||||
const isActive = (state: PackageStates): boolean => {
|
||||
return state === PackageStates.INSTALLING || state === PackageStates.UPDATING;
|
||||
};
|
||||
const isActive = (state: PackageStates): boolean => {
|
||||
return state === PackageStates.INSTALLING || state === PackageStates.UPDATING;
|
||||
};
|
||||
|
||||
const badgeClass: Record<PackageStates, string> = {
|
||||
[PackageStates.AVAILABLE]: "install-badge",
|
||||
[PackageStates.INSTALLING]: "install-badge",
|
||||
[PackageStates.NEEDS_UPDATE]: "update-badge",
|
||||
[PackageStates.UPDATING]: "update-badge",
|
||||
[PackageStates.INSTALLED]: "installed-badge",
|
||||
};
|
||||
const badgeClass: Record<PackageStates, string> = {
|
||||
[PackageStates.AVAILABLE]: "install-badge",
|
||||
[PackageStates.INSTALLING]: "install-badge",
|
||||
[PackageStates.NEEDS_UPDATE]: "update-badge",
|
||||
[PackageStates.UPDATING]: "update-badge",
|
||||
[PackageStates.INSTALLED]: "installed-badge"
|
||||
};
|
||||
|
||||
const hasVersionSelectorDropdown = !!$$slots.selector;
|
||||
const hasVersionSelectorDropdown = !!$$slots.selector;
|
||||
|
||||
$: ctaLabel = $t(`package.cta-${pkg.state}`);
|
||||
$: ctaLabel = $t(`package.cta-${pkg.state}`);
|
||||
</script>
|
||||
|
||||
<Button
|
||||
class="w-full border p-0 text-xs text-white {buttonSize === 'small' ? 'h-8' : 'h-10'}"
|
||||
type="plain"
|
||||
color={getColor(pkg.state)}
|
||||
active={isActive(pkg.state)}
|
||||
{onClick}
|
||||
class="w-full border p-0 text-xs text-white {buttonSize === 'small' ? 'h-8' : 'h-10'}"
|
||||
type="plain"
|
||||
color={getColor(pkg.state)}
|
||||
active={isActive(pkg.state)}
|
||||
{onClick}
|
||||
>
|
||||
<div class="version-button h-full">
|
||||
<div class="flex h-full flex-col justify-center p-2">
|
||||
{#if hasVersionSelectorDropdown}
|
||||
<div class="flex items-center justify-between gap-x-2">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div>{ctaLabel}</div>
|
||||
<div class="version-label {badgeClass[pkg.state]}">{pkg.version}</div>
|
||||
</div>
|
||||
<i class="icon-downward-arrow flex" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-center gap-x-2">
|
||||
<div>{ctaLabel}</div>
|
||||
<div class="version-label {badgeClass[pkg.state]}">{pkg.version}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- This slot holds the drop down menu and it inside of the button so that the
|
||||
<div class="version-button h-full">
|
||||
<div class="flex h-full flex-col justify-center p-2">
|
||||
{#if hasVersionSelectorDropdown}
|
||||
<div class="flex items-center justify-between gap-x-2">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div>{ctaLabel}</div>
|
||||
<div class="version-label {badgeClass[pkg.state]}">{pkg.version}</div>
|
||||
</div>
|
||||
<i class="icon-downward-arrow flex" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-center gap-x-2">
|
||||
<div>{ctaLabel}</div>
|
||||
<div class="version-label {badgeClass[pkg.state]}">{pkg.version}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- This slot holds the drop down menu and it inside of the button so that the
|
||||
hover effect remain on the button while the user is hovering the dropdown items-->
|
||||
<slot name="selector" />
|
||||
</div>
|
||||
<slot name="selector" />
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<style>
|
||||
.version-label {
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.version-label {
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.install-badge {
|
||||
background-color: #dcb8ff;
|
||||
color: #8000ff;
|
||||
}
|
||||
.install-badge {
|
||||
background-color: #dcb8ff;
|
||||
color: #8000ff;
|
||||
}
|
||||
|
||||
.update-badge {
|
||||
background-color: #04957a;
|
||||
color: #00ffd0;
|
||||
}
|
||||
.update-badge {
|
||||
background-color: #04957a;
|
||||
color: #00ffd0;
|
||||
}
|
||||
|
||||
.installed-badge {
|
||||
background-color: white;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.installed-badge {
|
||||
background-color: white;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.version-button:hover .install-badge {
|
||||
background-color: white;
|
||||
}
|
||||
.version-button:hover .install-badge {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.version-button:hover .update-badge {
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
.version-button:hover .update-badge {
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.version-button:hover .installed-badge {
|
||||
background-color: #1a1a1a;
|
||||
color: white;
|
||||
}
|
||||
.version-button:hover .installed-badge {
|
||||
background-color: #1a1a1a;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
<script lang="ts">
|
||||
export let version = "1.0.0";
|
||||
export let version = "1.0.0";
|
||||
</script>
|
||||
|
||||
<div class="container relative h-full">
|
||||
<div class="content flex items-center justify-center gap-2 p-2">
|
||||
<i class="icon-check-circle-o flex text-sm text-[#00ffd0]" />
|
||||
<div class="text-xs">INSTALLED</div>
|
||||
<div class="rounded-sm bg-white px-1 text-[10px] leading-[12px] text-black">
|
||||
v{version}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content flex items-center justify-center gap-2 p-2">
|
||||
<i class="icon-check-circle-o flex text-sm text-[#00ffd0]" />
|
||||
<div class="text-xs">INSTALLED</div>
|
||||
<div class="rounded-sm bg-white px-1 text-[10px] leading-[12px] text-black">
|
||||
v{version}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.container::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
mix-blend-mode: overlay;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: #dedede;
|
||||
z-index: 0;
|
||||
}
|
||||
.container::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
mix-blend-mode: overlay;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: #dedede;
|
||||
z-index: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,147 +1,147 @@
|
|||
<script lang="ts">
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import clickOutside from "@tea/ui/lib/clickOutside";
|
||||
import PackageStateButton from "./package-install-button.svelte";
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import clickOutside from "@tea/ui/lib/clickOutside";
|
||||
import PackageStateButton from "./package-install-button.svelte";
|
||||
|
||||
export let buttonSize: "small" | "large" = "small";
|
||||
export let pkg: GUIPackage;
|
||||
export let availableVersions: string[] = [];
|
||||
export let buttonSize: "small" | "large" = "small";
|
||||
export let pkg: GUIPackage;
|
||||
export let availableVersions: string[] = [];
|
||||
|
||||
export let onClick = async (_version: string) => {
|
||||
console.log("do nothing");
|
||||
};
|
||||
export let onClick = async (_version: string) => {
|
||||
console.log("do nothing");
|
||||
};
|
||||
|
||||
$: isOpened = false;
|
||||
$: isOpened = false;
|
||||
|
||||
const toggleOpen = (evt?: MouseEvent) => {
|
||||
evt?.preventDefault();
|
||||
const toggleOpen = (evt?: MouseEvent) => {
|
||||
evt?.preventDefault();
|
||||
|
||||
if ([PackageStates.INSTALLING, PackageStates.UPDATING].includes(pkg.state)) {
|
||||
return;
|
||||
}
|
||||
isOpened = !isOpened;
|
||||
};
|
||||
if ([PackageStates.INSTALLING, PackageStates.UPDATING].includes(pkg.state)) {
|
||||
return;
|
||||
}
|
||||
isOpened = !isOpened;
|
||||
};
|
||||
|
||||
const isInstalled = (version: string) => pkg.installed_versions?.includes(version);
|
||||
const isInstalled = (version: string) => pkg.installed_versions?.includes(version);
|
||||
|
||||
$: installedVersions = pkg.installed_versions || [];
|
||||
$: installedVersions = pkg.installed_versions || [];
|
||||
|
||||
const handleClick = (evt: MouseEvent, version: string) => {
|
||||
if (isInstalled(version)) {
|
||||
return;
|
||||
}
|
||||
const handleClick = (evt: MouseEvent, version: string) => {
|
||||
if (isInstalled(version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
isOpened = false;
|
||||
if (version) {
|
||||
onClick(version);
|
||||
}
|
||||
};
|
||||
isOpened = false;
|
||||
if (version) {
|
||||
onClick(version);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOutside = () => (isOpened = false);
|
||||
const handleClickOutside = () => (isOpened = false);
|
||||
</script>
|
||||
|
||||
<div class="dropdown z-10" use:clickOutside on:click_outside={handleClickOutside}>
|
||||
<PackageStateButton {buttonSize} {pkg} onClick={toggleOpen}>
|
||||
<div slot="selector" class="pt-2">
|
||||
<div class="version-list" class:visible={isOpened}>
|
||||
{#each availableVersions as version, idx}
|
||||
{#if idx !== 0}<hr class="divider" />{/if}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="version-item flex items-center justify-start gap-x-1 text-xs"
|
||||
class:installable-version={!installedVersions.includes(version)}
|
||||
on:click={(evt) => handleClick(evt, version)}
|
||||
>
|
||||
<div class:installed-text={installedVersions.includes(version)}>v{version}</div>
|
||||
{#if idx === 0}
|
||||
<div class="latest-version">(latest)</div>
|
||||
{/if}
|
||||
{#if installedVersions.includes(version)}
|
||||
<div class="flex grow justify-end">
|
||||
<i class="installed-text icon-check-circle flex" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</PackageStateButton>
|
||||
<PackageStateButton {buttonSize} {pkg} onClick={toggleOpen}>
|
||||
<div slot="selector" class="pt-2">
|
||||
<div class="version-list" class:visible={isOpened}>
|
||||
{#each availableVersions as version, idx}
|
||||
{#if idx !== 0}<hr class="divider" />{/if}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="version-item flex items-center justify-start gap-x-1 text-xs"
|
||||
class:installable-version={!installedVersions.includes(version)}
|
||||
on:click={(evt) => handleClick(evt, version)}
|
||||
>
|
||||
<div class:installed-text={installedVersions.includes(version)}>v{version}</div>
|
||||
{#if idx === 0}
|
||||
<div class="latest-version">(latest)</div>
|
||||
{/if}
|
||||
{#if installedVersions.includes(version)}
|
||||
<div class="flex grow justify-end">
|
||||
<i class="installed-text icon-check-circle flex" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</PackageStateButton>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.version-list {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
color: white;
|
||||
background-color: #1a1a1a;
|
||||
border: 0.5px solid #949494;
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 2px;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.version-list {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
color: white;
|
||||
background-color: #1a1a1a;
|
||||
border: 0.5px solid #949494;
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 2px;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.version-item {
|
||||
margin: 4px 6px;
|
||||
padding: 4px 6px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.version-item {
|
||||
margin: 4px 6px;
|
||||
padding: 4px 6px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.installable-version {
|
||||
cursor: pointer;
|
||||
}
|
||||
.installable-version {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.installable-version:hover {
|
||||
outline: 1px solid #949494;
|
||||
background-color: rgba(148, 148, 148, 0.35);
|
||||
}
|
||||
.installable-version:hover {
|
||||
outline: 1px solid #949494;
|
||||
background-color: rgba(148, 148, 148, 0.35);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: 1px solid #272626;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.divider {
|
||||
border: 1px solid #272626;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.installed-text {
|
||||
color: #00ffd0;
|
||||
}
|
||||
.installed-text {
|
||||
color: #00ffd0;
|
||||
}
|
||||
|
||||
.latest-version {
|
||||
color: #af5fff;
|
||||
}
|
||||
.latest-version {
|
||||
color: #af5fff;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: block;
|
||||
}
|
||||
.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { afterUpdate } from 'svelte';
|
||||
import ReviewCard from '@tea/ui/review-card/review-card.svelte';
|
||||
import "$appcss";
|
||||
import { afterUpdate } from "svelte";
|
||||
import ReviewCard from "@tea/ui/review-card/review-card.svelte";
|
||||
|
||||
import type { Review } from '@tea/ui/types';
|
||||
export let reviews: Review[];
|
||||
import type { Review } from "@tea/ui/types";
|
||||
export let reviews: Review[];
|
||||
|
||||
export let showLimit = 9;
|
||||
let showMore = false;
|
||||
export let showLimit = 9;
|
||||
let showMore = false;
|
||||
|
||||
const getColReviews = (n: number) => {
|
||||
const showReviews = reviews.filter((_item, i) => (i - n) % 3 === 0);
|
||||
return showMore ? showReviews : showReviews.slice(0, showLimit / 3);
|
||||
};
|
||||
const getColReviews = (n: number) => {
|
||||
const showReviews = reviews.filter((_item, i) => (i - n) % 3 === 0);
|
||||
return showMore ? showReviews : showReviews.slice(0, showLimit / 3);
|
||||
};
|
||||
|
||||
let col1: Review[] = [];
|
||||
let col2: Review[] = [];
|
||||
let col3: Review[] = [];
|
||||
let col1: Review[] = [];
|
||||
let col2: Review[] = [];
|
||||
let col3: Review[] = [];
|
||||
|
||||
afterUpdate(() => {
|
||||
col1 = getColReviews(0);
|
||||
col2 = getColReviews(1);
|
||||
col3 = getColReviews(2);
|
||||
});
|
||||
afterUpdate(() => {
|
||||
col1 = getColReviews(0);
|
||||
col2 = getColReviews(1);
|
||||
col3 = getColReviews(2);
|
||||
});
|
||||
|
||||
// TODO: problem with reviews with differing heights
|
||||
// ideally they should work like metro-ui to not have extreme height diff between columns
|
||||
// TODO: problem with reviews with differing heights
|
||||
// ideally they should work like metro-ui to not have extreme height diff between columns
|
||||
</script>
|
||||
|
||||
<header class="border-gray text-primary border bg-black p-4">REVIEWS ({reviews.length})</header>
|
||||
<section class="flex flex-row flex-wrap bg-black">
|
||||
<div class="border-gray w-1/3 border-0 border-l-2 border-b-2 p-4">
|
||||
{#each col1 as review}
|
||||
<ReviewCard {review} />
|
||||
<div class="mt-4" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="border-gray w-1/3 border-0 border-l-2 border-b-2 p-4">
|
||||
{#each col2 as review}
|
||||
<ReviewCard {review} />
|
||||
<div class="mt-4" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="border-gray w-1/3 border-0 border-x-2 border-b-2 p-4">
|
||||
{#each col3 as review}
|
||||
<ReviewCard {review} />
|
||||
<div class="mt-4" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="border-gray w-1/3 border-0 border-l-2 border-b-2 p-4">
|
||||
{#each col1 as review}
|
||||
<ReviewCard {review} />
|
||||
<div class="mt-4" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="border-gray w-1/3 border-0 border-l-2 border-b-2 p-4">
|
||||
{#each col2 as review}
|
||||
<ReviewCard {review} />
|
||||
<div class="mt-4" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="border-gray w-1/3 border-0 border-x-2 border-b-2 p-4">
|
||||
{#each col3 as review}
|
||||
<ReviewCard {review} />
|
||||
<div class="mt-4" />
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{#if showLimit <= reviews.length && showMore === false}
|
||||
<footer class="border-gray border bg-black p-4">
|
||||
<button on:click={() => (showMore = true)}>SHOW MORE</button>
|
||||
</footer>
|
||||
<footer class="border-gray border bg-black p-4">
|
||||
<button on:click={() => (showMore = true)}>SHOW MORE</button>
|
||||
</footer>
|
||||
{/if}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<script>
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { goto } from "$app/navigation";
|
||||
import { SideMenuOptions } from "$libs/types";
|
||||
</script>
|
||||
|
||||
<div class="flex w-full h-3/4 justify-center items-center flex-col">
|
||||
<div class="text-2xl text-[#e1e1e1] bg-[#252424] px-9 py-3">
|
||||
You don’t have anything installed
|
||||
<div class="flex h-3/4 w-full flex-col items-center justify-center">
|
||||
<div class="bg-[#252424] px-9 py-3 text-2xl text-[#e1e1e1]">
|
||||
You don’t have anything installed
|
||||
</div>
|
||||
<div class="mt-6 h-6 w-40">
|
||||
<Button type="plain" color="blue" onClick={() => goto(`/?tab=${SideMenuOptions.discover}`)}>DISCOVER</Button>
|
||||
<Button type="plain" color="blue" onClick={() => goto(`/?tab=${SideMenuOptions.discover}`)}
|
||||
>DISCOVER</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<div class="flex w-full h-3/4 justify-center items-center">
|
||||
<div class="text-2xl text-[#e1e1e1] bg-[#252424] px-9 py-3">
|
||||
You’re all up to date 👍
|
||||
</div>
|
||||
<div class="flex h-3/4 w-full items-center justify-center">
|
||||
<div class="bg-[#252424] px-9 py-3 text-2xl text-[#e1e1e1]">You’re all up to date 👍</div>
|
||||
</div>
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import "$appcss";
|
||||
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { onMount } from "svelte";
|
||||
import PackageCard from "$components/package-card/package-card.svelte";
|
||||
import { PackageStates, type GUIPackage } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { onMount } from "svelte";
|
||||
import PackageCard from "$components/package-card/package-card.svelte";
|
||||
|
||||
export let tab = "all";
|
||||
export let pkg: GUIPackage;
|
||||
export let layout: "bottom" | "left" | "right" = "bottom";
|
||||
export let tab = "all";
|
||||
export let pkg: GUIPackage;
|
||||
export let layout: "bottom" | "left" | "right" = "bottom";
|
||||
|
||||
onMount(() => {
|
||||
packagesStore.fetchPackageBottles(pkg.full_name);
|
||||
});
|
||||
onMount(() => {
|
||||
packagesStore.fetchPackageBottles(pkg.full_name);
|
||||
});
|
||||
</script>
|
||||
|
||||
<PackageCard
|
||||
{pkg}
|
||||
{layout}
|
||||
link="/packages/{pkg.slug}?tab={tab}"
|
||||
progessLoading={pkg.install_progress_percentage}
|
||||
onClickCTA={async () => {
|
||||
if (
|
||||
[PackageStates.INSTALLED, PackageStates.INSTALLING, PackageStates.UPDATING].includes(
|
||||
pkg.state
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
packagesStore.installPkg(pkg);
|
||||
}}
|
||||
{pkg}
|
||||
{layout}
|
||||
link="/packages/{pkg.slug}?tab={tab}"
|
||||
progessLoading={pkg.install_progress_percentage}
|
||||
onClickCTA={async () => {
|
||||
if (
|
||||
[PackageStates.INSTALLED, PackageStates.INSTALLING, PackageStates.UPDATING].includes(
|
||||
pkg.state
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
packagesStore.installPkg(pkg);
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,159 +1,159 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import { watchResize } from "svelte-watch-resize";
|
||||
import InfiniteScroll from "svelte-infinite-scroll";
|
||||
// import { t } from '$libs/translations';
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import moment from "moment";
|
||||
import { PackageStates, SideMenuOptions } from "$libs/types";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import Package from "./package.svelte";
|
||||
import NoInstalls from "./no-installs.svelte";
|
||||
import NoUpdates from "./no-updates.svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import "$appcss";
|
||||
import { watchResize } from "svelte-watch-resize";
|
||||
import InfiniteScroll from "svelte-infinite-scroll";
|
||||
// import { t } from '$libs/translations';
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import moment from "moment";
|
||||
import { PackageStates, SideMenuOptions } from "$libs/types";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import Package from "./package.svelte";
|
||||
import NoInstalls from "./no-installs.svelte";
|
||||
import NoUpdates from "./no-updates.svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
|
||||
const { packageList: allPackages } = packagesStore;
|
||||
export let packageFilter: SideMenuOptions = SideMenuOptions.all;
|
||||
const { packageList: allPackages } = packagesStore;
|
||||
export let packageFilter: SideMenuOptions = SideMenuOptions.all;
|
||||
|
||||
export let sortBy: "popularity" | "most recent" = "most recent";
|
||||
export let sortDirection: "asc" | "desc" = "desc";
|
||||
export let sortBy: "popularity" | "most recent" = "most recent";
|
||||
export let sortDirection: "asc" | "desc" = "desc";
|
||||
|
||||
export let scrollY = 0;
|
||||
export let scrollY = 0;
|
||||
|
||||
let loadMore = 9;
|
||||
let limit = loadMore + 9;
|
||||
let loadMore = 9;
|
||||
let limit = loadMore + 9;
|
||||
|
||||
// TODO: figure out a better type strategy here so that this breaks if SideMenuOptions is updated
|
||||
const pkgFilters: { [key: string]: (pkg: GUIPackage) => boolean } = {
|
||||
[SideMenuOptions.all]: (_pkg: GUIPackage) => true,
|
||||
[SideMenuOptions.installed]: (pkg: GUIPackage) => {
|
||||
return [
|
||||
PackageStates.INSTALLED,
|
||||
PackageStates.INSTALLING,
|
||||
PackageStates.NEEDS_UPDATE,
|
||||
PackageStates.UPDATING
|
||||
].includes(pkg.state);
|
||||
},
|
||||
[SideMenuOptions.installed_updates_available]: (pkg: GUIPackage) => {
|
||||
return [PackageStates.UPDATING, PackageStates.NEEDS_UPDATE].includes(pkg.state);
|
||||
},
|
||||
[SideMenuOptions.recently_updated]: (pkg: GUIPackage) => {
|
||||
return moment(pkg.last_modified).isAfter(moment().subtract(30, "days"));
|
||||
},
|
||||
[SideMenuOptions.new_packages]: (pkg: GUIPackage) => {
|
||||
return moment(pkg.created).isAfter(moment().subtract(30, "days"));
|
||||
},
|
||||
[SideMenuOptions.popular]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.popular),
|
||||
[SideMenuOptions.featured]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.featured),
|
||||
[SideMenuOptions.essentials]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.essentials),
|
||||
[SideMenuOptions.starstruck]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.starstruck),
|
||||
[SideMenuOptions.made_by_tea]: (pkg: GUIPackage) => pkg.full_name.includes("tea.xyz")
|
||||
};
|
||||
// TODO: figure out a better type strategy here so that this breaks if SideMenuOptions is updated
|
||||
const pkgFilters: { [key: string]: (pkg: GUIPackage) => boolean } = {
|
||||
[SideMenuOptions.all]: (_pkg: GUIPackage) => true,
|
||||
[SideMenuOptions.installed]: (pkg: GUIPackage) => {
|
||||
return [
|
||||
PackageStates.INSTALLED,
|
||||
PackageStates.INSTALLING,
|
||||
PackageStates.NEEDS_UPDATE,
|
||||
PackageStates.UPDATING
|
||||
].includes(pkg.state);
|
||||
},
|
||||
[SideMenuOptions.installed_updates_available]: (pkg: GUIPackage) => {
|
||||
return [PackageStates.UPDATING, PackageStates.NEEDS_UPDATE].includes(pkg.state);
|
||||
},
|
||||
[SideMenuOptions.recently_updated]: (pkg: GUIPackage) => {
|
||||
return moment(pkg.last_modified).isAfter(moment().subtract(30, "days"));
|
||||
},
|
||||
[SideMenuOptions.new_packages]: (pkg: GUIPackage) => {
|
||||
return moment(pkg.created).isAfter(moment().subtract(30, "days"));
|
||||
},
|
||||
[SideMenuOptions.popular]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.popular),
|
||||
[SideMenuOptions.featured]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.featured),
|
||||
[SideMenuOptions.essentials]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.essentials),
|
||||
[SideMenuOptions.starstruck]: (pkg: GUIPackage) =>
|
||||
pkg.categories.includes(SideMenuOptions.starstruck),
|
||||
[SideMenuOptions.made_by_tea]: (pkg: GUIPackage) => pkg.full_name.includes("tea.xyz")
|
||||
};
|
||||
|
||||
const onScroll = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
scrollY = target.scrollTop || 0;
|
||||
};
|
||||
const onScroll = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
scrollY = target.scrollTop || 0;
|
||||
};
|
||||
|
||||
$: packages = $allPackages.filter(pkgFilters[packageFilter] || pkgFilters.all).sort((a, b) => {
|
||||
if (sortBy === "popularity") {
|
||||
const aPop = +a.dl_count + a.installs;
|
||||
const bPop = +b.dl_count + b.installs;
|
||||
return sortDirection === "asc" ? aPop - bPop : bPop - aPop;
|
||||
} else {
|
||||
// most recent
|
||||
const aDate = new Date(a.last_modified);
|
||||
const bDate = new Date(b.last_modified);
|
||||
return sortDirection === "asc" ? +aDate - +bDate : +bDate - +aDate;
|
||||
}
|
||||
});
|
||||
$: packages = $allPackages.filter(pkgFilters[packageFilter] || pkgFilters.all).sort((a, b) => {
|
||||
if (sortBy === "popularity") {
|
||||
const aPop = +a.dl_count + a.installs;
|
||||
const bPop = +b.dl_count + b.installs;
|
||||
return sortDirection === "asc" ? aPop - bPop : bPop - aPop;
|
||||
} else {
|
||||
// most recent
|
||||
const aDate = new Date(a.last_modified);
|
||||
const bDate = new Date(b.last_modified);
|
||||
return sortDirection === "asc" ? +aDate - +bDate : +bDate - +aDate;
|
||||
}
|
||||
});
|
||||
|
||||
const onResize = (node: HTMLElement) => {
|
||||
const assumedCardHeight = 250;
|
||||
const cardRows = Math.floor(packages.length / 3);
|
||||
const minCardRows = Math.floor(node.scrollHeight / assumedCardHeight);
|
||||
if (cardRows < minCardRows) {
|
||||
const addLimit = 3 * (minCardRows - cardRows);
|
||||
limit += addLimit;
|
||||
}
|
||||
};
|
||||
const onResize = (node: HTMLElement) => {
|
||||
const assumedCardHeight = 250;
|
||||
const cardRows = Math.floor(packages.length / 3);
|
||||
const minCardRows = Math.floor(node.scrollHeight / assumedCardHeight);
|
||||
if (cardRows < minCardRows) {
|
||||
const addLimit = 3 * (minCardRows - cardRows);
|
||||
limit += addLimit;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="relative h-full w-full">
|
||||
<ul class="flex flex-wrap content-start bg-black" use:watchResize={onResize} on:scroll={onScroll}>
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg, index}
|
||||
{#if index < limit}
|
||||
<div class="card z-1 p-1" class:animate-puls={pkg.state === PackageStates.INSTALLING}>
|
||||
<Package tab={packageFilter} {pkg} layout="bottom"/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if packageFilter === SideMenuOptions.installed}
|
||||
<NoInstalls />
|
||||
{:else if packageFilter === SideMenuOptions.installed_updates_available}
|
||||
<NoUpdates />
|
||||
{:else}
|
||||
{#each Array(9) as _}
|
||||
<section class="card p-1 h-{238}">
|
||||
<div class="border-gray h-full w-full border">
|
||||
<Preloader />
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
<InfiniteScroll threshold={100} on:loadMore={() => (limit += loadMore)} />
|
||||
</ul>
|
||||
<ul class="flex flex-wrap content-start bg-black" use:watchResize={onResize} on:scroll={onScroll}>
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg, index}
|
||||
{#if index < limit}
|
||||
<div class="card z-1 p-1" class:animate-puls={pkg.state === PackageStates.INSTALLING}>
|
||||
<Package tab={packageFilter} {pkg} layout="bottom" />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if packageFilter === SideMenuOptions.installed}
|
||||
<NoInstalls />
|
||||
{:else if packageFilter === SideMenuOptions.installed_updates_available}
|
||||
<NoUpdates />
|
||||
{:else}
|
||||
{#each Array(9) as _}
|
||||
<section class="card p-1 h-{238}">
|
||||
<div class="border-gray h-full w-full border">
|
||||
<Preloader />
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
<InfiniteScroll threshold={100} on:loadMore={() => (limit += loadMore)} />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
margin-top: 0px;
|
||||
padding-top: 80px;
|
||||
height: calc(100vh - 49px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
}
|
||||
ul {
|
||||
margin-top: 0px;
|
||||
padding-top: 80px;
|
||||
height: calc(100vh - 49px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
}
|
||||
.card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 650px) {
|
||||
.card {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 650px) {
|
||||
.card {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
.card {
|
||||
width: 33.333333%;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 1000px) {
|
||||
.card {
|
||||
width: 33.333333%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts">
|
||||
export let coverUrl = '';
|
||||
let clazz = '';
|
||||
export { clazz as class };
|
||||
export let coverUrl = "";
|
||||
let clazz = "";
|
||||
export { clazz as class };
|
||||
</script>
|
||||
|
||||
<figure class="relative mb-8 h-32 w-full uppercase {clazz}">
|
||||
{#if coverUrl}
|
||||
<img src={coverUrl} class="absolute z-0 h-32 w-full object-cover" alt="cover" />
|
||||
{/if}
|
||||
<div class="text-primary absolute bottom-0 text-6xl leading-[32px]">
|
||||
<slot />
|
||||
</div>
|
||||
{#if coverUrl}
|
||||
<img src={coverUrl} class="absolute z-0 h-32 w-full object-cover" alt="cover" />
|
||||
{/if}
|
||||
<div class="text-primary absolute bottom-0 text-6xl leading-[32px]">
|
||||
<slot />
|
||||
</div>
|
||||
</figure>
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<script lang="ts">
|
||||
export let label = '';
|
||||
export let label = "";
|
||||
</script>
|
||||
|
||||
<section class="bg-gray p-8">
|
||||
<header>{label}</header>
|
||||
<slot />
|
||||
<header>{label}</header>
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
position: relative;
|
||||
min-height: 240px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
/* background-color: #ccc; */
|
||||
display: flex;
|
||||
}
|
||||
header {
|
||||
color: rgb(50, 48, 48);
|
||||
font-size: 3em;
|
||||
}
|
||||
section {
|
||||
position: relative;
|
||||
min-height: 240px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
/* background-color: #ccc; */
|
||||
display: flex;
|
||||
}
|
||||
header {
|
||||
color: rgb(50, 48, 48);
|
||||
font-size: 3em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { shellOpenExternal } from '@native';
|
||||
const openGithub = () => {
|
||||
shellOpenExternal("https://github.com/teaxyz");
|
||||
};
|
||||
import "$appcss";
|
||||
import { shellOpenExternal } from "@native";
|
||||
const openGithub = () => {
|
||||
shellOpenExternal("https://github.com/teaxyz");
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="card social-box" style="width: 100%; float:right;">
|
||||
<header class="border-gray text-primary border-b pt-7 pb-7 pl-5">PRE-FLIGHT</header>
|
||||
<div class="listbox-item border-gray border-b p-6">
|
||||
<a href="/cli">
|
||||
<p>Install Tea</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="listbox-item border-gray border-b p-6">
|
||||
<div>
|
||||
<p>Authenticate</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listbox-item p-6">
|
||||
<button on:click={openGithub}>Give tea a star</button>
|
||||
</div>
|
||||
<header class="border-gray text-primary border-b pt-7 pb-7 pl-5">PRE-FLIGHT</header>
|
||||
<div class="listbox-item border-gray border-b p-6">
|
||||
<a href="/cli">
|
||||
<p>Install Tea</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="listbox-item border-gray border-b p-6">
|
||||
<div>
|
||||
<p>Authenticate</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listbox-item p-6">
|
||||
<button on:click={openGithub}>Give tea a star</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
border: 2px solid #949494;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
.card {
|
||||
border: 2px solid #949494;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.listbox-item {
|
||||
height: 75px;
|
||||
}
|
||||
.listbox-item {
|
||||
height: 75px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { authStore } from '$libs/stores';
|
||||
import "$appcss";
|
||||
import { authStore } from "$libs/stores";
|
||||
|
||||
const { user } = authStore;
|
||||
// const authPage = `http://localhost:3000/v1/auth/user?device_id=${authStore.deviceId}`; // https://api.tea.xyz/v1/auth/user?device_id=device_id
|
||||
const { user } = authStore;
|
||||
// const authPage = `http://localhost:3000/v1/auth/user?device_id=${authStore.deviceId}`; // https://api.tea.xyz/v1/auth/user?device_id=device_id
|
||||
</script>
|
||||
|
||||
{#if $user}
|
||||
<section class="border-gray border-2 bg-black p-2">
|
||||
<div class="profile_banner border-gray container flex border bg-black">
|
||||
<img class="w-1/5" src={$user.avatar_url || '/images/bored-ape.png'} alt="profile" />
|
||||
<div class="flex w-4/5 items-center p-5">
|
||||
<div class="w-1/2 pl-5">
|
||||
<p class="text-gray uppercase">Authenticated with GitHub</p>
|
||||
<p />
|
||||
<p class="text-primary text-4xl">@{$user.login}</p>
|
||||
</div>
|
||||
<div class="border-gray h-full border-l" />
|
||||
<div class="w-1/2 pl-10">
|
||||
<p class="text-gray uppercase leading-loose">
|
||||
Country: <span>$user?.country}</span><br />Wallet:
|
||||
{#if $user.wallet}
|
||||
<span>{$user.wallet}</span>
|
||||
{:else}
|
||||
<a class="text-green underline" href="/">Connect Now</a>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="border-gray border-2 bg-black p-2">
|
||||
<div class="profile_banner border-gray container flex border bg-black">
|
||||
<img class="w-1/5" src={$user.avatar_url || "/images/bored-ape.png"} alt="profile" />
|
||||
<div class="flex w-4/5 items-center p-5">
|
||||
<div class="w-1/2 pl-5">
|
||||
<p class="text-gray uppercase">Authenticated with GitHub</p>
|
||||
<p />
|
||||
<p class="text-primary text-4xl">@{$user.login}</p>
|
||||
</div>
|
||||
<div class="border-gray h-full border-l" />
|
||||
<div class="w-1/2 pl-10">
|
||||
<p class="text-gray uppercase leading-loose">
|
||||
Country: <span>$user?.country}</span><br />Wallet:
|
||||
{#if $user.wallet}
|
||||
<span>{$user.wallet}</span>
|
||||
{:else}
|
||||
<a class="text-green underline" href="/">Connect Now</a>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
|
|
@ -20,6 +20,18 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<path d="M50,5A45 45 0 1 1 49.9999 5" />
|
||||
<path d={progressPath()} />
|
||||
</svg>
|
||||
<div>
|
||||
<slot>
|
||||
<span>{value}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
fill: var(--progress-fill, transparent);
|
||||
|
@ -48,14 +60,3 @@
|
|||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<path d="M50,5A45 45 0 1 1 49.9999 5" />
|
||||
<path d="{progressPath()}" />
|
||||
</svg>
|
||||
<div>
|
||||
<slot>
|
||||
<span>{value}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
|
@ -1,139 +1,138 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import { t } from '$libs/translations';
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
|
||||
type SortOption = "popularity" | "most recent";
|
||||
export let onSort: (opt: SortOption, dir: "asc" | "desc") => void;
|
||||
|
||||
type SortOption = "popularity" | "most recent";
|
||||
export let onSort: (opt: SortOption, dir: 'asc' | 'desc') => void;
|
||||
let sortBy: SortOption = "popularity";
|
||||
let sortDirection: "asc" | "desc" = "desc";
|
||||
|
||||
let sortBy: SortOption = "popularity";
|
||||
let sortDirection: 'asc' | 'desc' = 'desc';
|
||||
const sortOptions: SortOption[] = ["popularity", "most recent"];
|
||||
|
||||
const sortOptions: SortOption[] = ["popularity", "most recent"];
|
||||
const optionLabels = {
|
||||
[sortOptions[0]]: $t("sorting.popularity"),
|
||||
[sortOptions[1]]: $t("sorting.most-recent")
|
||||
};
|
||||
|
||||
const optionLabels = {
|
||||
[sortOptions[0]]: $t("sorting.popularity"),
|
||||
[sortOptions[1]]: $t("sorting.most-recent")
|
||||
}
|
||||
|
||||
const setSortBy = (opt: SortOption) => {
|
||||
sortBy = opt;
|
||||
if (onSort) {
|
||||
onSort(sortBy, sortDirection);
|
||||
}
|
||||
};
|
||||
const setSortDir = (opt: SortOption, dir: 'asc' | 'desc') => {
|
||||
sortDirection = dir;
|
||||
setSortBy(opt);
|
||||
};
|
||||
const setSortBy = (opt: SortOption) => {
|
||||
sortBy = opt;
|
||||
if (onSort) {
|
||||
onSort(sortBy, sortDirection);
|
||||
}
|
||||
};
|
||||
const setSortDir = (opt: SortOption, dir: "asc" | "desc") => {
|
||||
sortDirection = dir;
|
||||
setSortBy(opt);
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="sorting-container bg-black text-gray">
|
||||
<div class="dropdown">
|
||||
<div class="dropdown-title">{$t("sorting.label")}</div>
|
||||
<ul class="dropdown-content column flex">
|
||||
{#each sortOptions as option}
|
||||
<li class="flex items-center">
|
||||
<button
|
||||
class="sort-btn"
|
||||
class:active={sortBy === option}
|
||||
on:click={() => setSortBy(option)}
|
||||
>
|
||||
{optionLabels[option]}
|
||||
</button>
|
||||
<div class="direction-arrows">
|
||||
<button
|
||||
on:click={() => setSortDir(option, 'asc')}
|
||||
class={sortBy === option && sortDirection === 'asc' ? 'active' : ''}>↑</button
|
||||
>
|
||||
<button
|
||||
on:click={() => setSortDir(option, 'desc')}
|
||||
class={sortBy === option && sortDirection === 'desc' ? 'active' : ''}>↓</button
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<section class="sorting-container text-gray bg-black">
|
||||
<div class="dropdown">
|
||||
<div class="dropdown-title">{$t("sorting.label")}</div>
|
||||
<ul class="dropdown-content column flex">
|
||||
{#each sortOptions as option}
|
||||
<li class="flex items-center">
|
||||
<button
|
||||
class="sort-btn"
|
||||
class:active={sortBy === option}
|
||||
on:click={() => setSortBy(option)}
|
||||
>
|
||||
{optionLabels[option]}
|
||||
</button>
|
||||
<div class="direction-arrows">
|
||||
<button
|
||||
on:click={() => setSortDir(option, "asc")}
|
||||
class={sortBy === option && sortDirection === "asc" ? "active" : ""}>↑</button
|
||||
>
|
||||
<button
|
||||
on:click={() => setSortDir(option, "desc")}
|
||||
class={sortBy === option && sortDirection === "desc" ? "active" : ""}>↓</button
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.direction-arrows {
|
||||
float: right;
|
||||
}
|
||||
.direction-arrows button {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.direction-arrows button.active {
|
||||
opacity: 1;
|
||||
}
|
||||
.direction-arrows {
|
||||
float: right;
|
||||
}
|
||||
.direction-arrows button {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.direction-arrows button.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sorting-container {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
max-width: 240px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 30px;
|
||||
transition: 0.1s linear;
|
||||
}
|
||||
.sorting-container {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
max-width: 240px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 30px;
|
||||
transition: 0.1s linear;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #1a1a1a;
|
||||
width: 100%;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
color: white;
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
}
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #1a1a1a;
|
||||
width: 100%;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
color: white;
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.dropdown-content li {
|
||||
position: relative;
|
||||
padding: 0px 10px;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
line-height: 36px;
|
||||
}
|
||||
.dropdown-content li {
|
||||
position: relative;
|
||||
padding: 0px 10px;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.dropdown-content li .sort-btn {
|
||||
height: 100%;
|
||||
width: calc(100% - 40px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.dropdown-content li .sort-btn {
|
||||
height: 100%;
|
||||
width: calc(100% - 40px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dropdown-content li .sort-btn.active {
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
.dropdown-content li .sort-btn.active {
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dropdown-content li .direction-arrows {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0px;
|
||||
}
|
||||
.dropdown-content li .direction-arrows {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.dropdown-content li:hover {
|
||||
background: #00ffd0;
|
||||
color: black;
|
||||
}
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
.dropdown-content li:hover {
|
||||
background: #00ffd0;
|
||||
color: black;
|
||||
}
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-title {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.dropdown-title {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
import { shellOpenExternal } from "@native";
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-center items-center h-full">
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<div class="text-2xl">Hmm, we don't have that one yet...</div>
|
||||
<div class="text-xl mt-4">But we'd love for you to submit it to the pantry!</div>
|
||||
<div class="mt-4 text-xl">But we'd love for you to submit it to the pantry!</div>
|
||||
<div class="mt-10">
|
||||
<Button type="plain" color="blue" onClick={() => shellOpenExternal("https://github.com/teaxyz/pantry")}>
|
||||
<span class="text-sm px-12">VISIT PANTRY</span>
|
||||
<Button
|
||||
type="plain"
|
||||
color="blue"
|
||||
onClick={() => shellOpenExternal("https://github.com/teaxyz/pantry")}
|
||||
>
|
||||
<span class="px-12 text-sm">VISIT PANTRY</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="text-gray text-xs mt-2">Redirects to github</div>
|
||||
<div class="text-gray mt-2 text-xs">Redirects to github</div>
|
||||
</div>
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
<script lang="ts">
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { onMount } from "svelte";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import ImgLoader from "@tea/ui/img-loader/img-loader.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
|
||||
export let pkg: GUIPackage; // Fuse package search result probably not updated
|
||||
export let onClick: () => Promise<void>;
|
||||
export let onClose: () => void;
|
||||
import ImgLoader from "@tea/ui/img-loader/img-loader.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
|
||||
export let pkg: GUIPackage; // Fuse package search result probably not updated
|
||||
export let onClick: () => Promise<void>;
|
||||
export let onClose: () => void;
|
||||
|
||||
const { packageList } = packagesStore;
|
||||
const { packageList } = packagesStore;
|
||||
|
||||
$: updatedPkg = $packageList.find((p) => p.full_name === pkg.full_name);
|
||||
$: updatedPkg = $packageList.find((p) => p.full_name === pkg.full_name);
|
||||
|
||||
onMount(() => {
|
||||
packagesStore.fetchPackageBottles(pkg.full_name);
|
||||
});
|
||||
onMount(() => {
|
||||
packagesStore.fetchPackageBottles(pkg.full_name);
|
||||
});
|
||||
|
||||
const gotoPackagePage = () => {
|
||||
goto(`/packages/${pkg.slug}?tab=all`);
|
||||
onClose();
|
||||
};
|
||||
const gotoPackagePage = () => {
|
||||
goto(`/packages/${pkg.slug}?tab=all`);
|
||||
onClose();
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<figure class="border-gray flex flex-row gap-2 border p-2">
|
||||
<ImgLoader
|
||||
on:click={() => gotoPackagePage()}
|
||||
class="pkg-image h-16 w-16 object-cover"
|
||||
src={!pkg.thumb_image_url.includes("https://tea.xyz")
|
||||
? "/images/default-thumb.jpg"
|
||||
: pkg.thumb_image_url}
|
||||
alt={pkg.name}
|
||||
/>
|
||||
<header class="flex-grow" on:click={() => gotoPackagePage()}>
|
||||
<h1>{pkg.full_name}</h1>
|
||||
<p class="line-clamp-2 text-xs">{pkg.desc}</p>
|
||||
</header>
|
||||
<aside>
|
||||
<div>
|
||||
{#if updatedPkg}
|
||||
<PackageInstallButton pkg={updatedPkg} {onClick} />
|
||||
{/if}
|
||||
</div>
|
||||
</aside>
|
||||
<ImgLoader
|
||||
on:click={() => gotoPackagePage()}
|
||||
class="pkg-image h-16 w-16 object-cover"
|
||||
src={!pkg.thumb_image_url.includes("https://tea.xyz")
|
||||
? "/images/default-thumb.jpg"
|
||||
: pkg.thumb_image_url}
|
||||
alt={pkg.name}
|
||||
/>
|
||||
<header class="flex-grow" on:click={() => gotoPackagePage()}>
|
||||
<h1>{pkg.full_name}</h1>
|
||||
<p class="line-clamp-2 text-xs">{pkg.desc}</p>
|
||||
</header>
|
||||
<aside>
|
||||
<div>
|
||||
{#if updatedPkg}
|
||||
<PackageInstallButton pkg={updatedPkg} {onClick} />
|
||||
{/if}
|
||||
</div>
|
||||
</aside>
|
||||
</figure>
|
||||
|
||||
<style>
|
||||
figure:hover {
|
||||
background-color: #252525;
|
||||
}
|
||||
figure:hover {
|
||||
background-color: #252525;
|
||||
}
|
||||
|
||||
aside {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 140px;
|
||||
margin-right: 22px;
|
||||
}
|
||||
aside {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 140px;
|
||||
margin-right: 22px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,111 +1,111 @@
|
|||
<script lang="ts">
|
||||
import { packagesStore, searchStore } from "$libs/stores";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import SearchInput from "@tea/ui/search-input/search-input.svelte";
|
||||
import { t } from "$libs/translations";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import Package from "$components/packages/package.svelte";
|
||||
import { PackageStates } from "$libs/types";
|
||||
import PackageResult from "./package-search-result.svelte";
|
||||
import Mousetrap from "mousetrap";
|
||||
// import Posts from '@tea/ui/posts/posts.svelte';
|
||||
import { packagesStore, searchStore } from "$libs/stores";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import SearchInput from "@tea/ui/search-input/search-input.svelte";
|
||||
import { t } from "$libs/translations";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import Package from "$components/packages/package.svelte";
|
||||
import { PackageStates } from "$libs/types";
|
||||
import PackageResult from "./package-search-result.svelte";
|
||||
import Mousetrap from "mousetrap";
|
||||
// import Posts from '@tea/ui/posts/posts.svelte';
|
||||
|
||||
import { installPackage } from "@native";
|
||||
import { onMount } from "svelte";
|
||||
import { installPackage } from "@native";
|
||||
import { onMount } from "svelte";
|
||||
import NoSearchResults from "./no-search-results.svelte";
|
||||
|
||||
const { searching, packagesSearch } = searchStore;
|
||||
// import type { AirtablePost } from '@tea/ui/types';
|
||||
let term: string;
|
||||
// let articles: AirtablePost[] = []; // news, blogs, etc
|
||||
// let workshops: AirtablePost[] = []; // workshops, course
|
||||
let loading = true;
|
||||
const { searching, packagesSearch } = searchStore;
|
||||
// import type { AirtablePost } from '@tea/ui/types';
|
||||
let term: string;
|
||||
// let articles: AirtablePost[] = []; // news, blogs, etc
|
||||
// let workshops: AirtablePost[] = []; // workshops, course
|
||||
let loading = true;
|
||||
|
||||
// searchStore.packagesSearch.subscribe((pkgs) => {
|
||||
// packages = pkgs;
|
||||
// });
|
||||
// searchStore.postsSearch.subscribe((posts) => {
|
||||
// let partialArticles: AirtablePost[] = [];
|
||||
// let partialWorkshops: AirtablePost[] = [];
|
||||
// for (let post of posts) {
|
||||
// if (post.tags.includes('news')) {
|
||||
// partialArticles.push(post);
|
||||
// }
|
||||
// if (post.tags.includes('course') || post.tags.includes('featured_course')) {
|
||||
// partialWorkshops.push(post);
|
||||
// }
|
||||
// }
|
||||
// searchStore.packagesSearch.subscribe((pkgs) => {
|
||||
// packages = pkgs;
|
||||
// });
|
||||
// searchStore.postsSearch.subscribe((posts) => {
|
||||
// let partialArticles: AirtablePost[] = [];
|
||||
// let partialWorkshops: AirtablePost[] = [];
|
||||
// for (let post of posts) {
|
||||
// if (post.tags.includes('news')) {
|
||||
// partialArticles.push(post);
|
||||
// }
|
||||
// if (post.tags.includes('course') || post.tags.includes('featured_course')) {
|
||||
// partialWorkshops.push(post);
|
||||
// }
|
||||
// }
|
||||
|
||||
// articles = partialArticles;
|
||||
// workshops = partialWorkshops;
|
||||
// });
|
||||
// articles = partialArticles;
|
||||
// workshops = partialWorkshops;
|
||||
// });
|
||||
|
||||
searchStore.searching.subscribe((v) => (loading = v));
|
||||
searchStore.searching.subscribe((v) => (loading = v));
|
||||
|
||||
const onClose = () => {
|
||||
term = "";
|
||||
searchStore.searching.set(false);
|
||||
};
|
||||
const onClose = () => {
|
||||
term = "";
|
||||
searchStore.searching.set(false);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if $searching === true}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div id="bg-close" class="z-40" on:click={onClose} />
|
||||
<section class="z-50">
|
||||
<header class="border-gray flex border border-x-0 border-t-0 bg-black">
|
||||
<div class="relative w-full">
|
||||
<SearchInput
|
||||
class="h-9 w-full rounded-sm"
|
||||
size="small"
|
||||
autofocus={true}
|
||||
placeholder={$t("store-search-placeholder")}
|
||||
onSearch={(search) => {
|
||||
term = search;
|
||||
searchStore.search(search);
|
||||
}}
|
||||
/>
|
||||
<div class="absolute top-1 right-4 flex items-center gap-1 pt-[1px] opacity-50">
|
||||
<span class="mr-1 text-xs">clear</span>
|
||||
<kbd class=" bg-gray flex items-center rounded-sm px-2 pt-[1px] text-white">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div id="bg-close" class="z-40" on:click={onClose} />
|
||||
<section class="z-50">
|
||||
<header class="border-gray flex border border-x-0 border-t-0 bg-black">
|
||||
<div class="relative w-full">
|
||||
<SearchInput
|
||||
class="h-9 w-full rounded-sm"
|
||||
size="small"
|
||||
autofocus={true}
|
||||
placeholder={$t("store-search-placeholder")}
|
||||
onSearch={(search) => {
|
||||
term = search;
|
||||
searchStore.search(search);
|
||||
}}
|
||||
/>
|
||||
<div class="absolute top-1 right-4 flex items-center gap-1 pt-[1px] opacity-50">
|
||||
<span class="mr-1 text-xs">clear</span>
|
||||
<kbd class=" bg-gray flex items-center rounded-sm px-2 pt-[1px] text-white">
|
||||
<!-- using apple system ui font as our default renders the symbols wonky -->
|
||||
<span class="" style="font-family: system-ui, -apple-system, sans-serif">⌘⇧⌫</span>
|
||||
</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mr-2" on:click={onClose}>✕</button>
|
||||
</header>
|
||||
{#if term}
|
||||
<div class="z-20 bg-black">
|
||||
{#if $packagesSearch.length > 0}
|
||||
<header class="text-gray p-4 text-lg">
|
||||
packages ({$packagesSearch.length})
|
||||
</header>
|
||||
<ul class="flex flex-col gap-2 p-2">
|
||||
{#each $packagesSearch as pkg}
|
||||
<div class={pkg.state === PackageStates.INSTALLING ? "animate-pulse" : ""}>
|
||||
<PackageResult
|
||||
{pkg}
|
||||
{onClose}
|
||||
onClick={async () => {
|
||||
if (
|
||||
[
|
||||
PackageStates.INSTALLED,
|
||||
PackageStates.INSTALLING,
|
||||
PackageStates.UPDATING
|
||||
].includes(pkg.state)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
packagesStore.installPkg(pkg);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<NoSearchResults />
|
||||
{/if}
|
||||
<!-- <header class="text-primary p-4 text-lg">
|
||||
<span class="" style="font-family: system-ui, -apple-system, sans-serif">⌘⇧⌫</span>
|
||||
</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mr-2" on:click={onClose}>✕</button>
|
||||
</header>
|
||||
{#if term}
|
||||
<div class="z-20 bg-black">
|
||||
{#if $packagesSearch.length > 0}
|
||||
<header class="text-gray p-4 text-lg">
|
||||
packages ({$packagesSearch.length})
|
||||
</header>
|
||||
<ul class="flex flex-col gap-2 p-2">
|
||||
{#each $packagesSearch as pkg}
|
||||
<div class={pkg.state === PackageStates.INSTALLING ? "animate-pulse" : ""}>
|
||||
<PackageResult
|
||||
{pkg}
|
||||
{onClose}
|
||||
onClick={async () => {
|
||||
if (
|
||||
[
|
||||
PackageStates.INSTALLED,
|
||||
PackageStates.INSTALLING,
|
||||
PackageStates.UPDATING
|
||||
].includes(pkg.state)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
packagesStore.installPkg(pkg);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<NoSearchResults />
|
||||
{/if}
|
||||
<!-- <header class="text-primary p-4 text-lg">
|
||||
Top Article Results ({articles.length})
|
||||
</header>
|
||||
{#if articles.length}
|
||||
|
@ -125,67 +125,67 @@
|
|||
<Preloader />
|
||||
</section>
|
||||
{/if} -->
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full w-full flex-col justify-center bg-black">
|
||||
<p class="text-gray text-center">start typing to search</p>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full w-full flex-col justify-center bg-black">
|
||||
<p class="text-gray text-center">start typing to search</p>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#bg-close {
|
||||
position: fixed;
|
||||
width: calc(100vw - 2px);
|
||||
height: calc(100vh - 2px);
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 12px;
|
||||
}
|
||||
section {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
border: gray 1px solid;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#bg-close {
|
||||
position: fixed;
|
||||
width: calc(100vw - 2px);
|
||||
height: calc(100vh - 2px);
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 12px;
|
||||
}
|
||||
section {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
border: gray 1px solid;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
section > div {
|
||||
position: relative;
|
||||
margin-top: 2px;
|
||||
height: calc(100% - 40px);
|
||||
width: 100%;
|
||||
transition: height 0.6s ease-in-out;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
section > div {
|
||||
position: relative;
|
||||
margin-top: 2px;
|
||||
height: calc(100% - 40px);
|
||||
width: 100%;
|
||||
transition: height 0.6s ease-in-out;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { t, locales, locale } from "$libs/translations";
|
||||
import SelectExpand from "@tea/ui/select-expand/select-expand.svelte";
|
||||
import { t, locales, locale } from "$libs/translations";
|
||||
import SelectExpand from "@tea/ui/select-expand/select-expand.svelte";
|
||||
|
||||
const label = "language";
|
||||
const label = "language";
|
||||
</script>
|
||||
|
||||
<SelectExpand
|
||||
{label}
|
||||
bind:value={$locale}
|
||||
options={$locales.map((value) => ({
|
||||
label: $t(`lang.${value}`),
|
||||
value,
|
||||
selected: value === $locale
|
||||
}))}
|
||||
{label}
|
||||
bind:value={$locale}
|
||||
options={$locales.map((value) => ({
|
||||
label: $t(`lang.${value}`),
|
||||
value,
|
||||
selected: value === $locale
|
||||
}))}
|
||||
/>
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
<script lang="ts">
|
||||
import { shellOpenExternal, submitLogs } from "@native";
|
||||
import mouseLeaveDelay from "@tea/ui/lib/mouse-leave-delay";
|
||||
import UpdateButton from "./update-button.svelte";
|
||||
import { appUpdateStore } from "$libs/stores";
|
||||
const { updateStatus } = appUpdateStore;
|
||||
import { shellOpenExternal, submitLogs } from "@native";
|
||||
import mouseLeaveDelay from "@tea/ui/lib/mouse-leave-delay";
|
||||
import UpdateButton from "./update-button.svelte";
|
||||
import { appUpdateStore } from "$libs/stores";
|
||||
const { updateStatus } = appUpdateStore;
|
||||
|
||||
const hidePopup = () => {
|
||||
isOpen = false;
|
||||
};
|
||||
const hidePopup = () => {
|
||||
isOpen = false;
|
||||
};
|
||||
|
||||
const preventDoubleClick = (evt: MouseEvent) => evt.stopPropagation();
|
||||
const preventDoubleClick = (evt: MouseEvent) => evt.stopPropagation();
|
||||
|
||||
$: isOpen = false;
|
||||
$: isOpen = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative"
|
||||
use:mouseLeaveDelay={2000}
|
||||
on:leave_delay={() => hidePopup()}
|
||||
on:dblclick={preventDoubleClick}
|
||||
class="relative"
|
||||
use:mouseLeaveDelay={2000}
|
||||
on:leave_delay={() => hidePopup()}
|
||||
on:dblclick={preventDoubleClick}
|
||||
>
|
||||
<button
|
||||
class="border-gray group flex h-[28px] w-[28px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
||||
class:circle-badge={$updateStatus.status === "available" || $updateStatus.status === "ready"}
|
||||
on:click={() => (isOpen = !isOpen)}
|
||||
<button
|
||||
class="border-gray group flex h-[28px] w-[28px] items-center justify-center rounded-sm border hover:bg-[#e1e1e1]"
|
||||
class:circle-badge={$updateStatus.status === "available" || $updateStatus.status === "ready"}
|
||||
on:click={() => (isOpen = !isOpen)}
|
||||
title="settings"
|
||||
>
|
||||
<div class="icon-gear text-l text-gray flex group-hover:text-black" />
|
||||
</button>
|
||||
>
|
||||
<div class="icon-gear text-l text-gray flex group-hover:text-black" />
|
||||
</button>
|
||||
|
||||
<nav
|
||||
class="menu border-gray absolute w-full border bg-black p-2 text-xs transition-all"
|
||||
class:invisible={!isOpen}
|
||||
class:visible={isOpen}
|
||||
>
|
||||
<!-- TODO: what is this supposed to do? -->
|
||||
<!-- <button
|
||||
<nav
|
||||
class="menu border-gray absolute w-full border bg-black p-2 text-xs transition-all"
|
||||
class:invisible={!isOpen}
|
||||
class:visible={isOpen}
|
||||
>
|
||||
<!-- TODO: what is this supposed to do? -->
|
||||
<!-- <button
|
||||
class="hover:bg-gray outline-gray focus:bg-secondary h-8 w-full p-1 text-left outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
||||
>
|
||||
language
|
||||
</button>
|
||||
<hr /> -->
|
||||
<button
|
||||
class="hover:bg-gray outline-gray h-7 w-full p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
on:click={() => shellOpenExternal("https://docs.tea.xyz")}
|
||||
>
|
||||
docs
|
||||
</button>
|
||||
<hr />
|
||||
<UpdateButton />
|
||||
</nav>
|
||||
<button
|
||||
class="hover:bg-gray outline-gray h-7 w-full p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
on:click={() => shellOpenExternal("https://docs.tea.xyz")}
|
||||
>
|
||||
docs
|
||||
</button>
|
||||
<hr />
|
||||
<UpdateButton />
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
hr {
|
||||
border: 1px solid #272626;
|
||||
margin: 1px 0;
|
||||
}
|
||||
hr {
|
||||
border: 1px solid #272626;
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
top: calc(100% + 4px);
|
||||
left: calc(50% - 80px);
|
||||
width: 160px;
|
||||
}
|
||||
.menu {
|
||||
top: calc(100% + 4px);
|
||||
left: calc(50% - 80px);
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.circle-badge::after {
|
||||
content: "1";
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
background: #ff4100;
|
||||
font-size: 10px;
|
||||
top: -7px;
|
||||
right: -7px;
|
||||
z-index: 1;
|
||||
}
|
||||
.circle-badge::after {
|
||||
content: "1";
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
background: #ff4100;
|
||||
font-size: 10px;
|
||||
top: -7px;
|
||||
right: -7px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
<script lang="ts">
|
||||
import Spinner from "@tea/ui/spinner/spinner.svelte";
|
||||
import { relaunch } from "@native";
|
||||
import { appUpdateStore } from "$libs/stores";
|
||||
import Spinner from "@tea/ui/spinner/spinner.svelte";
|
||||
import { relaunch } from "@native";
|
||||
import { appUpdateStore } from "$libs/stores";
|
||||
|
||||
const { updateStatus } = appUpdateStore;
|
||||
const { updateStatus } = appUpdateStore;
|
||||
</script>
|
||||
|
||||
{#if $updateStatus.status === "up-to-date"}
|
||||
<div
|
||||
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
>
|
||||
<div>up to date</div>
|
||||
<i class="installed-text icon-check-circle-o flex text-[#00ffd0]" />
|
||||
</div>
|
||||
<div
|
||||
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
>
|
||||
<div>up to date</div>
|
||||
<i class="installed-text icon-check-circle-o flex text-[#00ffd0]" />
|
||||
</div>
|
||||
{:else if $updateStatus.status === "available"}
|
||||
<div
|
||||
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
>
|
||||
<div>fetching update</div>
|
||||
<Spinner />
|
||||
</div>
|
||||
<div
|
||||
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
>
|
||||
<div>fetching update</div>
|
||||
<Spinner />
|
||||
</div>
|
||||
{:else if $updateStatus.status === "ready"}
|
||||
<button
|
||||
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
on:click={relaunch}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div class="circle-badge mr-2">1</div>
|
||||
<div>update</div>
|
||||
</div>
|
||||
<div class="rounded-sm bg-white px-2 text-[10px] leading-[12px] text-black">
|
||||
v{$updateStatus.version}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="hover:bg-gray outline-gray flex h-7 w-full items-center justify-between p-1 text-left outline-1 hover:bg-opacity-25 hover:outline"
|
||||
on:click={relaunch}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div class="circle-badge mr-2">1</div>
|
||||
<div>update</div>
|
||||
</div>
|
||||
<div class="rounded-sm bg-white px-2 text-[10px] leading-[12px] text-black">
|
||||
v{$updateStatus.version}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.circle-badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background: #ff4100;
|
||||
font-size: 10px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.circle-badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background: #ff4100;
|
||||
font-size: 10px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
export let icon: string;
|
||||
export let label: string;
|
||||
import "$appcss";
|
||||
export let icon: string;
|
||||
export let label: string;
|
||||
|
||||
export let active = false;
|
||||
export let active = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click
|
||||
class="outline-gray hover:bg-gray box-border flex w-full items-center gap-2 rounded-sm px-1 text-left align-middle text-xs outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
||||
class:active
|
||||
on:click
|
||||
class="outline-gray hover:bg-gray box-border flex w-full items-center gap-2 rounded-sm px-1 text-left align-middle text-xs outline-1 transition-all hover:bg-opacity-25 hover:outline"
|
||||
class:active
|
||||
>
|
||||
<i class="icon-{icon} mt-1" />
|
||||
<div class="text-sm font-thin">
|
||||
{label}
|
||||
</div>
|
||||
<slot />
|
||||
<i class="icon-{icon} mt-1" />
|
||||
<div class="text-sm font-thin">
|
||||
{label}
|
||||
</div>
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
height: 37px;
|
||||
}
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
button.active {
|
||||
background: rgba(148, 148, 148, 0.5);
|
||||
border: rgba(148, 148, 148, 1) 1px solid;
|
||||
}
|
||||
button.active {
|
||||
background: rgba(148, 148, 148, 0.5);
|
||||
border: rgba(148, 148, 148, 1) 1px solid;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,105 +1,105 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import { PackageStates, SideMenuOptions } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import MenuButton from "./menu-button.svelte";
|
||||
import { t } from "$libs/translations";
|
||||
import { goto } from "$app/navigation";
|
||||
const { packageList } = packagesStore;
|
||||
import "$appcss";
|
||||
import { PackageStates, SideMenuOptions } from "$libs/types";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import MenuButton from "./menu-button.svelte";
|
||||
import { t } from "$libs/translations";
|
||||
import { goto } from "$app/navigation";
|
||||
const { packageList } = packagesStore;
|
||||
|
||||
export let activeOption: SideMenuOptions;
|
||||
export let activeOption: SideMenuOptions;
|
||||
|
||||
$: needsUpdateCount = $packageList.filter((p) => p.state === PackageStates.NEEDS_UPDATE).length;
|
||||
$: needsUpdateCount = $packageList.filter((p) => p.state === PackageStates.NEEDS_UPDATE).length;
|
||||
</script>
|
||||
|
||||
<aside class="border-gray border border-t-0 border-b-0 border-l-0 p-2">
|
||||
<ul class="flex flex-col gap-1 pt-4 pl-1">
|
||||
<MenuButton
|
||||
label={$t("tags.discover").toLowerCase()}
|
||||
icon="map"
|
||||
active={activeOption === SideMenuOptions.discover}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.discover}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("side-menu-title.all").toLowerCase()}
|
||||
icon="grid"
|
||||
active={activeOption === SideMenuOptions.all}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.all}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label="installed"
|
||||
icon="tea-checkmark"
|
||||
active={activeOption === SideMenuOptions.installed}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.installed}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.installed_updates_available").toLowerCase()}
|
||||
icon="update"
|
||||
active={activeOption === SideMenuOptions.installed_updates_available}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.installed_updates_available}`)}
|
||||
>
|
||||
{#if needsUpdateCount > 0}
|
||||
<div class="update-count-badge">{needsUpdateCount}</div>
|
||||
{/if}
|
||||
</MenuButton>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.new_packages").toLowerCase()}
|
||||
icon="birthday-cake"
|
||||
active={activeOption === SideMenuOptions.new_packages}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.new_packages}`)}
|
||||
/>
|
||||
<!-- <hr />
|
||||
<ul class="flex flex-col gap-1 pt-4 pl-1">
|
||||
<MenuButton
|
||||
label={$t("tags.discover").toLowerCase()}
|
||||
icon="map"
|
||||
active={activeOption === SideMenuOptions.discover}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.discover}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("side-menu-title.all").toLowerCase()}
|
||||
icon="grid"
|
||||
active={activeOption === SideMenuOptions.all}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.all}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label="installed"
|
||||
icon="tea-checkmark"
|
||||
active={activeOption === SideMenuOptions.installed}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.installed}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.installed_updates_available").toLowerCase()}
|
||||
icon="update"
|
||||
active={activeOption === SideMenuOptions.installed_updates_available}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.installed_updates_available}`)}
|
||||
>
|
||||
{#if needsUpdateCount > 0}
|
||||
<div class="update-count-badge">{needsUpdateCount}</div>
|
||||
{/if}
|
||||
</MenuButton>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.new_packages").toLowerCase()}
|
||||
icon="birthday-cake"
|
||||
active={activeOption === SideMenuOptions.new_packages}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.new_packages}`)}
|
||||
/>
|
||||
<!-- <hr />
|
||||
<MenuButton
|
||||
label={$t("tags.popular").toLowerCase()}
|
||||
icon="bar-chart"
|
||||
active={activeOption === SideMenuOptions.popular}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.popular}`)}
|
||||
/> -->
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.recently_updated").toLowerCase()}
|
||||
icon="back-in-time"
|
||||
active={activeOption === SideMenuOptions.recently_updated}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.recently_updated}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.made_by_tea").toLowerCase()}
|
||||
icon="tea-logo-iconasset-1"
|
||||
active={activeOption === SideMenuOptions.made_by_tea}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.made_by_tea}`)}
|
||||
/>
|
||||
</ul>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.recently_updated").toLowerCase()}
|
||||
icon="back-in-time"
|
||||
active={activeOption === SideMenuOptions.recently_updated}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.recently_updated}`)}
|
||||
/>
|
||||
<hr />
|
||||
<MenuButton
|
||||
label={$t("tags.made_by_tea").toLowerCase()}
|
||||
icon="tea-logo-iconasset-1"
|
||||
active={activeOption === SideMenuOptions.made_by_tea}
|
||||
on:click={() => goto(`/?tab=${SideMenuOptions.made_by_tea}`)}
|
||||
/>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
aside {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: calc(100vh - 48px); /* win.height - title-bar.height */
|
||||
width: 210px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
aside {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: calc(100vh - 48px); /* win.height - title-bar.height */
|
||||
width: 210px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid #272626;
|
||||
}
|
||||
hr {
|
||||
border-top: 1px solid #272626;
|
||||
}
|
||||
|
||||
.update-count-badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background: #ff4100;
|
||||
font-size: 11px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.update-count-badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background: #ff4100;
|
||||
font-size: 11px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import type { Package } from "@tea/ui/types";
|
||||
import { PackageStates } from "$libs/types";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import PackageCard from "$components/packages/package.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import "$appcss";
|
||||
import type { GUIPackage } from "$libs/types";
|
||||
import type { Package } from "@tea/ui/types";
|
||||
import { PackageStates } from "$libs/types";
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import PackageCard from "$components/packages/package.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
|
||||
export let pkg: Package;
|
||||
export let pkg: Package;
|
||||
|
||||
let packages: GUIPackage[] = [];
|
||||
let packages: GUIPackage[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
if (!packages.length) {
|
||||
const matches = await packagesStore.search(pkg.desc, 4);
|
||||
packages = matches.filter((mp) => mp.full_name !== pkg.full_name).slice(0, 3);
|
||||
}
|
||||
});
|
||||
onMount(async () => {
|
||||
if (!packages.length) {
|
||||
const matches = await packagesStore.search(pkg.desc, 4);
|
||||
packages = matches.filter((mp) => mp.full_name !== pkg.full_name).slice(0, 3);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<header class="border-gray text-primary flex items-center justify-between border bg-black p-4">
|
||||
<span>MORE LIKE THIS</span>
|
||||
<span>MORE LIKE THIS</span>
|
||||
</header>
|
||||
<ul class="grid grid-cols-3 bg-black">
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg}
|
||||
<div class={pkg.state === PackageStates.INSTALLING ? "animate-pulse" : ""}>
|
||||
<PackageCard {pkg} />
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Array(9) as _}
|
||||
<section class="h-50 border-gray border p-4">
|
||||
<Preloader />
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg}
|
||||
<div class={pkg.state === PackageStates.INSTALLING ? "animate-pulse" : ""}>
|
||||
<PackageCard {pkg} />
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Array(9) as _}
|
||||
<section class="h-50 border-gray border p-4">
|
||||
<Preloader />
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
|
|
|
@ -1,84 +1,84 @@
|
|||
<script lang="ts">
|
||||
import { authStore, navStore } from "$libs/stores";
|
||||
import { getSession } from "@native";
|
||||
import { baseUrl } from "$libs/v1-client";
|
||||
import { shellOpenExternal } from "@native";
|
||||
import mouseLeaveDelay from "@tea/ui/lib/mouse-leave-delay";
|
||||
const { user } = authStore;
|
||||
import { authStore, navStore } from "$libs/stores";
|
||||
import { getSession } from "@native";
|
||||
import { baseUrl } from "$libs/v1-client";
|
||||
import { shellOpenExternal } from "@native";
|
||||
import mouseLeaveDelay from "@tea/ui/lib/mouse-leave-delay";
|
||||
const { user } = authStore;
|
||||
|
||||
$: authenticating = false;
|
||||
$: authenticating = false;
|
||||
|
||||
$: isLogoutOpen = false;
|
||||
$: isLogoutOpen = false;
|
||||
|
||||
const openGithub = async () => {
|
||||
if (!authenticating) {
|
||||
authenticating = true;
|
||||
try {
|
||||
const session = await getSession();
|
||||
const openGithub = async () => {
|
||||
if (!authenticating) {
|
||||
authenticating = true;
|
||||
try {
|
||||
const session = await getSession();
|
||||
|
||||
if (session && session.device_id) {
|
||||
shellOpenExternal(`${baseUrl}/auth/user?device_id=${session.device_id}`);
|
||||
authStore.pollSession();
|
||||
} else {
|
||||
throw new Error("possible no internet connection");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
authenticating = false;
|
||||
}
|
||||
}
|
||||
isLogoutOpen = false;
|
||||
};
|
||||
if (session && session.device_id) {
|
||||
shellOpenExternal(`${baseUrl}/auth/user?device_id=${session.device_id}`);
|
||||
authStore.pollSession();
|
||||
} else {
|
||||
throw new Error("possible no internet connection");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
authenticating = false;
|
||||
}
|
||||
}
|
||||
isLogoutOpen = false;
|
||||
};
|
||||
|
||||
const logout = () => authStore.clearSession();
|
||||
const logout = () => authStore.clearSession();
|
||||
|
||||
const preventDoubleClick = (evt: MouseEvent) => evt.stopPropagation();
|
||||
const preventDoubleClick = (evt: MouseEvent) => evt.stopPropagation();
|
||||
</script>
|
||||
|
||||
{#if $user}
|
||||
<div class="relative" use:mouseLeaveDelay={2000} on:leave_delay={() => (isLogoutOpen = false)}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<section
|
||||
class="border-gray text-gray group flex h-[28px] min-w-[120px] max-w-[160px] items-center justify-between rounded-sm border bg-black pl-2 text-sm transition-all
|
||||
<div class="relative" use:mouseLeaveDelay={2000} on:leave_delay={() => (isLogoutOpen = false)}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<section
|
||||
class="border-gray text-gray group flex h-[28px] min-w-[120px] max-w-[160px] items-center justify-between rounded-sm border bg-black pl-2 text-sm transition-all
|
||||
hover:bg-[#e1e1e1] hover:text-black"
|
||||
on:click={() => (isLogoutOpen = !isLogoutOpen)}
|
||||
on:dblclick={preventDoubleClick}
|
||||
>
|
||||
<div class="text-gray line-clamp-1 mr-1 group-hover:text-black">@{$user?.login}</div>
|
||||
<img
|
||||
id="avatar"
|
||||
class="flex rounded-sm"
|
||||
src={$user?.avatar_url || "/images/bored-ape.png"}
|
||||
alt="profile"
|
||||
/>
|
||||
</section>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="border-gray text-gray group absolute z-50 mt-1 flex h-[28px] w-[120px] items-center justify-between rounded-sm border bg-black pl-3 text-sm transition-all
|
||||
on:click={() => (isLogoutOpen = !isLogoutOpen)}
|
||||
on:dblclick={preventDoubleClick}
|
||||
>
|
||||
<div class="text-gray line-clamp-1 mr-1 group-hover:text-black">@{$user?.login}</div>
|
||||
<img
|
||||
id="avatar"
|
||||
class="flex rounded-sm"
|
||||
src={$user?.avatar_url || "/images/bored-ape.png"}
|
||||
alt="profile"
|
||||
/>
|
||||
</section>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="border-gray text-gray group absolute z-50 mt-1 flex h-[28px] w-[120px] items-center justify-between rounded-sm border bg-black pl-3 text-sm transition-all
|
||||
hover:bg-[#e1e1e1] hover:text-black"
|
||||
class:invisible={!isLogoutOpen}
|
||||
class:visible={isLogoutOpen}
|
||||
on:click={logout}
|
||||
>
|
||||
log out
|
||||
</div>
|
||||
</div>
|
||||
class:invisible={!isLogoutOpen}
|
||||
class:visible={isLogoutOpen}
|
||||
on:click={logout}
|
||||
>
|
||||
log out
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="border-gray text-gray h-[28px] w-[120px] rounded-sm border px-1 text-sm transition-all hover:bg-[#e1e1e1] hover:text-black"
|
||||
class:animate-pulse={authenticating}
|
||||
on:click={openGithub}
|
||||
on:dblclick={preventDoubleClick}
|
||||
>
|
||||
log in
|
||||
</button>
|
||||
<button
|
||||
class="border-gray text-gray h-[28px] w-[120px] rounded-sm border px-1 text-sm transition-all hover:bg-[#e1e1e1] hover:text-black"
|
||||
class:animate-pulse={authenticating}
|
||||
on:click={openGithub}
|
||||
on:dblclick={preventDoubleClick}
|
||||
>
|
||||
log in
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#avatar {
|
||||
padding: 1px;
|
||||
height: 26px !important;
|
||||
width: 26px !important;
|
||||
}
|
||||
#avatar {
|
||||
padding: 1px;
|
||||
height: 26px !important;
|
||||
width: 26px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { shellOpenExternal, submitLogs } from "@native";
|
||||
import LoginButton from "./login-button.svelte";
|
||||
import ButtonIcon from "@tea/ui/button-icon/button-icon.svelte";
|
||||
import SettingsMenu from "$components/settings-menu/settings-menu.svelte";
|
||||
import { shellOpenExternal, submitLogs } from "@native";
|
||||
import LoginButton from "./login-button.svelte";
|
||||
import ButtonIcon from "@tea/ui/button-icon/button-icon.svelte";
|
||||
import SettingsMenu from "$components/settings-menu/settings-menu.svelte";
|
||||
|
||||
const submitBugReport = async () => {
|
||||
const logId = await submitLogs();
|
||||
const bugFormUrl = `https://airtable.com/shravDxWeNwwpPkFV?prefill_log_id=${logId}&hide_log_id=true`;
|
||||
shellOpenExternal(bugFormUrl);
|
||||
};
|
||||
const submitBugReport = async () => {
|
||||
const logId = await submitLogs();
|
||||
const bugFormUrl = `https://airtable.com/shravDxWeNwwpPkFV?prefill_log_id=${logId}&hide_log_id=true`;
|
||||
shellOpenExternal(bugFormUrl);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="mr-1 flex h-full items-center justify-end gap-2 p-2">
|
||||
<ButtonIcon icon="bug" helpText="report feedback" on:click={() => submitBugReport()} />
|
||||
<SettingsMenu />
|
||||
<LoginButton />
|
||||
<ButtonIcon icon="bug" helpText="report feedback" on:click={() => submitBugReport()} />
|
||||
<SettingsMenu />
|
||||
<LoginButton />
|
||||
</div>
|
||||
|
|
|
@ -1,96 +1,102 @@
|
|||
<script lang="ts">
|
||||
import { searchStore } from "$libs/stores";
|
||||
import SearchInput from "@tea/ui/search-input/search-input.svelte";
|
||||
import { navStore } from "$libs/stores";
|
||||
import { t } from "$libs/translations";
|
||||
import { searchStore } from "$libs/stores";
|
||||
import SearchInput from "@tea/ui/search-input/search-input.svelte";
|
||||
import { navStore } from "$libs/stores";
|
||||
import { t } from "$libs/translations";
|
||||
|
||||
import TopBarMenu from "./top-bar-menu.svelte";
|
||||
import { topbarDoubleClick } from "$libs/native-electron";
|
||||
import TopBarMenu from "./top-bar-menu.svelte";
|
||||
import { topbarDoubleClick } from "$libs/native-electron";
|
||||
|
||||
let { nextPath, prevPath } = navStore;
|
||||
let { nextPath, prevPath } = navStore;
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="border-gray relative z-20 flex h-12 w-full items-center justify-between border border-x-0 border-t-0 pr-2"
|
||||
style="-webkit-app-region: drag"
|
||||
on:dblclick={topbarDoubleClick}
|
||||
class="border-gray relative z-20 flex h-12 w-full items-center justify-between border border-x-0 border-t-0 pr-2"
|
||||
style="-webkit-app-region: drag"
|
||||
on:dblclick={topbarDoubleClick}
|
||||
>
|
||||
<ul class="text-gray flex h-10 gap-1 pl-20 align-middle items-center leading-10">
|
||||
<a href="/?tab=all">
|
||||
<div class="home-btn w-12 text-center text-2xl">
|
||||
<i class="icon-tea-logo-iconasset-1" />
|
||||
</div>
|
||||
</a>
|
||||
<p class="text-gray px-2">beta</p>
|
||||
<button on:click={navStore.back} class:active={$prevPath} class="pt-1 px-2 h-[28px] text-xs rounded-sm transition-all opacity-50 hover:bg-gray hover:text-black" title="go back"
|
||||
><i class="icon-arrow-left" /></button
|
||||
>
|
||||
<button on:click={navStore.next} class:active={$nextPath} class="pt-1 px-2 h-[28px] text-xs rounded-sm transition-all opacity-50 hover:bg-gray hover:text-black" title="go forward"
|
||||
><i class="icon-arrow-right" /></button
|
||||
>
|
||||
</ul>
|
||||
<div class="relative w-1/3 px-2">
|
||||
<SearchInput
|
||||
class="border-gray h-9 w-full rounded-sm border"
|
||||
size="small"
|
||||
placeholder={$t("store-search-placeholder")}
|
||||
onFocus={() => {
|
||||
searchStore.searching.set(true);
|
||||
}}
|
||||
readonly={true}
|
||||
/>
|
||||
<ul class="text-gray flex h-10 items-center gap-1 pl-20 align-middle leading-10">
|
||||
<a href="/?tab=all">
|
||||
<div class="home-btn w-12 text-center text-2xl">
|
||||
<i class="icon-tea-logo-iconasset-1" />
|
||||
</div>
|
||||
</a>
|
||||
<p class="text-gray px-2">beta</p>
|
||||
<button
|
||||
on:click={navStore.back}
|
||||
class:active={$prevPath}
|
||||
class="hover:bg-gray h-[28px] rounded-sm px-2 pt-1 text-xs opacity-50 transition-all hover:text-black"
|
||||
title="go back"><i class="icon-arrow-left" /></button
|
||||
>
|
||||
<button
|
||||
on:click={navStore.next}
|
||||
class:active={$nextPath}
|
||||
class="hover:bg-gray h-[28px] rounded-sm px-2 pt-1 text-xs opacity-50 transition-all hover:text-black"
|
||||
title="go forward"><i class="icon-arrow-right" /></button
|
||||
>
|
||||
</ul>
|
||||
<div class="relative w-1/3 px-2">
|
||||
<SearchInput
|
||||
class="border-gray h-9 w-full rounded-sm border"
|
||||
size="small"
|
||||
placeholder={$t("store-search-placeholder")}
|
||||
onFocus={() => {
|
||||
searchStore.searching.set(true);
|
||||
}}
|
||||
readonly={true}
|
||||
/>
|
||||
|
||||
<kbd
|
||||
class="bg-gray pointer-events-none absolute top-0 right-3 mt-1 flex items-center rounded-sm px-2 text-white opacity-50"
|
||||
style="letter-spacing: 0.5pt"
|
||||
>
|
||||
<span class="text-lg">⌘</span>
|
||||
<span class="text-xs" style="font-size: smaller">K</span>
|
||||
</kbd>
|
||||
</div>
|
||||
<TopBarMenu />
|
||||
<kbd
|
||||
class="bg-gray pointer-events-none absolute top-0 right-3 mt-1 flex items-center rounded-sm px-2 text-white opacity-50"
|
||||
style="letter-spacing: 0.5pt"
|
||||
>
|
||||
<span class="text-lg">⌘</span>
|
||||
<span class="text-xs" style="font-size: smaller">K</span>
|
||||
</kbd>
|
||||
</div>
|
||||
<TopBarMenu />
|
||||
</header>
|
||||
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
header {
|
||||
background: rgba(26, 26, 26, 0.9);
|
||||
backdrop-filter: blur(2px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.home-btn {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
line-height: 46px;
|
||||
padding-left: 3px;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-image: url("/images/gradient-bg.png");
|
||||
color: #222222;
|
||||
}
|
||||
header {
|
||||
background: rgba(26, 26, 26, 0.9);
|
||||
backdrop-filter: blur(2px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.home-btn {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
line-height: 46px;
|
||||
padding-left: 3px;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-image: url("/images/gradient-bg.png");
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.home-btn:hover {
|
||||
color: white;
|
||||
}
|
||||
.home-btn:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.home-btn:active {
|
||||
color: #222222;
|
||||
border: 2px solid #222222
|
||||
}
|
||||
.home-btn:active {
|
||||
color: #222222;
|
||||
border: 2px solid #222222;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 10px;
|
||||
}
|
||||
ul button {
|
||||
pointer-events: none;
|
||||
}
|
||||
p {
|
||||
font-size: 10px;
|
||||
}
|
||||
ul button {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ul button.active {
|
||||
color: white;
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
ul button.active {
|
||||
color: white;
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,37 +1,40 @@
|
|||
<script lang="ts">
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import clickOutside from "@tea/ui/lib/clickOutside";
|
||||
import clickOutside from "@tea/ui/lib/clickOutside";
|
||||
|
||||
import { authStore } from "$libs/stores"
|
||||
import { authStore } from "$libs/stores";
|
||||
|
||||
const close = () => {
|
||||
authStore.updateSession({ hide_welcome: true });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="fixed z-50 top-0 left-0 flex items-center justify-center">
|
||||
<section class="fixed top-0 left-0 z-50 flex items-center justify-center">
|
||||
<aside class="relative" use:clickOutside on:click_outside={() => close()}>
|
||||
<article class="flex margin-auto p-2 border border-gray rounded-md">
|
||||
<article class="margin-auto border-gray flex rounded-md border p-2">
|
||||
<figure>
|
||||
<img class="object-contain" src="/images/welcome-bg.png" alt="welcome"/>
|
||||
<img class="object-contain" src="/images/welcome-bg.png" alt="welcome" />
|
||||
</figure>
|
||||
<div class="flex-grow mt-20 px-12 relative">
|
||||
<h1 class="text-primary text-4xl mb-4">Welcome to the tea app!</h1>
|
||||
<p class="font-inter mb-4">This app is your gateway into the world of open-source software. Easily explore and manage packages with a click of a button. This app will notify you of any available software updates to ensure you’re safe and secure. Under the hood is the powerful tea cli.</p>
|
||||
<div class="relative mt-20 flex-grow px-12">
|
||||
<h1 class="text-primary mb-4 text-4xl">Welcome to the tea app!</h1>
|
||||
<p class="font-inter mb-4">
|
||||
This app is your gateway into the world of open-source software. Easily explore and manage
|
||||
packages with a click of a button. This app will notify you of any available software
|
||||
updates to ensure you’re safe and secure. Under the hood is the powerful tea cli.
|
||||
</p>
|
||||
|
||||
<Button type="plain" color="secondary" class="w-7/12"
|
||||
onClick={() => close()}
|
||||
>
|
||||
<Button type="plain" color="secondary" class="w-7/12" onClick={() => close()}>
|
||||
START EXPLORING
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<button class="icon-tea-x-btn absolute text-gray top-5 right-5"
|
||||
<button
|
||||
class="icon-tea-x-btn text-gray absolute top-5 right-5"
|
||||
on:click={() => {
|
||||
close()
|
||||
close();
|
||||
}}
|
||||
></button>
|
||||
/>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
|
@ -39,13 +42,13 @@
|
|||
section {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: rgba(0,0,0, 0.5);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
article {
|
||||
height: 472px;
|
||||
width: 725px;
|
||||
background: rgba(0,0,0, 0.8);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
figure {
|
||||
|
@ -54,4 +57,4 @@
|
|||
img {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -3,47 +3,47 @@ import * as pub from "$env/static/public";
|
|||
import { getSession } from "@native";
|
||||
|
||||
type DefaultMixpanelProps = {
|
||||
device_id: string;
|
||||
uid: string;
|
||||
distinct_id: string;
|
||||
locale: string;
|
||||
gui_version: string;
|
||||
device_id: string;
|
||||
uid: string;
|
||||
distinct_id: string;
|
||||
locale: string;
|
||||
gui_version: string;
|
||||
};
|
||||
let mixpanelDefaultData: DefaultMixpanelProps;
|
||||
const getLocalSession = async (): Promise<DefaultMixpanelProps> => {
|
||||
if (mixpanelDefaultData) return mixpanelDefaultData;
|
||||
const session = await getSession();
|
||||
mixpanelDefaultData = {
|
||||
device_id: session?.device_id || "unregistered",
|
||||
uid: session?.user?.developer_id || "unauthenticated",
|
||||
distinct_id: session?.device_id || "unregistered",
|
||||
locale: session?.locale || "unknown",
|
||||
gui_version: pub.PUBLIC_VERSION
|
||||
};
|
||||
return mixpanelDefaultData;
|
||||
if (mixpanelDefaultData) return mixpanelDefaultData;
|
||||
const session = await getSession();
|
||||
mixpanelDefaultData = {
|
||||
device_id: session?.device_id || "unregistered",
|
||||
uid: session?.user?.developer_id || "unauthenticated",
|
||||
distinct_id: session?.device_id || "unregistered",
|
||||
locale: session?.locale || "unknown",
|
||||
gui_version: pub.PUBLIC_VERSION
|
||||
};
|
||||
return mixpanelDefaultData;
|
||||
};
|
||||
|
||||
mixpanel.init(pub.PUBLIC_MIXPANEL_TOKEN, { debug: true });
|
||||
|
||||
enum AnalyticsAction {
|
||||
install = "INSTALL_ACTION",
|
||||
install_failed = "INSTALL_ACTION_FAILED",
|
||||
search = "SEARCH_ACTION",
|
||||
search_failed = "SEARCH_ACTION_FAILED"
|
||||
install = "INSTALL_ACTION",
|
||||
install_failed = "INSTALL_ACTION_FAILED",
|
||||
search = "SEARCH_ACTION",
|
||||
search_failed = "SEARCH_ACTION_FAILED"
|
||||
}
|
||||
|
||||
const trackAction = (action: AnalyticsAction, data?: { [key: string]: any }) => {
|
||||
getLocalSession()
|
||||
.then((props) => {
|
||||
mixpanel.track(action, {
|
||||
...(data || {}),
|
||||
...props
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO: log remote error stream
|
||||
console.error(error);
|
||||
});
|
||||
getLocalSession()
|
||||
.then((props) => {
|
||||
mixpanel.track(action, {
|
||||
...(data || {}),
|
||||
...props
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO: log remote error stream
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -52,7 +52,7 @@ const trackAction = (action: AnalyticsAction, data?: { [key: string]: any }) =>
|
|||
* @returns void
|
||||
*/
|
||||
export const trackInstall = (packageFullname: string) =>
|
||||
trackAction(AnalyticsAction.install, { pkg: packageFullname });
|
||||
trackAction(AnalyticsAction.install, { pkg: packageFullname });
|
||||
|
||||
/**
|
||||
* save failed installation event to mixpanel
|
||||
|
@ -60,21 +60,21 @@ export const trackInstall = (packageFullname: string) =>
|
|||
* @param error error message
|
||||
*/
|
||||
export const trackInstallFailed = (packageFullname: string, error: string) => {
|
||||
trackAction(AnalyticsAction.install_failed, {
|
||||
pkg: packageFullname,
|
||||
error
|
||||
});
|
||||
trackAction(AnalyticsAction.install_failed, {
|
||||
pkg: packageFullname,
|
||||
error
|
||||
});
|
||||
};
|
||||
|
||||
export const trackSearch = (search_term: string, result_count: number) => {
|
||||
if (result_count > 0) {
|
||||
trackAction(AnalyticsAction.search, {
|
||||
search_term,
|
||||
result_count
|
||||
});
|
||||
} else {
|
||||
trackAction(AnalyticsAction.search_failed, {
|
||||
search_term
|
||||
});
|
||||
}
|
||||
if (result_count > 0) {
|
||||
trackAction(AnalyticsAction.search, {
|
||||
search_term,
|
||||
result_count
|
||||
});
|
||||
} else {
|
||||
trackAction(AnalyticsAction.search_failed, {
|
||||
search_term
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,57 +2,57 @@ import axios from "axios";
|
|||
import type { Contributor, Package } from "@tea/ui/types";
|
||||
import yaml from "js-yaml";
|
||||
export async function getPackageYaml(pkgYamlUrl: string) {
|
||||
const url = pkgYamlUrl.replace("/github.com", "/raw.githubusercontent.com").replace("/blob", "");
|
||||
const url = pkgYamlUrl.replace("/github.com", "/raw.githubusercontent.com").replace("/blob", "");
|
||||
|
||||
const { data: rawYaml } = await axios.get(url);
|
||||
const { data: rawYaml } = await axios.get(url);
|
||||
|
||||
const data = await yaml.load(rawYaml);
|
||||
const data = await yaml.load(rawYaml);
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getReadme(
|
||||
owner: string,
|
||||
repo: string
|
||||
owner: string,
|
||||
repo: string
|
||||
): Promise<{ data: string; type: "md" | "rst" }> {
|
||||
let type: "md" | "rst" = "md";
|
||||
let data = "";
|
||||
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}/readme`);
|
||||
let type: "md" | "rst" = "md";
|
||||
let data = "";
|
||||
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}/readme`);
|
||||
|
||||
if (req.data?.download_url) {
|
||||
type = req.data.name.endsWith(".rst") ? "rst" : "md";
|
||||
const reqDl = await axios.get(req.data.download_url);
|
||||
data = reqDl.data;
|
||||
}
|
||||
return { data, type };
|
||||
if (req.data?.download_url) {
|
||||
type = req.data.name.endsWith(".rst") ? "rst" : "md";
|
||||
const reqDl = await axios.get(req.data.download_url);
|
||||
data = reqDl.data;
|
||||
}
|
||||
return { data, type };
|
||||
}
|
||||
|
||||
export async function getContributors(owner: string, repo: string): Promise<Contributor[]> {
|
||||
// maintainer/repo
|
||||
let contributors: Contributor[] = [];
|
||||
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}/contributors`);
|
||||
if (req.data) {
|
||||
contributors = req.data.map((c: Contributor & { id: number }) => ({
|
||||
login: c.login,
|
||||
avatar_url: c.avatar_url,
|
||||
name: c.name || "",
|
||||
github_id: c.id,
|
||||
contributions: c.contributions
|
||||
}));
|
||||
}
|
||||
return contributors;
|
||||
// maintainer/repo
|
||||
let contributors: Contributor[] = [];
|
||||
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}/contributors`);
|
||||
if (req.data) {
|
||||
contributors = req.data.map((c: Contributor & { id: number }) => ({
|
||||
login: c.login,
|
||||
avatar_url: c.avatar_url,
|
||||
name: c.name || "",
|
||||
github_id: c.id,
|
||||
contributions: c.contributions
|
||||
}));
|
||||
}
|
||||
return contributors;
|
||||
}
|
||||
|
||||
export async function getRepoAsPackage(owner: string, repo: string): Promise<Partial<Package>> {
|
||||
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}`);
|
||||
const pkg: Partial<Package> = {};
|
||||
if (req.data) {
|
||||
pkg.license = req.data?.license?.name || "";
|
||||
}
|
||||
return pkg;
|
||||
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}`);
|
||||
const pkg: Partial<Package> = {};
|
||||
if (req.data) {
|
||||
pkg.license = req.data?.license?.name || "";
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
export const trimGithubSlug = (slug: string): string => {
|
||||
const [owner, repo] = slug.split("/");
|
||||
return [owner, repo].join("/");
|
||||
const [owner, repo] = slug.split("/");
|
||||
return [owner, repo].join("/");
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { captureException } from "./sentry";
|
|||
// TODO: figure out how to detect if pkaged
|
||||
const oldError = log.error;
|
||||
log.error = (...params: any[]) => {
|
||||
oldError(params);
|
||||
captureException(params[0].message);
|
||||
oldError(params);
|
||||
captureException(params[0].message);
|
||||
};
|
||||
export default log;
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
|
||||
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
|
||||
import {
|
||||
type GUIPackage,
|
||||
type DeviceAuth,
|
||||
type Session,
|
||||
AuthStatus,
|
||||
type Packages,
|
||||
type AutoUpdateStatus
|
||||
type GUIPackage,
|
||||
type DeviceAuth,
|
||||
type Session,
|
||||
AuthStatus,
|
||||
type Packages,
|
||||
type AutoUpdateStatus
|
||||
} from "./types";
|
||||
|
||||
import * as mock from "./native-mock";
|
||||
|
@ -31,270 +31,270 @@ import log from "./logger";
|
|||
const { ipcRenderer, shell } = window.require("electron");
|
||||
|
||||
export async function getDistPackages(): Promise<Package[]> {
|
||||
try {
|
||||
return withRetry(async () => {
|
||||
const req = await axios.get<Package[]>(
|
||||
"https://s3.amazonaws.com/preview.gui.tea.xyz/packages.json"
|
||||
);
|
||||
log.info("packages received:", req.data.length);
|
||||
return req.data;
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("getDistPackagesList:", error);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
return withRetry(async () => {
|
||||
const req = await axios.get<Package[]>(
|
||||
"https://s3.amazonaws.com/preview.gui.tea.xyz/packages.json"
|
||||
);
|
||||
log.info("packages received:", req.data.length);
|
||||
return req.data;
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("getDistPackagesList:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getInstalledPackages(): Promise<InstalledPackage[]> {
|
||||
let pkgs: InstalledPackage[] = [];
|
||||
try {
|
||||
log.info("getting installed packages");
|
||||
pkgs = (await ipcRenderer.invoke("get-installed-packages")) as InstalledPackage[];
|
||||
log.info("got installed packages:", pkgs.length);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
return pkgs;
|
||||
let pkgs: InstalledPackage[] = [];
|
||||
try {
|
||||
log.info("getting installed packages");
|
||||
pkgs = (await ipcRenderer.invoke("get-installed-packages")) as InstalledPackage[];
|
||||
log.info("got installed packages:", pkgs.length);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
return pkgs;
|
||||
}
|
||||
|
||||
export async function getPackages(): Promise<GUIPackage[]> {
|
||||
const [packages, installedPackages] = await Promise.all([
|
||||
getDistPackages(),
|
||||
ipcRenderer.invoke("get-installed-packages") as InstalledPackage[]
|
||||
]);
|
||||
const [packages, installedPackages] = await Promise.all([
|
||||
getDistPackages(),
|
||||
ipcRenderer.invoke("get-installed-packages") as InstalledPackage[]
|
||||
]);
|
||||
|
||||
// NOTE: its not ideal to get bottles or set package states here maybe do it async in the package store init
|
||||
// --- it has noticeable slowness
|
||||
log.info(`native: installed ${installedPackages.length} out of ${(packages || []).length}`);
|
||||
return (packages || []).map((pkg) => {
|
||||
const installedPkg = installedPackages.find((p) => p.full_name === pkg.full_name);
|
||||
return {
|
||||
...pkg,
|
||||
state: installedPkg ? PackageStates.INSTALLED : PackageStates.AVAILABLE,
|
||||
installed_versions: installedPkg?.installed_versions || []
|
||||
};
|
||||
});
|
||||
// NOTE: its not ideal to get bottles or set package states here maybe do it async in the package store init
|
||||
// --- it has noticeable slowness
|
||||
log.info(`native: installed ${installedPackages.length} out of ${(packages || []).length}`);
|
||||
return (packages || []).map((pkg) => {
|
||||
const installedPkg = installedPackages.find((p) => p.full_name === pkg.full_name);
|
||||
return {
|
||||
...pkg,
|
||||
state: installedPkg ? PackageStates.INSTALLED : PackageStates.AVAILABLE,
|
||||
installed_versions: installedPkg?.installed_versions || []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFeaturedPackages(): Promise<Package[]> {
|
||||
const packages = await mock.getFeaturedPackages();
|
||||
return packages;
|
||||
const packages = await mock.getFeaturedPackages();
|
||||
return packages;
|
||||
}
|
||||
|
||||
export async function getPackageReviews(full_name: string): Promise<Review[]> {
|
||||
console.log(`getting reviews for ${full_name}`);
|
||||
const reviews: Review[] =
|
||||
(await apiGet<Review[]>(`packages/${full_name.replaceAll("/", ":")}/reviews`)) ?? [];
|
||||
console.log(`getting reviews for ${full_name}`);
|
||||
const reviews: Review[] =
|
||||
(await apiGet<Review[]>(`packages/${full_name.replaceAll("/", ":")}/reviews`)) ?? [];
|
||||
|
||||
return reviews;
|
||||
return reviews;
|
||||
}
|
||||
|
||||
export async function installPackage(pkg: GUIPackage, version?: string) {
|
||||
const latestVersion = pkg.version;
|
||||
const specificVersion = version || latestVersion;
|
||||
const latestVersion = pkg.version;
|
||||
const specificVersion = version || latestVersion;
|
||||
|
||||
log.info(`installing package: ${pkg.name} version: ${specificVersion}`);
|
||||
log.info(`installing package: ${pkg.name} version: ${specificVersion}`);
|
||||
|
||||
const res = await ipcRenderer.invoke("install-package", {
|
||||
full_name: pkg.full_name,
|
||||
version: specificVersion
|
||||
});
|
||||
const res = await ipcRenderer.invoke("install-package", {
|
||||
full_name: pkg.full_name,
|
||||
version: specificVersion
|
||||
});
|
||||
|
||||
if (res instanceof Error) {
|
||||
throw res;
|
||||
}
|
||||
if (res instanceof Error) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncPantry() {
|
||||
const res = await ipcRenderer.invoke("sync-pantry");
|
||||
if (res instanceof Error) {
|
||||
throw res;
|
||||
}
|
||||
const res = await ipcRenderer.invoke("sync-pantry");
|
||||
if (res instanceof Error) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTopPackages(): Promise<GUIPackage[]> {
|
||||
const packages = await mock.getTopPackages();
|
||||
return packages;
|
||||
const packages = await mock.getTopPackages();
|
||||
return packages;
|
||||
}
|
||||
|
||||
export async function getAllPosts(tag?: string): Promise<AirtablePost[]> {
|
||||
// add filter here someday: tag = news | course
|
||||
try {
|
||||
const queryParams = {
|
||||
...(tag ? { tag } : {}),
|
||||
nocache: "true"
|
||||
};
|
||||
const posts = await apiGet<AirtablePost[]>("posts", queryParams);
|
||||
return posts || [];
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return [];
|
||||
}
|
||||
// add filter here someday: tag = news | course
|
||||
try {
|
||||
const queryParams = {
|
||||
...(tag ? { tag } : {}),
|
||||
nocache: "true"
|
||||
};
|
||||
const posts = await apiGet<AirtablePost[]>("posts", queryParams);
|
||||
return posts || [];
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> {
|
||||
let auth: DeviceAuth = {
|
||||
status: AuthStatus.UNKNOWN,
|
||||
key: ""
|
||||
};
|
||||
try {
|
||||
const data = await apiGet<DeviceAuth>(`/auth/device/${deviceId}`);
|
||||
if (data) auth = data;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
auth = await getDeviceAuth(deviceId);
|
||||
}
|
||||
return auth;
|
||||
let auth: DeviceAuth = {
|
||||
status: AuthStatus.UNKNOWN,
|
||||
key: ""
|
||||
};
|
||||
try {
|
||||
const data = await apiGet<DeviceAuth>(`/auth/device/${deviceId}`);
|
||||
if (data) auth = data;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
auth = await getDeviceAuth(deviceId);
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
|
||||
export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
|
||||
try {
|
||||
return withRetry(async () => {
|
||||
const pkg = await apiGet<Package>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
log.info(`got ${pkg?.bottles?.length || 0} bottles for ${packageName}`);
|
||||
return (pkg && pkg.bottles) || [];
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("getPackageBottles:", error);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
return withRetry(async () => {
|
||||
const pkg = await apiGet<Package>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
log.info(`got ${pkg?.bottles?.length || 0} bottles for ${packageName}`);
|
||||
return (pkg && pkg.bottles) || [];
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("getPackageBottles:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPackage(packageName: string): Promise<Partial<Package>> {
|
||||
try {
|
||||
return await withRetry(async () => {
|
||||
const data = await apiGet<Partial<Package>>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
if (data) {
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(`package:${packageName} not found`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("getPackage:", error);
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
return await withRetry(async () => {
|
||||
const data = await apiGet<Partial<Package>>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
if (data) {
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(`package:${packageName} not found`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("getPackage:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export const getSession = async (): Promise<Session | null> => {
|
||||
try {
|
||||
const session = await ipcRenderer.invoke("get-session");
|
||||
if (!session) throw new Error("no session found");
|
||||
return session;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const session = await ipcRenderer.invoke("get-session");
|
||||
if (!session) throw new Error("no session found");
|
||||
return session;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateSession = async (session: Partial<Session>) => {
|
||||
try {
|
||||
await ipcRenderer.invoke("update-session", session);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
await ipcRenderer.invoke("update-session", session);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const openPackageEntrypointInTerminal = (pkg: string) => {
|
||||
try {
|
||||
ipcRenderer.invoke("open-terminal", { pkg });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
ipcRenderer.invoke("open-terminal", { pkg });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const shellOpenExternal = (link?: string) => {
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
shell.openExternal(link);
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
shell.openExternal(link);
|
||||
};
|
||||
|
||||
export const listenToChannel = (channel: string, callback: (data: any) => void) => {
|
||||
ipcRenderer.on(channel, (_: any, data: any) => callback(data));
|
||||
ipcRenderer.on(channel, (_: any, data: any) => callback(data));
|
||||
};
|
||||
|
||||
export const relaunch = () => ipcRenderer.invoke("relaunch");
|
||||
|
||||
export const getProtocolPath = async (): Promise<string> => {
|
||||
const path = await ipcRenderer.invoke("get-protocol-path");
|
||||
return path;
|
||||
const path = await ipcRenderer.invoke("get-protocol-path");
|
||||
return path;
|
||||
};
|
||||
|
||||
export const submitLogs = async (): Promise<string> => {
|
||||
const response = await ipcRenderer.invoke("submit-logs");
|
||||
return response;
|
||||
const response = await ipcRenderer.invoke("submit-logs");
|
||||
return response;
|
||||
};
|
||||
|
||||
export const isPackageInstalled = async (fullName: string, version?: string): Promise<boolean> => {
|
||||
let isInstalled = false;
|
||||
const pkgs = await getInstalledPackages();
|
||||
const pkg = pkgs.find((p) => p.full_name === fullName);
|
||||
let isInstalled = false;
|
||||
const pkgs = await getInstalledPackages();
|
||||
const pkg = pkgs.find((p) => p.full_name === fullName);
|
||||
|
||||
if (pkg) {
|
||||
isInstalled = true;
|
||||
if (version) {
|
||||
isInstalled = pkg.installed_versions.includes(version);
|
||||
}
|
||||
}
|
||||
if (pkg) {
|
||||
isInstalled = true;
|
||||
if (version) {
|
||||
isInstalled = pkg.installed_versions.includes(version);
|
||||
}
|
||||
}
|
||||
|
||||
return isInstalled;
|
||||
return isInstalled;
|
||||
};
|
||||
|
||||
export const setBadgeCount = async (count: number) => {
|
||||
try {
|
||||
await ipcRenderer.invoke("set-badge-count", { count });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
await ipcRenderer.invoke("set-badge-count", { count });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePackage = async (args: { fullName: string; version: string }) => {
|
||||
const result = await ipcRenderer.invoke("delete-package", args);
|
||||
if (result instanceof Error) {
|
||||
throw result;
|
||||
}
|
||||
const result = await ipcRenderer.invoke("delete-package", args);
|
||||
if (result instanceof Error) {
|
||||
throw result;
|
||||
}
|
||||
};
|
||||
|
||||
export const loadPackageCache = async () => {
|
||||
try {
|
||||
return await ipcRenderer.invoke("load-package-cache");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
return await ipcRenderer.invoke("load-package-cache");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const writePackageCache = async (pkgs: Packages) => {
|
||||
try {
|
||||
await ipcRenderer.invoke("write-package-cache", pkgs);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
await ipcRenderer.invoke("write-package-cache", pkgs);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const topbarDoubleClick = async () => {
|
||||
try {
|
||||
ipcRenderer.invoke("topbar-double-click");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
ipcRenderer.invoke("topbar-double-click");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const cacheImageURL = async (url: string): Promise<string | undefined> => {
|
||||
if (!url) return "";
|
||||
try {
|
||||
const cachedSrc = await ipcRenderer.invoke("cache-image", url);
|
||||
return cachedSrc;
|
||||
} catch (error) {
|
||||
log.error("Failed to cache image:", error);
|
||||
}
|
||||
if (!url) return "";
|
||||
try {
|
||||
const cachedSrc = await ipcRenderer.invoke("cache-image", url);
|
||||
return cachedSrc;
|
||||
} catch (error) {
|
||||
log.error("Failed to cache image:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getAutoUpdateStatus = async (): Promise<AutoUpdateStatus> => {
|
||||
try {
|
||||
return await ipcRenderer.invoke("get-auto-update-status");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { status: "up-to-date" };
|
||||
}
|
||||
try {
|
||||
return await ipcRenderer.invoke("get-auto-update-status");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { status: "up-to-date" };
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,399 +15,399 @@ import _ from "lodash";
|
|||
import * as v1Client from "$libs/v1-client";
|
||||
|
||||
const packages: Package[] = [
|
||||
{
|
||||
slug: "mesonbuild_com",
|
||||
homepage: "https://mesonbuild.com",
|
||||
name: "mesonbuild.com",
|
||||
version: "0.63.3",
|
||||
last_modified: "2022-10-06T15:45:08.000Z",
|
||||
full_name: "mesonbuild.com",
|
||||
dl_count: 270745,
|
||||
thumb_image_name: "mesonbuild_com_option 1.jpg ",
|
||||
maintainer: "",
|
||||
desc: "Fast and user friendly build system",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/mesonbuild_com.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-06T15:45:08.000Z",
|
||||
manual_sorting: 0,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "pixman_org",
|
||||
homepage: "http://www.pixman.org/",
|
||||
maintainer: "freedesktop",
|
||||
name: "pixman.org",
|
||||
version: "0.40.0",
|
||||
last_modified: "2022-09-26T19:37:47.000Z",
|
||||
full_name: "pixman.org",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "pixman_org_option 1.jpg ",
|
||||
desc: "Pixman is a library that provides low-level pixel manipulation features such as image compositing and trapezoid rasterization.",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/pixman_org.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-09-26T19:37:47.000Z",
|
||||
manual_sorting: 1,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "freedesktop_org_pkg_config",
|
||||
homepage: "https://freedesktop.org",
|
||||
maintainer: "freedesktop.org",
|
||||
name: "pkg-config",
|
||||
version: "0.29.2",
|
||||
last_modified: "2022-10-20T01:32:15.000Z",
|
||||
full_name: "freedesktop.org/pkg-config",
|
||||
dl_count: 2661501,
|
||||
thumb_image_name: "freedecktop_org_pkg_config option 1.jpg ",
|
||||
desc: "Manage compile and link flags for libraries",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/freedesktop_org_pkg_config.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-20T01:32:15.000Z",
|
||||
manual_sorting: 2,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "gnu_org_gettext",
|
||||
homepage: "https://gnu.org",
|
||||
maintainer: "gnu.org",
|
||||
name: "gettext",
|
||||
version: "0.21.1",
|
||||
last_modified: "2022-10-20T01:23:46.000Z",
|
||||
full_name: "gnu.org/gettext",
|
||||
dl_count: 3715970,
|
||||
thumb_image_name: "gnu_org_gettext_option 1.jpg ",
|
||||
desc: "GNU internationalization (i18n) and localization (l10n) library",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/gnu_org_gettext.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-20T01:23:46.000Z",
|
||||
manual_sorting: 3,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "ipfs_tech",
|
||||
homepage: "https://ipfs.tech",
|
||||
name: "ipfs.tech",
|
||||
version: "0.16.0",
|
||||
last_modified: "2022-10-19T21:36:52.000Z",
|
||||
full_name: "ipfs.tech",
|
||||
dl_count: 14457,
|
||||
thumb_image_name: "ipfs_tech_option 2.jpg ",
|
||||
maintainer: "",
|
||||
desc: "Peer-to-peer hypermedia protocol",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/ipfs_tech.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-19T21:36:52.000Z",
|
||||
manual_sorting: 4,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "nixos_org_patchelf",
|
||||
homepage: "https://nixos.org",
|
||||
maintainer: "nixos.org",
|
||||
name: "patchelf",
|
||||
version: "0.15.0",
|
||||
last_modified: "2022-09-27T04:50:44.000Z",
|
||||
full_name: "nixos.org/patchelf",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "nixos_org_patchelf_option 1.jpg ",
|
||||
desc: "PatchELF is a simple utility for modifying existing ELF executables and libraries.",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/nixos_org_patchelf.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-09-27T04:50:44.000Z",
|
||||
manual_sorting: 5,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "tea_xyz",
|
||||
homepage: "https://tea.xyz",
|
||||
maintainer: "tea.xyz",
|
||||
name: "tea.xyz",
|
||||
version: "0.8.6",
|
||||
last_modified: "2022-10-19T19:13:51.000Z",
|
||||
full_name: "tea.xyz",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "tea_xyz_option 2.jpg ",
|
||||
desc: "Website of tea.xyz",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/tea_xyz.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-19T19:13:51.000Z",
|
||||
manual_sorting: 6,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "charm_sh_gum",
|
||||
homepage: "https://charm.sh",
|
||||
maintainer: "charm.sh",
|
||||
name: "gum",
|
||||
version: "0.8.0",
|
||||
last_modified: "2022-10-21T02:15:16.000Z",
|
||||
full_name: "charm.sh/gum",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "charm_sh_gum.jpg ",
|
||||
desc: "",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/charm_sh_gum.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-21T02:15:16.000Z",
|
||||
manual_sorting: 7,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "pyyaml_org",
|
||||
homepage: "https://pyyaml.org",
|
||||
name: "pyyaml.org",
|
||||
version: "0.2.5",
|
||||
last_modified: "2022-10-03T15:35:14.000Z",
|
||||
full_name: "pyyaml.org",
|
||||
dl_count: 107505,
|
||||
thumb_image_name: "pyyaml_org_option 1.jpg ",
|
||||
maintainer: "",
|
||||
desc: "YAML framework for Python",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/pyyaml_org.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-03T15:35:14.000Z",
|
||||
manual_sorting: 8,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "tea_xyz_gx_cc",
|
||||
homepage: "https://tea.xyz",
|
||||
maintainer: "tea.xyz",
|
||||
name: "cc",
|
||||
version: "0.1.0",
|
||||
last_modified: "2022-10-19T16:47:44.000Z",
|
||||
full_name: "tea.xyz/gx/cc",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "tea_xyz_gx.jpg ",
|
||||
desc: "",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/tea_xyz_gx_cc.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-19T16:47:44.000Z",
|
||||
manual_sorting: 9,
|
||||
card_layout: "bottom"
|
||||
}
|
||||
{
|
||||
slug: "mesonbuild_com",
|
||||
homepage: "https://mesonbuild.com",
|
||||
name: "mesonbuild.com",
|
||||
version: "0.63.3",
|
||||
last_modified: "2022-10-06T15:45:08.000Z",
|
||||
full_name: "mesonbuild.com",
|
||||
dl_count: 270745,
|
||||
thumb_image_name: "mesonbuild_com_option 1.jpg ",
|
||||
maintainer: "",
|
||||
desc: "Fast and user friendly build system",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/mesonbuild_com.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-06T15:45:08.000Z",
|
||||
manual_sorting: 0,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "pixman_org",
|
||||
homepage: "http://www.pixman.org/",
|
||||
maintainer: "freedesktop",
|
||||
name: "pixman.org",
|
||||
version: "0.40.0",
|
||||
last_modified: "2022-09-26T19:37:47.000Z",
|
||||
full_name: "pixman.org",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "pixman_org_option 1.jpg ",
|
||||
desc: "Pixman is a library that provides low-level pixel manipulation features such as image compositing and trapezoid rasterization.",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/pixman_org.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-09-26T19:37:47.000Z",
|
||||
manual_sorting: 1,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "freedesktop_org_pkg_config",
|
||||
homepage: "https://freedesktop.org",
|
||||
maintainer: "freedesktop.org",
|
||||
name: "pkg-config",
|
||||
version: "0.29.2",
|
||||
last_modified: "2022-10-20T01:32:15.000Z",
|
||||
full_name: "freedesktop.org/pkg-config",
|
||||
dl_count: 2661501,
|
||||
thumb_image_name: "freedecktop_org_pkg_config option 1.jpg ",
|
||||
desc: "Manage compile and link flags for libraries",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/freedesktop_org_pkg_config.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-20T01:32:15.000Z",
|
||||
manual_sorting: 2,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "gnu_org_gettext",
|
||||
homepage: "https://gnu.org",
|
||||
maintainer: "gnu.org",
|
||||
name: "gettext",
|
||||
version: "0.21.1",
|
||||
last_modified: "2022-10-20T01:23:46.000Z",
|
||||
full_name: "gnu.org/gettext",
|
||||
dl_count: 3715970,
|
||||
thumb_image_name: "gnu_org_gettext_option 1.jpg ",
|
||||
desc: "GNU internationalization (i18n) and localization (l10n) library",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/gnu_org_gettext.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-20T01:23:46.000Z",
|
||||
manual_sorting: 3,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "ipfs_tech",
|
||||
homepage: "https://ipfs.tech",
|
||||
name: "ipfs.tech",
|
||||
version: "0.16.0",
|
||||
last_modified: "2022-10-19T21:36:52.000Z",
|
||||
full_name: "ipfs.tech",
|
||||
dl_count: 14457,
|
||||
thumb_image_name: "ipfs_tech_option 2.jpg ",
|
||||
maintainer: "",
|
||||
desc: "Peer-to-peer hypermedia protocol",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/ipfs_tech.jpg",
|
||||
installs: 0,
|
||||
categories: ["foundation_essentials"],
|
||||
created: "2022-10-19T21:36:52.000Z",
|
||||
manual_sorting: 4,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "nixos_org_patchelf",
|
||||
homepage: "https://nixos.org",
|
||||
maintainer: "nixos.org",
|
||||
name: "patchelf",
|
||||
version: "0.15.0",
|
||||
last_modified: "2022-09-27T04:50:44.000Z",
|
||||
full_name: "nixos.org/patchelf",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "nixos_org_patchelf_option 1.jpg ",
|
||||
desc: "PatchELF is a simple utility for modifying existing ELF executables and libraries.",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/nixos_org_patchelf.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-09-27T04:50:44.000Z",
|
||||
manual_sorting: 5,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "tea_xyz",
|
||||
homepage: "https://tea.xyz",
|
||||
maintainer: "tea.xyz",
|
||||
name: "tea.xyz",
|
||||
version: "0.8.6",
|
||||
last_modified: "2022-10-19T19:13:51.000Z",
|
||||
full_name: "tea.xyz",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "tea_xyz_option 2.jpg ",
|
||||
desc: "Website of tea.xyz",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/tea_xyz.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-19T19:13:51.000Z",
|
||||
manual_sorting: 6,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "charm_sh_gum",
|
||||
homepage: "https://charm.sh",
|
||||
maintainer: "charm.sh",
|
||||
name: "gum",
|
||||
version: "0.8.0",
|
||||
last_modified: "2022-10-21T02:15:16.000Z",
|
||||
full_name: "charm.sh/gum",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "charm_sh_gum.jpg ",
|
||||
desc: "",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/charm_sh_gum.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-21T02:15:16.000Z",
|
||||
manual_sorting: 7,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "pyyaml_org",
|
||||
homepage: "https://pyyaml.org",
|
||||
name: "pyyaml.org",
|
||||
version: "0.2.5",
|
||||
last_modified: "2022-10-03T15:35:14.000Z",
|
||||
full_name: "pyyaml.org",
|
||||
dl_count: 107505,
|
||||
thumb_image_name: "pyyaml_org_option 1.jpg ",
|
||||
maintainer: "",
|
||||
desc: "YAML framework for Python",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/pyyaml_org.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-03T15:35:14.000Z",
|
||||
manual_sorting: 8,
|
||||
card_layout: "bottom"
|
||||
},
|
||||
{
|
||||
slug: "tea_xyz_gx_cc",
|
||||
homepage: "https://tea.xyz",
|
||||
maintainer: "tea.xyz",
|
||||
name: "cc",
|
||||
version: "0.1.0",
|
||||
last_modified: "2022-10-19T16:47:44.000Z",
|
||||
full_name: "tea.xyz/gx/cc",
|
||||
dl_count: 0,
|
||||
thumb_image_name: "tea_xyz_gx.jpg ",
|
||||
desc: "",
|
||||
thumb_image_url: "https://tea.xyz/Images/packages/tea_xyz_gx_cc.jpg",
|
||||
installs: 0,
|
||||
categories: ["top_packages", "foundation_essentials"],
|
||||
created: "2022-10-19T16:47:44.000Z",
|
||||
manual_sorting: 9,
|
||||
card_layout: "bottom"
|
||||
}
|
||||
];
|
||||
|
||||
export const getInstalledPackages = () => [];
|
||||
export async function getDistPackages(): Promise<Package[]> {
|
||||
return packages;
|
||||
return packages;
|
||||
}
|
||||
|
||||
export async function getPackages(): Promise<GUIPackage[]> {
|
||||
return packages.map((pkg) => {
|
||||
return {
|
||||
...pkg,
|
||||
state: PackageStates.AVAILABLE
|
||||
};
|
||||
});
|
||||
return packages.map((pkg) => {
|
||||
return {
|
||||
...pkg,
|
||||
state: PackageStates.AVAILABLE
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFeaturedPackages(): Promise<Package[]> {
|
||||
await delay(2000);
|
||||
return packages.slice(0, 4);
|
||||
await delay(2000);
|
||||
return packages.slice(0, 4);
|
||||
}
|
||||
|
||||
export async function getPackageReviews(full_name: string): Promise<Review[]> {
|
||||
console.log(`generating reviews for ${full_name}`);
|
||||
console.log(`generating reviews for ${full_name}`);
|
||||
|
||||
const reviewCount = _.random(9, 21);
|
||||
const reviews: Review[] = [];
|
||||
const reviewCount = _.random(9, 21);
|
||||
const reviews: Review[] = [];
|
||||
|
||||
for (let i = 0; i < reviewCount; i++) {
|
||||
const title = loremIpsum({
|
||||
count: _.random(2, 5),
|
||||
format: "plain",
|
||||
paragraphLowerBound: 3,
|
||||
paragraphUpperBound: 7,
|
||||
random: Math.random,
|
||||
sentenceLowerBound: 5,
|
||||
sentenceUpperBound: 15,
|
||||
units: "words"
|
||||
});
|
||||
const comment = loremIpsum({
|
||||
count: 2,
|
||||
format: "plain",
|
||||
paragraphLowerBound: 3,
|
||||
paragraphUpperBound: 7,
|
||||
random: Math.random,
|
||||
sentenceLowerBound: 5,
|
||||
sentenceUpperBound: 15,
|
||||
units: "sentences"
|
||||
});
|
||||
const rating = _.random(0, 5);
|
||||
reviews.push({
|
||||
title,
|
||||
comment,
|
||||
rating
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < reviewCount; i++) {
|
||||
const title = loremIpsum({
|
||||
count: _.random(2, 5),
|
||||
format: "plain",
|
||||
paragraphLowerBound: 3,
|
||||
paragraphUpperBound: 7,
|
||||
random: Math.random,
|
||||
sentenceLowerBound: 5,
|
||||
sentenceUpperBound: 15,
|
||||
units: "words"
|
||||
});
|
||||
const comment = loremIpsum({
|
||||
count: 2,
|
||||
format: "plain",
|
||||
paragraphLowerBound: 3,
|
||||
paragraphUpperBound: 7,
|
||||
random: Math.random,
|
||||
sentenceLowerBound: 5,
|
||||
sentenceUpperBound: 15,
|
||||
units: "sentences"
|
||||
});
|
||||
const rating = _.random(0, 5);
|
||||
reviews.push({
|
||||
title,
|
||||
comment,
|
||||
rating
|
||||
});
|
||||
}
|
||||
|
||||
await delay(2000);
|
||||
return reviews;
|
||||
await delay(2000);
|
||||
return reviews;
|
||||
}
|
||||
|
||||
export async function installPackage(pkg: GUIPackage, version?: string) {
|
||||
console.log("installing: ", pkg.full_name, version);
|
||||
await delay(10000);
|
||||
console.log("installing: ", pkg.full_name, version);
|
||||
await delay(10000);
|
||||
}
|
||||
|
||||
export async function syncPantry() {
|
||||
console.log("syncing pantry");
|
||||
await delay(1000);
|
||||
console.log("syncing pantry");
|
||||
await delay(1000);
|
||||
}
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export async function getTopPackages(): Promise<GUIPackage[]> {
|
||||
await delay(500);
|
||||
return packages.slice(0, 9).map((pkg) => {
|
||||
return {
|
||||
...pkg,
|
||||
state: PackageStates.AVAILABLE
|
||||
};
|
||||
});
|
||||
await delay(500);
|
||||
return packages.slice(0, 9).map((pkg) => {
|
||||
return {
|
||||
...pkg,
|
||||
state: PackageStates.AVAILABLE
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllPosts(type: string): Promise<AirtablePost[]> {
|
||||
console.log("filter by type:", type);
|
||||
const posts: AirtablePost[] = [
|
||||
{
|
||||
airtable_record_id: "a",
|
||||
link: "https://google.com",
|
||||
title: "Tea Inc releases game changing api!",
|
||||
sub_title: "lorem ipsum dolor sit amet",
|
||||
short_description: "lorem ipsum dolor sit amet",
|
||||
thumb_image_url: "/images/bored-ape.png",
|
||||
thumb_image_name: "borred-api.png",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
published_at: new Date(),
|
||||
tags: ["news"]
|
||||
},
|
||||
{
|
||||
airtable_record_id: "b",
|
||||
link: "https://google.com",
|
||||
title: "Bored Ape not bored anymore",
|
||||
sub_title: "lorem ipsum dolor sit amet",
|
||||
short_description: "lorem ipsum dolor sit amet",
|
||||
thumb_image_url: "/images/bored-ape.png",
|
||||
thumb_image_name: "borred-api.png",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
published_at: new Date(),
|
||||
tags: ["news"]
|
||||
},
|
||||
{
|
||||
airtable_record_id: "c",
|
||||
link: "https://google.com",
|
||||
title: "Markdown can be executed! hoohah!",
|
||||
sub_title: "lorem ipsum dolor sit amet",
|
||||
short_description: "lorem ipsum dolor sit amet",
|
||||
thumb_image_url: "/images/bored-ape.png",
|
||||
thumb_image_name: "borred-api.png",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
published_at: new Date(),
|
||||
tags: ["news"]
|
||||
}
|
||||
];
|
||||
console.log("filter by type:", type);
|
||||
const posts: AirtablePost[] = [
|
||||
{
|
||||
airtable_record_id: "a",
|
||||
link: "https://google.com",
|
||||
title: "Tea Inc releases game changing api!",
|
||||
sub_title: "lorem ipsum dolor sit amet",
|
||||
short_description: "lorem ipsum dolor sit amet",
|
||||
thumb_image_url: "/images/bored-ape.png",
|
||||
thumb_image_name: "borred-api.png",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
published_at: new Date(),
|
||||
tags: ["news"]
|
||||
},
|
||||
{
|
||||
airtable_record_id: "b",
|
||||
link: "https://google.com",
|
||||
title: "Bored Ape not bored anymore",
|
||||
sub_title: "lorem ipsum dolor sit amet",
|
||||
short_description: "lorem ipsum dolor sit amet",
|
||||
thumb_image_url: "/images/bored-ape.png",
|
||||
thumb_image_name: "borred-api.png",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
published_at: new Date(),
|
||||
tags: ["news"]
|
||||
},
|
||||
{
|
||||
airtable_record_id: "c",
|
||||
link: "https://google.com",
|
||||
title: "Markdown can be executed! hoohah!",
|
||||
sub_title: "lorem ipsum dolor sit amet",
|
||||
short_description: "lorem ipsum dolor sit amet",
|
||||
thumb_image_url: "/images/bored-ape.png",
|
||||
thumb_image_name: "borred-api.png",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
published_at: new Date(),
|
||||
tags: ["news"]
|
||||
}
|
||||
];
|
||||
|
||||
return posts;
|
||||
return posts;
|
||||
}
|
||||
|
||||
export async function getDeviceAuth(deviceId: string): Promise<any> {
|
||||
const data = await v1Client.get<any>(`/auth/device/${deviceId}`);
|
||||
return data;
|
||||
const data = await v1Client.get<any>(`/auth/device/${deviceId}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getPackageBottles(name: string): Promise<Bottle[]> {
|
||||
return [
|
||||
{ name, platform: "darwin", arch: "aarch64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "darwin", arch: "aarch64", version: "3.40.0", bytes: 123456 },
|
||||
{ name, platform: "darwin", arch: "x86-64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "darwin", arch: "x86-64", version: "3.40.0", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "aarch64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "aarch64", version: "3.40.0", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "x86-64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "x86-64", version: "3.40.0", bytes: 123456 }
|
||||
];
|
||||
return [
|
||||
{ name, platform: "darwin", arch: "aarch64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "darwin", arch: "aarch64", version: "3.40.0", bytes: 123456 },
|
||||
{ name, platform: "darwin", arch: "x86-64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "darwin", arch: "x86-64", version: "3.40.0", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "aarch64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "aarch64", version: "3.40.0", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "x86-64", version: "3.39.4", bytes: 123456 },
|
||||
{ name, platform: "linux", arch: "x86-64", version: "3.40.0", bytes: 123456 }
|
||||
];
|
||||
}
|
||||
|
||||
export async function getPackage(packageName: string): Promise<Partial<Package>> {
|
||||
return packages.find((pkg) => pkg.full_name === packageName) || packages[0];
|
||||
return packages.find((pkg) => pkg.full_name === packageName) || packages[0];
|
||||
}
|
||||
|
||||
export const getSession = async (): Promise<Session | null> => {
|
||||
return null;
|
||||
return null;
|
||||
};
|
||||
|
||||
export const updateSession = async (session: Partial<Session>) => {
|
||||
console.log(session);
|
||||
console.log(session);
|
||||
};
|
||||
|
||||
export const openTerminal = (cmd: string) => console.log(cmd);
|
||||
|
||||
export const shellOpenExternal = (link?: string) => {
|
||||
window.open(link, "_blank");
|
||||
window.open(link, "_blank");
|
||||
};
|
||||
|
||||
export const listenToChannel = (channel: string, callback: (msg: string, ...args: any) => void) => {
|
||||
console.log("listen to channel", channel, callback);
|
||||
console.log("listen to channel", channel, callback);
|
||||
};
|
||||
|
||||
export const relaunch = () => {
|
||||
console.log("relaunch");
|
||||
console.log("relaunch");
|
||||
};
|
||||
|
||||
export const getProtocolPath = async (): Promise<string> => "";
|
||||
|
||||
export const submitLogs = async (): Promise<string> => {
|
||||
return "deviceId---logid";
|
||||
return "deviceId---logid";
|
||||
};
|
||||
|
||||
export const isPackageInstalled = async (_v?: string): Promise<boolean> => {
|
||||
return true;
|
||||
return true;
|
||||
};
|
||||
|
||||
export const setBadgeCount = async (count: number) => {
|
||||
console.log("set badge count", count);
|
||||
console.log("set badge count", count);
|
||||
};
|
||||
|
||||
export const deletePackage = async (args: { fullName: string; version: string }) => {
|
||||
console.log("delete package", args);
|
||||
console.log("delete package", args);
|
||||
};
|
||||
|
||||
export const loadPackageCache = async () => {
|
||||
return { version: "1", packages: {} };
|
||||
return { version: "1", packages: {} };
|
||||
};
|
||||
|
||||
export const writePackageCache = async (pkgs: Packages) => {
|
||||
console.log("write package cache", pkgs);
|
||||
console.log("write package cache", pkgs);
|
||||
};
|
||||
|
||||
export const topbarDoubleClick = async () => {
|
||||
console.log("topbar double click");
|
||||
console.log("topbar double click");
|
||||
};
|
||||
|
||||
export const cacheImageURL = async (_url: string): Promise<string | undefined> => {
|
||||
return undefined;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getAutoUpdateStatus = async (): Promise<AutoUpdateStatus> => {
|
||||
return { status: "up-to-date" };
|
||||
return { status: "up-to-date" };
|
||||
};
|
||||
|
||||
export async function openPackageEntrypointInTerminal(pkg: string) {
|
||||
//noop
|
||||
//noop
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
import { getPkgBottles } from "../tea-dir";
|
||||
|
||||
describe("tea-dir module", () => {
|
||||
it("should getPkgBottles from nested Dir object/s", () => {
|
||||
const results = getPkgBottles({
|
||||
name: "kkos",
|
||||
path: "/Users/x/.tea/github.com/kkos",
|
||||
children: [
|
||||
{ name: ".DS_Store", path: "/Users/x/.tea/github.com/kkos/.DS_Store" },
|
||||
{
|
||||
name: "oniguruma",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma",
|
||||
children: [
|
||||
{ name: ".DS_Store", path: "/Users/x/.tea/github.com/kkos/oniguruma/.DS_Store" },
|
||||
{
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v6",
|
||||
name: "v6",
|
||||
children: [
|
||||
{ name: ".DS_Store", path: "/Users/x/.tea/github.com/kkos/oniguruma/v6/.DS_Store" }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "v*",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v*",
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "v6.9.8",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v6.9.8",
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "v6.9",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v6.9",
|
||||
children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
it("should getPkgBottles from nested Dir object/s", () => {
|
||||
const results = getPkgBottles({
|
||||
name: "kkos",
|
||||
path: "/Users/x/.tea/github.com/kkos",
|
||||
children: [
|
||||
{ name: ".DS_Store", path: "/Users/x/.tea/github.com/kkos/.DS_Store" },
|
||||
{
|
||||
name: "oniguruma",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma",
|
||||
children: [
|
||||
{ name: ".DS_Store", path: "/Users/x/.tea/github.com/kkos/oniguruma/.DS_Store" },
|
||||
{
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v6",
|
||||
name: "v6",
|
||||
children: [
|
||||
{ name: ".DS_Store", path: "/Users/x/.tea/github.com/kkos/oniguruma/v6/.DS_Store" }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "v*",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v*",
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "v6.9.8",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v6.9.8",
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "v6.9",
|
||||
path: "/Users/x/.tea/github.com/kkos/oniguruma/v6.9",
|
||||
children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(results).toEqual([
|
||||
"github.com/kkos/oniguruma/v*",
|
||||
"github.com/kkos/oniguruma/v6",
|
||||
"github.com/kkos/oniguruma/v6.9",
|
||||
"github.com/kkos/oniguruma/v6.9.8"
|
||||
]);
|
||||
});
|
||||
expect(results).toEqual([
|
||||
"github.com/kkos/oniguruma/v*",
|
||||
"github.com/kkos/oniguruma/v6",
|
||||
"github.com/kkos/oniguruma/v6.9",
|
||||
"github.com/kkos/oniguruma/v6.9.8"
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,30 +3,30 @@
|
|||
// import { join } from 'upath';
|
||||
|
||||
type Dir = {
|
||||
name: string;
|
||||
path: string;
|
||||
children?: Dir[];
|
||||
name: string;
|
||||
path: string;
|
||||
children?: Dir[];
|
||||
};
|
||||
|
||||
const semverTest =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g;
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g;
|
||||
|
||||
export const getPkgBottles = (packageDir: Dir): string[] => {
|
||||
const bottles: string[] = [];
|
||||
const bottles: string[] = [];
|
||||
|
||||
const pkg = packageDir.path.split(".tea/")[1];
|
||||
const version = pkg.split("/v")[1];
|
||||
const pkg = packageDir.path.split(".tea/")[1];
|
||||
const version = pkg.split("/v")[1];
|
||||
|
||||
const isVersion = semverTest.test(version) || !isNaN(+version) || version === "*";
|
||||
const isVersion = semverTest.test(version) || !isNaN(+version) || version === "*";
|
||||
|
||||
if (version && isVersion) {
|
||||
bottles.push(pkg);
|
||||
} else if (packageDir?.children?.length) {
|
||||
const childBottles = packageDir.children
|
||||
.map(getPkgBottles)
|
||||
.reduce((arr, bottles) => [...arr, ...bottles], []);
|
||||
bottles.push(...childBottles);
|
||||
}
|
||||
if (version && isVersion) {
|
||||
bottles.push(pkg);
|
||||
} else if (packageDir?.children?.length) {
|
||||
const childBottles = packageDir.children
|
||||
.map(getPkgBottles)
|
||||
.reduce((arr, bottles) => [...arr, ...bottles], []);
|
||||
bottles.push(...childBottles);
|
||||
}
|
||||
|
||||
return bottles.filter((b) => b !== undefined).sort(); // ie: ["gohugo.io/v*", "gohugo.io/v0", "gohugo.io/v0.108", "gohugo.io/v0.108.0"]
|
||||
return bottles.filter((b) => b !== undefined).sort(); // ie: ["gohugo.io/v*", "gohugo.io/v0", "gohugo.io/v0.108", "gohugo.io/v0.108.0"]
|
||||
};
|
||||
|
|
|
@ -4,44 +4,44 @@ import semverCompare from "semver/functions/compare";
|
|||
|
||||
// Find a list of available versions for a package based on the bottles
|
||||
export const findAvailableVersions = (pkg: GUIPackage) => {
|
||||
// default to just showing the latest if bottles haven't loaded yet
|
||||
if (!pkg.bottles) {
|
||||
return [pkg.version];
|
||||
}
|
||||
// default to just showing the latest if bottles haven't loaded yet
|
||||
if (!pkg.bottles) {
|
||||
return [pkg.version];
|
||||
}
|
||||
|
||||
const versionSet = new Set<string>();
|
||||
for (const b of pkg.bottles) {
|
||||
versionSet.add(b.version);
|
||||
}
|
||||
const versionSet = new Set<string>();
|
||||
for (const b of pkg.bottles) {
|
||||
versionSet.add(b.version);
|
||||
}
|
||||
|
||||
return Array.from(versionSet).sort((a, b) => semverCompare(cleanVersion(b), cleanVersion(a)));
|
||||
return Array.from(versionSet).sort((a, b) => semverCompare(cleanVersion(b), cleanVersion(a)));
|
||||
};
|
||||
|
||||
export const cleanVersion = (version: string) => clean(version) || "0.0.0";
|
||||
|
||||
// Add a new version to the list of installed versions while maintaining the sort order
|
||||
export const addInstalledVersion = (
|
||||
installedVersions: string[] | undefined,
|
||||
newVersion: string
|
||||
installedVersions: string[] | undefined,
|
||||
newVersion: string
|
||||
) => {
|
||||
if (!installedVersions) {
|
||||
return [newVersion];
|
||||
}
|
||||
if (!installedVersions) {
|
||||
return [newVersion];
|
||||
}
|
||||
|
||||
return [...installedVersions, newVersion].sort((a, b) =>
|
||||
semverCompare(cleanVersion(b), cleanVersion(a))
|
||||
);
|
||||
return [...installedVersions, newVersion].sort((a, b) =>
|
||||
semverCompare(cleanVersion(b), cleanVersion(a))
|
||||
);
|
||||
};
|
||||
|
||||
export const findRecentInstalledVersion = (pkg: GUIPackage) => {
|
||||
// this assumes that the versions are already sorted
|
||||
return pkg.installed_versions?.[0];
|
||||
// this assumes that the versions are already sorted
|
||||
return pkg.installed_versions?.[0];
|
||||
};
|
||||
|
||||
export const isInstalling = (pkg: GUIPackage) => {
|
||||
return (
|
||||
pkg.install_progress_percentage &&
|
||||
pkg.install_progress_percentage > 0 &&
|
||||
pkg.install_progress_percentage < 100
|
||||
);
|
||||
return (
|
||||
pkg.install_progress_percentage &&
|
||||
pkg.install_progress_percentage > 0 &&
|
||||
pkg.install_progress_percentage < 100
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,20 +2,20 @@ import * as Sentry from "@sentry/browser";
|
|||
import type { Session } from "./types";
|
||||
|
||||
export function initSentry(session?: Session) {
|
||||
Sentry.init({
|
||||
dsn: "https://5ff29bb5b3b64cd4bd4f4960ef1db2e3@o4504750197899264.ingest.sentry.io/4504750206746624"
|
||||
});
|
||||
if (session) {
|
||||
console.log("sentry init", session);
|
||||
Sentry.configureScope(async (scope) => {
|
||||
scope.setUser({
|
||||
id: session.device_id, // device_id this should exist in our pg db: developer_id is to many device_id
|
||||
username: session?.user?.login || "" // github username or handler
|
||||
});
|
||||
});
|
||||
}
|
||||
Sentry.init({
|
||||
dsn: "https://5ff29bb5b3b64cd4bd4f4960ef1db2e3@o4504750197899264.ingest.sentry.io/4504750206746624"
|
||||
});
|
||||
if (session) {
|
||||
console.log("sentry init", session);
|
||||
Sentry.configureScope(async (scope) => {
|
||||
scope.setUser({
|
||||
id: session.device_id, // device_id this should exist in our pg db: developer_id is to many device_id
|
||||
username: session?.user?.login || "" // github username or handler
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function captureException(exception: any) {
|
||||
Sentry.captureException(exception);
|
||||
Sentry.captureException(exception);
|
||||
}
|
||||
|
|
|
@ -17,118 +17,118 @@ export const featuredPackages = writable<Package[]>([]);
|
|||
export const packagesStore = initPackagesStore();
|
||||
|
||||
export const initializeFeaturedPackages = async () => {
|
||||
console.log("intialize featured packages");
|
||||
const packages = await getFeaturedPackages();
|
||||
featuredPackages.set(packages);
|
||||
console.log("intialize featured packages");
|
||||
const packages = await getFeaturedPackages();
|
||||
featuredPackages.set(packages);
|
||||
};
|
||||
|
||||
interface PackagesReview {
|
||||
[full_name: string]: Review[];
|
||||
[full_name: string]: Review[];
|
||||
}
|
||||
|
||||
function initPackagesReviewStore() {
|
||||
const { update, subscribe } = writable<PackagesReview>({});
|
||||
const { update, subscribe } = writable<PackagesReview>({});
|
||||
|
||||
let packagesReviews: PackagesReview = {};
|
||||
let packagesReviews: PackagesReview = {};
|
||||
|
||||
subscribe((v) => (packagesReviews = v));
|
||||
subscribe((v) => (packagesReviews = v));
|
||||
|
||||
const getSetPackageReviews = async (full_name: string) => {
|
||||
if (full_name && !packagesReviews[full_name]) {
|
||||
packagesReviews[full_name] = [];
|
||||
const reviews = await getPackageReviews(full_name);
|
||||
update((v) => {
|
||||
return {
|
||||
...v,
|
||||
[full_name]: reviews
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
const getSetPackageReviews = async (full_name: string) => {
|
||||
if (full_name && !packagesReviews[full_name]) {
|
||||
packagesReviews[full_name] = [];
|
||||
const reviews = await getPackageReviews(full_name);
|
||||
update((v) => {
|
||||
return {
|
||||
...v,
|
||||
[full_name]: reviews
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
subscribe: (full_name: string, reset: (reviews: Review[]) => void) => {
|
||||
getSetPackageReviews(full_name);
|
||||
return subscribe((value) => {
|
||||
if (value[full_name]) {
|
||||
reset(value[full_name]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
subscribe: (full_name: string, reset: (reviews: Review[]) => void) => {
|
||||
getSetPackageReviews(full_name);
|
||||
return subscribe((value) => {
|
||||
if (value[full_name]) {
|
||||
reset(value[full_name]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const packagesReviewStore = initPackagesReviewStore();
|
||||
|
||||
function initPosts() {
|
||||
let initialized = false;
|
||||
const { subscribe } = writable<AirtablePost[]>([]);
|
||||
const posts: AirtablePost[] = [];
|
||||
let postsIndex: Fuse<AirtablePost>;
|
||||
let initialized = false;
|
||||
const { subscribe } = writable<AirtablePost[]>([]);
|
||||
const posts: AirtablePost[] = [];
|
||||
let postsIndex: Fuse<AirtablePost>;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
// getAllPosts().then(set);
|
||||
}
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
// getAllPosts().then(set);
|
||||
}
|
||||
|
||||
subscribe((v) => {
|
||||
posts.push(...v);
|
||||
postsIndex = new Fuse(posts, {
|
||||
keys: ["title", "sub_title", "short_description", "tags"]
|
||||
});
|
||||
});
|
||||
subscribe((v) => {
|
||||
posts.push(...v);
|
||||
postsIndex = new Fuse(posts, {
|
||||
keys: ["title", "sub_title", "short_description", "tags"]
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
search: async (term: string, limit = 10) => {
|
||||
const res = postsIndex.search(term, { limit });
|
||||
const matchingPosts: AirtablePost[] = res.map((v) => v.item);
|
||||
return matchingPosts;
|
||||
},
|
||||
subscribeByTag: (tag: string, cb: (posts: AirtablePost[]) => void) => {
|
||||
subscribe((newPosts: AirtablePost[]) => {
|
||||
const filteredPosts = newPosts.filter((post) => post.tags.includes(tag));
|
||||
cb(filteredPosts);
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
subscribe,
|
||||
search: async (term: string, limit = 10) => {
|
||||
const res = postsIndex.search(term, { limit });
|
||||
const matchingPosts: AirtablePost[] = res.map((v) => v.item);
|
||||
return matchingPosts;
|
||||
},
|
||||
subscribeByTag: (tag: string, cb: (posts: AirtablePost[]) => void) => {
|
||||
subscribe((newPosts: AirtablePost[]) => {
|
||||
const filteredPosts = newPosts.filter((post) => post.tags.includes(tag));
|
||||
cb(filteredPosts);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
export const postsStore = initPosts();
|
||||
|
||||
function initSearchStore() {
|
||||
const searching = writable<boolean>(false);
|
||||
const packagesSearch = writable<GUIPackage[]>([]);
|
||||
const postsSearch = writable<AirtablePost[]>([]);
|
||||
const searching = writable<boolean>(false);
|
||||
const packagesSearch = writable<GUIPackage[]>([]);
|
||||
const postsSearch = writable<AirtablePost[]>([]);
|
||||
|
||||
// TODO:
|
||||
// should use algolia if user is somehow online
|
||||
// TODO:
|
||||
// should use algolia if user is somehow online
|
||||
|
||||
return {
|
||||
searching,
|
||||
packagesSearch,
|
||||
postsSearch,
|
||||
search: async (term: string) => {
|
||||
try {
|
||||
if (term) {
|
||||
const [
|
||||
resultPkgs
|
||||
// resultPosts
|
||||
] = await Promise.all([
|
||||
packagesStore.search(term, 5)
|
||||
// postsStore.search(term, 10)
|
||||
]);
|
||||
trackSearch(term, resultPkgs.length);
|
||||
packagesSearch.set(resultPkgs);
|
||||
// postsSearch.set(resultPosts);
|
||||
} else {
|
||||
packagesSearch.set([]);
|
||||
// postsSearch.set([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
searching,
|
||||
packagesSearch,
|
||||
postsSearch,
|
||||
search: async (term: string) => {
|
||||
try {
|
||||
if (term) {
|
||||
const [
|
||||
resultPkgs
|
||||
// resultPosts
|
||||
] = await Promise.all([
|
||||
packagesStore.search(term, 5)
|
||||
// postsStore.search(term, 10)
|
||||
]);
|
||||
trackSearch(term, resultPkgs.length);
|
||||
packagesSearch.set(resultPkgs);
|
||||
// postsSearch.set(resultPosts);
|
||||
} else {
|
||||
packagesSearch.set([]);
|
||||
// postsSearch.set([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const searchStore = initSearchStore();
|
||||
|
|
|
@ -8,81 +8,81 @@ import { initSentry } from "../sentry";
|
|||
|
||||
export let session: Session | null = null;
|
||||
export const getSession = async (): Promise<Session | null> => {
|
||||
session = await electronGetSession();
|
||||
return session;
|
||||
session = await electronGetSession();
|
||||
return session;
|
||||
};
|
||||
|
||||
export default function initAuthStore() {
|
||||
const user = writable<Developer | undefined>();
|
||||
const sessionStore = writable<Session>({});
|
||||
let pollLoop = 0;
|
||||
const user = writable<Developer | undefined>();
|
||||
const sessionStore = writable<Session>({});
|
||||
let pollLoop = 0;
|
||||
|
||||
const deviceIdStore = writable<string>("");
|
||||
let deviceId = "";
|
||||
const deviceIdStore = writable<string>("");
|
||||
let deviceId = "";
|
||||
|
||||
getSession().then((sess) => {
|
||||
if (sess) {
|
||||
session = sess;
|
||||
initSentry(sess);
|
||||
sessionStore.set(sess);
|
||||
deviceIdStore.set(sess.device_id!);
|
||||
deviceId = sess.device_id!;
|
||||
if (sess.user) user.set(sess.user);
|
||||
}
|
||||
});
|
||||
getSession().then((sess) => {
|
||||
if (sess) {
|
||||
session = sess;
|
||||
initSentry(sess);
|
||||
sessionStore.set(sess);
|
||||
deviceIdStore.set(sess.device_id!);
|
||||
deviceId = sess.device_id!;
|
||||
if (sess.user) user.set(sess.user);
|
||||
}
|
||||
});
|
||||
|
||||
let timer: NodeJS.Timer | null;
|
||||
let timer: NodeJS.Timer | null;
|
||||
|
||||
async function updateSession(data: Session) {
|
||||
sessionStore.update((val) => ({
|
||||
...val,
|
||||
...data
|
||||
}));
|
||||
async function updateSession(data: Session) {
|
||||
sessionStore.update((val) => ({
|
||||
...val,
|
||||
...data
|
||||
}));
|
||||
|
||||
initSentry(data);
|
||||
await electronUpdateSession(data);
|
||||
}
|
||||
initSentry(data);
|
||||
await electronUpdateSession(data);
|
||||
}
|
||||
|
||||
async function pollSession() {
|
||||
if (!timer) {
|
||||
timer = setInterval(async () => {
|
||||
pollLoop++;
|
||||
try {
|
||||
const data = await getDeviceAuth(deviceId);
|
||||
if (data.status === "SUCCESS") {
|
||||
updateSession({
|
||||
key: data.key,
|
||||
user: data.user
|
||||
});
|
||||
user.set(data.user!);
|
||||
timer && clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
async function pollSession() {
|
||||
if (!timer) {
|
||||
timer = setInterval(async () => {
|
||||
pollLoop++;
|
||||
try {
|
||||
const data = await getDeviceAuth(deviceId);
|
||||
if (data.status === "SUCCESS") {
|
||||
updateSession({
|
||||
key: data.key,
|
||||
user: data.user
|
||||
});
|
||||
user.set(data.user!);
|
||||
timer && clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (pollLoop > 20 && timer) {
|
||||
clearInterval(timer);
|
||||
pollLoop = 0;
|
||||
timer = null;
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
if (pollLoop > 20 && timer) {
|
||||
clearInterval(timer);
|
||||
pollLoop = 0;
|
||||
timer = null;
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function clearSession() {
|
||||
updateSession({ key: undefined, user: undefined });
|
||||
user.set(undefined);
|
||||
}
|
||||
function clearSession() {
|
||||
updateSession({ key: undefined, user: undefined });
|
||||
user.set(undefined);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
session: sessionStore,
|
||||
deviceId,
|
||||
deviceIdStore,
|
||||
pollSession,
|
||||
clearSession,
|
||||
updateSession
|
||||
};
|
||||
return {
|
||||
user,
|
||||
session: sessionStore,
|
||||
deviceId,
|
||||
deviceIdStore,
|
||||
pollSession,
|
||||
clearSession,
|
||||
updateSession
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,55 +2,55 @@ import { writable } from "svelte/store";
|
|||
import { goto } from "$app/navigation";
|
||||
|
||||
export default function initNavStore() {
|
||||
const historyStore = writable<string[]>(["/"]);
|
||||
const historyStore = writable<string[]>(["/"]);
|
||||
|
||||
let history = ["/"];
|
||||
let history = ["/"];
|
||||
|
||||
historyStore.subscribe((v) => (history = v));
|
||||
historyStore.subscribe((v) => (history = v));
|
||||
|
||||
const prevPathStore = writable<string>("");
|
||||
const nextPathStore = writable<string>("");
|
||||
const prevPathStore = writable<string>("");
|
||||
const nextPathStore = writable<string>("");
|
||||
|
||||
let currentIndex = 0; // if non next/back click
|
||||
let currentIndex = 0; // if non next/back click
|
||||
|
||||
let isMovingNext = false;
|
||||
let isMovingBack = false;
|
||||
let isMovingNext = false;
|
||||
let isMovingBack = false;
|
||||
|
||||
return {
|
||||
historyStore,
|
||||
prevPath: prevPathStore,
|
||||
nextPath: nextPathStore,
|
||||
next: () => {
|
||||
if (currentIndex < history.length - 1) {
|
||||
isMovingNext = true;
|
||||
goto(history[currentIndex + 1]);
|
||||
prevPathStore.set(history[currentIndex]);
|
||||
currentIndex++;
|
||||
if (currentIndex >= history.length - 1) nextPathStore.set("");
|
||||
}
|
||||
},
|
||||
back: () => {
|
||||
if (currentIndex > 0) {
|
||||
isMovingBack = true;
|
||||
goto(history[currentIndex - 1]);
|
||||
nextPathStore.set(history[currentIndex]);
|
||||
currentIndex--;
|
||||
if (currentIndex === 0) prevPathStore.set("");
|
||||
}
|
||||
},
|
||||
setNewPath: (newNextPath: string, _newPrevPath: string) => {
|
||||
const oldCurrentPath = history[currentIndex];
|
||||
const isNavArrows = isMovingBack || isMovingNext;
|
||||
if (!isNavArrows && newNextPath !== oldCurrentPath) {
|
||||
historyStore.update((history) => {
|
||||
const cleanHistory = history.filter((_v, i) => i <= currentIndex);
|
||||
currentIndex = cleanHistory.length;
|
||||
prevPathStore.set(cleanHistory[currentIndex - 1]);
|
||||
return [...cleanHistory, newNextPath];
|
||||
});
|
||||
}
|
||||
isMovingNext = false;
|
||||
isMovingBack = false;
|
||||
}
|
||||
};
|
||||
return {
|
||||
historyStore,
|
||||
prevPath: prevPathStore,
|
||||
nextPath: nextPathStore,
|
||||
next: () => {
|
||||
if (currentIndex < history.length - 1) {
|
||||
isMovingNext = true;
|
||||
goto(history[currentIndex + 1]);
|
||||
prevPathStore.set(history[currentIndex]);
|
||||
currentIndex++;
|
||||
if (currentIndex >= history.length - 1) nextPathStore.set("");
|
||||
}
|
||||
},
|
||||
back: () => {
|
||||
if (currentIndex > 0) {
|
||||
isMovingBack = true;
|
||||
goto(history[currentIndex - 1]);
|
||||
nextPathStore.set(history[currentIndex]);
|
||||
currentIndex--;
|
||||
if (currentIndex === 0) prevPathStore.set("");
|
||||
}
|
||||
},
|
||||
setNewPath: (newNextPath: string, _newPrevPath: string) => {
|
||||
const oldCurrentPath = history[currentIndex];
|
||||
const isNavArrows = isMovingBack || isMovingNext;
|
||||
if (!isNavArrows && newNextPath !== oldCurrentPath) {
|
||||
historyStore.update((history) => {
|
||||
const cleanHistory = history.filter((_v, i) => i <= currentIndex);
|
||||
currentIndex = cleanHistory.length;
|
||||
prevPathStore.set(cleanHistory[currentIndex - 1]);
|
||||
return [...cleanHistory, newNextPath];
|
||||
});
|
||||
}
|
||||
isMovingNext = false;
|
||||
isMovingBack = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,51 +7,51 @@ import type { Notification } from "@tea/ui/types";
|
|||
import { listenToChannel, relaunch } from "@native";
|
||||
|
||||
export default function initNotificationStore() {
|
||||
const notifications: Notification[] = [];
|
||||
const { update, subscribe } = writable<Notification[]>([]);
|
||||
const notifications: Notification[] = [];
|
||||
const { update, subscribe } = writable<Notification[]>([]);
|
||||
|
||||
const remove = (id: string) => {
|
||||
update((notifications) => notifications.filter((n) => n.id != id));
|
||||
};
|
||||
const remove = (id: string) => {
|
||||
update((notifications) => notifications.filter((n) => n.id != id));
|
||||
};
|
||||
|
||||
listenToChannel("message", (data: any) => {
|
||||
const { message, params }: { message: string; params: { [key: string]: string } } = data;
|
||||
listenToChannel("message", (data: any) => {
|
||||
const { message, params }: { message: string; params: { [key: string]: string } } = data;
|
||||
|
||||
update((value) => {
|
||||
const newNotification: Notification = {
|
||||
id: nanoid(4),
|
||||
message,
|
||||
i18n_key: params["i18n_key"] || "",
|
||||
type: NotificationType.ACTION_BANNER,
|
||||
params
|
||||
};
|
||||
if (params.action) {
|
||||
newNotification.callback_label = params.action.toUpperCase();
|
||||
newNotification.callback = () => {
|
||||
relaunch();
|
||||
remove(newNotification.id); // not sure yet
|
||||
};
|
||||
}
|
||||
return [...value, newNotification];
|
||||
});
|
||||
});
|
||||
update((value) => {
|
||||
const newNotification: Notification = {
|
||||
id: nanoid(4),
|
||||
message,
|
||||
i18n_key: params["i18n_key"] || "",
|
||||
type: NotificationType.ACTION_BANNER,
|
||||
params
|
||||
};
|
||||
if (params.action) {
|
||||
newNotification.callback_label = params.action.toUpperCase();
|
||||
newNotification.callback = () => {
|
||||
relaunch();
|
||||
remove(newNotification.id); // not sure yet
|
||||
};
|
||||
}
|
||||
return [...value, newNotification];
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
notifications,
|
||||
subscribe,
|
||||
remove,
|
||||
add: (partialNotification: Partial<Notification>) => {
|
||||
if (!partialNotification.message) throw new Error("message is required");
|
||||
return {
|
||||
notifications,
|
||||
subscribe,
|
||||
remove,
|
||||
add: (partialNotification: Partial<Notification>) => {
|
||||
if (!partialNotification.message) throw new Error("message is required");
|
||||
|
||||
const notification: Notification = {
|
||||
id: nanoid(4),
|
||||
i18n_key: partialNotification.i18n_key || "",
|
||||
type: NotificationType.MESSAGE,
|
||||
message: partialNotification.message || "",
|
||||
...partialNotification
|
||||
};
|
||||
const notification: Notification = {
|
||||
id: nanoid(4),
|
||||
i18n_key: partialNotification.i18n_key || "",
|
||||
type: NotificationType.MESSAGE,
|
||||
message: partialNotification.message || "",
|
||||
...partialNotification
|
||||
};
|
||||
|
||||
update((values) => [notification, ...values]);
|
||||
}
|
||||
};
|
||||
update((values) => [notification, ...values]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,18 +3,18 @@ import type { GUIPackage, InstalledPackage, Packages } from "../types";
|
|||
import { PackageStates } from "../types";
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
getPackage,
|
||||
getDistPackages,
|
||||
getInstalledPackages,
|
||||
installPackage,
|
||||
deletePackage,
|
||||
getPackageBottles,
|
||||
setBadgeCount,
|
||||
loadPackageCache,
|
||||
writePackageCache,
|
||||
syncPantry,
|
||||
cacheImageURL,
|
||||
listenToChannel
|
||||
getPackage,
|
||||
getDistPackages,
|
||||
getInstalledPackages,
|
||||
installPackage,
|
||||
deletePackage,
|
||||
getPackageBottles,
|
||||
setBadgeCount,
|
||||
loadPackageCache,
|
||||
writePackageCache,
|
||||
syncPantry,
|
||||
cacheImageURL,
|
||||
listenToChannel
|
||||
} from "@native";
|
||||
|
||||
import { getReadme, getContributors, getRepoAsPackage } from "$libs/github";
|
||||
|
@ -31,337 +31,337 @@ import log from "$libs/logger";
|
|||
const packageRefreshInterval = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
export default function initPackagesStore() {
|
||||
let initialized = false;
|
||||
let isDestroyed = false;
|
||||
let refreshTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
let initialized = false;
|
||||
let isDestroyed = false;
|
||||
let refreshTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const packageMap = writable<Packages>({ version: "0", packages: {} });
|
||||
const packageList = derived(packageMap, ($packages) => Object.values($packages.packages));
|
||||
const packageMap = writable<Packages>({ version: "0", packages: {} });
|
||||
const packageList = derived(packageMap, ($packages) => Object.values($packages.packages));
|
||||
|
||||
let packagesIndex: Fuse<GUIPackage>;
|
||||
let packagesIndex: Fuse<GUIPackage>;
|
||||
|
||||
const updateAllPackages = (guiPkgs: GUIPackage[]) => {
|
||||
packageMap.update((pkgs) => {
|
||||
guiPkgs.forEach((pkg) => {
|
||||
const oldPkg = pkgs.packages[pkg.full_name];
|
||||
pkgs.packages[pkg.full_name] = { ...oldPkg, ...pkg };
|
||||
});
|
||||
setBadgeCountFromPkgs(pkgs);
|
||||
return pkgs;
|
||||
});
|
||||
};
|
||||
const updateAllPackages = (guiPkgs: GUIPackage[]) => {
|
||||
packageMap.update((pkgs) => {
|
||||
guiPkgs.forEach((pkg) => {
|
||||
const oldPkg = pkgs.packages[pkg.full_name];
|
||||
pkgs.packages[pkg.full_name] = { ...oldPkg, ...pkg };
|
||||
});
|
||||
setBadgeCountFromPkgs(pkgs);
|
||||
return pkgs;
|
||||
});
|
||||
};
|
||||
|
||||
const updatePackage = (full_name: string, props: Partial<GUIPackage>, newVersion?: string) => {
|
||||
packageMap.update((pkgs) => {
|
||||
const pkg = pkgs.packages[full_name];
|
||||
if (pkg) {
|
||||
const updatedPkg = { ...pkg, ...props };
|
||||
const updatePackage = (full_name: string, props: Partial<GUIPackage>, newVersion?: string) => {
|
||||
packageMap.update((pkgs) => {
|
||||
const pkg = pkgs.packages[full_name];
|
||||
if (pkg) {
|
||||
const updatedPkg = { ...pkg, ...props };
|
||||
|
||||
if (newVersion) {
|
||||
updatedPkg.installed_versions = addInstalledVersion(
|
||||
updatedPkg.installed_versions,
|
||||
newVersion
|
||||
);
|
||||
}
|
||||
if (newVersion) {
|
||||
updatedPkg.installed_versions = addInstalledVersion(
|
||||
updatedPkg.installed_versions,
|
||||
newVersion
|
||||
);
|
||||
}
|
||||
|
||||
updatedPkg.state = getPackageState(updatedPkg);
|
||||
pkgs.packages[full_name] = updatedPkg;
|
||||
updatedPkg.state = getPackageState(updatedPkg);
|
||||
pkgs.packages[full_name] = updatedPkg;
|
||||
|
||||
setBadgeCountFromPkgs(pkgs);
|
||||
}
|
||||
return pkgs;
|
||||
});
|
||||
};
|
||||
setBadgeCountFromPkgs(pkgs);
|
||||
}
|
||||
return pkgs;
|
||||
});
|
||||
};
|
||||
|
||||
// getPackage state centralizes the logic for determining the state of the package based on the other properties
|
||||
const getPackageState = (pkg: GUIPackage): PackageStates => {
|
||||
if (pkg.isUninstalling) {
|
||||
//TODO: maybe there should be an uninstalling state too? Although that needs UI/UX changes
|
||||
return PackageStates.AVAILABLE;
|
||||
}
|
||||
// getPackage state centralizes the logic for determining the state of the package based on the other properties
|
||||
const getPackageState = (pkg: GUIPackage): PackageStates => {
|
||||
if (pkg.isUninstalling) {
|
||||
//TODO: maybe there should be an uninstalling state too? Although that needs UI/UX changes
|
||||
return PackageStates.AVAILABLE;
|
||||
}
|
||||
|
||||
const isUpToDate = pkg.version === pkg.installed_versions?.[0];
|
||||
const isUpToDate = pkg.version === pkg.installed_versions?.[0];
|
||||
|
||||
if (isInstalling(pkg)) {
|
||||
const hasNoVersions = !pkg.installed_versions?.length;
|
||||
if (hasNoVersions || isUpToDate) {
|
||||
return PackageStates.INSTALLING;
|
||||
}
|
||||
return PackageStates.UPDATING;
|
||||
}
|
||||
if (isInstalling(pkg)) {
|
||||
const hasNoVersions = !pkg.installed_versions?.length;
|
||||
if (hasNoVersions || isUpToDate) {
|
||||
return PackageStates.INSTALLING;
|
||||
}
|
||||
return PackageStates.UPDATING;
|
||||
}
|
||||
|
||||
if (!pkg.installed_versions?.length) {
|
||||
return PackageStates.AVAILABLE;
|
||||
}
|
||||
if (!pkg.installed_versions?.length) {
|
||||
return PackageStates.AVAILABLE;
|
||||
}
|
||||
|
||||
return isUpToDate ? PackageStates.INSTALLED : PackageStates.NEEDS_UPDATE;
|
||||
};
|
||||
return isUpToDate ? PackageStates.INSTALLED : PackageStates.NEEDS_UPDATE;
|
||||
};
|
||||
|
||||
const syncPackageData = async (guiPkg: Partial<GUIPackage> | undefined) => {
|
||||
if (!guiPkg) return;
|
||||
const syncPackageData = async (guiPkg: Partial<GUIPackage> | undefined) => {
|
||||
if (!guiPkg) return;
|
||||
|
||||
const pkg = await getPackage(guiPkg.full_name!); // ATM: pkg only bottles and github:string
|
||||
const readmeMd = `# ${guiPkg.full_name} #
|
||||
const pkg = await getPackage(guiPkg.full_name!); // ATM: pkg only bottles and github:string
|
||||
const readmeMd = `# ${guiPkg.full_name} #
|
||||
To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
|
||||
`;
|
||||
|
||||
const updatedPackage: Partial<GUIPackage> = {
|
||||
...pkg,
|
||||
readme: {
|
||||
data: readmeMd,
|
||||
type: "md"
|
||||
},
|
||||
synced: true,
|
||||
github: pkg.github
|
||||
? trimGithubSlug(pkg.github)
|
||||
: pkg.full_name?.includes("github.com")
|
||||
? trimGithubSlug(pkg.full_name.split("github.com/")[1])
|
||||
: ""
|
||||
};
|
||||
if (updatedPackage.github) {
|
||||
const [owner, repo] = updatedPackage.github.split("/");
|
||||
const [readme, contributors, repoData] = await Promise.all([
|
||||
getReadme(owner, repo),
|
||||
getContributors(owner, repo),
|
||||
getRepoAsPackage(owner, repo)
|
||||
]);
|
||||
if (readme) {
|
||||
updatedPackage.readme = readme;
|
||||
}
|
||||
updatedPackage.contributors = contributors;
|
||||
updatedPackage.license = repoData.license;
|
||||
}
|
||||
const updatedPackage: Partial<GUIPackage> = {
|
||||
...pkg,
|
||||
readme: {
|
||||
data: readmeMd,
|
||||
type: "md"
|
||||
},
|
||||
synced: true,
|
||||
github: pkg.github
|
||||
? trimGithubSlug(pkg.github)
|
||||
: pkg.full_name?.includes("github.com")
|
||||
? trimGithubSlug(pkg.full_name.split("github.com/")[1])
|
||||
: ""
|
||||
};
|
||||
if (updatedPackage.github) {
|
||||
const [owner, repo] = updatedPackage.github.split("/");
|
||||
const [readme, contributors, repoData] = await Promise.all([
|
||||
getReadme(owner, repo),
|
||||
getContributors(owner, repo),
|
||||
getRepoAsPackage(owner, repo)
|
||||
]);
|
||||
if (readme) {
|
||||
updatedPackage.readme = readme;
|
||||
}
|
||||
updatedPackage.contributors = contributors;
|
||||
updatedPackage.license = repoData.license;
|
||||
}
|
||||
|
||||
updatePackage(guiPkg.full_name!, updatedPackage);
|
||||
};
|
||||
updatePackage(guiPkg.full_name!, updatedPackage);
|
||||
};
|
||||
|
||||
const init = async function () {
|
||||
log.info("packages store: try initialize");
|
||||
const init = async function () {
|
||||
log.info("packages store: try initialize");
|
||||
|
||||
if (!initialized) {
|
||||
const cachedPkgs: Packages = await loadPackageCache();
|
||||
log.info(`Loaded ${Object.keys(cachedPkgs.packages).length} packages from cache`);
|
||||
packageMap.set(cachedPkgs);
|
||||
if (!initialized) {
|
||||
const cachedPkgs: Packages = await loadPackageCache();
|
||||
log.info(`Loaded ${Object.keys(cachedPkgs.packages).length} packages from cache`);
|
||||
packageMap.set(cachedPkgs);
|
||||
|
||||
await refreshPackages();
|
||||
initialized = true;
|
||||
}
|
||||
log.info("packages store: initialized!");
|
||||
};
|
||||
await refreshPackages();
|
||||
initialized = true;
|
||||
}
|
||||
log.info("packages store: initialized!");
|
||||
};
|
||||
|
||||
const refreshPackages = async () => {
|
||||
if (isDestroyed) return;
|
||||
const refreshPackages = async () => {
|
||||
if (isDestroyed) return;
|
||||
|
||||
log.info("packages store: refreshing...");
|
||||
log.info("packages store: refreshing...");
|
||||
|
||||
const pkgs = await getDistPackages();
|
||||
const guiPkgs: GUIPackage[] = pkgs.map((p) => ({
|
||||
...p,
|
||||
state: PackageStates.AVAILABLE
|
||||
}));
|
||||
const pkgs = await getDistPackages();
|
||||
const guiPkgs: GUIPackage[] = pkgs.map((p) => ({
|
||||
...p,
|
||||
state: PackageStates.AVAILABLE
|
||||
}));
|
||||
|
||||
if (!initialized) {
|
||||
// set packages data so that i can render something in the UI already
|
||||
updateAllPackages(guiPkgs);
|
||||
log.info("initialized packages store with ", guiPkgs.length);
|
||||
}
|
||||
if (!initialized) {
|
||||
// set packages data so that i can render something in the UI already
|
||||
updateAllPackages(guiPkgs);
|
||||
log.info("initialized packages store with ", guiPkgs.length);
|
||||
}
|
||||
|
||||
packagesIndex = new Fuse(guiPkgs, {
|
||||
keys: ["name", "full_name", "desc", "categories"],
|
||||
minMatchCharLength: 3,
|
||||
threshold: 0.3
|
||||
});
|
||||
log.info("refreshed packages fuse index");
|
||||
packagesIndex = new Fuse(guiPkgs, {
|
||||
keys: ["name", "full_name", "desc", "categories"],
|
||||
minMatchCharLength: 3,
|
||||
threshold: 0.3
|
||||
});
|
||||
log.info("refreshed packages fuse index");
|
||||
|
||||
try {
|
||||
const installedPkgs: InstalledPackage[] = await getInstalledPackages();
|
||||
try {
|
||||
const installedPkgs: InstalledPackage[] = await getInstalledPackages();
|
||||
|
||||
log.info("updating state of packages");
|
||||
for (const pkg of guiPkgs) {
|
||||
const iPkg = installedPkgs.find((p) => p.full_name === pkg.full_name);
|
||||
if (iPkg) {
|
||||
pkg.installed_versions = iPkg.installed_versions;
|
||||
updatePackage(pkg.full_name, {
|
||||
installed_versions: iPkg.installed_versions
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
log.info("updating state of packages");
|
||||
for (const pkg of guiPkgs) {
|
||||
const iPkg = installedPkgs.find((p) => p.full_name === pkg.full_name);
|
||||
if (iPkg) {
|
||||
pkg.installed_versions = iPkg.installed_versions;
|
||||
updatePackage(pkg.full_name, {
|
||||
installed_versions: iPkg.installed_versions
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await withRetry(syncPantry);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
}
|
||||
try {
|
||||
await withRetry(syncPantry);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
}
|
||||
|
||||
refreshTimeoutId = setTimeout(() => refreshPackages(), packageRefreshInterval); // refresh every hour
|
||||
};
|
||||
refreshTimeoutId = setTimeout(() => refreshPackages(), packageRefreshInterval); // refresh every hour
|
||||
};
|
||||
|
||||
// Destructor for the package store
|
||||
const destroy = () => {
|
||||
isDestroyed = true;
|
||||
if (refreshTimeoutId) {
|
||||
clearTimeout(refreshTimeoutId);
|
||||
}
|
||||
log.info("packages store: destroyed");
|
||||
};
|
||||
// Destructor for the package store
|
||||
const destroy = () => {
|
||||
isDestroyed = true;
|
||||
if (refreshTimeoutId) {
|
||||
clearTimeout(refreshTimeoutId);
|
||||
}
|
||||
log.info("packages store: destroyed");
|
||||
};
|
||||
|
||||
const installPkg = async (pkg: GUIPackage, version?: string) => {
|
||||
const versionToInstall = version || pkg.version;
|
||||
const installPkg = async (pkg: GUIPackage, version?: string) => {
|
||||
const versionToInstall = version || pkg.version;
|
||||
|
||||
try {
|
||||
updatePackage(pkg.full_name, { install_progress_percentage: 0.01 });
|
||||
await installPackage(pkg, versionToInstall);
|
||||
trackInstall(pkg.full_name);
|
||||
notificationStore.add({
|
||||
message: `Package ${pkg.full_name} v${versionToInstall} has been installed.`
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
let message = "Unknown Error";
|
||||
if (error instanceof Error) message = error.message;
|
||||
trackInstallFailed(pkg.full_name, message || "unknown");
|
||||
try {
|
||||
updatePackage(pkg.full_name, { install_progress_percentage: 0.01 });
|
||||
await installPackage(pkg, versionToInstall);
|
||||
trackInstall(pkg.full_name);
|
||||
notificationStore.add({
|
||||
message: `Package ${pkg.full_name} v${versionToInstall} has been installed.`
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
let message = "Unknown Error";
|
||||
if (error instanceof Error) message = error.message;
|
||||
trackInstallFailed(pkg.full_name, message || "unknown");
|
||||
|
||||
notificationStore.add({
|
||||
message: `Package ${pkg.full_name} v${versionToInstall} failed to install.`,
|
||||
type: NotificationType.ERROR
|
||||
});
|
||||
} finally {
|
||||
updatePackage(pkg.full_name, { install_progress_percentage: 100 });
|
||||
}
|
||||
};
|
||||
notificationStore.add({
|
||||
message: `Package ${pkg.full_name} v${versionToInstall} failed to install.`,
|
||||
type: NotificationType.ERROR
|
||||
});
|
||||
} finally {
|
||||
updatePackage(pkg.full_name, { install_progress_percentage: 100 });
|
||||
}
|
||||
};
|
||||
|
||||
const uninstallPkg = async (pkg: GUIPackage) => {
|
||||
let fakeTimer: NodeJS.Timer | null = null;
|
||||
try {
|
||||
fakeTimer = withFakeLoader(pkg, (progress) => {
|
||||
updatePackage(pkg.full_name, {
|
||||
install_progress_percentage: progress,
|
||||
isUninstalling: true
|
||||
});
|
||||
});
|
||||
const uninstallPkg = async (pkg: GUIPackage) => {
|
||||
let fakeTimer: NodeJS.Timer | null = null;
|
||||
try {
|
||||
fakeTimer = withFakeLoader(pkg, (progress) => {
|
||||
updatePackage(pkg.full_name, {
|
||||
install_progress_percentage: progress,
|
||||
isUninstalling: true
|
||||
});
|
||||
});
|
||||
|
||||
for (const v of pkg.installed_versions || []) {
|
||||
await deletePkg(pkg, v);
|
||||
}
|
||||
for (const v of pkg.installed_versions || []) {
|
||||
await deletePkg(pkg, v);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
updatePackage(pkg.full_name, {
|
||||
installed_versions: []
|
||||
});
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
notificationStore.add({
|
||||
message: `Package ${pkg.full_name} failed to uninstall.`,
|
||||
type: NotificationType.ERROR
|
||||
});
|
||||
} finally {
|
||||
fakeTimer && clearTimeout(fakeTimer);
|
||||
updatePackage(pkg.full_name, { install_progress_percentage: 0, isUninstalling: false });
|
||||
}
|
||||
};
|
||||
setTimeout(() => {
|
||||
updatePackage(pkg.full_name, {
|
||||
installed_versions: []
|
||||
});
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
notificationStore.add({
|
||||
message: `Package ${pkg.full_name} failed to uninstall.`,
|
||||
type: NotificationType.ERROR
|
||||
});
|
||||
} finally {
|
||||
fakeTimer && clearTimeout(fakeTimer);
|
||||
updatePackage(pkg.full_name, { install_progress_percentage: 0, isUninstalling: false });
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPackageBottles = async (pkgName: string) => {
|
||||
// TODO: this api should take an architecture argument or else an architecture filter should be applied downstreawm
|
||||
const bottles = await getPackageBottles(pkgName);
|
||||
if (bottles?.length) {
|
||||
updatePackage(pkgName, { bottles });
|
||||
}
|
||||
};
|
||||
const fetchPackageBottles = async (pkgName: string) => {
|
||||
// TODO: this api should take an architecture argument or else an architecture filter should be applied downstreawm
|
||||
const bottles = await getPackageBottles(pkgName);
|
||||
if (bottles?.length) {
|
||||
updatePackage(pkgName, { bottles });
|
||||
}
|
||||
};
|
||||
|
||||
const deletePkg = async (pkg: GUIPackage, version: string) => {
|
||||
log.info("deleting package: ", pkg.full_name, " version: ", version);
|
||||
await deletePackage({ fullName: pkg.full_name, version });
|
||||
updatePackage(pkg.full_name, {
|
||||
installed_versions: pkg.installed_versions?.filter((v) => v !== version)
|
||||
});
|
||||
};
|
||||
const deletePkg = async (pkg: GUIPackage, version: string) => {
|
||||
log.info("deleting package: ", pkg.full_name, " version: ", version);
|
||||
await deletePackage({ fullName: pkg.full_name, version });
|
||||
updatePackage(pkg.full_name, {
|
||||
installed_versions: pkg.installed_versions?.filter((v) => v !== version)
|
||||
});
|
||||
};
|
||||
|
||||
const writePackageCacheWithDebounce = withDebounce(writePackageCache);
|
||||
packageMap.subscribe(async (pkgs) => {
|
||||
writePackageCacheWithDebounce(pkgs);
|
||||
});
|
||||
const writePackageCacheWithDebounce = withDebounce(writePackageCache);
|
||||
packageMap.subscribe(async (pkgs) => {
|
||||
writePackageCacheWithDebounce(pkgs);
|
||||
});
|
||||
|
||||
const cachePkgImage = async (pkg: GUIPackage): Promise<string> => {
|
||||
let cacheFileURL = "";
|
||||
updatePackage(pkg.full_name, { cached_image_url: "" });
|
||||
if (pkg.thumb_image_url && !pkg.thumb_image_url.includes("package-thumb-nolabel4.jpg")) {
|
||||
const result = await cacheImageURL(pkg.thumb_image_url);
|
||||
if (result) {
|
||||
cacheFileURL = result;
|
||||
updatePackage(pkg.full_name, { cached_image_url: cacheFileURL });
|
||||
}
|
||||
}
|
||||
return cacheFileURL;
|
||||
};
|
||||
const cachePkgImage = async (pkg: GUIPackage): Promise<string> => {
|
||||
let cacheFileURL = "";
|
||||
updatePackage(pkg.full_name, { cached_image_url: "" });
|
||||
if (pkg.thumb_image_url && !pkg.thumb_image_url.includes("package-thumb-nolabel4.jpg")) {
|
||||
const result = await cacheImageURL(pkg.thumb_image_url);
|
||||
if (result) {
|
||||
cacheFileURL = result;
|
||||
updatePackage(pkg.full_name, { cached_image_url: cacheFileURL });
|
||||
}
|
||||
}
|
||||
return cacheFileURL;
|
||||
};
|
||||
|
||||
listenToChannel("install-progress", ({ full_name, progress }: any) => {
|
||||
if (!full_name) {
|
||||
return;
|
||||
}
|
||||
updatePackage(full_name, { install_progress_percentage: progress });
|
||||
});
|
||||
listenToChannel("install-progress", ({ full_name, progress }: any) => {
|
||||
if (!full_name) {
|
||||
return;
|
||||
}
|
||||
updatePackage(full_name, { install_progress_percentage: progress });
|
||||
});
|
||||
|
||||
listenToChannel("pkg-installed", ({ full_name, version }: any) => {
|
||||
if (!full_name) {
|
||||
return;
|
||||
}
|
||||
updatePackage(full_name, {}, version);
|
||||
});
|
||||
listenToChannel("pkg-installed", ({ full_name, version }: any) => {
|
||||
if (!full_name) {
|
||||
return;
|
||||
}
|
||||
updatePackage(full_name, {}, version);
|
||||
});
|
||||
|
||||
return {
|
||||
packageList,
|
||||
search: async (term: string, limit = 5): Promise<GUIPackage[]> => {
|
||||
if (!term || !packagesIndex) return [];
|
||||
// TODO: if online, use algolia else use Fuse
|
||||
const res = packagesIndex.search(term, { limit });
|
||||
const matchingPackages: GUIPackage[] = res.map((v) => v.item);
|
||||
return matchingPackages;
|
||||
},
|
||||
fetchPackageBottles,
|
||||
init,
|
||||
installPkg,
|
||||
uninstallPkg,
|
||||
syncPackageData,
|
||||
deletePkg,
|
||||
destroy,
|
||||
cachePkgImage
|
||||
};
|
||||
return {
|
||||
packageList,
|
||||
search: async (term: string, limit = 5): Promise<GUIPackage[]> => {
|
||||
if (!term || !packagesIndex) return [];
|
||||
// TODO: if online, use algolia else use Fuse
|
||||
const res = packagesIndex.search(term, { limit });
|
||||
const matchingPackages: GUIPackage[] = res.map((v) => v.item);
|
||||
return matchingPackages;
|
||||
},
|
||||
fetchPackageBottles,
|
||||
init,
|
||||
installPkg,
|
||||
uninstallPkg,
|
||||
syncPackageData,
|
||||
deletePkg,
|
||||
destroy,
|
||||
cachePkgImage
|
||||
};
|
||||
}
|
||||
|
||||
// This is only used for uninstall now
|
||||
export const withFakeLoader = (
|
||||
pkg: GUIPackage,
|
||||
callback: (progress: number) => void
|
||||
pkg: GUIPackage,
|
||||
callback: (progress: number) => void
|
||||
): NodeJS.Timer => {
|
||||
let fakeLoadingProgress = 1;
|
||||
const ms = 100;
|
||||
const assumedDlSpeedMb = 1024 * 1024 * 3; // 3mbps
|
||||
const size = pkg?.bottles?.length ? pkg.bottles[0].bytes : assumedDlSpeedMb * 10;
|
||||
const eta = size / assumedDlSpeedMb;
|
||||
let fakeLoadingProgress = 1;
|
||||
const ms = 100;
|
||||
const assumedDlSpeedMb = 1024 * 1024 * 3; // 3mbps
|
||||
const size = pkg?.bottles?.length ? pkg.bottles[0].bytes : assumedDlSpeedMb * 10;
|
||||
const eta = size / assumedDlSpeedMb;
|
||||
|
||||
const increment = 1 / eta / 10;
|
||||
const increment = 1 / eta / 10;
|
||||
|
||||
const fakeTimer = setInterval(() => {
|
||||
const progressLeft = 100 - fakeLoadingProgress;
|
||||
const addProgress = progressLeft * increment;
|
||||
fakeLoadingProgress = fakeLoadingProgress + addProgress;
|
||||
callback(fakeLoadingProgress);
|
||||
}, ms);
|
||||
const fakeTimer = setInterval(() => {
|
||||
const progressLeft = 100 - fakeLoadingProgress;
|
||||
const addProgress = progressLeft * increment;
|
||||
fakeLoadingProgress = fakeLoadingProgress + addProgress;
|
||||
callback(fakeLoadingProgress);
|
||||
}, ms);
|
||||
|
||||
return fakeTimer;
|
||||
return fakeTimer;
|
||||
};
|
||||
|
||||
const setBadgeCountFromPkgs = (pkgs: Packages) => {
|
||||
try {
|
||||
const needsUpdateCount = Object.values(pkgs.packages).filter(
|
||||
(p) => p.state === PackageStates.NEEDS_UPDATE
|
||||
).length;
|
||||
setBadgeCount(needsUpdateCount);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
try {
|
||||
const needsUpdateCount = Object.values(pkgs.packages).filter(
|
||||
(p) => p.state === PackageStates.NEEDS_UPDATE
|
||||
).length;
|
||||
setBadgeCount(needsUpdateCount);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,19 +3,19 @@ import { getAutoUpdateStatus, listenToChannel } from "@native";
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export default function initAppUpdateStore() {
|
||||
const updateStatus = writable<AutoUpdateStatus>({ status: "up-to-date" });
|
||||
const updateStatus = writable<AutoUpdateStatus>({ status: "up-to-date" });
|
||||
|
||||
getAutoUpdateStatus().then((status: AutoUpdateStatus) => {
|
||||
updateStatus.update(() => status);
|
||||
});
|
||||
getAutoUpdateStatus().then((status: AutoUpdateStatus) => {
|
||||
updateStatus.update(() => status);
|
||||
});
|
||||
|
||||
listenToChannel("app-update-status", (status: AutoUpdateStatus) => {
|
||||
if (status.status) {
|
||||
updateStatus.update(() => status);
|
||||
}
|
||||
});
|
||||
listenToChannel("app-update-status", (status: AutoUpdateStatus) => {
|
||||
if (status.status) {
|
||||
updateStatus.update(() => status);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
updateStatus
|
||||
};
|
||||
return {
|
||||
updateStatus
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ import translations from "./translations.json";
|
|||
|
||||
/** @type {import('sveltekit-i18n').Config} */
|
||||
const config = {
|
||||
initLocale: "en",
|
||||
fallbackLocale: "en",
|
||||
translations
|
||||
initLocale: "en",
|
||||
fallbackLocale: "en",
|
||||
translations
|
||||
};
|
||||
|
||||
export const { t, l, locales, locale } = new i18n(config);
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
{
|
||||
"en": {
|
||||
"lang": {
|
||||
"en": "English"
|
||||
},
|
||||
"store-search-placeholder": "search packages",
|
||||
"package": {
|
||||
"update-all": "UPDATE ALL",
|
||||
"cta-AVAILABLE": "INSTALL",
|
||||
"cta-INSTALLED": "INSTALLED",
|
||||
"cta-INSTALLING": "INSTALLING",
|
||||
"cta-UNINSTALLED": "RE-INSTALL",
|
||||
"cta-UNINSTALL": "UNINSTALL",
|
||||
"cta-NEEDS_UPDATE": "UPDATE",
|
||||
"cta-UPDATING": "UPDATING",
|
||||
"cta-PRUNE": "PRUNE",
|
||||
"cta-PRUNING": "PRUNING"
|
||||
},
|
||||
"footer": {
|
||||
"quick-links-title": "quick links",
|
||||
"about-tea-store": "about the tea store",
|
||||
"report-a-problem": "report a problem",
|
||||
"visit-website": "visit tea.xyz",
|
||||
"terms-services": "terms & services",
|
||||
"privacy-policy": "privacy-policy"
|
||||
},
|
||||
"documentation": {
|
||||
"title": "documentation",
|
||||
"featured-courses-title": "featured courses",
|
||||
"workshops": "workshops"
|
||||
},
|
||||
"view-all": "view all",
|
||||
"sorting": {
|
||||
"label": "Sort by",
|
||||
"popularity": "Most popular",
|
||||
"most-recent": "Most recent"
|
||||
},
|
||||
"common": {
|
||||
"home": "home",
|
||||
"all": "All",
|
||||
"articles": "Articles",
|
||||
"workshops": "Workshops",
|
||||
"details": "details",
|
||||
"versions": "versions",
|
||||
"metadata": "Metadata",
|
||||
"homepage": "Homepage",
|
||||
"documentation": "Documentation",
|
||||
"github-repository": "Github Repository",
|
||||
"contributors": "Contributors",
|
||||
"view-on-github": "VIEW ON GITHUB"
|
||||
},
|
||||
"notification": {
|
||||
"gui-downloading": "A new tea gui({{version}}) is being downloaded. Please don't close the app.",
|
||||
"gui-downloaded": "A new tea gui({{version}}) is now available. Relaunch the app to update."
|
||||
},
|
||||
"side-menu-title": {
|
||||
"discover": "discover",
|
||||
"all": "All Packages",
|
||||
"installed": "Installed Packages",
|
||||
"installed_updates_available": "Available Updates",
|
||||
"recently_updated": "Recently Updated",
|
||||
"new_packages": "New Packages",
|
||||
"popular": "Popular",
|
||||
"featured": "Featured",
|
||||
"essentials": "Essentials",
|
||||
"starstruck": "Starstruck Heavyweights",
|
||||
"made_by_tea": "made by tea"
|
||||
},
|
||||
"tags": {
|
||||
"discover": "discover",
|
||||
"all": "all",
|
||||
"installed": "installed",
|
||||
"installed_updates_available": "Updates available",
|
||||
"recently_updated": "Recently updated",
|
||||
"new_packages": "New packages",
|
||||
"popular": "Popular",
|
||||
"featured": "Featured",
|
||||
"essentials": "Essentials",
|
||||
"starstruck": "Starstruck",
|
||||
"made_by_tea": "Made by tea"
|
||||
}
|
||||
}
|
||||
"en": {
|
||||
"lang": {
|
||||
"en": "English"
|
||||
},
|
||||
"store-search-placeholder": "search packages",
|
||||
"package": {
|
||||
"update-all": "UPDATE ALL",
|
||||
"cta-AVAILABLE": "INSTALL",
|
||||
"cta-INSTALLED": "INSTALLED",
|
||||
"cta-INSTALLING": "INSTALLING",
|
||||
"cta-UNINSTALLED": "RE-INSTALL",
|
||||
"cta-UNINSTALL": "UNINSTALL",
|
||||
"cta-NEEDS_UPDATE": "UPDATE",
|
||||
"cta-UPDATING": "UPDATING",
|
||||
"cta-PRUNE": "PRUNE",
|
||||
"cta-PRUNING": "PRUNING"
|
||||
},
|
||||
"footer": {
|
||||
"quick-links-title": "quick links",
|
||||
"about-tea-store": "about the tea store",
|
||||
"report-a-problem": "report a problem",
|
||||
"visit-website": "visit tea.xyz",
|
||||
"terms-services": "terms & services",
|
||||
"privacy-policy": "privacy-policy"
|
||||
},
|
||||
"documentation": {
|
||||
"title": "documentation",
|
||||
"featured-courses-title": "featured courses",
|
||||
"workshops": "workshops"
|
||||
},
|
||||
"view-all": "view all",
|
||||
"sorting": {
|
||||
"label": "Sort by",
|
||||
"popularity": "Most popular",
|
||||
"most-recent": "Most recent"
|
||||
},
|
||||
"common": {
|
||||
"home": "home",
|
||||
"all": "All",
|
||||
"articles": "Articles",
|
||||
"workshops": "Workshops",
|
||||
"details": "details",
|
||||
"versions": "versions",
|
||||
"metadata": "Metadata",
|
||||
"homepage": "Homepage",
|
||||
"documentation": "Documentation",
|
||||
"github-repository": "Github Repository",
|
||||
"contributors": "Contributors",
|
||||
"view-on-github": "VIEW ON GITHUB"
|
||||
},
|
||||
"notification": {
|
||||
"gui-downloading": "A new tea gui({{version}}) is being downloaded. Please don't close the app.",
|
||||
"gui-downloaded": "A new tea gui({{version}}) is now available. Relaunch the app to update."
|
||||
},
|
||||
"side-menu-title": {
|
||||
"discover": "discover",
|
||||
"all": "All Packages",
|
||||
"installed": "Installed Packages",
|
||||
"installed_updates_available": "Available Updates",
|
||||
"recently_updated": "Recently Updated",
|
||||
"new_packages": "New Packages",
|
||||
"popular": "Popular",
|
||||
"featured": "Featured",
|
||||
"essentials": "Essentials",
|
||||
"starstruck": "Starstruck Heavyweights",
|
||||
"made_by_tea": "made by tea"
|
||||
},
|
||||
"tags": {
|
||||
"discover": "discover",
|
||||
"all": "all",
|
||||
"installed": "installed",
|
||||
"installed_updates_available": "Updates available",
|
||||
"recently_updated": "Recently updated",
|
||||
"new_packages": "New packages",
|
||||
"popular": "Popular",
|
||||
"featured": "Featured",
|
||||
"essentials": "Essentials",
|
||||
"starstruck": "Starstruck",
|
||||
"made_by_tea": "Made by tea"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,77 +6,77 @@
|
|||
import type { Package, Developer } from "@tea/ui/types";
|
||||
|
||||
export enum PackageStates {
|
||||
AVAILABLE = "AVAILABLE",
|
||||
INSTALLED = "INSTALLED",
|
||||
INSTALLING = "INSTALLING",
|
||||
NEEDS_UPDATE = "NEEDS_UPDATE",
|
||||
UPDATING = "UPDATING"
|
||||
AVAILABLE = "AVAILABLE",
|
||||
INSTALLED = "INSTALLED",
|
||||
INSTALLING = "INSTALLING",
|
||||
NEEDS_UPDATE = "NEEDS_UPDATE",
|
||||
UPDATING = "UPDATING"
|
||||
}
|
||||
|
||||
export type Packages = {
|
||||
version: string;
|
||||
packages: { [full_name: string]: GUIPackage };
|
||||
version: string;
|
||||
packages: { [full_name: string]: GUIPackage };
|
||||
};
|
||||
|
||||
export type GUIPackage = Package & {
|
||||
state: PackageStates;
|
||||
installed_versions?: string[];
|
||||
synced?: boolean;
|
||||
install_progress_percentage?: number;
|
||||
isUninstalling?: boolean;
|
||||
cached_image_url?: string;
|
||||
state: PackageStates;
|
||||
installed_versions?: string[];
|
||||
synced?: boolean;
|
||||
install_progress_percentage?: number;
|
||||
isUninstalling?: boolean;
|
||||
cached_image_url?: string;
|
||||
};
|
||||
|
||||
export type Course = {
|
||||
title: string;
|
||||
sub_title: string;
|
||||
banner_image_url: string;
|
||||
link: string;
|
||||
title: string;
|
||||
sub_title: string;
|
||||
banner_image_url: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export type Category = {
|
||||
label: string;
|
||||
cta_label: string;
|
||||
packages: GUIPackage[];
|
||||
label: string;
|
||||
cta_label: string;
|
||||
packages: GUIPackage[];
|
||||
};
|
||||
|
||||
export enum AuthStatus {
|
||||
UNKNOWN = "UNKNOWN",
|
||||
PENDING = "PENDING",
|
||||
SUCCESS = "SUCCESS",
|
||||
FAILED = "FAILED"
|
||||
UNKNOWN = "UNKNOWN",
|
||||
PENDING = "PENDING",
|
||||
SUCCESS = "SUCCESS",
|
||||
FAILED = "FAILED"
|
||||
}
|
||||
|
||||
export type DeviceAuth = {
|
||||
status: AuthStatus;
|
||||
user?: Developer;
|
||||
key: string;
|
||||
status: AuthStatus;
|
||||
user?: Developer;
|
||||
key: string;
|
||||
};
|
||||
export interface Session {
|
||||
device_id?: string;
|
||||
key?: string;
|
||||
user?: Developer;
|
||||
locale?: string;
|
||||
hide_welcome?: boolean;
|
||||
device_id?: string;
|
||||
key?: string;
|
||||
user?: Developer;
|
||||
locale?: string;
|
||||
hide_welcome?: boolean;
|
||||
}
|
||||
|
||||
export enum SideMenuOptions {
|
||||
discover = "discover",
|
||||
all = "all",
|
||||
installed = "installed",
|
||||
installed_updates_available = "installed_updates_available",
|
||||
recently_updated = "recently_updated",
|
||||
new_packages = "new_packages",
|
||||
popular = "popular",
|
||||
featured = "featured",
|
||||
essentials = "essentials",
|
||||
starstruck = "starstruck",
|
||||
made_by_tea = "made_by_tea"
|
||||
discover = "discover",
|
||||
all = "all",
|
||||
installed = "installed",
|
||||
installed_updates_available = "installed_updates_available",
|
||||
recently_updated = "recently_updated",
|
||||
new_packages = "new_packages",
|
||||
popular = "popular",
|
||||
featured = "featured",
|
||||
essentials = "essentials",
|
||||
starstruck = "starstruck",
|
||||
made_by_tea = "made_by_tea"
|
||||
}
|
||||
|
||||
export type InstalledPackage = Required<Pick<GUIPackage, "full_name" | "installed_versions">>;
|
||||
|
||||
export type AutoUpdateStatus = {
|
||||
status: "up-to-date" | "available" | "ready";
|
||||
version?: string;
|
||||
status: "up-to-date" | "available" | "ready";
|
||||
version?: string;
|
||||
};
|
||||
|
|
|
@ -3,27 +3,27 @@ import log from "$libs/logger";
|
|||
type DebounceableFunc = (...args: any[]) => void;
|
||||
|
||||
export type DebounceOptions = {
|
||||
lingerMs?: number;
|
||||
lingerMs?: number;
|
||||
};
|
||||
|
||||
export default function withDebounce(
|
||||
f: DebounceableFunc,
|
||||
{ lingerMs = 1000 }: DebounceOptions = {}
|
||||
f: DebounceableFunc,
|
||||
{ lingerMs = 1000 }: DebounceOptions = {}
|
||||
) {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
|
||||
return (...args: any[]) => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
return (...args: any[]) => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
try {
|
||||
f(...args);
|
||||
} catch (err) {
|
||||
//swallow the error, there is no good way to signal failure to the caller
|
||||
log.error(err);
|
||||
}
|
||||
}, lingerMs);
|
||||
};
|
||||
timeoutId = setTimeout(() => {
|
||||
try {
|
||||
f(...args);
|
||||
} catch (err) {
|
||||
//swallow the error, there is no good way to signal failure to the caller
|
||||
log.error(err);
|
||||
}
|
||||
}, lingerMs);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
import log from "$libs/logger";
|
||||
|
||||
export type RetryOptions = {
|
||||
// Number of times to retry. default 10
|
||||
maxRetries?: number;
|
||||
// Initial delay in ms. default 100
|
||||
initialDelayMs?: number;
|
||||
// Maximum delay in ms. default 5000
|
||||
maxDelayMs?: number;
|
||||
// Number of times to retry. default 10
|
||||
maxRetries?: number;
|
||||
// Initial delay in ms. default 100
|
||||
initialDelayMs?: number;
|
||||
// Maximum delay in ms. default 5000
|
||||
maxDelayMs?: number;
|
||||
};
|
||||
|
||||
// Retry a function up to maxRetries times, with exponential backoff
|
||||
// With defaults retry cadence will look like this:
|
||||
// 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms, 5000ms, 5000ms, 5000ms, 5000ms
|
||||
export default async function withRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
{ maxRetries = 10, initialDelayMs = 100, maxDelayMs = 5000 }: RetryOptions = {}
|
||||
fn: () => Promise<T>,
|
||||
{ maxRetries = 10, initialDelayMs = 100, maxDelayMs = 5000 }: RetryOptions = {}
|
||||
) {
|
||||
let retries = 0;
|
||||
let currentDelay = initialDelayMs;
|
||||
while (retries <= maxRetries) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
retries++;
|
||||
await wait(currentDelay);
|
||||
currentDelay = Math.min(currentDelay * 2, maxDelayMs);
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed after ${maxRetries} retries`);
|
||||
let retries = 0;
|
||||
let currentDelay = initialDelayMs;
|
||||
while (retries <= maxRetries) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
retries++;
|
||||
await wait(currentDelay);
|
||||
currentDelay = Math.min(currentDelay * 2, maxDelayMs);
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed after ${maxRetries} retries`);
|
||||
}
|
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
|
|
@ -6,42 +6,42 @@ import { getSession } from "$libs/stores/auth";
|
|||
export const baseUrl = "https://api.tea.xyz/v1";
|
||||
|
||||
export async function get<T>(
|
||||
urlPath: string,
|
||||
params?: { [key: string]: string }
|
||||
urlPath: string,
|
||||
params?: { [key: string]: string }
|
||||
): Promise<T | null> {
|
||||
console.log(`GET /v1/${urlPath}`);
|
||||
console.log(`GET /v1/${urlPath}`);
|
||||
|
||||
const [session] = await Promise.all([getSession()]);
|
||||
const [session] = await Promise.all([getSession()]);
|
||||
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: { Authorization: "public " };
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: { Authorization: "public " };
|
||||
|
||||
const req = await axios.request({
|
||||
method: "GET",
|
||||
baseURL: "https://api.tea.xyz",
|
||||
url: ["v1", ...urlPath.split("/")].filter((p) => p).join("/"),
|
||||
headers,
|
||||
params,
|
||||
validateStatus: (status) => status >= 200 && status < 300
|
||||
});
|
||||
const req = await axios.request({
|
||||
method: "GET",
|
||||
baseURL: "https://api.tea.xyz",
|
||||
url: ["v1", ...urlPath.split("/")].filter((p) => p).join("/"),
|
||||
headers,
|
||||
params,
|
||||
validateStatus: (status) => status >= 200 && status < 300
|
||||
});
|
||||
|
||||
return req.data as T;
|
||||
return req.data as T;
|
||||
}
|
||||
|
||||
async function getHeaders(path: string, session: Session) {
|
||||
const unixMs = new Date().getTime();
|
||||
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
|
||||
const deviceId = session.device_id?.split("-")[0];
|
||||
const preHash = [unixHexSecs, session.key, deviceId, path].join("");
|
||||
const unixMs = new Date().getTime();
|
||||
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
|
||||
const deviceId = session.device_id?.split("-")[0];
|
||||
const preHash = [unixHexSecs, session.key, deviceId, path].join("");
|
||||
|
||||
const Authorization = bcrypt.hashSync(preHash, 10);
|
||||
const Authorization = bcrypt.hashSync(preHash, 10);
|
||||
|
||||
return {
|
||||
Authorization,
|
||||
["tea-ts"]: unixMs.toString(),
|
||||
["tea-uid"]: session.user?.developer_id,
|
||||
["tea-gui_id"]: session.device_id
|
||||
};
|
||||
return {
|
||||
Authorization,
|
||||
["tea-ts"]: unixMs.toString(),
|
||||
["tea-uid"]: session.user?.developer_id,
|
||||
["tea-gui_id"]: session.device_id
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,135 +1,135 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import { goto } from "$app/navigation";
|
||||
import { initSentry } from "$libs/sentry";
|
||||
import { navigating } from "$app/stores";
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import TopBar from "$components/top-bar/top-bar.svelte";
|
||||
import { navStore, packagesStore, searchStore } from "$libs/stores";
|
||||
import { listenToChannel } from "@native";
|
||||
import Mousetrap from "mousetrap";
|
||||
import "$appcss";
|
||||
import { goto } from "$app/navigation";
|
||||
import { initSentry } from "$libs/sentry";
|
||||
import { navigating } from "$app/stores";
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import TopBar from "$components/top-bar/top-bar.svelte";
|
||||
import { navStore, packagesStore, searchStore } from "$libs/stores";
|
||||
import { listenToChannel } from "@native";
|
||||
import Mousetrap from "mousetrap";
|
||||
|
||||
import SearchPopupResults from "$components/search-popup-results/search-popup-results.svelte";
|
||||
import { getProtocolPath } from "@native";
|
||||
import SearchPopupResults from "$components/search-popup-results/search-popup-results.svelte";
|
||||
import { getProtocolPath } from "@native";
|
||||
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
|
||||
let view: HTMLElement;
|
||||
let view: HTMLElement;
|
||||
|
||||
const { setNewPath } = navStore;
|
||||
const { searching } = searchStore;
|
||||
const { setNewPath } = navStore;
|
||||
const { searching } = searchStore;
|
||||
|
||||
$: if ($navigating) view.scrollTop = 0;
|
||||
$: if ($navigating) view.scrollTop = 0;
|
||||
|
||||
afterNavigate(({ from, to }) => {
|
||||
if (to && to?.route.id && from && from?.url) {
|
||||
const nextPath = to.url.href.replace(to.url.origin, "");
|
||||
const fromPath = from?.url.href.replace(from.url.origin, "");
|
||||
setNewPath(nextPath, fromPath || "/");
|
||||
}
|
||||
});
|
||||
afterNavigate(({ from, to }) => {
|
||||
if (to && to?.route.id && from && from?.url) {
|
||||
const nextPath = to.url.href.replace(to.url.origin, "");
|
||||
const fromPath = from?.url.href.replace(from.url.origin, "");
|
||||
setNewPath(nextPath, fromPath || "/");
|
||||
}
|
||||
});
|
||||
|
||||
const syncPath = async () => {
|
||||
// used by the tea:// protocol to suggest a path to open
|
||||
const path = await getProtocolPath();
|
||||
if (path) goto(path);
|
||||
};
|
||||
const syncPath = async () => {
|
||||
// used by the tea:// protocol to suggest a path to open
|
||||
const path = await getProtocolPath();
|
||||
if (path) goto(path);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
// used by the tea:// protocol to suggest a path to open
|
||||
syncPath();
|
||||
listenToChannel("sync-path", syncPath);
|
||||
Mousetrap.bind(["command+k", "ctrl+k"], function () {
|
||||
searchStore.searching.set(!$searching);
|
||||
// return false to prevent default browser behavior
|
||||
// and stop event from bubbling
|
||||
return false;
|
||||
});
|
||||
Mousetrap.bind(["esc"], function () {
|
||||
searchStore.searching.set(false);
|
||||
return false;
|
||||
});
|
||||
packagesStore.init();
|
||||
initSentry();
|
||||
});
|
||||
onMount(async () => {
|
||||
// used by the tea:// protocol to suggest a path to open
|
||||
syncPath();
|
||||
listenToChannel("sync-path", syncPath);
|
||||
Mousetrap.bind(["command+k", "ctrl+k"], function () {
|
||||
searchStore.searching.set(!$searching);
|
||||
// return false to prevent default browser behavior
|
||||
// and stop event from bubbling
|
||||
return false;
|
||||
});
|
||||
Mousetrap.bind(["esc"], function () {
|
||||
searchStore.searching.set(false);
|
||||
return false;
|
||||
});
|
||||
packagesStore.init();
|
||||
initSentry();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
packagesStore.destroy();
|
||||
});
|
||||
onDestroy(() => {
|
||||
packagesStore.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="main-layout" class="font-inter border-gray rounded-xl border transition-all">
|
||||
<TopBar />
|
||||
<div class="scroll-manager relative z-10">
|
||||
<section class="relative" bind:this={view}>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<SearchPopupResults />
|
||||
</section>
|
||||
</div>
|
||||
<TopBar />
|
||||
<div class="scroll-manager relative z-10">
|
||||
<section class="relative" bind:this={view}>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<SearchPopupResults />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#main-layout {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
#main-layout {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scroll-manager {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding-right: 8px;
|
||||
}
|
||||
section {
|
||||
height: calc(100vh - 50px); /* win.height - header*/
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.scroll-manager {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding-right: 8px;
|
||||
}
|
||||
section {
|
||||
height: calc(100vh - 50px); /* win.height - header*/
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
slot {
|
||||
z-index: 1;
|
||||
}
|
||||
slot {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div {
|
||||
position: relative;
|
||||
}
|
||||
div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
aside {
|
||||
top: 52px;
|
||||
right: 5px;
|
||||
width: 210px;
|
||||
overflow: clip;
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
aside {
|
||||
top: 52px;
|
||||
right: 5px;
|
||||
width: 210px;
|
||||
overflow: clip;
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: auto;
|
||||
overflow-y: hidden;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.content {
|
||||
height: auto;
|
||||
overflow-y: hidden;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #272626;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #949494;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,83 +1,81 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import "$appcss";
|
||||
|
||||
import { page } from "$app/stores";
|
||||
import { t } from "$libs/translations";
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import { packagesStore, authStore } from "$libs/stores";
|
||||
import Packages from "$components/packages/packages.svelte";
|
||||
import DiscoverPackages from "$components/discover-packages/discover-packages.svelte";
|
||||
import { PackageStates, SideMenuOptions, type GUIPackage } from "$libs/types";
|
||||
// import SortingButtons from "$components/search-packages/sorting-buttons.svelte";
|
||||
import SideMenu from "$components/side-menu/side-menu.svelte";
|
||||
import NotificationBar from "$components/notification-bar/notification-bar.svelte";
|
||||
import WelcomeModal from "$components/welcome-modal/welcome-modal.svelte";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import log from "$libs/logger";
|
||||
import { page } from "$app/stores";
|
||||
import { t } from "$libs/translations";
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import { packagesStore, authStore } from "$libs/stores";
|
||||
import Packages from "$components/packages/packages.svelte";
|
||||
import DiscoverPackages from "$components/discover-packages/discover-packages.svelte";
|
||||
import { PackageStates, SideMenuOptions, type GUIPackage } from "$libs/types";
|
||||
// import SortingButtons from "$components/search-packages/sorting-buttons.svelte";
|
||||
import SideMenu from "$components/side-menu/side-menu.svelte";
|
||||
import NotificationBar from "$components/notification-bar/notification-bar.svelte";
|
||||
import WelcomeModal from "$components/welcome-modal/welcome-modal.svelte";
|
||||
import Button from "@tea/ui/button/button.svelte";
|
||||
import log from "$libs/logger";
|
||||
|
||||
const { packageList } = packagesStore;
|
||||
const { session } = authStore;
|
||||
const { packageList } = packagesStore;
|
||||
const { session } = authStore;
|
||||
|
||||
const url = $page.url;
|
||||
const url = $page.url;
|
||||
|
||||
let sideMenuOption = (url.searchParams.get("tab") as SideMenuOptions) || SideMenuOptions.discover;
|
||||
let sideMenuOption = (url.searchParams.get("tab") as SideMenuOptions) || SideMenuOptions.discover;
|
||||
|
||||
let sortBy: "popularity" | "most recent" = "most recent";
|
||||
let sortDirection: "asc" | "desc" = "desc";
|
||||
let sortBy: "popularity" | "most recent" = "most recent";
|
||||
let sortDirection: "asc" | "desc" = "desc";
|
||||
|
||||
let updating = false;
|
||||
let updating = false;
|
||||
|
||||
let packagesScrollY = 0;
|
||||
$: currentUpdatingPkg = $packageList.find((p) => p.state === PackageStates.UPDATING);
|
||||
$: updatingMessage = `updating ${currentUpdatingPkg?.full_name} (${currentUpdatingPkg?.install_progress_percentage}%)`;
|
||||
let packagesScrollY = 0;
|
||||
$: currentUpdatingPkg = $packageList.find((p) => p.state === PackageStates.UPDATING);
|
||||
$: updatingMessage = `updating ${currentUpdatingPkg?.full_name} (${currentUpdatingPkg?.install_progress_percentage}%)`;
|
||||
|
||||
$: pkgsToUpdate = $packageList.filter((p: GUIPackage) => p.state === PackageStates.NEEDS_UPDATE);
|
||||
async function updateAll() {
|
||||
updating = true;
|
||||
log.info(`updating: ${pkgsToUpdate.length} packages`);
|
||||
for (const pkg of pkgsToUpdate) {
|
||||
try {
|
||||
await packagesStore.installPkg(pkg);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
updating = false;
|
||||
sideMenuOption = SideMenuOptions.all;
|
||||
}
|
||||
$: pkgsToUpdate = $packageList.filter((p: GUIPackage) => p.state === PackageStates.NEEDS_UPDATE);
|
||||
async function updateAll() {
|
||||
updating = true;
|
||||
log.info(`updating: ${pkgsToUpdate.length} packages`);
|
||||
for (const pkg of pkgsToUpdate) {
|
||||
try {
|
||||
await packagesStore.installPkg(pkg);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
updating = false;
|
||||
sideMenuOption = SideMenuOptions.all;
|
||||
}
|
||||
|
||||
$: needsUpdateCount = pkgsToUpdate.length;
|
||||
$: needsUpdateCount = pkgsToUpdate.length;
|
||||
|
||||
afterNavigate(({ to }) => {
|
||||
if (to?.url?.pathname === "/") {
|
||||
const tab = to.url.searchParams.get("tab");
|
||||
sideMenuOption = !tab ? SideMenuOptions.discover : (tab as SideMenuOptions);
|
||||
}
|
||||
});
|
||||
afterNavigate(({ to }) => {
|
||||
if (to?.url?.pathname === "/") {
|
||||
const tab = to.url.searchParams.get("tab");
|
||||
sideMenuOption = !tab ? SideMenuOptions.discover : (tab as SideMenuOptions);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="flex flex-col">
|
||||
<NotificationBar />
|
||||
<article class="relative h-auto w-full flex-grow overflow-hidden">
|
||||
<ul class="px-2">
|
||||
{#if sideMenuOption == SideMenuOptions.discover}
|
||||
<DiscoverPackages
|
||||
bind:scrollY={packagesScrollY}
|
||||
/>
|
||||
{:else}
|
||||
<Packages
|
||||
packageFilter={sideMenuOption}
|
||||
{sortBy}
|
||||
{sortDirection}
|
||||
bind:scrollY={packagesScrollY}
|
||||
/>
|
||||
{/if}
|
||||
</ul>
|
||||
<header class="z-30 flex items-center justify-between" class:scrolling={packagesScrollY > 150}>
|
||||
<h1 class="text-primary font-mona pl-3 text-2xl font-bold">
|
||||
{$t(`side-menu-title.${sideMenuOption}`).toLowerCase()}
|
||||
</h1>
|
||||
<!--
|
||||
<NotificationBar />
|
||||
<article class="relative h-auto w-full flex-grow overflow-hidden">
|
||||
<ul class="px-2">
|
||||
{#if sideMenuOption == SideMenuOptions.discover}
|
||||
<DiscoverPackages bind:scrollY={packagesScrollY} />
|
||||
{:else}
|
||||
<Packages
|
||||
packageFilter={sideMenuOption}
|
||||
{sortBy}
|
||||
{sortDirection}
|
||||
bind:scrollY={packagesScrollY}
|
||||
/>
|
||||
{/if}
|
||||
</ul>
|
||||
<header class="z-30 flex items-center justify-between" class:scrolling={packagesScrollY > 150}>
|
||||
<h1 class="text-primary font-mona pl-3 text-2xl font-bold">
|
||||
{$t(`side-menu-title.${sideMenuOption}`).toLowerCase()}
|
||||
</h1>
|
||||
<!--
|
||||
<section class="border-gray mt-4 mr-4 h-10 w-48 border rounded-sm">
|
||||
|
||||
we might bring it back?
|
||||
|
@ -87,63 +85,63 @@
|
|||
}} />
|
||||
</section>
|
||||
-->
|
||||
{#if needsUpdateCount && sideMenuOption === SideMenuOptions.installed_updates_available}
|
||||
<!-- 22px right margin to account for the scrollbar on the package cards -->
|
||||
<div class="mr-[22px] flex items-center justify-end text-sm">
|
||||
{#if currentUpdatingPkg}
|
||||
<p class="text-gray px-2">{updatingMessage}</p>
|
||||
{/if}
|
||||
<div>
|
||||
<Button
|
||||
class="h-8 w-48 text-xs"
|
||||
loading={updating}
|
||||
type="plain"
|
||||
color="secondary"
|
||||
onClick={updateAll}
|
||||
>
|
||||
{$t(`package.update-all`)} [{needsUpdateCount}]
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
</article>
|
||||
{#if needsUpdateCount && sideMenuOption === SideMenuOptions.installed_updates_available}
|
||||
<!-- 22px right margin to account for the scrollbar on the package cards -->
|
||||
<div class="mr-[22px] flex items-center justify-end text-sm">
|
||||
{#if currentUpdatingPkg}
|
||||
<p class="text-gray px-2">{updatingMessage}</p>
|
||||
{/if}
|
||||
<div>
|
||||
<Button
|
||||
class="h-8 w-48 text-xs"
|
||||
loading={updating}
|
||||
type="plain"
|
||||
color="secondary"
|
||||
onClick={updateAll}
|
||||
>
|
||||
{$t(`package.update-all`)} [{needsUpdateCount}]
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<SideMenu bind:activeOption={sideMenuOption} />
|
||||
{#if !$session.hide_welcome}
|
||||
<WelcomeModal />
|
||||
<WelcomeModal />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#content {
|
||||
width: calc(100vw - 211px);
|
||||
margin-left: 205px;
|
||||
height: calc(100vh - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
#content {
|
||||
width: calc(100vw - 211px);
|
||||
margin-left: 205px;
|
||||
height: calc(100vh - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 1px;
|
||||
height: 72px;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(rgba(26, 26, 26, 1), rgba(26, 26, 26, 0));
|
||||
padding-top: 15px;
|
||||
}
|
||||
header {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 1px;
|
||||
height: 72px;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(rgba(26, 26, 26, 1), rgba(26, 26, 26, 0));
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
padding-top: 8px;
|
||||
}
|
||||
header h1 {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
header.scrolling {
|
||||
height: 60px;
|
||||
background-color: #222222;
|
||||
padding-top: 5px;
|
||||
}
|
||||
header.scrolling {
|
||||
height: 60px;
|
||||
background-color: #222222;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
header.scrolling h1 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
header.scrolling h1 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<script>
|
||||
import '$appcss';
|
||||
import { t } from '$libs/translations';
|
||||
import PageHeader from '$components/page-header/page-header.svelte';
|
||||
import FeaturedCourses from '$components/featured-courses/featured-courses.svelte';
|
||||
import EssentialWorkshops from '$components/essential-workshops/essential-workshops.svelte';
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
import PageHeader from "$components/page-header/page-header.svelte";
|
||||
import FeaturedCourses from "$components/featured-courses/featured-courses.svelte";
|
||||
import EssentialWorkshops from "$components/essential-workshops/essential-workshops.svelte";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<PageHeader>{$t('documentation.title').toUpperCase()}</PageHeader>
|
||||
<PageHeader>{$t("documentation.title").toUpperCase()}</PageHeader>
|
||||
|
||||
<section>
|
||||
<FeaturedCourses />
|
||||
</section>
|
||||
<section>
|
||||
<FeaturedCourses />
|
||||
</section>
|
||||
|
||||
<section class="mt-8">
|
||||
<EssentialWorkshops
|
||||
title={$t('documentation.workshops').toUpperCase()}
|
||||
ctaLabel={$t("view-all")}
|
||||
/>
|
||||
</section>
|
||||
<section class="mt-8">
|
||||
<EssentialWorkshops
|
||||
title={$t("documentation.workshops").toUpperCase()}
|
||||
ctaLabel={$t("view-all")}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
import '$appcss';
|
||||
import PageHeader from '$components/page-header/page-header.svelte';
|
||||
import "$appcss";
|
||||
import PageHeader from "$components/page-header/page-header.svelte";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<PageHeader>Packages</PageHeader>
|
||||
<PageHeader>Packages</PageHeader>
|
||||
</div>
|
||||
|
|
|
@ -1,95 +1,97 @@
|
|||
<script lang="ts">
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
import "$appcss";
|
||||
import { t } from "$libs/translations";
|
||||
|
||||
import { page } from "$app/stores";
|
||||
// import PageHeader from '$components/page-header/page-header.svelte';
|
||||
import PackageBanner from "$components/package-banner/package-banner.svelte";
|
||||
// import SuggestedPackages from '$components/suggested-packages/suggested-packages.svelte';
|
||||
import Tabs from "@tea/ui/tabs/tabs.svelte";
|
||||
import type { Tab } from "@tea/ui/types";
|
||||
import Bottles from "@tea/ui/bottles/bottles.svelte";
|
||||
import PackageMetas from "@tea/ui/package-metas/package-metas.svelte";
|
||||
import Markdown from "@tea/ui/markdown/markdown.svelte";
|
||||
// import PackageSnippets from '@tea/ui/package-snippets/package-snippets.svelte';
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
import { page } from "$app/stores";
|
||||
// import PageHeader from '$components/page-header/page-header.svelte';
|
||||
import PackageBanner from "$components/package-banner/package-banner.svelte";
|
||||
// import SuggestedPackages from '$components/suggested-packages/suggested-packages.svelte';
|
||||
import Tabs from "@tea/ui/tabs/tabs.svelte";
|
||||
import type { Tab } from "@tea/ui/types";
|
||||
import Bottles from "@tea/ui/bottles/bottles.svelte";
|
||||
import PackageMetas from "@tea/ui/package-metas/package-metas.svelte";
|
||||
import Markdown from "@tea/ui/markdown/markdown.svelte";
|
||||
// import PackageSnippets from '@tea/ui/package-snippets/package-snippets.svelte';
|
||||
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data: { slug: string; content: string; title: string };
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data: { slug: string; content: string; title: string };
|
||||
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { onMount } from "svelte";
|
||||
import NotificationBar from "$components/notification-bar/notification-bar.svelte";
|
||||
import { packagesStore } from "$libs/stores";
|
||||
import { onMount } from "svelte";
|
||||
import NotificationBar from "$components/notification-bar/notification-bar.svelte";
|
||||
|
||||
const { packageList } = packagesStore;
|
||||
const { packageList } = packagesStore;
|
||||
|
||||
$: pkg = $packageList.find((p) => p.slug === data?.slug);
|
||||
$: pkg = $packageList.find((p) => p.slug === data?.slug);
|
||||
|
||||
// let reviews: Review[];
|
||||
$: bottles = pkg?.bottles || [];
|
||||
$: versions = [...new Set(bottles.map((b) => b.version))];
|
||||
$: readme = pkg?.readme || { data: "", type: "md" };
|
||||
// let reviews: Review[];
|
||||
$: bottles = pkg?.bottles || [];
|
||||
$: versions = [...new Set(bottles.map((b) => b.version))];
|
||||
$: readme = pkg?.readme || { data: "", type: "md" };
|
||||
|
||||
$: tabs = [
|
||||
readme?.data !== "" && {
|
||||
label: $t("common.details"),
|
||||
component: Markdown,
|
||||
props: { pkg, source: readme }
|
||||
},
|
||||
bottles?.length && {
|
||||
label: `${$t("common.versions")} (${versions.length || 0})`,
|
||||
component: Bottles,
|
||||
props: {
|
||||
bottles
|
||||
}
|
||||
}
|
||||
].filter((t) => t && t?.label) as unknown as Tab[];
|
||||
$: tabs = [
|
||||
readme?.data !== "" && {
|
||||
label: $t("common.details"),
|
||||
component: Markdown,
|
||||
props: { pkg, source: readme }
|
||||
},
|
||||
bottles?.length && {
|
||||
label: `${$t("common.versions")} (${versions.length || 0})`,
|
||||
component: Bottles,
|
||||
props: {
|
||||
bottles
|
||||
}
|
||||
}
|
||||
].filter((t) => t && t?.label) as unknown as Tab[];
|
||||
|
||||
const url = $page.url;
|
||||
const url = $page.url;
|
||||
|
||||
const tab = url.searchParams.get("tab");
|
||||
const tab = url.searchParams.get("tab");
|
||||
|
||||
onMount(() => {
|
||||
packagesStore.syncPackageData(pkg);
|
||||
});
|
||||
onMount(() => {
|
||||
packagesStore.syncPackageData(pkg);
|
||||
});
|
||||
</script>
|
||||
|
||||
<header class="text-gray mx-16 mb-4 border border-x-0 border-t-0 py-5">
|
||||
<a class="hover:text-white hover:opacity-80" href="/">{$t("common.home")}</a>
|
||||
›
|
||||
{#if tab && tab !== "all"}
|
||||
<a class="hover:text-white hover:opacity-80" href="/?tab={tab || "all"}">{$t(`tags.${tab}`).toLowerCase() || "all"}</a>
|
||||
›
|
||||
{/if}
|
||||
<span class="text-white">{pkg?.full_name}</span>
|
||||
<a class="hover:text-white hover:opacity-80" href="/">{$t("common.home")}</a>
|
||||
›
|
||||
{#if tab && tab !== "all"}
|
||||
<a class="hover:text-white hover:opacity-80" href="/?tab={tab || 'all'}"
|
||||
>{$t(`tags.${tab}`).toLowerCase() || "all"}</a
|
||||
>
|
||||
›
|
||||
{/if}
|
||||
<span class="text-white">{pkg?.full_name}</span>
|
||||
</header>
|
||||
<div class="mx-16 mb-4">
|
||||
<NotificationBar />
|
||||
<NotificationBar />
|
||||
</div>
|
||||
{#if pkg}
|
||||
<div class="px-16">
|
||||
<section>
|
||||
<PackageBanner {pkg} />
|
||||
</section>
|
||||
<div class="px-16">
|
||||
<section>
|
||||
<PackageBanner {pkg} />
|
||||
</section>
|
||||
|
||||
<section class="mt-8 flex gap-8">
|
||||
<div class="w-2/3">
|
||||
<Tabs {tabs} defaultTab={$t("common.details")} />
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
{#if pkg}
|
||||
<PackageMetas {pkg} />
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
<!-- <PageHeader class="mt-8" coverUrl="/images/headers/header_bg_1.png">SNIPPETS</PageHeader> -->
|
||||
<!-- <section class="mt-8">
|
||||
<section class="mt-8 flex gap-8">
|
||||
<div class="w-2/3">
|
||||
<Tabs {tabs} defaultTab={$t("common.details")} />
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
{#if pkg}
|
||||
<PackageMetas {pkg} />
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
<!-- <PageHeader class="mt-8" coverUrl="/images/headers/header_bg_1.png">SNIPPETS</PageHeader> -->
|
||||
<!-- <section class="mt-8">
|
||||
<PackageSnippets />
|
||||
</section> -->
|
||||
<!-- <section class="mt-8">
|
||||
<!-- <section class="mt-8">
|
||||
<PackageReviews reviews={reviews || []} />
|
||||
</section> -->
|
||||
<!-- {#if pkg}
|
||||
<!-- {#if pkg}
|
||||
<PageHeader class="mt-8" coverUrl="/images/headers/header_bg_1.png"
|
||||
>YOU MAY ALSO LIKE...</PageHeader
|
||||
>
|
||||
|
@ -97,7 +99,7 @@
|
|||
<SuggestedPackages {pkg} />
|
||||
</section>
|
||||
{/if} -->
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Preloader />
|
||||
<Preloader />
|
||||
{/if}
|
||||
|
|
|
@ -2,10 +2,10 @@ import type { LoadEvent } from "@sveltejs/kit";
|
|||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export function load({ params }: LoadEvent) {
|
||||
// TODO: search package details here
|
||||
return {
|
||||
title: `${params.slug}`,
|
||||
content: "",
|
||||
slug: params.slug
|
||||
};
|
||||
// TODO: search package details here
|
||||
return {
|
||||
title: `${params.slug}`,
|
||||
content: "",
|
||||
slug: params.slug
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,26 +3,26 @@ import preprocess from "svelte-preprocess";
|
|||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true
|
||||
})
|
||||
],
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
pages: "build",
|
||||
assets: "build",
|
||||
fallback: "app.html"
|
||||
}),
|
||||
alias: {
|
||||
"@tea/ui/*": "../ui/src/*"
|
||||
}
|
||||
// ssr: false,
|
||||
// hydrate the <div id="svelte"> element in src/app.html
|
||||
// target: '#svelte'
|
||||
}
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true
|
||||
})
|
||||
],
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
pages: "build",
|
||||
assets: "build",
|
||||
fallback: "app.html"
|
||||
}),
|
||||
alias: {
|
||||
"@tea/ui/*": "../ui/src/*"
|
||||
}
|
||||
// ssr: false,
|
||||
// hydrate the <div id="svelte"> element in src/app.html
|
||||
// target: '#svelte'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { theme, plugins } from "@tea/ui/tailwind.config.cjs";
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{html,svelte,ts,js}", "../ui/src/**/*.{html,svelte,ts,js}"],
|
||||
theme,
|
||||
plugins: [...plugins]
|
||||
content: ["./src/**/*.{html,svelte,ts,js}", "../ui/src/**/*.{html,svelte,ts,js}"],
|
||||
theme,
|
||||
plugins: [...plugins]
|
||||
};
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"],
|
||||
"paths": {
|
||||
"$appcss": ["src/app.css"],
|
||||
"$libs/*": ["src/libs/*"],
|
||||
"@native": ["src/libs/native-electron.ts"],
|
||||
"$components/*": ["src/components/*"],
|
||||
"@tea/ui/*": ["../ui/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["../ui/types/*.d.ts"]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"],
|
||||
"paths": {
|
||||
"$appcss": ["src/app.css"],
|
||||
"$libs/*": ["src/libs/*"],
|
||||
"@native": ["src/libs/native-electron.ts"],
|
||||
"$components/*": ["src/components/*"],
|
||||
"@tea/ui/*": ["../ui/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["../ui/types/*.d.ts"]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
|
|
@ -5,37 +5,37 @@ import path from "path";
|
|||
const isMock = process.env.BUILD_FOR === "preview";
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@tea/ui/*": path.resolve("../ui/src/*"),
|
||||
// this dynamic-ish static importing is followed by the svelte build
|
||||
// but for vscode editing intellisense tsconfig.json is being used
|
||||
"@native": isMock
|
||||
? path.resolve("src/libs/native-mock.ts")
|
||||
: path.resolve("src/libs/native-electron.ts"),
|
||||
$components: path.resolve("./src/components"),
|
||||
$libs: path.resolve("./src/libs"),
|
||||
$appcss: path.resolve("./src/app.css")
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
fs: {
|
||||
allow: [".."]
|
||||
}
|
||||
},
|
||||
test: {
|
||||
// Jest like globals
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
include: ["src/**/*.{test,spec}.ts"],
|
||||
// Extend jest-dom matchers
|
||||
setupFiles: ["./setupTest.js"],
|
||||
coverage: {
|
||||
provider: "c8"
|
||||
}
|
||||
}
|
||||
plugins: [sveltekit()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@tea/ui/*": path.resolve("../ui/src/*"),
|
||||
// this dynamic-ish static importing is followed by the svelte build
|
||||
// but for vscode editing intellisense tsconfig.json is being used
|
||||
"@native": isMock
|
||||
? path.resolve("src/libs/native-mock.ts")
|
||||
: path.resolve("src/libs/native-electron.ts"),
|
||||
$components: path.resolve("./src/components"),
|
||||
$libs: path.resolve("./src/libs"),
|
||||
$appcss: path.resolve("./src/app.css")
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
fs: {
|
||||
allow: [".."]
|
||||
}
|
||||
},
|
||||
test: {
|
||||
// Jest like globals
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
include: ["src/**/*.{test,spec}.ts"],
|
||||
// Extend jest-dom matchers
|
||||
setupFiles: ["./setupTest.js"],
|
||||
coverage: {
|
||||
provider: "c8"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
globals: {
|
||||
NodeJS: true
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
plugins: ["svelte3", "@typescript-eslint"],
|
||||
ignorePatterns: ["*.cjs"],
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.svelte"],
|
||||
processor: "svelte3/svelte3"
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
"svelte3/typescript": () => require("typescript")
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_"
|
||||
}
|
||||
]
|
||||
}
|
||||
root: true,
|
||||
globals: {
|
||||
NodeJS: true
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
plugins: ["svelte3", "@typescript-eslint"],
|
||||
ignorePatterns: ["*.cjs"],
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.svelte"],
|
||||
processor: "svelte3/svelte3"
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
"svelte3/typescript": () => require("typescript")
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"pluginSearchDirs": ["../../node_modules"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
const path = require("path");
|
||||
module.exports = {
|
||||
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx|svelte)"],
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions"
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/svelte-vite",
|
||||
options: {}
|
||||
},
|
||||
docs: {
|
||||
docsPage: true
|
||||
}
|
||||
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx|svelte)"],
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions"
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/svelte-vite",
|
||||
options: {}
|
||||
},
|
||||
docs: {
|
||||
docsPage: true
|
||||
}
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue