mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-09-11 12:25:42 +03:00
sort out lint/format setup
This commit is contained in:
parent
894e31881d
commit
d80c96ce80
18
.eslintignore
Normal file
18
.eslintignore
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Ignore not svelte dirs
|
||||||
|
/.github
|
||||||
|
/.vscode
|
||||||
|
/src-tauri
|
20
.eslintrc.cjs
Normal file
20
.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: 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
|
||||||
|
}
|
||||||
|
};
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"es2022": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:unicorn/recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"plugin:svelte/recommended",
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"unicorn",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.svelte"],
|
|
||||||
"processor": "svelte3/svelte3"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"root": true
|
|
||||||
}
|
|
18
.prettierignore
Normal file
18
.prettierignore
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Ignore not svelte dirs
|
||||||
|
/.github
|
||||||
|
/.vscode
|
||||||
|
/src-tauri
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"pluginSearchDirs": ["."],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 4,
|
|
||||||
"plugins": [
|
|
||||||
"prettier-plugin-svelte",
|
|
||||||
"prettier-plugin-tailwindcss"
|
|
||||||
],
|
|
||||||
"pluginSearchDirs": false
|
|
||||||
}
|
|
107
package.json
107
package.json
@ -1,56 +1,55 @@
|
|||||||
{
|
{
|
||||||
"name": "git-butler-tauri",
|
"name": "git-butler-tauri",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
"tauri": "tauri"
|
"format": "prettier --plugin-search-dir . --write .",
|
||||||
},
|
"tauri": "tauri"
|
||||||
"dependencies": {
|
},
|
||||||
"@codemirror/commands": "^6.2.0",
|
"dependencies": {
|
||||||
"@codemirror/merge": "^0.1.3",
|
"@codemirror/commands": "^6.2.0",
|
||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/merge": "^0.1.3",
|
||||||
"@codemirror/view": "^6.7.3",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@tauri-apps/api": "^1.2.0",
|
"@codemirror/view": "^6.7.3",
|
||||||
"date-fns": "^2.29.3",
|
"@tauri-apps/api": "^1.2.0",
|
||||||
"fluent-svelte": "^1.6.0",
|
"date-fns": "^2.29.3",
|
||||||
"idb-keyval": "^6.2.0",
|
"fluent-svelte": "^1.6.0",
|
||||||
"inter-ui": "^3.19.3",
|
"idb-keyval": "^6.2.0",
|
||||||
"mm-jsr": "^3.0.2",
|
"inter-ui": "^3.19.3",
|
||||||
"nanoid": "^4.0.1",
|
"mm-jsr": "^3.0.2",
|
||||||
"posthog-js": "^1.46.1",
|
"nanoid": "^4.0.1",
|
||||||
"seti-icons": "^0.0.4",
|
"posthog-js": "^1.46.1",
|
||||||
"svelte-french-toast": "^1.0.3",
|
"seti-icons": "^0.0.4",
|
||||||
"svelte-icons": "^2.1.0",
|
"svelte-french-toast": "^1.0.3",
|
||||||
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log"
|
"svelte-icons": "^2.1.0",
|
||||||
},
|
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log"
|
||||||
"devDependencies": {
|
},
|
||||||
"@sveltejs/adapter-static": "next",
|
"devDependencies": {
|
||||||
"@sveltejs/kit": "next",
|
"@sveltejs/adapter-static": "next",
|
||||||
"@tauri-apps/cli": "^1.2.2",
|
"@sveltejs/kit": "next",
|
||||||
"@types/diff": "^5.0.2",
|
"@tauri-apps/cli": "^1.2.2",
|
||||||
"autoprefixer": "^10.4.7",
|
"@types/diff": "^5.0.2",
|
||||||
"eslint": "^8.34.0",
|
"autoprefixer": "^10.4.7",
|
||||||
"eslint-config-prettier": "^8.6.0",
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"@typescript-eslint/parser": "^5.45.0",
|
||||||
"eslint-plugin-svelte": "^2.18.0",
|
"eslint": "^8.28.0",
|
||||||
"eslint-plugin-unicorn": "^45.0.2",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"postcss": "^8.4.14",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"postcss-load-config": "^4.0.1",
|
"prettier": "^2.8.0",
|
||||||
"prettier": "^2.8.4",
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
"prettier-plugin-svelte": "^2.9.0",
|
"postcss": "^8.4.14",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
"postcss-load-config": "^4.0.1",
|
||||||
"svelte": "^3.55.1",
|
"svelte": "^3.55.1",
|
||||||
"svelte-check": "^3.0.0",
|
"svelte-check": "^3.0.1",
|
||||||
"svelte-preprocess": "^4.10.7",
|
"tailwindcss": "^3.1.5",
|
||||||
"tailwindcss": "^3.1.5",
|
"tslib": "^2.4.1",
|
||||||
"tslib": "^2.4.1",
|
"typescript": "^4.8.4",
|
||||||
"typescript": "^4.8.4",
|
"vite": "^4.0.0"
|
||||||
"vite": "^4.0.0"
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
701
pnpm-lock.yaml
701
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,13 @@
|
|||||||
const tailwindcss = require("tailwindcss");
|
const tailwindcss = require('tailwindcss');
|
||||||
const autoprefixer = require("autoprefixer");
|
const autoprefixer = require('autoprefixer');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
plugins: [
|
plugins: [
|
||||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
//But others, like autoprefixer, need to run after,
|
//But others, like autoprefixer, need to run after,
|
||||||
autoprefixer,
|
autoprefixer
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
18
src/app.html
18
src/app.html
@ -1,13 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="dark">
|
<html lang="en" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="fixed h-full w-full overflow-hidden font-sans antialiased text-base">
|
<body class="fixed h-full w-full overflow-hidden font-sans antialiased text-base">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@import "inter-ui/inter.css";
|
@import 'inter-ui/inter.css';
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
282
src/lib/api.ts
282
src/lib/api.ts
@ -1,174 +1,164 @@
|
|||||||
import { PUBLIC_API_BASE_URL } from "$env/static/public";
|
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
||||||
import * as log from "$lib/log";
|
import * as log from '$lib/log';
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
const apiUrl = new URL("/api/", new URL(PUBLIC_API_BASE_URL));
|
const apiUrl = new URL('/api/', new URL(PUBLIC_API_BASE_URL));
|
||||||
|
|
||||||
const getUrl = (path: string) => new URL(path, apiUrl).toString();
|
const getUrl = (path: string) => new URL(path, apiUrl).toString();
|
||||||
|
|
||||||
export type LoginToken = {
|
export type LoginToken = {
|
||||||
token: string;
|
token: string;
|
||||||
expires: string;
|
expires: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
picture: string;
|
picture: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Project = {
|
export type Project = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
repository_id: string;
|
repository_id: string;
|
||||||
git_url: string;
|
git_url: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseResponseJSON = async (response: Response) => {
|
const parseResponseJSON = async (response: Response) => {
|
||||||
if (response.status === 204 || response.status === 205) {
|
if (response.status === 204 || response.status === 205) {
|
||||||
return null;
|
return null;
|
||||||
} else if (response.status >= 400) {
|
} else if (response.status >= 400) {
|
||||||
throw new Error(
|
throw new Error(`HTTP Error ${response.statusText}: ${await response.text()}`);
|
||||||
`HTTP Error ${response.statusText}: ${await response.text()}`
|
} else {
|
||||||
);
|
return await response.json();
|
||||||
} else {
|
}
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type FetchMiddleware = (f: typeof fetch) => typeof fetch;
|
type FetchMiddleware = (f: typeof fetch) => typeof fetch;
|
||||||
|
|
||||||
const fetchWith = (
|
const fetchWith = (fetch: typeof window.fetch, ...middlewares: FetchMiddleware[]) =>
|
||||||
fetch: typeof window.fetch,
|
middlewares.reduce((f, middleware) => middleware(f), fetch);
|
||||||
...middlewares: FetchMiddleware[]
|
|
||||||
) => middlewares.reduce((f, middleware) => middleware(f), fetch);
|
|
||||||
|
|
||||||
const withRequestId: FetchMiddleware = (fetch) => async (url, options) => {
|
const withRequestId: FetchMiddleware = (fetch) => async (url, options) => {
|
||||||
const requestId = nanoid();
|
const requestId = nanoid();
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options?.headers,
|
...options?.headers,
|
||||||
"X-Request-Id": requestId,
|
'X-Request-Id': requestId
|
||||||
};
|
};
|
||||||
return fetch(url, options);
|
return fetch(url, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const withLog: FetchMiddleware = (fetch) => async (url, options) => {
|
const withLog: FetchMiddleware = (fetch) => async (url, options) => {
|
||||||
log.info("fetch", url, options);
|
log.info('fetch', url, options);
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(url, options);
|
const resp = await fetch(url, options);
|
||||||
log.info(resp);
|
log.info(resp);
|
||||||
return resp;
|
return resp;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
log.error("fetch", e);
|
log.error('fetch', e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
{ fetch: realFetch }: { fetch: typeof window.fetch } = {
|
{ fetch: realFetch }: { fetch: typeof window.fetch } = {
|
||||||
fetch: window.fetch,
|
fetch: window.fetch
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const fetch = fetchWith(realFetch, withRequestId, withLog);
|
const fetch = fetchWith(realFetch, withRequestId, withLog);
|
||||||
return {
|
return {
|
||||||
login: {
|
login: {
|
||||||
token: {
|
token: {
|
||||||
create: (params: {} = {}): Promise<LoginToken> =>
|
create: (params: {} = {}): Promise<LoginToken> =>
|
||||||
fetch(getUrl("login/token.json"), {
|
fetch(getUrl('login/token.json'), {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params)
|
||||||
})
|
})
|
||||||
.then(parseResponseJSON)
|
.then(parseResponseJSON)
|
||||||
.then((token) => {
|
.then((token) => {
|
||||||
const url = new URL(token.url);
|
const url = new URL(token.url);
|
||||||
url.host = apiUrl.host;
|
url.host = apiUrl.host;
|
||||||
return {
|
return {
|
||||||
...token,
|
...token,
|
||||||
url: url.toString(),
|
url: url.toString()
|
||||||
};
|
};
|
||||||
}),
|
})
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
get: (token: string): Promise<User> =>
|
get: (token: string): Promise<User> =>
|
||||||
fetch(getUrl(`login/user/${token}.json`), {
|
fetch(getUrl(`login/user/${token}.json`), {
|
||||||
method: "GET",
|
method: 'GET'
|
||||||
}).then(parseResponseJSON),
|
}).then(parseResponseJSON)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
get: async (token: string): Promise<User> =>
|
get: async (token: string): Promise<User> =>
|
||||||
fetch(getUrl(`user.json`), {
|
fetch(getUrl(`user.json`), {
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth-Token": token,
|
'X-Auth-Token': token
|
||||||
},
|
}
|
||||||
}).then(parseResponseJSON),
|
}).then(parseResponseJSON),
|
||||||
update: async (
|
update: async (token: string, params: { name?: string; picture?: File }) => {
|
||||||
token: string,
|
const formData = new FormData();
|
||||||
params: { name?: string; picture?: File }
|
if (params.name) {
|
||||||
) => {
|
formData.append('name', params.name);
|
||||||
const formData = new FormData();
|
}
|
||||||
if (params.name) {
|
if (params.picture) {
|
||||||
formData.append("name", params.name);
|
formData.append('avatar', params.picture);
|
||||||
}
|
}
|
||||||
if (params.picture) {
|
return fetch(getUrl(`user.json`), {
|
||||||
formData.append("avatar", params.picture);
|
method: 'PUT',
|
||||||
}
|
headers: {
|
||||||
return fetch(getUrl(`user.json`), {
|
'X-Auth-Token': token
|
||||||
method: "PUT",
|
},
|
||||||
headers: {
|
body: formData
|
||||||
"X-Auth-Token": token,
|
}).then(parseResponseJSON);
|
||||||
},
|
}
|
||||||
body: formData,
|
},
|
||||||
}).then(parseResponseJSON);
|
projects: {
|
||||||
},
|
create: (token: string, params: { name: string; uid?: string }): Promise<Project> =>
|
||||||
},
|
fetch(getUrl('projects.json'), {
|
||||||
projects: {
|
method: 'POST',
|
||||||
create: (
|
headers: {
|
||||||
token: string,
|
'Content-Type': 'application/json',
|
||||||
params: { name: string; uid?: string }
|
'X-Auth-Token': token
|
||||||
): Promise<Project> =>
|
},
|
||||||
fetch(getUrl("projects.json"), {
|
body: JSON.stringify(params)
|
||||||
method: "POST",
|
}).then(parseResponseJSON),
|
||||||
headers: {
|
list: (token: string): Promise<Project[]> =>
|
||||||
"Content-Type": "application/json",
|
fetch(getUrl('projects.json'), {
|
||||||
"X-Auth-Token": token,
|
method: 'GET',
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify(params),
|
'X-Auth-Token': token
|
||||||
}).then(parseResponseJSON),
|
}
|
||||||
list: (token: string): Promise<Project[]> =>
|
}).then(parseResponseJSON),
|
||||||
fetch(getUrl("projects.json"), {
|
get: (token: string, repositoryId: string): Promise<Project> =>
|
||||||
method: "GET",
|
fetch(getUrl(`projects/${repositoryId}.json`), {
|
||||||
headers: {
|
method: 'GET',
|
||||||
"X-Auth-Token": token,
|
headers: {
|
||||||
},
|
'X-Auth-Token': token
|
||||||
}).then(parseResponseJSON),
|
}
|
||||||
get: (token: string, repositoryId: string): Promise<Project> =>
|
}).then(parseResponseJSON),
|
||||||
fetch(getUrl(`projects/${repositoryId}.json`), {
|
delete: (token: string, repositoryId: string): Promise<void> =>
|
||||||
method: "GET",
|
fetch(getUrl(`projects/${repositoryId}.json`), {
|
||||||
headers: {
|
method: 'DELETE',
|
||||||
"X-Auth-Token": token,
|
headers: {
|
||||||
},
|
'X-Auth-Token': token
|
||||||
}).then(parseResponseJSON),
|
}
|
||||||
delete: (token: string, repositoryId: string): Promise<void> =>
|
}).then(parseResponseJSON)
|
||||||
fetch(getUrl(`projects/${repositoryId}.json`), {
|
}
|
||||||
method: "DELETE",
|
};
|
||||||
headers: {
|
|
||||||
"X-Auth-Token": token,
|
|
||||||
},
|
|
||||||
}).then(parseResponseJSON),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const toHumanBranchName = (branch: string) => {
|
export const toHumanBranchName = (branch: string) => {
|
||||||
return branch.replace("refs/heads/", "");
|
return branch.replace('refs/heads/', '');
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import FaArrowLeft from "svelte-icons/fa/FaArrowLeft.svelte";
|
import FaArrowLeft from 'svelte-icons/fa/FaArrowLeft.svelte';
|
||||||
import FaArrowRight from "svelte-icons/fa/FaArrowRight.svelte";
|
import FaArrowRight from 'svelte-icons/fa/FaArrowRight.svelte';
|
||||||
let history = window.history;
|
let history = window.history;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center justify-center space-x-3 text-zinc-400">
|
<div class="flex items-center justify-center space-x-3 text-zinc-400">
|
||||||
<button
|
<button class="w-4 h-4 hover:text-zinc-200" title="Go back" on:click={() => history.back()}
|
||||||
class="w-4 h-4 hover:text-zinc-200"
|
><FaArrowLeft /></button
|
||||||
title="Go back"
|
>
|
||||||
on:click={() => history.back()}><FaArrowLeft /></button
|
<button class="w-4 h-4 hover:text-zinc-200" title="Go forward" on:click={() => history.forward()}
|
||||||
>
|
><FaArrowRight /></button
|
||||||
<button
|
>
|
||||||
class="w-4 h-4 hover:text-zinc-200"
|
|
||||||
title="Go forward"
|
|
||||||
on:click={() => history.forward()}><FaArrowRight /></button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,42 +1,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Project } from "$lib/projects";
|
import type { Project } from '$lib/projects';
|
||||||
import type { Session } from "$lib/sessions";
|
import type { Session } from '$lib/sessions';
|
||||||
import { toHumanReadableTime } from "$lib/time";
|
import { toHumanReadableTime } from '$lib/time';
|
||||||
import { getContext } from "svelte";
|
import { getContext } from 'svelte';
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from 'svelte/store';
|
||||||
import IoIosBowtie from "svelte-icons/io/IoIosBowtie.svelte";
|
import IoIosBowtie from 'svelte-icons/io/IoIosBowtie.svelte';
|
||||||
import MdKeyboardArrowRight from "svelte-icons/md/MdKeyboardArrowRight.svelte";
|
import MdKeyboardArrowRight from 'svelte-icons/md/MdKeyboardArrowRight.svelte';
|
||||||
|
|
||||||
let project: Writable<Project | null | undefined> = getContext("project");
|
let project: Writable<Project | null | undefined> = getContext('project');
|
||||||
let session: Writable<Session | null | undefined> = getContext("session");
|
let session: Writable<Session | null | undefined> = getContext('session');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="flex flex-row items-center space-x-1 bg-zinc-900 text-zinc-400 h-8">
|
||||||
class="flex flex-row items-center space-x-1 bg-zinc-900 text-zinc-400 h-8"
|
<a class="hover:text-zinc-200" href="/">
|
||||||
>
|
<div class="w-6 h-6">
|
||||||
<a class="hover:text-zinc-200" href="/">
|
<IoIosBowtie />
|
||||||
<div class="w-6 h-6">
|
</div>
|
||||||
<IoIosBowtie />
|
</a>
|
||||||
</div>
|
{#if $project}
|
||||||
</a>
|
<div class="w-8 h-8 text-zinc-700">
|
||||||
{#if $project}
|
<MdKeyboardArrowRight />
|
||||||
<div class="w-8 h-8 text-zinc-700">
|
</div>
|
||||||
<MdKeyboardArrowRight />
|
<a class="hover:text-zinc-200" href="/projects/{$project.id}">{$project.title}</a>
|
||||||
</div>
|
{/if}
|
||||||
<a class="hover:text-zinc-200" href="/projects/{$project.id}"
|
{#if $project && $session}
|
||||||
>{$project.title}</a
|
<div class="w-8 h-8 text-zinc-700">
|
||||||
>
|
<MdKeyboardArrowRight />
|
||||||
{/if}
|
</div>
|
||||||
{#if $project && $session}
|
<a class="hover:text-zinc-200" href="/projects/{$project.id}/sessions/{$session.id}">
|
||||||
<div class="w-8 h-8 text-zinc-700">
|
{toHumanReadableTime($session.meta.startTimestampMs)}
|
||||||
<MdKeyboardArrowRight />
|
{toHumanReadableTime($session.meta.lastTimestampMs)}
|
||||||
</div>
|
</a>
|
||||||
<a
|
{/if}
|
||||||
class="hover:text-zinc-200"
|
|
||||||
href="/projects/{$project.id}/sessions/{$session.id}"
|
|
||||||
>
|
|
||||||
{toHumanReadableTime($session.meta.startTimestampMs)}
|
|
||||||
{toHumanReadableTime($session.meta.lastTimestampMs)}
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,77 +1,75 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import { EditorState } from "@codemirror/state";
|
import { EditorState } from '@codemirror/state';
|
||||||
import { EditorView, lineNumbers } from "@codemirror/view";
|
import { EditorView, lineNumbers } from '@codemirror/view';
|
||||||
|
|
||||||
let editorTheme = EditorView.theme(
|
let editorTheme = EditorView.theme(
|
||||||
{
|
{
|
||||||
"&": {
|
'&': {
|
||||||
color: "#d4d4d8",
|
color: '#d4d4d8',
|
||||||
backgroundColor: "#18181b",
|
backgroundColor: '#18181b'
|
||||||
},
|
},
|
||||||
".cm-content": {
|
'.cm-content': {
|
||||||
caretColor: "#0e9",
|
caretColor: '#0e9'
|
||||||
},
|
},
|
||||||
"&.cm-focused .cm-cursor": {
|
'&.cm-focused .cm-cursor': {
|
||||||
borderLeftColor: "#0e9",
|
borderLeftColor: '#0e9'
|
||||||
},
|
},
|
||||||
"&.cm-focused .cm-selectionBackground, ::selection": {
|
'&.cm-focused .cm-selectionBackground, ::selection': {
|
||||||
backgroundColor: "#0284c7",
|
backgroundColor: '#0284c7'
|
||||||
},
|
},
|
||||||
".cm-gutters": {
|
'.cm-gutters': {
|
||||||
backgroundColor: "#18181b",
|
backgroundColor: '#18181b',
|
||||||
color: "#3f3f46",
|
color: '#3f3f46',
|
||||||
border: "none",
|
border: 'none'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{ dark: true }
|
{ dark: true }
|
||||||
);
|
);
|
||||||
const fixedHeightEditor = EditorView.theme({
|
const fixedHeightEditor = EditorView.theme({
|
||||||
"&": { height: "600px" },
|
'&': { height: '600px' },
|
||||||
".cm-scroller": { overflow: "auto" },
|
'.cm-scroller': { overflow: 'auto' }
|
||||||
});
|
});
|
||||||
|
|
||||||
export let value: string;
|
export let value: string;
|
||||||
|
|
||||||
let element: HTMLDivElement;
|
let element: HTMLDivElement;
|
||||||
let editorView: EditorView;
|
let editorView: EditorView;
|
||||||
|
|
||||||
onMount(() => (editorView = create_editor_view(value)));
|
onMount(() => (editorView = create_editor_view(value)));
|
||||||
onDestroy(() => editorView?.destroy());
|
onDestroy(() => editorView?.destroy());
|
||||||
|
|
||||||
$: editorView && update(value);
|
$: editorView && update(value);
|
||||||
|
|
||||||
// There may be a more graceful way to update the two editors
|
// There may be a more graceful way to update the two editors
|
||||||
function update(value: string): void {
|
function update(value: string): void {
|
||||||
editorView?.destroy();
|
editorView?.destroy();
|
||||||
editorView = create_editor_view(value);
|
editorView = create_editor_view(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_editor_state(
|
function create_editor_state(value: string | null | undefined): EditorState {
|
||||||
value: string | null | undefined
|
return EditorState.create({
|
||||||
): EditorState {
|
doc: value ?? undefined,
|
||||||
return EditorState.create({
|
extensions: state_extensions
|
||||||
doc: value ?? undefined,
|
});
|
||||||
extensions: state_extensions,
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_editor_view(value: string): EditorView {
|
function create_editor_view(value: string): EditorView {
|
||||||
return new EditorView({
|
return new EditorView({
|
||||||
state: create_editor_state(value),
|
state: create_editor_state(value),
|
||||||
parent: element,
|
parent: element
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let state_extensions = [
|
let state_extensions = [
|
||||||
EditorView.editable.of(false),
|
EditorView.editable.of(false),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
lineNumbers(),
|
lineNumbers(),
|
||||||
editorTheme,
|
editorTheme,
|
||||||
fixedHeightEditor,
|
fixedHeightEditor
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<code>
|
<code>
|
||||||
<div bind:this={element} />
|
<div bind:this={element} />
|
||||||
</code>
|
</code>
|
||||||
|
@ -1,51 +1,45 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type Users from "$lib/users";
|
import type Users from '$lib/users';
|
||||||
import type Api from "$lib/api";
|
import type Api from '$lib/api';
|
||||||
import type { LoginToken } from "$lib/api";
|
import type { LoginToken } from '$lib/api';
|
||||||
import { derived, writable } from "svelte/store";
|
import { derived, writable } from 'svelte/store';
|
||||||
import { open } from "@tauri-apps/api/shell";
|
import { open } from '@tauri-apps/api/shell';
|
||||||
|
|
||||||
export let user: Awaited<ReturnType<typeof Users>>;
|
export let user: Awaited<ReturnType<typeof Users>>;
|
||||||
export let api: Awaited<ReturnType<typeof Api>>;
|
export let api: Awaited<ReturnType<typeof Api>>;
|
||||||
|
|
||||||
const pollForUser = async (token: string) => {
|
const pollForUser = async (token: string) => {
|
||||||
const apiUser = await api.login.user.get(token).catch(() => null);
|
const apiUser = await api.login.user.get(token).catch(() => null);
|
||||||
if (apiUser) {
|
if (apiUser) {
|
||||||
user.set(apiUser);
|
user.set(apiUser);
|
||||||
return apiUser;
|
return apiUser;
|
||||||
}
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
resolve(await pollForUser(token));
|
resolve(await pollForUser(token));
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const token = writable<LoginToken | null>(null);
|
const token = writable<LoginToken | null>(null);
|
||||||
const authUrl = derived(token, ($token) => $token?.url as string);
|
const authUrl = derived(token, ($token) => $token?.url as string);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{#if $user}
|
{#if $user}
|
||||||
<button
|
<button class="text-zinc-400 hover:underline" on:click={() => user.delete()}>Log out</button>
|
||||||
class="text-zinc-400 hover:underline"
|
{:else if $token !== null}
|
||||||
on:click={() => user.delete()}>Log out</button
|
{#await Promise.all([open($token.url), pollForUser($token.token)])}
|
||||||
>
|
<div>Log in in your system browser</div>
|
||||||
{:else if $token !== null}
|
{/await}
|
||||||
{#await Promise.all([open($token.url), pollForUser($token.token)])}
|
<p>
|
||||||
<div>Log in in your system browser</div>
|
<button class="underline" on:click={() => open($authUrl)}>Click here</button>
|
||||||
{/await}
|
if you are not redirected automatically, you can
|
||||||
<p>
|
</p>
|
||||||
<button class="underline" on:click={() => open($authUrl)}
|
{:else}
|
||||||
>Click here</button
|
<button
|
||||||
>
|
class="py-1 px-3 rounded text-white bg-blue-400"
|
||||||
if you are not redirected automatically, you can
|
on:click={() => api.login.token.create().then(token.set)}>Sign up or Log in</button
|
||||||
</p>
|
>
|
||||||
{:else}
|
{/if}
|
||||||
<button
|
|
||||||
class="py-1 px-3 rounded text-white bg-blue-400"
|
|
||||||
on:click={() => api.login.token.create().then(token.set)}
|
|
||||||
>Sign up or Log in</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export { default as CodeViewer } from "./CodeViewer.svelte";
|
export { default as CodeViewer } from './CodeViewer.svelte';
|
||||||
export { default as BackForwardButtons } from "./BackForwardButtons.svelte";
|
export { default as BackForwardButtons } from './BackForwardButtons.svelte';
|
||||||
export { default as Login } from "./Login.svelte";
|
export { default as Login } from './Login.svelte';
|
||||||
export { default as Breadcrumbs } from "./Breadcrumbs.svelte";
|
export { default as Breadcrumbs } from './Breadcrumbs.svelte';
|
||||||
|
@ -1,111 +1,104 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { themeIcons } from "seti-icons";
|
import { themeIcons } from 'seti-icons';
|
||||||
import type { Session } from "$lib/sessions";
|
import type { Session } from '$lib/sessions';
|
||||||
import { toHumanReadableTime, toHumanReadableDate } from "$lib/time";
|
import { toHumanReadableTime, toHumanReadableDate } from '$lib/time';
|
||||||
import { toHumanBranchName } from "$lib/branch";
|
import { toHumanBranchName } from '$lib/branch';
|
||||||
import TimelineDaySessionActivities from "./TimelineDaySessionActivities.svelte";
|
import TimelineDaySessionActivities from './TimelineDaySessionActivities.svelte';
|
||||||
import { list } from "$lib/deltas";
|
import { list } from '$lib/deltas';
|
||||||
export let session: Session;
|
export let session: Session;
|
||||||
export let projectId: string;
|
export let projectId: string;
|
||||||
|
|
||||||
const getIcon = themeIcons({
|
const getIcon = themeIcons({
|
||||||
blue: "#268bd2",
|
blue: '#268bd2',
|
||||||
grey: "#657b83",
|
grey: '#657b83',
|
||||||
"grey-light": "#839496",
|
'grey-light': '#839496',
|
||||||
green: "#859900",
|
green: '#859900',
|
||||||
orange: "#cb4b16",
|
orange: '#cb4b16',
|
||||||
pink: "#d33682",
|
pink: '#d33682',
|
||||||
purple: "#6c71c4",
|
purple: '#6c71c4',
|
||||||
red: "#dc322f",
|
red: '#dc322f',
|
||||||
white: "#fdf6e3",
|
white: '#fdf6e3',
|
||||||
yellow: "#b58900",
|
yellow: '#b58900',
|
||||||
ignore: "#586e75",
|
ignore: '#586e75'
|
||||||
});
|
});
|
||||||
|
|
||||||
function pathToName(path: string) {
|
function pathToName(path: string) {
|
||||||
return path.split("/").slice(-1)[0];
|
return path.split('/').slice(-1)[0];
|
||||||
}
|
}
|
||||||
function pathToIconSvg(path: string) {
|
function pathToIconSvg(path: string) {
|
||||||
let name: string = pathToName(path);
|
let name: string = pathToName(path);
|
||||||
let { svg, color } = getIcon(name);
|
let { svg, color } = getIcon(name);
|
||||||
return svg;
|
return svg;
|
||||||
}
|
}
|
||||||
const colorFromBranchName = (branchName: string) => {
|
const colorFromBranchName = (branchName: string) => {
|
||||||
const colors = [
|
const colors = [
|
||||||
"bg-red-500 border-red-700",
|
'bg-red-500 border-red-700',
|
||||||
"bg-green-500 border-green-700",
|
'bg-green-500 border-green-700',
|
||||||
"bg-blue-500 border-blue-700",
|
'bg-blue-500 border-blue-700',
|
||||||
"bg-yellow-500 border-yellow-700",
|
'bg-yellow-500 border-yellow-700',
|
||||||
"bg-purple-500 border-purple-700",
|
'bg-purple-500 border-purple-700',
|
||||||
"bg-pink-500 border-pink-700",
|
'bg-pink-500 border-pink-700',
|
||||||
"bg-indigo-500 border-indigo-700",
|
'bg-indigo-500 border-indigo-700',
|
||||||
"bg-orange-500 border-orange-700",
|
'bg-orange-500 border-orange-700'
|
||||||
];
|
];
|
||||||
const hash = branchName.split("").reduce((acc, char) => {
|
const hash = branchName.split('').reduce((acc, char) => {
|
||||||
return acc + char.charCodeAt(0);
|
return acc + char.charCodeAt(0);
|
||||||
}, 0);
|
}, 0);
|
||||||
return colors[hash % colors.length];
|
return colors[hash % colors.length];
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<span class="relative inline-flex">
|
<span class="relative inline-flex">
|
||||||
<a
|
<a
|
||||||
id="block"
|
id="block"
|
||||||
class="inline-flex flex-grow items-center truncate transition ease-in-out duration-150 border px-4 py-2 text-slate-50 rounded-lg {colorFromBranchName(
|
class="inline-flex flex-grow items-center truncate transition ease-in-out duration-150 border px-4 py-2 text-slate-50 rounded-lg {colorFromBranchName(
|
||||||
session.meta.branch
|
session.meta.branch
|
||||||
)}"
|
)}"
|
||||||
title={session.meta.branch}
|
title={session.meta.branch}
|
||||||
href="/projects/{projectId}/sessions/{session.id}/"
|
href="/projects/{projectId}/sessions/{session.id}/"
|
||||||
>
|
>
|
||||||
{toHumanBranchName(session.meta.branch)}
|
{toHumanBranchName(session.meta.branch)}
|
||||||
</a>
|
</a>
|
||||||
{#if !session.hash}
|
{#if !session.hash}
|
||||||
<span
|
<span class="flex absolute h-3 w-3 top-0 right-0 -mt-1 -mr-1" title="Current session">
|
||||||
class="flex absolute h-3 w-3 top-0 right-0 -mt-1 -mr-1"
|
<span
|
||||||
title="Current session"
|
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-200 opacity-75"
|
||||||
>
|
/>
|
||||||
<span
|
<span
|
||||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-200 opacity-75"
|
class="relative inline-flex rounded-full h-3 w-3 bg-zinc-200 border border-orange-200"
|
||||||
/>
|
/>
|
||||||
<span
|
</span>
|
||||||
class="relative inline-flex rounded-full h-3 w-3 bg-zinc-200 border border-orange-200"
|
{/if}
|
||||||
/>
|
</span>
|
||||||
</span>
|
<div id="activities">
|
||||||
{/if}
|
<div class="my-2 mx-1">
|
||||||
</span>
|
<TimelineDaySessionActivities
|
||||||
<div id="activities">
|
activities={session.activity}
|
||||||
<div class="my-2 mx-1">
|
sessionStart={session.meta.startTimestampMs}
|
||||||
<TimelineDaySessionActivities
|
sessionEnd={session.meta.lastTimestampMs}
|
||||||
activities={session.activity}
|
/>
|
||||||
sessionStart={session.meta.startTimestampMs}
|
</div>
|
||||||
sessionEnd={session.meta.lastTimestampMs}
|
</div>
|
||||||
/>
|
<div id="time-range" class="">
|
||||||
</div>
|
{toHumanReadableDate(session.meta.startTimestampMs)},
|
||||||
</div>
|
{toHumanReadableTime(session.meta.startTimestampMs)}
|
||||||
<div id="time-range" class="">
|
<div class=" text-zinc-600">
|
||||||
{toHumanReadableDate(session.meta.startTimestampMs)},
|
{Math.round((session.meta.lastTimestampMs - session.meta.startTimestampMs) / 60 / 1000)} min
|
||||||
{toHumanReadableTime(session.meta.startTimestampMs)}
|
</div>
|
||||||
<div class=" text-zinc-600">
|
</div>
|
||||||
{Math.round(
|
<div id="files">
|
||||||
(session.meta.lastTimestampMs - session.meta.startTimestampMs) /
|
{#await list({ projectId: projectId, sessionId: session.id }) then deltas}
|
||||||
60 /
|
{#each Object.keys(deltas) as delta}
|
||||||
1000
|
<div class="flex flex-row w-32 items-center">
|
||||||
)} min
|
<div class="w-6 h-6 text-white fill-blue-400">
|
||||||
</div>
|
{@html pathToIconSvg(delta)}
|
||||||
</div>
|
</div>
|
||||||
<div id="files">
|
<div class="text-white w-24 truncate">
|
||||||
{#await list( { projectId: projectId, sessionId: session.id } ) then deltas}
|
{pathToName(delta)}
|
||||||
{#each Object.keys(deltas) as delta}
|
</div>
|
||||||
<div class="flex flex-row w-32 items-center">
|
</div>
|
||||||
<div class="w-6 h-6 text-white fill-blue-400">
|
{/each}
|
||||||
{@html pathToIconSvg(delta)}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-white w-24 truncate">
|
|
||||||
{pathToName(delta)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,63 +1,59 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Activity } from "$lib/sessions";
|
import type { Activity } from '$lib/sessions';
|
||||||
import FaSquare from "svelte-icons/fa/FaSquare.svelte";
|
import FaSquare from 'svelte-icons/fa/FaSquare.svelte';
|
||||||
import FaCircle from "svelte-icons/fa/FaCircle.svelte";
|
import FaCircle from 'svelte-icons/fa/FaCircle.svelte';
|
||||||
import FaAdjust from "svelte-icons/fa/FaAdjust.svelte";
|
import FaAdjust from 'svelte-icons/fa/FaAdjust.svelte';
|
||||||
import FaMapMarker from "svelte-icons/fa/FaMapMarker.svelte";
|
import FaMapMarker from 'svelte-icons/fa/FaMapMarker.svelte';
|
||||||
|
|
||||||
export let activities: Activity[];
|
export let activities: Activity[];
|
||||||
export let sessionStart: number;
|
export let sessionStart: number;
|
||||||
export let sessionEnd: number;
|
export let sessionEnd: number;
|
||||||
|
|
||||||
$: sessionDuration = sessionEnd - sessionStart;
|
$: sessionDuration = sessionEnd - sessionStart;
|
||||||
|
|
||||||
let proportionOfTime = (time: number) => {
|
let proportionOfTime = (time: number) => {
|
||||||
return ((time - sessionStart) / sessionDuration) * 100;
|
return ((time - sessionStart) / sessionDuration) * 100;
|
||||||
};
|
};
|
||||||
const toHumanReadableTime = (timestamp: number) => {
|
const toHumanReadableTime = (timestamp: number) => {
|
||||||
return new Date(timestamp).toLocaleTimeString("en-US", {
|
return new Date(timestamp).toLocaleTimeString('en-US', {
|
||||||
hour: "numeric",
|
hour: 'numeric',
|
||||||
minute: "numeric",
|
minute: 'numeric'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<hr class="h-px bg-slate-400 border-0 z-0" />
|
<hr class="h-px bg-slate-400 border-0 z-0" />
|
||||||
<div class="absolute inset-0 -mt-1.5">
|
<div class="absolute inset-0 -mt-1.5">
|
||||||
{#each activities as activity}
|
{#each activities as activity}
|
||||||
<div
|
<div
|
||||||
class="flex -mx-1.5"
|
class="flex -mx-1.5"
|
||||||
style="position:relative; left: {proportionOfTime(
|
style="position:relative; left: {proportionOfTime(activity.timestampMs)}%;"
|
||||||
activity.timestampMs
|
>
|
||||||
)}%;"
|
<div
|
||||||
>
|
class="w-3 h-3 text-slate-700 z-50 absolute inset-0"
|
||||||
<div
|
style=""
|
||||||
class="w-3 h-3 text-slate-700 z-50 absolute inset-0"
|
title="{activity.type}: {activity.message} at {toHumanReadableTime(activity.timestampMs)}"
|
||||||
style=""
|
>
|
||||||
title="{activity.type}: {activity.message} at {toHumanReadableTime(
|
{#if activity.type === 'commit'}
|
||||||
activity.timestampMs
|
<div class="text-sky-500 hover:text-sky-600">
|
||||||
)}"
|
<FaSquare />
|
||||||
>
|
</div>
|
||||||
{#if activity.type === "commit"}
|
{:else if activity.type === 'merge'}
|
||||||
<div class="text-sky-500 hover:text-sky-600">
|
<div class="text-green-500 hover:text-green-600">
|
||||||
<FaSquare />
|
<FaMapMarker />
|
||||||
</div>
|
</div>
|
||||||
{:else if activity.type === "merge"}
|
{:else if activity.type === 'rebase'}
|
||||||
<div class="text-green-500 hover:text-green-600">
|
<div class="text-orange-500 hover:text-orange-600">
|
||||||
<FaMapMarker />
|
<FaAdjust />
|
||||||
</div>
|
</div>
|
||||||
{:else if activity.type === "rebase"}
|
{:else if activity.type === 'push'}
|
||||||
<div class="text-orange-500 hover:text-orange-600">
|
<div class="text-purple-500 hover:text-purple-600">
|
||||||
<FaAdjust />
|
<FaCircle />
|
||||||
</div>
|
</div>
|
||||||
{:else if activity.type === "push"}
|
{/if}
|
||||||
<div class="text-purple-500 hover:text-purple-600">
|
</div>
|
||||||
<FaCircle />
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default as TimelineDaySession } from "./TimelineDaySession.svelte";
|
export { default as TimelineDaySession } from './TimelineDaySession.svelte';
|
||||||
|
@ -1,50 +1,47 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toHumanBranchName } from "$lib/branch";
|
import { toHumanBranchName } from '$lib/branch';
|
||||||
|
|
||||||
export let startTime: Date;
|
export let startTime: Date;
|
||||||
export let endTime: Date;
|
export let endTime: Date;
|
||||||
export let label: string;
|
export let label: string;
|
||||||
export let href: string;
|
export let href: string;
|
||||||
|
|
||||||
const timeToGridRow = (time: Date) => {
|
const timeToGridRow = (time: Date) => {
|
||||||
const hours = time.getHours();
|
const hours = time.getHours();
|
||||||
const minutes = time.getMinutes();
|
const minutes = time.getMinutes();
|
||||||
const totalMinutes = hours * 60 + minutes;
|
const totalMinutes = hours * 60 + minutes;
|
||||||
const totalMinutesPerDay = 24 * 60;
|
const totalMinutesPerDay = 24 * 60;
|
||||||
const gridRow = Math.floor((totalMinutes / totalMinutesPerDay) * 96);
|
const gridRow = Math.floor((totalMinutes / totalMinutesPerDay) * 96);
|
||||||
return gridRow + 1; // offset the first row
|
return gridRow + 1; // offset the first row
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateToGridCol = (date: Date) => {
|
const dateToGridCol = (date: Date) => {
|
||||||
return date.getDay();
|
return date.getDay();
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeToSpan = (startTime: Date, endTime: Date) => {
|
const timeToSpan = (startTime: Date, endTime: Date) => {
|
||||||
const startMinutes = startTime.getHours() * 60 + startTime.getMinutes();
|
const startMinutes = startTime.getHours() * 60 + startTime.getMinutes();
|
||||||
const endMinutes = endTime.getHours() * 60 + endTime.getMinutes();
|
const endMinutes = endTime.getHours() * 60 + endTime.getMinutes();
|
||||||
const span = Math.round((endMinutes - startMinutes) / 15); // 4 spans per hour
|
const span = Math.round((endMinutes - startMinutes) / 15); // 4 spans per hour
|
||||||
if (span < 1) {
|
if (span < 1) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return span;
|
return span;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="relative mt-px flex col-start-{dateToGridCol(startTime)}"
|
class="relative mt-px flex col-start-{dateToGridCol(startTime)}"
|
||||||
style="grid-row: {timeToGridRow(startTime)} / span {timeToSpan(
|
style="grid-row: {timeToGridRow(startTime)} / span {timeToSpan(startTime, endTime)};"
|
||||||
startTime,
|
|
||||||
endTime
|
|
||||||
)};"
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
{href}
|
{href}
|
||||||
title={startTime.toLocaleTimeString()}
|
title={startTime.toLocaleTimeString()}
|
||||||
class="group absolute inset-1 flex flex-col items-center justify-center rounded-lg bg-zinc-300 p-3 leading-5 hover:bg-zinc-200 shadow"
|
class="group absolute inset-1 flex flex-col items-center justify-center rounded-lg bg-zinc-300 p-3 leading-5 hover:bg-zinc-200 shadow"
|
||||||
>
|
>
|
||||||
<p class="order-1 font-semibold text-zinc-800">
|
<p class="order-1 font-semibold text-zinc-800">
|
||||||
{toHumanBranchName(label)}
|
{toHumanBranchName(label)}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default as WeekBlockEntry } from "./WeekBlockEntry.svelte";
|
export { default as WeekBlockEntry } from './WeekBlockEntry.svelte';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { log } from "$lib";
|
import { log } from '$lib';
|
||||||
import { invoke } from "@tauri-apps/api";
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { appWindow } from "@tauri-apps/api/window";
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
import { writable, type Readable } from "svelte/store";
|
import { writable, type Readable } from 'svelte/store';
|
||||||
|
|
||||||
export type OperationDelete = { delete: [number, number] };
|
export type OperationDelete = { delete: [number, number] };
|
||||||
export type OperationInsert = { insert: [number, string] };
|
export type OperationInsert = { insert: [number, string] };
|
||||||
@ -9,37 +9,35 @@ export type OperationInsert = { insert: [number, string] };
|
|||||||
export type Operation = OperationDelete | OperationInsert;
|
export type Operation = OperationDelete | OperationInsert;
|
||||||
|
|
||||||
export namespace Operation {
|
export namespace Operation {
|
||||||
export const isDelete = (
|
export const isDelete = (operation: Operation): operation is OperationDelete =>
|
||||||
operation: Operation
|
'delete' in operation;
|
||||||
): operation is OperationDelete => "delete" in operation;
|
|
||||||
|
|
||||||
export const isInsert = (
|
export const isInsert = (operation: Operation): operation is OperationInsert =>
|
||||||
operation: Operation
|
'insert' in operation;
|
||||||
): operation is OperationInsert => "insert" in operation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Delta = { timestampMs: number; operations: Operation[] };
|
export type Delta = { timestampMs: number; operations: Operation[] };
|
||||||
|
|
||||||
type DeltasEvent = {
|
type DeltasEvent = {
|
||||||
deltas: Delta[];
|
deltas: Delta[];
|
||||||
filePath: string;
|
filePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const list = (params: { projectId: string; sessionId: string }) =>
|
export const list = (params: { projectId: string; sessionId: string }) =>
|
||||||
invoke<Record<string, Delta[]>>("list_deltas", params);
|
invoke<Record<string, Delta[]>>('list_deltas', params);
|
||||||
|
|
||||||
export default async (params: { projectId: string; sessionId: string }) => {
|
export default async (params: { projectId: string; sessionId: string }) => {
|
||||||
const init = await list(params);
|
const init = await list(params);
|
||||||
|
|
||||||
const store = writable<Record<string, Delta[]>>(init);
|
const store = writable<Record<string, Delta[]>>(init);
|
||||||
const eventName = `project://${params.projectId}/sessions/${params.sessionId}/deltas`;
|
const eventName = `project://${params.projectId}/sessions/${params.sessionId}/deltas`;
|
||||||
await appWindow.listen<DeltasEvent>(eventName, (event) => {
|
await appWindow.listen<DeltasEvent>(eventName, (event) => {
|
||||||
log.info(`Received deltas event ${eventName}`);
|
log.info(`Received deltas event ${eventName}`);
|
||||||
store.update((deltas) => ({
|
store.update((deltas) => ({
|
||||||
...deltas,
|
...deltas,
|
||||||
[event.payload.filePath]: event.payload.deltas,
|
[event.payload.filePath]: event.payload.deltas
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
return store as Readable<Record<string, Delta[]>>;
|
return store as Readable<Record<string, Delta[]>>;
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export * as deltas from "./deltas";
|
export * as deltas from './deltas';
|
||||||
export * as projects from "./projects";
|
export * as projects from './projects';
|
||||||
export * as log from "./log";
|
export * as log from './log';
|
||||||
export * as toasts from "./toasts";
|
export * as toasts from './toasts';
|
||||||
export * as sessions from "./sessions";
|
export * as sessions from './sessions';
|
||||||
export * as week from "./week";
|
export * as week from './week';
|
||||||
|
@ -1,39 +1,36 @@
|
|||||||
import { building } from "$app/environment";
|
import { building } from '$app/environment';
|
||||||
|
|
||||||
export const setup = async () => {
|
export const setup = async () => {
|
||||||
if (!building) {
|
if (!building) {
|
||||||
await (await import("tauri-plugin-log-api")).attachConsole();
|
await (await import('tauri-plugin-log-api')).attachConsole();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = async () =>
|
const logger = async () =>
|
||||||
building
|
building
|
||||||
? {
|
? {
|
||||||
debug: (..._: any[]) => { },
|
debug: (..._: any[]) => {},
|
||||||
info: (..._: any[]) => { },
|
info: (..._: any[]) => {},
|
||||||
error: (..._: any[]) => { },
|
error: (..._: any[]) => {}
|
||||||
}
|
}
|
||||||
: import("tauri-plugin-log-api").then((tauri) => ({
|
: import('tauri-plugin-log-api').then((tauri) => ({
|
||||||
debug: tauri.debug,
|
debug: tauri.debug,
|
||||||
info: tauri.info,
|
info: tauri.info,
|
||||||
error: tauri.error,
|
error: tauri.error
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const toString = (value: any) => {
|
const toString = (value: any) => {
|
||||||
if (value instanceof Error) {
|
if (value instanceof Error) {
|
||||||
return value.message;
|
return value.message;
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === 'object') {
|
||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
} else {
|
} else {
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const debug = async (...args: any[]) =>
|
export const debug = async (...args: any[]) => (await logger()).debug(args.map(toString).join(' '));
|
||||||
(await logger()).debug(args.map(toString).join(" "));
|
|
||||||
|
|
||||||
export const info = async (...args: any[]) =>
|
export const info = async (...args: any[]) => (await logger()).info(args.map(toString).join(' '));
|
||||||
(await logger()).info(args.map(toString).join(" "));
|
|
||||||
|
|
||||||
export const error = async (...args: any[]) =>
|
export const error = async (...args: any[]) => (await logger()).error(args.map(toString).join(' '));
|
||||||
(await logger()).error(args.map(toString).join(" "));
|
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
import posthog from "posthog-js";
|
import posthog from 'posthog-js';
|
||||||
import { PUBLIC_POSTHOG_API_KEY } from "$env/static/public";
|
import { PUBLIC_POSTHOG_API_KEY } from '$env/static/public';
|
||||||
import type { User } from "$lib/api";
|
import type { User } from '$lib/api';
|
||||||
import * as log from "$lib/log";
|
import * as log from '$lib/log';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const instance = posthog.init(PUBLIC_POSTHOG_API_KEY, {
|
const instance = posthog.init(PUBLIC_POSTHOG_API_KEY, {
|
||||||
api_host: "https://eu.posthog.com",
|
api_host: 'https://eu.posthog.com',
|
||||||
capture_performance: false,
|
capture_performance: false
|
||||||
});
|
});
|
||||||
log.info("posthog initialized");
|
log.info('posthog initialized');
|
||||||
return {
|
return {
|
||||||
identify: (user: User | undefined) => {
|
identify: (user: User | undefined) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
log.info("posthog identify", {
|
log.info('posthog identify', {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email,
|
email: user.email
|
||||||
});
|
});
|
||||||
instance?.identify(`user_${user.id}`, {
|
instance?.identify(`user_${user.id}`, {
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name: user.name,
|
name: user.name
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info("posthog reset");
|
log.info('posthog reset');
|
||||||
instance?.capture("log-out");
|
instance?.capture('log-out');
|
||||||
instance?.reset();
|
instance?.reset();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,67 +1,58 @@
|
|||||||
import { invoke } from "@tauri-apps/api";
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { derived, writable } from "svelte/store";
|
import { derived, writable } from 'svelte/store';
|
||||||
import type { Project as ApiProject } from "$lib/api";
|
import type { Project as ApiProject } from '$lib/api';
|
||||||
|
|
||||||
export type Project = {
|
export type Project = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
path: string;
|
path: string;
|
||||||
api: ApiProject & { sync: boolean };
|
api: ApiProject & { sync: boolean };
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = () => invoke<Project[]>("list_projects");
|
const list = () => invoke<Project[]>('list_projects');
|
||||||
|
|
||||||
const update = (params: {
|
const update = (params: {
|
||||||
project: {
|
project: {
|
||||||
id: string;
|
id: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
api?: ApiProject & { sync: boolean };
|
api?: ApiProject & { sync: boolean };
|
||||||
};
|
};
|
||||||
}) => invoke<Project>("update_project", params);
|
}) => invoke<Project>('update_project', params);
|
||||||
|
|
||||||
const add = (params: { path: string }) =>
|
const add = (params: { path: string }) => invoke<Project>('add_project', params);
|
||||||
invoke<Project>("add_project", params);
|
|
||||||
|
|
||||||
const del = (params: { id: string }) => invoke("delete_project", params);
|
const del = (params: { id: string }) => invoke('delete_project', params);
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const init = await list();
|
const init = await list();
|
||||||
const store = writable<Project[]>(init);
|
const store = writable<Project[]>(init);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
get: (id: string) => {
|
get: (id: string) => {
|
||||||
const project = derived(store, (store) =>
|
const project = derived(store, (store) => store.find((p) => p.id === id));
|
||||||
store.find((p) => p.id === id)
|
return {
|
||||||
);
|
subscribe: project.subscribe,
|
||||||
return {
|
update: (params: { title?: string; api?: Project['api'] }) =>
|
||||||
subscribe: project.subscribe,
|
update({
|
||||||
update: (params: { title?: string; api?: Project["api"] }) =>
|
project: {
|
||||||
update({
|
id,
|
||||||
project: {
|
...params
|
||||||
id,
|
}
|
||||||
...params,
|
}).then((project) => {
|
||||||
},
|
store.update((projects) => projects.map((p) => (p.id === project.id ? project : p)));
|
||||||
}).then((project) => {
|
return project;
|
||||||
store.update((projects) =>
|
})
|
||||||
projects.map((p) =>
|
};
|
||||||
p.id === project.id ? project : p
|
},
|
||||||
)
|
add: (params: { path: string }) =>
|
||||||
);
|
add(params).then((project) => {
|
||||||
return project;
|
store.update((projects) => [...projects, project]);
|
||||||
}),
|
return project;
|
||||||
};
|
}),
|
||||||
},
|
delete: (params: { id: string }) =>
|
||||||
add: (params: { path: string }) =>
|
del(params).then(() => {
|
||||||
add(params).then((project) => {
|
store.update((projects) => projects.filter((p) => p.id !== params.id));
|
||||||
store.update((projects) => [...projects, project]);
|
})
|
||||||
return project;
|
};
|
||||||
}),
|
|
||||||
delete: (params: { id: string }) =>
|
|
||||||
del(params).then(() => {
|
|
||||||
store.update((projects) =>
|
|
||||||
projects.filter((p) => p.id !== params.id)
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -1,56 +1,49 @@
|
|||||||
import { invoke } from "@tauri-apps/api";
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { appWindow } from "@tauri-apps/api/window";
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
import { writable } from "svelte/store";
|
import { writable } from 'svelte/store';
|
||||||
import { log } from "$lib";
|
import { log } from '$lib';
|
||||||
|
|
||||||
export type Activity = {
|
export type Activity = {
|
||||||
type: string;
|
type: string;
|
||||||
timestampMs: number;
|
timestampMs: number;
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Session = {
|
export type Session = {
|
||||||
id: string;
|
id: string;
|
||||||
hash?: string;
|
hash?: string;
|
||||||
meta: {
|
meta: {
|
||||||
startTimestampMs: number;
|
startTimestampMs: number;
|
||||||
lastTimestampMs: number;
|
lastTimestampMs: number;
|
||||||
branch: string;
|
branch: string;
|
||||||
commit: string;
|
commit: string;
|
||||||
};
|
};
|
||||||
activity: Activity[];
|
activity: Activity[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listFiles = (params: { projectId: string; sessionId: string }) =>
|
export const listFiles = (params: { projectId: string; sessionId: string }) =>
|
||||||
invoke<Record<string, string>>("list_session_files", params);
|
invoke<Record<string, string>>('list_session_files', params);
|
||||||
|
|
||||||
const list = (params: { projectId: string }) =>
|
const list = (params: { projectId: string }) => invoke<Session[]>('list_sessions', params);
|
||||||
invoke<Session[]>("list_sessions", params);
|
|
||||||
|
|
||||||
export default async (params: { projectId: string }) => {
|
export default async (params: { projectId: string }) => {
|
||||||
const init = await list(params);
|
const init = await list(params);
|
||||||
const store = writable(init);
|
const store = writable(init);
|
||||||
const eventName = `project://${params.projectId}/sessions`;
|
const eventName = `project://${params.projectId}/sessions`;
|
||||||
|
|
||||||
await appWindow.listen<Session>(eventName, (event) => {
|
await appWindow.listen<Session>(eventName, (event) => {
|
||||||
log.info(`Received sessions event ${eventName}`);
|
log.info(`Received sessions event ${eventName}`);
|
||||||
store.update((sessions) => {
|
store.update((sessions) => {
|
||||||
const index = sessions.findIndex(
|
const index = sessions.findIndex((session) => session.id === event.payload.id);
|
||||||
(session) => session.id === event.payload.id
|
if (index === -1) {
|
||||||
);
|
return [...sessions, event.payload];
|
||||||
if (index === -1) {
|
} else {
|
||||||
return [...sessions, event.payload];
|
return [...sessions.slice(0, index), event.payload, ...sessions.slice(index + 1)];
|
||||||
} else {
|
}
|
||||||
return [
|
});
|
||||||
...sessions.slice(0, index),
|
});
|
||||||
event.payload,
|
|
||||||
...sessions.slice(index + 1),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
export const toHumanReadableTime = (timestamp: number) => {
|
export const toHumanReadableTime = (timestamp: number) => {
|
||||||
return new Date(timestamp).toLocaleTimeString("en-US", {
|
return new Date(timestamp).toLocaleTimeString('en-US', {
|
||||||
hour: "numeric",
|
hour: 'numeric',
|
||||||
minute: "numeric",
|
minute: 'numeric'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toHumanReadableDate = (timestamp: number) => {
|
export const toHumanReadableDate = (timestamp: number) => {
|
||||||
return new Date(timestamp).toLocaleDateString("en-US", {
|
return new Date(timestamp).toLocaleDateString('en-US', {
|
||||||
dateStyle: "short",
|
dateStyle: 'short'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import toast, {
|
import toast, { type ToastOptions, type ToastPosition } from 'svelte-french-toast';
|
||||||
type ToastOptions,
|
|
||||||
type ToastPosition,
|
|
||||||
} from "svelte-french-toast";
|
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
position: "bottom-center" as ToastPosition,
|
position: 'bottom-center' as ToastPosition,
|
||||||
style: "border-radius: 200px; background: #333; color: #fff;",
|
style: 'border-radius: 200px; background: #333; color: #fff;'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const error = (msg: string, options: ToastOptions = {}) =>
|
export const error = (msg: string, options: ToastOptions = {}) =>
|
||||||
toast.error(msg, { ...defaultOptions, ...options });
|
toast.error(msg, { ...defaultOptions, ...options });
|
||||||
|
|
||||||
export const success = (msg: string, options: ToastOptions = {}) =>
|
export const success = (msg: string, options: ToastOptions = {}) =>
|
||||||
toast.success(msg, { ...defaultOptions, ...options });
|
toast.success(msg, { ...defaultOptions, ...options });
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import type { User } from "$lib/api";
|
import type { User } from '$lib/api';
|
||||||
import { writable } from "svelte/store";
|
import { writable } from 'svelte/store';
|
||||||
import { invoke } from "@tauri-apps/api";
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
|
||||||
const get = () => invoke<User | undefined>("get_user");
|
const get = () => invoke<User | undefined>('get_user');
|
||||||
|
|
||||||
const set = (params: { user: User }) => invoke<void>("set_user", params);
|
const set = (params: { user: User }) => invoke<void>('set_user', params);
|
||||||
|
|
||||||
const del = () => invoke<void>("delete_user");
|
const del = () => invoke<void>('delete_user');
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const store = writable<User | undefined>(undefined);
|
const store = writable<User | undefined>(undefined);
|
||||||
|
|
||||||
const init = await get();
|
const init = await get();
|
||||||
store.set(init);
|
store.set(init);
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
set: async (user: User) => {
|
set: async (user: User) => {
|
||||||
await set({ user });
|
await set({ user });
|
||||||
store.set(user);
|
store.set(user);
|
||||||
},
|
},
|
||||||
delete: async () => {
|
delete: async () => {
|
||||||
await del();
|
await del();
|
||||||
store.set(undefined);
|
store.set(undefined);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { startOfWeek, endOfWeek, addWeeks, subWeeks, addDays } from "date-fns";
|
import { startOfWeek, endOfWeek, addWeeks, subWeeks, addDays } from 'date-fns';
|
||||||
|
|
||||||
export type Week = {
|
export type Week = {
|
||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
};
|
};
|
||||||
export namespace Week {
|
export namespace Week {
|
||||||
export const from = (date: Date): Week => {
|
export const from = (date: Date): Week => {
|
||||||
return {
|
return {
|
||||||
start: startOfWeek(date, { weekStartsOn: 1 }),
|
start: startOfWeek(date, { weekStartsOn: 1 }),
|
||||||
end: endOfWeek(date),
|
end: endOfWeek(date)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const next = (week: Week): Week => {
|
export const next = (week: Week): Week => {
|
||||||
return { start: addWeeks(week.start, 1), end: addWeeks(week.end, 1) };
|
return { start: addWeeks(week.start, 1), end: addWeeks(week.end, 1) };
|
||||||
};
|
};
|
||||||
export const previous = (week: Week): Week => {
|
export const previous = (week: Week): Week => {
|
||||||
return { start: subWeeks(week.start, 1), end: subWeeks(week.end, 1) };
|
return { start: subWeeks(week.start, 1), end: subWeeks(week.end, 1) };
|
||||||
};
|
};
|
||||||
export const nThDay = (week: Week, n: number): Date => {
|
export const nThDay = (week: Week, n: number): Date => {
|
||||||
return addDays(week.start, n);
|
return addDays(week.start, n);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,45 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "../app.postcss";
|
import '../app.postcss';
|
||||||
|
|
||||||
import { Toaster } from "svelte-french-toast";
|
import { Toaster } from 'svelte-french-toast';
|
||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from './$types';
|
||||||
import { BackForwardButtons } from "$lib/components";
|
import { BackForwardButtons } from '$lib/components';
|
||||||
import { setContext } from "svelte";
|
import { setContext } from 'svelte';
|
||||||
import { writable } from "svelte/store";
|
import { writable } from 'svelte/store';
|
||||||
import Breadcrumbs from "$lib/components/Breadcrumbs.svelte";
|
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
|
||||||
|
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
const { user, posthog } = data;
|
const { user, posthog } = data;
|
||||||
|
|
||||||
setContext("project", writable(null));
|
setContext('project', writable(null));
|
||||||
setContext("session", writable(null));
|
setContext('session', writable(null));
|
||||||
|
|
||||||
user.subscribe(posthog.identify);
|
user.subscribe(posthog.identify);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header
|
<header
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
class="sticky top-0 z-50 flex flex-row items-center h-8 overflow-hidden border-b select-none text-zinc-400 border-zinc-700 bg-zinc-900 "
|
class="sticky top-0 z-50 flex flex-row items-center h-8 overflow-hidden border-b select-none text-zinc-400 border-zinc-700 bg-zinc-900 "
|
||||||
>
|
>
|
||||||
<div class="ml-24">
|
<div class="ml-24">
|
||||||
<BackForwardButtons />
|
<BackForwardButtons />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6"><Breadcrumbs /></div>
|
<div class="ml-6"><Breadcrumbs /></div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<a
|
<a href="/users/" class="flex items-center gap-2 mr-4 font-medium hover:text-zinc-200">
|
||||||
href="/users/"
|
{#if $user}
|
||||||
class="flex items-center gap-2 mr-4 font-medium hover:text-zinc-200"
|
{#if $user.picture}
|
||||||
>
|
<img class="inline-block w-6 h-6 rounded-full" src={$user.picture} alt="Avatar" />
|
||||||
{#if $user}
|
{/if}
|
||||||
{#if $user.picture}
|
<span>{$user.name}</span>
|
||||||
<img
|
{:else}
|
||||||
class="inline-block w-6 h-6 rounded-full"
|
<span>Connect to GitButler Cloud</span>
|
||||||
src={$user.picture}
|
{/if}
|
||||||
alt="Avatar"
|
</a>
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<span>{$user.name}</span>
|
|
||||||
{:else}
|
|
||||||
<span>Connect to GitButler Cloud</span>
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="h-0 min-h-full bg-zinc-800 text-zinc-400">
|
<div class="h-0 min-h-full bg-zinc-800 text-zinc-400">
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
import { readable } from "svelte/store";
|
import { readable } from 'svelte/store';
|
||||||
import type { LayoutLoad } from "./$types";
|
import type { LayoutLoad } from './$types';
|
||||||
import { building } from "$app/environment";
|
import { building } from '$app/environment';
|
||||||
import type { Project } from "$lib/projects";
|
import type { Project } from '$lib/projects';
|
||||||
import Api from "$lib/api";
|
import Api from '$lib/api';
|
||||||
import Posthog from "$lib/posthog";
|
import Posthog from '$lib/posthog';
|
||||||
import * as log from "$lib/log";
|
import * as log from '$lib/log';
|
||||||
|
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
export const csr = true;
|
export const csr = true;
|
||||||
|
|
||||||
export const load: LayoutLoad = async ({ fetch }) => {
|
export const load: LayoutLoad = async ({ fetch }) => {
|
||||||
const projects = building
|
const projects = building
|
||||||
? {
|
? {
|
||||||
...readable<Project[]>([]),
|
...readable<Project[]>([]),
|
||||||
add: () => {
|
add: () => {
|
||||||
throw new Error("not implemented");
|
throw new Error('not implemented');
|
||||||
},
|
},
|
||||||
get: () => {
|
get: () => {
|
||||||
throw new Error("not implemented");
|
throw new Error('not implemented');
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
: await (await import("$lib/projects")).default();
|
: await (await import('$lib/projects')).default();
|
||||||
const user = building
|
const user = building
|
||||||
? {
|
? {
|
||||||
...readable<undefined>(undefined),
|
...readable<undefined>(undefined),
|
||||||
set: () => {
|
set: () => {
|
||||||
throw new Error("not implemented");
|
throw new Error('not implemented');
|
||||||
},
|
},
|
||||||
delete: () => {
|
delete: () => {
|
||||||
throw new Error("not implemented");
|
throw new Error('not implemented');
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
: await (await import("$lib/users")).default();
|
: await (await import('$lib/users')).default();
|
||||||
await log.setup();
|
await log.setup();
|
||||||
return {
|
return {
|
||||||
projects,
|
projects,
|
||||||
user,
|
user,
|
||||||
api: Api({ fetch }),
|
api: Api({ fetch }),
|
||||||
posthog: Posthog(),
|
posthog: Posthog()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,59 +1,53 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FaFolderOpen from "svelte-icons/fa/FaFolderOpen.svelte";
|
import FaFolderOpen from 'svelte-icons/fa/FaFolderOpen.svelte';
|
||||||
import { open } from "@tauri-apps/api/dialog";
|
import { open } from '@tauri-apps/api/dialog';
|
||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from './$types';
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
|
|
||||||
const { projects } = data;
|
const { projects } = data;
|
||||||
|
|
||||||
const onAddLocalRepositoryClick = async () => {
|
const onAddLocalRepositoryClick = async () => {
|
||||||
const selectedPath = await open({
|
const selectedPath = await open({
|
||||||
directory: true,
|
directory: true,
|
||||||
recursive: true,
|
recursive: true
|
||||||
});
|
});
|
||||||
if (selectedPath === null) return;
|
if (selectedPath === null) return;
|
||||||
if (Array.isArray(selectedPath) && selectedPath.length !== 1) return;
|
if (Array.isArray(selectedPath) && selectedPath.length !== 1) return;
|
||||||
const projectPath = Array.isArray(selectedPath)
|
const projectPath = Array.isArray(selectedPath) ? selectedPath[0] : selectedPath;
|
||||||
? selectedPath[0]
|
|
||||||
: selectedPath;
|
|
||||||
|
|
||||||
const projectExists = $projects.some((p) => p.path === projectPath);
|
const projectExists = $projects.some((p) => p.path === projectPath);
|
||||||
if (projectExists) return;
|
if (projectExists) return;
|
||||||
|
|
||||||
await projects.add({ path: projectPath });
|
await projects.add({ path: projectPath });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="select-none p-16">
|
<div class="select-none p-16">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-4xl text-zinc-200 mb-2">GitButler</h1>
|
<h1 class="text-4xl text-zinc-200 mb-2">GitButler</h1>
|
||||||
<h2 class="text-2xl">Your Personal VCS Assistant</h2>
|
<h2 class="text-2xl">Your Personal VCS Assistant</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<div class="text-xl text-zinc-200 mb-1">Start</div>
|
<div class="text-xl text-zinc-200 mb-1">Start</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="w-4 h-4"><FaFolderOpen /></div>
|
<div class="w-4 h-4"><FaFolderOpen /></div>
|
||||||
<button
|
<button on:click={onAddLocalRepositoryClick} class="hover:text-zinc-200"
|
||||||
on:click={onAddLocalRepositoryClick}
|
>Add a local repository...</button
|
||||||
class="hover:text-zinc-200">Add a local repository...</button
|
>
|
||||||
>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="w-1/2">
|
||||||
<div class="w-1/2">
|
<div class="text-xl text-zinc-200 mb-1">Recent</div>
|
||||||
<div class="text-xl text-zinc-200 mb-1">Recent</div>
|
<div class="flex flex-col space-y-1">
|
||||||
<div class="flex flex-col space-y-1">
|
{#each $projects as project}
|
||||||
{#each $projects as project}
|
<div class="space-x-2">
|
||||||
<div class="space-x-2">
|
<a class="hover:text-zinc-200" href="/projects/{project.id}/">{project.title}</a>
|
||||||
<a
|
<span class="text-zinc-500">{project.path}</span>
|
||||||
class="hover:text-zinc-200"
|
</div>
|
||||||
href="/projects/{project.id}/">{project.title}</a
|
{/each}
|
||||||
>
|
</div>
|
||||||
<span class="text-zinc-500">{project.path}</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,128 +1,113 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from './$types';
|
||||||
import { getContext } from "svelte";
|
import { getContext } from 'svelte';
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from 'svelte/store';
|
||||||
import type { Project } from "$lib/projects";
|
import type { Project } from '$lib/projects';
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from 'svelte';
|
||||||
import { page } from "$app/stores";
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
|
|
||||||
$: project = data.project;
|
$: project = data.project;
|
||||||
$: sessions = data.sessions;
|
$: sessions = data.sessions;
|
||||||
$: lastSessionId = $sessions[$sessions.length - 1]?.id;
|
$: lastSessionId = $sessions[$sessions.length - 1]?.id;
|
||||||
|
|
||||||
function projectUrl(project: Project) {
|
function projectUrl(project: Project) {
|
||||||
const gitUrl = project.api?.git_url;
|
const gitUrl = project.api?.git_url;
|
||||||
// get host from git url
|
// get host from git url
|
||||||
const url = new URL(gitUrl);
|
const url = new URL(gitUrl);
|
||||||
const host = url.origin;
|
const host = url.origin;
|
||||||
const projectId = gitUrl.split("/").pop();
|
const projectId = gitUrl.split('/').pop();
|
||||||
|
|
||||||
return `${host}/projects/${projectId}`;
|
return `${host}/projects/${projectId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextProjectStore: Writable<Project | null | undefined> =
|
const contextProjectStore: Writable<Project | null | undefined> = getContext('project');
|
||||||
getContext("project");
|
$: contextProjectStore.set($project);
|
||||||
$: contextProjectStore.set($project);
|
onDestroy(() => {
|
||||||
onDestroy(() => {
|
contextProjectStore.set(null);
|
||||||
contextProjectStore.set(null);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
$: selection = $page?.route?.id?.split("/")?.[3];
|
$: selection = $page?.route?.id?.split('/')?.[3];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
class="flex items-center flex-none justify-between py-2 px-8 space-x-3 border-b select-none text-zinc-300 border-zinc-700"
|
class="flex items-center flex-none justify-between py-2 px-8 space-x-3 border-b select-none text-zinc-300 border-zinc-700"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="text-zinc-400 w-64 font-medium grid grid-cols-3 items-center bg-zinc-700/50 rounded-lg h-7 px-4 gap-1"
|
class="text-zinc-400 w-64 font-medium grid grid-cols-3 items-center bg-zinc-700/50 rounded-lg h-7 px-4 gap-1"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="
|
class="
|
||||||
{selection === 'week' ? 'bg-zinc-600/70 text-zinc-100' : ''}
|
{selection === 'week' ? 'bg-zinc-600/70 text-zinc-100' : ''}
|
||||||
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
|
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
|
||||||
href="/projects/{$project?.id}/week">Week</a
|
href="/projects/{$project?.id}/week">Week</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="/projects/{$project?.id}/day"
|
href="/projects/{$project?.id}/day"
|
||||||
class="
|
class="
|
||||||
{selection === 'day' ? 'bg-zinc-600/70 text-zinc-100' : ''}
|
{selection === 'day' ? 'bg-zinc-600/70 text-zinc-100' : ''}
|
||||||
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
|
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100">Day</a
|
||||||
>Day</a
|
>
|
||||||
>
|
<a
|
||||||
<a
|
href="/projects/{$project?.id}/sessions/{lastSessionId}"
|
||||||
href="/projects/{$project?.id}/sessions/{lastSessionId}"
|
class="
|
||||||
class="
|
|
||||||
{selection === 'sessions' ? 'bg-zinc-600/70 text-zinc-100' : ''}
|
{selection === 'sessions' ? 'bg-zinc-600/70 text-zinc-100' : ''}
|
||||||
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
|
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
|
||||||
title="go to current session">Session</a
|
title="go to current session">Session</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a href="/projects/{$project?.id}/settings" class="text-zinc-400 hover:text-zinc-300">
|
||||||
href="/projects/{$project?.id}/settings"
|
<svg
|
||||||
class="text-zinc-400 hover:text-zinc-300"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
fill="none"
|
||||||
<svg
|
viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
stroke-width="1.5"
|
||||||
fill="none"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
class="w-6 h-6"
|
||||||
stroke-width="1.5"
|
>
|
||||||
stroke="currentColor"
|
<path
|
||||||
class="w-6 h-6"
|
stroke-linecap="round"
|
||||||
>
|
stroke-linejoin="round"
|
||||||
<path
|
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
||||||
stroke-linecap="round"
|
/>
|
||||||
stroke-linejoin="round"
|
<path
|
||||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
stroke-linecap="round"
|
||||||
/>
|
stroke-linejoin="round"
|
||||||
<path
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
stroke-linecap="round"
|
/>
|
||||||
stroke-linejoin="round"
|
</svg>
|
||||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
</a>
|
||||||
/>
|
</li>
|
||||||
</svg>
|
</ul>
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<div class="absolute bottom-0 left-0 w-full">
|
<div class="absolute bottom-0 left-0 w-full">
|
||||||
<div
|
<div
|
||||||
class="flex items-center flex-shrink-0 h-6 border-t select-none border-zinc-700 bg-zinc-900 "
|
class="flex items-center flex-shrink-0 h-6 border-t select-none border-zinc-700 bg-zinc-900 "
|
||||||
>
|
>
|
||||||
<div
|
<div class="flex flex-row mx-4 items-center space-x-2 justify-between w-full">
|
||||||
class="flex flex-row mx-4 items-center space-x-2 justify-between w-full"
|
{#if $project?.api?.sync}
|
||||||
>
|
<a href="/projects/{$project?.id}/settings" class="text-zinc-400 hover:text-zinc-300">
|
||||||
{#if $project?.api?.sync}
|
<div class="flex flex-row items-center space-x-2 ">
|
||||||
<a
|
<div class="w-2 h-2 bg-green-700 rounded-full" />
|
||||||
href="/projects/{$project?.id}/settings"
|
<div class="text-zinc-200">Syncing</div>
|
||||||
class="text-zinc-400 hover:text-zinc-300"
|
</div>
|
||||||
>
|
</a>
|
||||||
<div class="flex flex-row items-center space-x-2 ">
|
<a target="_blank" rel="noreferrer" href={projectUrl($project)}>Open in GitButler Cloud</a>
|
||||||
<div class="w-2 h-2 bg-green-700 rounded-full" />
|
{:else}
|
||||||
<div class="text-zinc-200">Syncing</div>
|
<a href="/projects/{$project?.id}/settings" class="text-zinc-400 hover:text-zinc-300">
|
||||||
</div>
|
<div class="flex flex-row items-center space-x-2 ">
|
||||||
</a>
|
<div class="w-2 h-2 bg-red-700 rounded-full" />
|
||||||
<a target="_blank" rel="noreferrer" href={projectUrl($project)}
|
<div class="text-zinc-200">Offline</div>
|
||||||
>Open in GitButler Cloud</a
|
</div>
|
||||||
>
|
</a>
|
||||||
{:else}
|
{/if}
|
||||||
<a
|
</div>
|
||||||
href="/projects/{$project?.id}/settings"
|
</div>
|
||||||
class="text-zinc-400 hover:text-zinc-300"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center space-x-2 ">
|
|
||||||
<div class="w-2 h-2 bg-red-700 rounded-full" />
|
|
||||||
<div class="text-zinc-200">Offline</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
import { readable, derived } from "svelte/store";
|
import { readable, derived } from 'svelte/store';
|
||||||
import type { LayoutLoad } from "./$types";
|
import type { LayoutLoad } from './$types';
|
||||||
import { building } from "$app/environment";
|
import { building } from '$app/environment';
|
||||||
import type { Session } from "$lib/sessions";
|
import type { Session } from '$lib/sessions';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
export const load: LayoutLoad = async ({ parent, params }) => {
|
export const load: LayoutLoad = async ({ parent, params }) => {
|
||||||
const { projects } = await parent();
|
const { projects } = await parent();
|
||||||
const sessions = building
|
const sessions = building
|
||||||
? readable<Session[]>([])
|
? readable<Session[]>([])
|
||||||
: await (
|
: await (await import('$lib/sessions')).default({ projectId: params.projectId });
|
||||||
await import("$lib/sessions")
|
const orderedSessions = derived(sessions, (sessions) => {
|
||||||
).default({ projectId: params.projectId });
|
return sessions.slice().sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs);
|
||||||
const orderedSessions = derived(sessions, (sessions) => {
|
});
|
||||||
return sessions
|
return {
|
||||||
.slice()
|
project: projects.get(params.projectId),
|
||||||
.sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs);
|
sessions: orderedSessions
|
||||||
});
|
};
|
||||||
return {
|
|
||||||
project: projects.get(params.projectId),
|
|
||||||
sessions: orderedSessions,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { project } = data;
|
const { project } = data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col mt-12">
|
<div class="flex flex-col mt-12">
|
||||||
<h1 class="text-zinc-200 text-2xl flex justify-center">
|
<h1 class="text-zinc-200 text-2xl flex justify-center">
|
||||||
Overview of {$project?.title}
|
Overview of {$project?.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex justify-center space-x-2">
|
<div class="flex justify-center space-x-2">
|
||||||
<a class="hover:text-zinc-200" href="/projects/{$project?.id}/week"
|
<a class="hover:text-zinc-200" href="/projects/{$project?.id}/week">Week</a>
|
||||||
>Week</a
|
<a href="/projects/{$project?.id}/day" class="hover:text-zinc-200">Day</a>
|
||||||
>
|
</div>
|
||||||
<a href="/projects/{$project?.id}/day" class="hover:text-zinc-200"
|
|
||||||
>Day</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,96 +1,85 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MdKeyboardArrowLeft from "svelte-icons/md/MdKeyboardArrowLeft.svelte";
|
import MdKeyboardArrowLeft from 'svelte-icons/md/MdKeyboardArrowLeft.svelte';
|
||||||
import MdKeyboardArrowRight from "svelte-icons/md/MdKeyboardArrowRight.svelte";
|
import MdKeyboardArrowRight from 'svelte-icons/md/MdKeyboardArrowRight.svelte';
|
||||||
import { TimelineDaySession } from "$lib/components/timeline";
|
import { TimelineDaySession } from '$lib/components/timeline';
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from './$types';
|
||||||
import type { Session } from "$lib/sessions";
|
import type { Session } from '$lib/sessions';
|
||||||
import { derived } from "svelte/store";
|
import { derived } from 'svelte/store';
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { project, sessions } = data;
|
const { project, sessions } = data;
|
||||||
|
|
||||||
let date = new Date();
|
let date = new Date();
|
||||||
$: canNavigateForwad =
|
$: canNavigateForwad = new Date(date.getTime() + 24 * 60 * 60 * 1000) < new Date();
|
||||||
new Date(date.getTime() + 24 * 60 * 60 * 1000) < new Date();
|
|
||||||
|
|
||||||
const formatDate = (date: Date) => {
|
const formatDate = (date: Date) => {
|
||||||
return new Intl.DateTimeFormat("default", {
|
return new Intl.DateTimeFormat('default', {
|
||||||
weekday: "short",
|
weekday: 'short',
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short'
|
||||||
}).format(date);
|
}).format(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessionDisplayWidth = (session: Session) => {
|
const sessionDisplayWidth = (session: Session) => {
|
||||||
let sessionDurationMinutes =
|
let sessionDurationMinutes =
|
||||||
(session.meta.lastTimestampMs - session.meta.startTimestampMs) / 60;
|
(session.meta.lastTimestampMs - session.meta.startTimestampMs) / 60;
|
||||||
if (sessionDurationMinutes <= 10) {
|
if (sessionDurationMinutes <= 10) {
|
||||||
return "w-40 min-w-40";
|
return 'w-40 min-w-40';
|
||||||
} else {
|
} else {
|
||||||
return "w-60 min-w-60";
|
return 'w-60 min-w-60';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$: sessionsInDay = derived([sessions], ([sessions]) => {
|
$: sessionsInDay = derived([sessions], ([sessions]) => {
|
||||||
const start = new Date(
|
const start = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||||
date.getFullYear(),
|
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000);
|
||||||
date.getMonth(),
|
return sessions.filter((session) => {
|
||||||
date.getDate()
|
return (
|
||||||
);
|
start <= new Date(session.meta.startTimestampMs) &&
|
||||||
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000);
|
new Date(session.meta.startTimestampMs) <= end
|
||||||
return sessions.filter((session) => {
|
);
|
||||||
return (
|
});
|
||||||
start <= new Date(session.meta.startTimestampMs) &&
|
});
|
||||||
new Date(session.meta.startTimestampMs) <= end
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-full select-none text-zinc-400">
|
<div class="flex flex-col h-full select-none text-zinc-400">
|
||||||
<header
|
<header class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700">
|
||||||
class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700"
|
<div class="flex items-center justify-start w-64">
|
||||||
>
|
<button
|
||||||
<div class="flex items-center justify-start w-64">
|
class="-ml-2 w-8 h-8 hover:text-zinc-100"
|
||||||
<button
|
on:click={() => (date = new Date(date.getTime() - 24 * 60 * 60 * 1000))}
|
||||||
class="-ml-2 w-8 h-8 hover:text-zinc-100"
|
>
|
||||||
on:click={() =>
|
<MdKeyboardArrowLeft />
|
||||||
(date = new Date(date.getTime() - 24 * 60 * 60 * 1000))}
|
</button>
|
||||||
>
|
<div class="flex-grow text-center">
|
||||||
<MdKeyboardArrowLeft />
|
{formatDate(date)}
|
||||||
</button>
|
</div>
|
||||||
<div class="flex-grow text-center">
|
<button
|
||||||
{formatDate(date)}
|
class="-mr-2 w-8 h-8 hover:text-zinc-100 disabled:text-zinc-700"
|
||||||
</div>
|
disabled={!canNavigateForwad}
|
||||||
<button
|
on:click={() => {
|
||||||
class="-mr-2 w-8 h-8 hover:text-zinc-100 disabled:text-zinc-700"
|
if (canNavigateForwad) {
|
||||||
disabled={!canNavigateForwad}
|
date = new Date(date.getTime() + 24 * 60 * 60 * 1000);
|
||||||
on:click={() => {
|
}
|
||||||
if (canNavigateForwad) {
|
}}
|
||||||
date = new Date(date.getTime() + 24 * 60 * 60 * 1000);
|
>
|
||||||
}
|
<MdKeyboardArrowRight />
|
||||||
}}
|
</button>
|
||||||
>
|
</div>
|
||||||
<MdKeyboardArrowRight />
|
</header>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="w-full h-full overflow-scroll mx-2 flex">
|
<div class="w-full h-full overflow-scroll mx-2 flex">
|
||||||
{#if $project}
|
{#if $project}
|
||||||
<div class="flex-grow items-center justify-center mt-4">
|
<div class="flex-grow items-center justify-center mt-4">
|
||||||
<div class="justify-center flex flex-row space-x-2 pt-2">
|
<div class="justify-center flex flex-row space-x-2 pt-2">
|
||||||
{#each $sessionsInDay as session}
|
{#each $sessionsInDay as session}
|
||||||
<div class={sessionDisplayWidth(session)}>
|
<div class={sessionDisplayWidth(session)}>
|
||||||
<TimelineDaySession
|
<TimelineDaySession projectId={$project.id} {session} />
|
||||||
projectId={$project.id}
|
</div>
|
||||||
{session}
|
{/each}
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{:else}
|
||||||
</div>
|
<p>Project not found</p>
|
||||||
</div>
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<p>Project not found</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,297 +1,267 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MdKeyboardArrowLeft from "svelte-icons/md/MdKeyboardArrowLeft.svelte";
|
import MdKeyboardArrowLeft from 'svelte-icons/md/MdKeyboardArrowLeft.svelte';
|
||||||
import MdKeyboardArrowRight from "svelte-icons/md/MdKeyboardArrowRight.svelte";
|
import MdKeyboardArrowRight from 'svelte-icons/md/MdKeyboardArrowRight.svelte';
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from './$types';
|
||||||
import { add, format, differenceInSeconds, addSeconds } from "date-fns";
|
import { add, format, differenceInSeconds, addSeconds } from 'date-fns';
|
||||||
import { page } from "$app/stores";
|
import { page } from '$app/stores';
|
||||||
import { onMount } from "svelte";
|
import { onMount } from 'svelte';
|
||||||
import { derived } from "svelte/store";
|
import { derived } from 'svelte/store';
|
||||||
import { Operation } from "$lib/deltas";
|
import { Operation } from '$lib/deltas';
|
||||||
import { Slider } from "fluent-svelte";
|
import { Slider } from 'fluent-svelte';
|
||||||
import { CodeViewer } from "$lib/components";
|
import { CodeViewer } from '$lib/components';
|
||||||
import "fluent-svelte/theme.css";
|
import 'fluent-svelte/theme.css';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
$: session = data.session;
|
$: session = data.session;
|
||||||
$: previousSesssion = data.previousSesssion;
|
$: previousSesssion = data.previousSesssion;
|
||||||
$: nextSession = data.nextSession;
|
$: nextSession = data.nextSession;
|
||||||
$: deltas = data.deltas;
|
$: deltas = data.deltas;
|
||||||
|
|
||||||
let time = new Date();
|
let time = new Date();
|
||||||
$: start = new Date($session.meta.startTimestampMs);
|
$: start = new Date($session.meta.startTimestampMs);
|
||||||
$: end = $session.hash
|
$: end = $session.hash ? addSeconds(new Date($session.meta.lastTimestampMs), 10) : time; // For some reason, some deltas are stamped a few seconds after the session end.
|
||||||
? addSeconds(new Date($session.meta.lastTimestampMs), 10)
|
// Also, if the session is current, the end time moves.
|
||||||
: time; // For some reason, some deltas are stamped a few seconds after the session end.
|
|
||||||
// Also, if the session is current, the end time moves.
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
time = new Date();
|
time = new Date();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
$: midpoint = add(start, {
|
$: midpoint = add(start, {
|
||||||
seconds: differenceInSeconds(end, start) * 0.5,
|
seconds: differenceInSeconds(end, start) * 0.5
|
||||||
});
|
});
|
||||||
$: quarter = add(start, {
|
$: quarter = add(start, {
|
||||||
seconds: differenceInSeconds(end, start) * 0.25,
|
seconds: differenceInSeconds(end, start) * 0.25
|
||||||
});
|
});
|
||||||
$: threequarters = add(start, {
|
$: threequarters = add(start, {
|
||||||
seconds: differenceInSeconds(end, start) * 0.75,
|
seconds: differenceInSeconds(end, start) * 0.75
|
||||||
});
|
});
|
||||||
const timeStampToCol = (deltaTimestamp: Date) => {
|
const timeStampToCol = (deltaTimestamp: Date) => {
|
||||||
if (deltaTimestamp < start || deltaTimestamp > end) {
|
if (deltaTimestamp < start || deltaTimestamp > end) {
|
||||||
console.error(
|
console.error(
|
||||||
`Delta timestamp out of session range. Delta timestamp: ${deltaTimestamp}, Session start: ${start}, Session end: ${end}`
|
`Delta timestamp out of session range. Delta timestamp: ${deltaTimestamp}, Session start: ${start}, Session end: ${end}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// there are 88 columns
|
// there are 88 columns
|
||||||
// start is column 17
|
// start is column 17
|
||||||
const totalDiff = differenceInSeconds(end, start);
|
const totalDiff = differenceInSeconds(end, start);
|
||||||
const eventDiff = differenceInSeconds(deltaTimestamp, start);
|
const eventDiff = differenceInSeconds(deltaTimestamp, start);
|
||||||
const rat = eventDiff / totalDiff;
|
const rat = eventDiff / totalDiff;
|
||||||
const col = Math.floor(rat * 63 + 17);
|
const col = Math.floor(rat * 63 + 17);
|
||||||
return col;
|
return col;
|
||||||
};
|
};
|
||||||
|
|
||||||
const colToTimestamp = (col: number) => {
|
const colToTimestamp = (col: number) => {
|
||||||
const totalDiff = differenceInSeconds(end, start);
|
const totalDiff = differenceInSeconds(end, start);
|
||||||
const colDiff = col - 17;
|
const colDiff = col - 17;
|
||||||
const rat = colDiff / 63;
|
const rat = colDiff / 63;
|
||||||
const eventDiff = totalDiff * rat;
|
const eventDiff = totalDiff * rat;
|
||||||
const timestamp = addSeconds(start, eventDiff);
|
const timestamp = addSeconds(start, eventDiff);
|
||||||
return timestamp;
|
return timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
$: tickSizeMs = Math.floor((end.getTime() - start.getTime()) / 63); // how many ms each column represents
|
$: tickSizeMs = Math.floor((end.getTime() - start.getTime()) / 63); // how many ms each column represents
|
||||||
let selectedFileIdx = 0;
|
let selectedFileIdx = 0;
|
||||||
let value = 0;
|
let value = 0;
|
||||||
|
|
||||||
$: doc = derived([data.deltas], ([allDeltas]) => {
|
$: doc = derived([data.deltas], ([allDeltas]) => {
|
||||||
const filePath = Object.keys(allDeltas)[selectedFileIdx];
|
const filePath = Object.keys(allDeltas)[selectedFileIdx];
|
||||||
const deltas = allDeltas[filePath];
|
const deltas = allDeltas[filePath];
|
||||||
|
|
||||||
let text = data.files[filePath] || "";
|
let text = data.files[filePath] || '';
|
||||||
if (!deltas) return text;
|
if (!deltas) return text;
|
||||||
|
|
||||||
const sliderValueTimestampMs =
|
const sliderValueTimestampMs = colToTimestamp(value).getTime() + tickSizeMs; // Include the tick size so that the slider value is always in the future
|
||||||
colToTimestamp(value).getTime() + tickSizeMs; // Include the tick size so that the slider value is always in the future
|
// Filter operations based on the current slider value
|
||||||
// Filter operations based on the current slider value
|
const operations = deltas
|
||||||
const operations = deltas
|
.filter(
|
||||||
.filter(
|
(delta) =>
|
||||||
(delta) =>
|
delta.timestampMs >= start.getTime() && delta.timestampMs <= sliderValueTimestampMs
|
||||||
delta.timestampMs >= start.getTime() &&
|
)
|
||||||
delta.timestampMs <= sliderValueTimestampMs
|
.sort((a, b) => a.timestampMs - b.timestampMs)
|
||||||
)
|
.flatMap((delta) => delta.operations);
|
||||||
.sort((a, b) => a.timestampMs - b.timestampMs)
|
|
||||||
.flatMap((delta) => delta.operations);
|
|
||||||
|
|
||||||
operations.forEach((operation) => {
|
operations.forEach((operation) => {
|
||||||
if (Operation.isInsert(operation)) {
|
if (Operation.isInsert(operation)) {
|
||||||
text =
|
text =
|
||||||
text.slice(0, operation.insert[0]) +
|
text.slice(0, operation.insert[0]) +
|
||||||
operation.insert[1] +
|
operation.insert[1] +
|
||||||
text.slice(operation.insert[0]);
|
text.slice(operation.insert[0]);
|
||||||
} else if (Operation.isDelete(operation)) {
|
} else if (Operation.isDelete(operation)) {
|
||||||
text =
|
text =
|
||||||
text.slice(0, operation.delete[0]) +
|
text.slice(0, operation.delete[0]) +
|
||||||
text.slice(operation.delete[0] + operation.delete[1]);
|
text.slice(operation.delete[0] + operation.delete[1]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatDate = (date: Date) => {
|
const formatDate = (date: Date) => {
|
||||||
return new Intl.DateTimeFormat("default", {
|
return new Intl.DateTimeFormat('default', {
|
||||||
weekday: "short",
|
weekday: 'short',
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
hour: "numeric",
|
hour: 'numeric',
|
||||||
minute: "numeric",
|
minute: 'numeric'
|
||||||
}).format(date);
|
}).format(date);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-full text-zinc-400 overflow-hidden">
|
<div class="flex flex-col h-full text-zinc-400 overflow-hidden">
|
||||||
<header
|
<header class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700">
|
||||||
class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700"
|
<div class="flex items-center justify-start w-64">
|
||||||
>
|
<a
|
||||||
<div class="flex items-center justify-start w-64">
|
href="/projects/{$page.params.projectId}/sessions/{$previousSesssion?.id}"
|
||||||
<a
|
class="-ml-2 w-8 h-8 hover:text-zinc-100 {$previousSesssion
|
||||||
href="/projects/{$page.params
|
? ''
|
||||||
.projectId}/sessions/{$previousSesssion?.id}"
|
: 'opacity-50 pointer-events-none cursor-not-allowed'}"
|
||||||
class="-ml-2 w-8 h-8 hover:text-zinc-100 {$previousSesssion
|
>
|
||||||
? ''
|
<MdKeyboardArrowLeft />
|
||||||
: 'opacity-50 pointer-events-none cursor-not-allowed'}"
|
</a>
|
||||||
>
|
<div class="flex-grow text-center cursor-default grid grid-cols-7">
|
||||||
<MdKeyboardArrowLeft />
|
<span class="col-span-3">{formatDate(start)}</span>
|
||||||
</a>
|
<span>—</span>
|
||||||
<div class="flex-grow text-center cursor-default grid grid-cols-7">
|
<span class="col-span-3">{formatDate(end)}</span>
|
||||||
<span class="col-span-3">{formatDate(start)}</span>
|
</div>
|
||||||
<span>—</span>
|
<a
|
||||||
<span class="col-span-3">{formatDate(end)}</span>
|
href="/projects/{$page.params.projectId}/sessions/{$nextSession?.id}"
|
||||||
</div>
|
class="-mr-2 w-8 h-8 hover:text-zinc-100 {$nextSession
|
||||||
<a
|
? ''
|
||||||
href="/projects/{$page.params
|
: 'text-zinc-700 pointer-events-none cursor-not-allowed'}"
|
||||||
.projectId}/sessions/{$nextSession?.id}"
|
>
|
||||||
class="-mr-2 w-8 h-8 hover:text-zinc-100 {$nextSession
|
<MdKeyboardArrowRight />
|
||||||
? ''
|
</a>
|
||||||
: 'text-zinc-700 pointer-events-none cursor-not-allowed'}"
|
</div>
|
||||||
>
|
</header>
|
||||||
<MdKeyboardArrowRight />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- main part -->
|
<!-- main part -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col flex-none max-w-full select-none border-b border-zinc-700 h-full overflow-auto"
|
class="flex flex-col flex-none max-w-full select-none border-b border-zinc-700 h-full overflow-auto"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col flex-none max-w-full mb-40">
|
<div class="flex flex-col flex-none max-w-full mb-40">
|
||||||
<!-- sticky header -->
|
<!-- sticky header -->
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden sticky top-0 z-30 bg-zinc-800 flex-none shadow shadow-zinc-700 ring-1 ring-zinc-700 ring-opacity-5 mb-1"
|
class="overflow-hidden sticky top-0 z-30 bg-zinc-800 flex-none shadow shadow-zinc-700 ring-1 ring-zinc-700 ring-opacity-5 mb-1"
|
||||||
>
|
>
|
||||||
<div class="grid-cols-11 -mr-px border-zinc-700 grid">
|
<div class="grid-cols-11 -mr-px border-zinc-700 grid">
|
||||||
<div />
|
<div />
|
||||||
<div
|
<div class="col-span-2 flex items-center justify-center py-2">
|
||||||
class="col-span-2 flex items-center justify-center py-2"
|
<span>{format(start, 'hh:mm')}</span>
|
||||||
>
|
</div>
|
||||||
<span>{format(start, "hh:mm")}</span>
|
<div class="col-span-2 flex items-center justify-center py-2">
|
||||||
</div>
|
<span>{format(quarter, 'hh:mm')}</span>
|
||||||
<div
|
</div>
|
||||||
class="col-span-2 flex items-center justify-center py-2"
|
<div class="col-span-2 flex items-center justify-center py-2">
|
||||||
>
|
<span>{format(midpoint, 'hh:mm')}</span>
|
||||||
<span>{format(quarter, "hh:mm")}</span>
|
</div>
|
||||||
</div>
|
<div class="col-span-2 flex items-center justify-center py-2">
|
||||||
<div
|
<span>{format(threequarters, 'hh:mm')}</span>
|
||||||
class="col-span-2 flex items-center justify-center py-2"
|
</div>
|
||||||
>
|
<div class="col-span-2 flex items-center justify-center py-2">
|
||||||
<span>{format(midpoint, "hh:mm")}</span>
|
<span>{format(end, 'hh:mm')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="col-span-2 flex items-center justify-center py-2"
|
<!-- needle -->
|
||||||
>
|
<div class="grid grid-cols-11">
|
||||||
<span>{format(threequarters, "hh:mm")}</span>
|
<div class="col-span-2 flex items-center justify-center" />
|
||||||
</div>
|
<div class="-mx-1 col-span-8 flex items-center justify-center">
|
||||||
<div
|
<Slider min={17} max={80} step={1} bind:value>
|
||||||
class="col-span-2 flex items-center justify-center py-2"
|
<svelte:fragment slot="tooltip" let:value>
|
||||||
>
|
{format(colToTimestamp(value), 'hh:mm')}
|
||||||
<span>{format(end, "hh:mm")}</span>
|
</svelte:fragment>
|
||||||
</div>
|
</Slider>
|
||||||
</div>
|
</div>
|
||||||
<!-- needle -->
|
<div class="col-span-1 flex items-center justify-center" />
|
||||||
<div class="grid grid-cols-11">
|
</div>
|
||||||
<div class="col-span-2 flex items-center justify-center" />
|
</div>
|
||||||
<div
|
<div class="flex flex-auto mb-1">
|
||||||
class="-mx-1 col-span-8 flex items-center justify-center"
|
<div class="grid flex-auto grid-cols-1 grid-rows-1">
|
||||||
>
|
<!-- file names list -->
|
||||||
<Slider min={17} max={80} step={1} bind:value>
|
<div
|
||||||
<svelte:fragment slot="tooltip" let:value>
|
class="bg-col-start-1 col-end-2 row-start-1 grid divide-y divide-zinc-700/20"
|
||||||
{format(colToTimestamp(value), "hh:mm")}
|
style="grid-template-rows: repeat({Object.keys($deltas).length}, minmax(2rem, 1fr));"
|
||||||
</svelte:fragment>
|
>
|
||||||
</Slider>
|
<!-- <div class="row-end-1 h-7" /> -->
|
||||||
</div>
|
|
||||||
<div class="col-span-1 flex items-center justify-center" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-auto mb-1">
|
|
||||||
<div class="grid flex-auto grid-cols-1 grid-rows-1">
|
|
||||||
<!-- file names list -->
|
|
||||||
<div
|
|
||||||
class="bg-col-start-1 col-end-2 row-start-1 grid divide-y divide-zinc-700/20"
|
|
||||||
style="grid-template-rows: repeat({Object.keys($deltas)
|
|
||||||
.length}, minmax(2rem, 1fr));"
|
|
||||||
>
|
|
||||||
<!-- <div class="row-end-1 h-7" /> -->
|
|
||||||
|
|
||||||
{#each Object.keys($deltas) as filePath, i}
|
{#each Object.keys($deltas) as filePath, i}
|
||||||
<div
|
<div class="flex {i == selectedFileIdx ? 'bg-zinc-500/70' : ''}">
|
||||||
class="flex {i == selectedFileIdx
|
<button
|
||||||
? 'bg-zinc-500/70'
|
class="z-20 flex justify-end items-center overflow-hidden sticky left-0 w-1/6 leading-5
|
||||||
: ''}"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="z-20 flex justify-end items-center overflow-hidden sticky left-0 w-1/6 leading-5
|
|
||||||
{selectedFileIdx == i
|
{selectedFileIdx == i
|
||||||
? 'text-zinc-200 cursor-default'
|
? 'text-zinc-200 cursor-default'
|
||||||
: 'text-zinc-400 hover:text-zinc-200 cursor-pointer'}"
|
: 'text-zinc-400 hover:text-zinc-200 cursor-pointer'}"
|
||||||
on:click={() => (selectedFileIdx = i)}
|
on:click={() => (selectedFileIdx = i)}
|
||||||
title={filePath}
|
title={filePath}
|
||||||
>
|
>
|
||||||
{filePath.split("/").pop()}
|
{filePath.split('/').pop()}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- col selection -->
|
<!-- col selection -->
|
||||||
<div
|
<div
|
||||||
class="col-start-1 col-end-2 row-start-1 grid"
|
class="col-start-1 col-end-2 row-start-1 grid"
|
||||||
style="grid-template-columns: repeat(88, minmax(0, 1fr));"
|
style="grid-template-columns: repeat(88, minmax(0, 1fr));"
|
||||||
>
|
>
|
||||||
<div
|
<div class="bg-sky-400/60 " style=" grid-column: {value};" />
|
||||||
class="bg-sky-400/60 "
|
</div>
|
||||||
style=" grid-column: {value};"
|
<!-- time vertical lines -->
|
||||||
/>
|
<div
|
||||||
</div>
|
class="col-start-1 col-end-2 row-start-1 grid-rows-1 divide-x divide-zinc-700/50 grid grid-cols-11"
|
||||||
<!-- time vertical lines -->
|
>
|
||||||
<div
|
<div class="col-span-2 row-span-full" />
|
||||||
class="col-start-1 col-end-2 row-start-1 grid-rows-1 divide-x divide-zinc-700/50 grid grid-cols-11"
|
<div class="col-span-2 row-span-full" />
|
||||||
>
|
<div class="col-span-2 row-span-full" />
|
||||||
<div class="col-span-2 row-span-full" />
|
<div class="col-span-2 row-span-full" />
|
||||||
<div class="col-span-2 row-span-full" />
|
<div class="col-span-2 row-span-full" />
|
||||||
<div class="col-span-2 row-span-full" />
|
<div class="col-span-2 row-span-full" />
|
||||||
<div class="col-span-2 row-span-full" />
|
</div>
|
||||||
<div class="col-span-2 row-span-full" />
|
|
||||||
<div class="col-span-2 row-span-full" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- actual entries -->
|
<!-- actual entries -->
|
||||||
<ol
|
<ol
|
||||||
class="col-start-1 col-end-2 row-start-1 grid"
|
class="col-start-1 col-end-2 row-start-1 grid"
|
||||||
style="
|
style="
|
||||||
grid-template-columns: repeat(88, minmax(0, 1fr));
|
grid-template-columns: repeat(88, minmax(0, 1fr));
|
||||||
grid-template-rows: repeat({Object.keys($deltas)
|
grid-template-rows: repeat({Object.keys($deltas)
|
||||||
.length}, minmax(0px, 1fr)) auto;"
|
.length}, minmax(0px, 1fr)) auto;"
|
||||||
>
|
>
|
||||||
{#each Object.entries($deltas) as [filePath, fileDeltas], idx}
|
{#each Object.entries($deltas) as [filePath, fileDeltas], idx}
|
||||||
{#each fileDeltas as delta}
|
{#each fileDeltas as delta}
|
||||||
<li
|
<li
|
||||||
class="relative flex items-center bg-zinc-300 hover:bg-zinc-100 rounded m-0.5 cursor-pointer"
|
class="relative flex items-center bg-zinc-300 hover:bg-zinc-100 rounded m-0.5 cursor-pointer"
|
||||||
style="
|
style="
|
||||||
grid-row: {idx + 1} / span 1;
|
grid-row: {idx + 1} / span 1;
|
||||||
grid-column: {timeStampToCol(
|
grid-column: {timeStampToCol(
|
||||||
new Date(delta.timestampMs)
|
new Date(delta.timestampMs)
|
||||||
)} / span 1;"
|
)} / span 1;"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="z-20 h-full flex flex-col w-full items-center justify-center"
|
class="z-20 h-full flex flex-col w-full items-center justify-center"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
value = timeStampToCol(
|
value = timeStampToCol(new Date(delta.timestampMs));
|
||||||
new Date(delta.timestampMs)
|
selectedFileIdx = idx;
|
||||||
);
|
}}
|
||||||
selectedFileIdx = idx;
|
/>
|
||||||
}}
|
</li>
|
||||||
/>
|
{/each}
|
||||||
</li>
|
{/each}
|
||||||
{/each}
|
</ol>
|
||||||
{/each}
|
</div>
|
||||||
</ol>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-11 mt-6">
|
||||||
</div>
|
<div class="col-span-2" />
|
||||||
<div class="grid grid-cols-11 mt-6">
|
<div class="col-span-8 p-1 bg-zinc-500/70 rounded select-text">
|
||||||
<div class="col-span-2" />
|
{#if $doc}
|
||||||
<div class="col-span-8 p-1 bg-zinc-500/70 rounded select-text">
|
<CodeViewer value={$doc} />
|
||||||
{#if $doc}
|
{/if}
|
||||||
<CodeViewer value={$doc} />
|
</div>
|
||||||
{/if}
|
<div class="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,51 +1,45 @@
|
|||||||
import type { PageLoad } from "./$types";
|
import type { PageLoad } from './$types';
|
||||||
import { derived, readable } from "svelte/store";
|
import { derived, readable } from 'svelte/store';
|
||||||
import { building } from "$app/environment";
|
import { building } from '$app/environment';
|
||||||
import type { Delta } from "$lib/deltas";
|
import type { Delta } from '$lib/deltas';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
export const load: PageLoad = async ({ parent, params }) => {
|
export const load: PageLoad = async ({ parent, params }) => {
|
||||||
const { sessions } = await parent();
|
const { sessions } = await parent();
|
||||||
const deltas = building
|
const deltas = building
|
||||||
? readable({} as Record<string, Delta[]>)
|
? readable({} as Record<string, Delta[]>)
|
||||||
: (await import("$lib/deltas")).default({
|
: (await import('$lib/deltas')).default({
|
||||||
projectId: params.projectId,
|
projectId: params.projectId,
|
||||||
sessionId: params.sessionId,
|
sessionId: params.sessionId
|
||||||
});
|
});
|
||||||
const files = building
|
const files = building
|
||||||
? ({} as Record<string, string>)
|
? ({} as Record<string, string>)
|
||||||
: (await import("$lib/sessions")).listFiles({
|
: (await import('$lib/sessions')).listFiles({
|
||||||
projectId: params.projectId,
|
projectId: params.projectId,
|
||||||
sessionId: params.sessionId,
|
sessionId: params.sessionId
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
session: derived(sessions, (sessions) => {
|
session: derived(sessions, (sessions) => {
|
||||||
const result = sessions.find(
|
const result = sessions.find((session) => session.id === params.sessionId);
|
||||||
(session) => session.id === params.sessionId
|
return result ? result : sessions[0];
|
||||||
);
|
}),
|
||||||
return result ? result : sessions[0];
|
previousSesssion: derived(sessions, (sessions) => {
|
||||||
}),
|
const currentSessionIndex = sessions.findIndex((session) => session.id === params.sessionId);
|
||||||
previousSesssion: derived(sessions, (sessions) => {
|
if (currentSessionIndex - 1 < sessions.length) {
|
||||||
const currentSessionIndex = sessions.findIndex(
|
return sessions[currentSessionIndex - 1];
|
||||||
(session) => session.id === params.sessionId
|
} else {
|
||||||
);
|
return undefined;
|
||||||
if (currentSessionIndex - 1 < sessions.length) {
|
}
|
||||||
return sessions[currentSessionIndex - 1];
|
}),
|
||||||
} else {
|
nextSession: derived(sessions, (sessions) => {
|
||||||
return undefined;
|
const currentSessionIndex = sessions.findIndex((session) => session.id === params.sessionId);
|
||||||
}
|
if (currentSessionIndex + 1 < sessions.length) {
|
||||||
}),
|
return sessions[currentSessionIndex + 1];
|
||||||
nextSession: derived(sessions, (sessions) => {
|
} else {
|
||||||
const currentSessionIndex = sessions.findIndex(
|
return undefined;
|
||||||
(session) => session.id === params.sessionId
|
}
|
||||||
);
|
}),
|
||||||
if (currentSessionIndex + 1 < sessions.length) {
|
files: files,
|
||||||
return sessions[currentSessionIndex + 1];
|
deltas: deltas
|
||||||
} else {
|
};
|
||||||
return undefined;
|
};
|
||||||
}
|
|
||||||
}),
|
|
||||||
files: files,
|
|
||||||
deltas: deltas,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,148 +1,138 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { derived } from "svelte/store";
|
import { derived } from 'svelte/store';
|
||||||
import { Login } from "$lib/components";
|
import { Login } from '$lib/components';
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { project, user, api } = data;
|
const { project, user, api } = data;
|
||||||
|
|
||||||
function repo_id(url: string) {
|
function repo_id(url: string) {
|
||||||
const hurl = new URL(url);
|
const hurl = new URL(url);
|
||||||
const path = hurl.pathname.split("/");
|
const path = hurl.pathname.split('/');
|
||||||
return path[path.length - 1];
|
return path[path.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function hostname(url: string) {
|
function hostname(url: string) {
|
||||||
const hurl = new URL(url);
|
const hurl = new URL(url);
|
||||||
return hurl.hostname;
|
return hurl.hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSyncing = derived(project, (project) => project?.api?.sync);
|
const isSyncing = derived(project, (project) => project?.api?.sync);
|
||||||
|
|
||||||
const onSyncChange = async (event: Event) => {
|
const onSyncChange = async (event: Event) => {
|
||||||
if ($project === undefined) return;
|
if ($project === undefined) return;
|
||||||
if ($user === undefined) return;
|
if ($user === undefined) return;
|
||||||
|
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
const sync = target.checked;
|
const sync = target.checked;
|
||||||
|
|
||||||
if (!$project.api) {
|
if (!$project.api) {
|
||||||
const apiProject = await api.projects.create($user.access_token, {
|
const apiProject = await api.projects.create($user.access_token, {
|
||||||
name: $project.title,
|
name: $project.title,
|
||||||
uid: $project.id,
|
uid: $project.id
|
||||||
});
|
});
|
||||||
await project.update({ api: { ...apiProject, sync } });
|
await project.update({ api: { ...apiProject, sync } });
|
||||||
} else {
|
} else {
|
||||||
await project.update({ api: { ...$project.api, sync } });
|
await project.update({ api: { ...$project.api, sync } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4 mx-auto h-full overflow-auto">
|
<div class="p-4 mx-auto h-full overflow-auto">
|
||||||
<div class="max-w-2xl mx-auto p-4">
|
<div class="max-w-2xl mx-auto p-4">
|
||||||
<div class="flex flex-col text-zinc-100 space-y-6">
|
<div class="flex flex-col text-zinc-100 space-y-6">
|
||||||
<div class="space-y-0">
|
<div class="space-y-0">
|
||||||
<div class="text-lg font-medium">Project Settings</div>
|
<div class="text-lg font-medium">Project Settings</div>
|
||||||
<div class="text-zinc-400">
|
<div class="text-zinc-400">
|
||||||
Manage your project settings for <strong
|
Manage your project settings for <strong>{$project?.title}</strong>
|
||||||
>{$project?.title}</strong
|
</div>
|
||||||
>
|
</div>
|
||||||
</div>
|
<hr class="border-zinc-600" />
|
||||||
</div>
|
{#if $user}
|
||||||
<hr class="border-zinc-600" />
|
<div class="space-y-2">
|
||||||
{#if $user}
|
<div class="ml-1">GitButler Cloud</div>
|
||||||
<div class="space-y-2">
|
<div
|
||||||
<div class="ml-1">GitButler Cloud</div>
|
class="flex flex-row justify-between border border-zinc-600 rounded-lg p-2 items-center"
|
||||||
<div
|
>
|
||||||
class="flex flex-row justify-between border border-zinc-600 rounded-lg p-2 items-center"
|
<div class="flex flex-row space-x-3">
|
||||||
>
|
<svg
|
||||||
<div class="flex flex-row space-x-3">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
stroke-width="1.5"
|
||||||
viewBox="0 0 24 24"
|
stroke="white"
|
||||||
stroke-width="1.5"
|
class="w-6 h-6"
|
||||||
stroke="white"
|
>
|
||||||
class="w-6 h-6"
|
<path
|
||||||
>
|
stroke-linecap="round"
|
||||||
<path
|
stroke-linejoin="round"
|
||||||
stroke-linecap="round"
|
d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z"
|
||||||
stroke-linejoin="round"
|
/>
|
||||||
d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z"
|
</svg>
|
||||||
/>
|
<div class="flex flex-row">
|
||||||
</svg>
|
{#if $project?.api?.git_url}
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-col">
|
||||||
{#if $project?.api?.git_url}
|
<div class="text-zinc-300">Git Host</div>
|
||||||
<div class="flex flex-col">
|
<div class="text-zinc-400 font-mono">
|
||||||
<div class="text-zinc-300">
|
{hostname($project?.api?.git_url)}
|
||||||
Git Host
|
</div>
|
||||||
</div>
|
<div class="text-zinc-300 mt-3">Repository ID</div>
|
||||||
<div class="text-zinc-400 font-mono">
|
<div class="text-zinc-400 font-mono">
|
||||||
{hostname($project?.api?.git_url)}
|
{repo_id($project?.api?.git_url)}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-zinc-300 mt-3">
|
</div>
|
||||||
Repository ID
|
{/if}
|
||||||
</div>
|
<div>
|
||||||
<div class="text-zinc-400 font-mono">
|
<form disabled={$user === undefined}>
|
||||||
{repo_id($project?.api?.git_url)}
|
<input
|
||||||
</div>
|
class="mr-1"
|
||||||
</div>
|
disabled={$user === undefined}
|
||||||
{/if}
|
type="checkbox"
|
||||||
<div>
|
checked={$isSyncing}
|
||||||
<form disabled={$user === undefined}>
|
on:change={onSyncChange}
|
||||||
<input
|
/>
|
||||||
class="mr-1"
|
<label for="sync">Send Data to Server</label>
|
||||||
disabled={$user === undefined}
|
</form>
|
||||||
type="checkbox"
|
</div>
|
||||||
checked={$isSyncing}
|
</div>
|
||||||
on:change={onSyncChange}
|
</div>
|
||||||
/>
|
</div>
|
||||||
<label for="sync"
|
</div>
|
||||||
>Send Data to Server</label
|
{:else}
|
||||||
>
|
<div class="space-y-2">
|
||||||
</form>
|
<div class="flex flex-row space-x-2 items-end">
|
||||||
</div>
|
<div class="">GitButler Cloud</div>
|
||||||
</div>
|
<div class="text-zinc-400">backup your work and access advanced features</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex flex-row items-center space-x-2">
|
||||||
</div>
|
<Login {user} {api} />
|
||||||
{:else}
|
</div>
|
||||||
<div class="space-y-2">
|
</div>
|
||||||
<div class="flex flex-row space-x-2 items-end">
|
{/if}
|
||||||
<div class="">GitButler Cloud</div>
|
<div class="space-y-2">
|
||||||
<div class="text-zinc-400">
|
<div class="ml-1">Path</div>
|
||||||
backup your work and access advanced features
|
<div class="text-zinc-400 font-mono">
|
||||||
</div>
|
{$project?.path}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-center space-x-2">
|
</div>
|
||||||
<Login {user} {api} />
|
<div class="space-y-2">
|
||||||
</div>
|
<div class="ml-1">Project Name</div>
|
||||||
</div>
|
<!-- text box -->
|
||||||
{/if}
|
<input
|
||||||
<div class="space-y-2">
|
type="text"
|
||||||
<div class="ml-1">Path</div>
|
class="p-2 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
||||||
<div class="text-zinc-400 font-mono">
|
value={$project?.title}
|
||||||
{$project?.path}
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="space-y-2">
|
||||||
<div class="space-y-2">
|
<div class="ml-1">Project Description</div>
|
||||||
<div class="ml-1">Project Name</div>
|
<!-- text box -->
|
||||||
<!-- text box -->
|
<textarea
|
||||||
<input
|
rows="3"
|
||||||
type="text"
|
class="p-2 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
||||||
class="p-2 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
value={$project?.api?.description}
|
||||||
value={$project?.title}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
</div>
|
||||||
<div class="ml-1">Project Description</div>
|
|
||||||
<!-- text box -->
|
|
||||||
<textarea
|
|
||||||
rows="3"
|
|
||||||
class="p-2 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
|
||||||
value={$project?.api?.description}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,362 +1,321 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Week } from "$lib/week";
|
import { Week } from '$lib/week';
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from './$types';
|
||||||
import { WeekBlockEntry } from "$lib/components/week";
|
import { WeekBlockEntry } from '$lib/components/week';
|
||||||
import MdKeyboardArrowLeft from "svelte-icons/md/MdKeyboardArrowLeft.svelte";
|
import MdKeyboardArrowLeft from 'svelte-icons/md/MdKeyboardArrowLeft.svelte';
|
||||||
import MdKeyboardArrowRight from "svelte-icons/md/MdKeyboardArrowRight.svelte";
|
import MdKeyboardArrowRight from 'svelte-icons/md/MdKeyboardArrowRight.svelte';
|
||||||
import { derived } from "svelte/store";
|
import { derived } from 'svelte/store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { project, sessions } = data;
|
const { project, sessions } = data;
|
||||||
|
|
||||||
let week = Week.from(new Date());
|
let week = Week.from(new Date());
|
||||||
|
|
||||||
$: canNavigateForwad = week.end.getTime() < new Date().getTime();
|
$: canNavigateForwad = week.end.getTime() < new Date().getTime();
|
||||||
const formatDate = (date: Date) => {
|
const formatDate = (date: Date) => {
|
||||||
return new Intl.DateTimeFormat("default", {
|
return new Intl.DateTimeFormat('default', {
|
||||||
weekday: "short",
|
weekday: 'short',
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short'
|
||||||
}).format(date);
|
}).format(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
$: sessionsInWeek = derived([sessions], ([sessions]) => {
|
$: sessionsInWeek = derived([sessions], ([sessions]) => {
|
||||||
return sessions.filter((session) => {
|
return sessions.filter((session) => {
|
||||||
return (
|
return (
|
||||||
week.start <= new Date(session.meta.startTimestampMs) &&
|
week.start <= new Date(session.meta.startTimestampMs) &&
|
||||||
new Date(session.meta.startTimestampMs) <= week.end
|
new Date(session.meta.startTimestampMs) <= week.end
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-full select-none text-zinc-400">
|
<div class="flex flex-col h-full select-none text-zinc-400">
|
||||||
<header
|
<header class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700">
|
||||||
class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700"
|
<div class="flex items-center justify-start w-64">
|
||||||
>
|
<button
|
||||||
<div class="flex items-center justify-start w-64">
|
class="-ml-2 w-8 h-8 hover:text-zinc-100"
|
||||||
<button
|
on:click={() => (week = Week.previous(week))}
|
||||||
class="-ml-2 w-8 h-8 hover:text-zinc-100"
|
>
|
||||||
on:click={() => (week = Week.previous(week))}
|
<MdKeyboardArrowLeft />
|
||||||
>
|
</button>
|
||||||
<MdKeyboardArrowLeft />
|
<div class="flex-grow text-center cursor-default grid grid-cols-7">
|
||||||
</button>
|
<span class="col-span-3">{formatDate(Week.nThDay(week, 0))}</span>
|
||||||
<div class="flex-grow text-center cursor-default grid grid-cols-7">
|
<span>—</span>
|
||||||
<span class="col-span-3"
|
<span class="col-span-3">{formatDate(Week.nThDay(week, 6))}</span>
|
||||||
>{formatDate(Week.nThDay(week, 0))}</span
|
</div>
|
||||||
>
|
<button
|
||||||
<span>—</span>
|
class="-mr-2 w-8 h-8 hover:text-zinc-100 disabled:text-zinc-700"
|
||||||
<span class="col-span-3"
|
disabled={!canNavigateForwad}
|
||||||
>{formatDate(Week.nThDay(week, 6))}</span
|
on:click={() => {
|
||||||
>
|
if (canNavigateForwad) {
|
||||||
</div>
|
week = Week.next(week);
|
||||||
<button
|
}
|
||||||
class="-mr-2 w-8 h-8 hover:text-zinc-100 disabled:text-zinc-700"
|
}}
|
||||||
disabled={!canNavigateForwad}
|
>
|
||||||
on:click={() => {
|
<MdKeyboardArrowRight />
|
||||||
if (canNavigateForwad) {
|
</button>
|
||||||
week = Week.next(week);
|
</div>
|
||||||
}
|
</header>
|
||||||
}}
|
<div class="isolate flex flex-col flex-auto overflow-auto">
|
||||||
>
|
<div class="h-4/5 overflow-auto flex flex-col flex-none max-w-full border-b border-zinc-700">
|
||||||
<MdKeyboardArrowRight />
|
<!-- sticky top -->
|
||||||
</button>
|
<div
|
||||||
</div>
|
class="overflow-hidden sticky top-0 z-30 bg-zinc-800 border-b border-zinc-700 flex-none px-8"
|
||||||
</header>
|
>
|
||||||
<div class="isolate flex flex-col flex-auto overflow-auto">
|
<div class="grid-cols-8 divide-x divide-zinc-700 grid">
|
||||||
<div class="h-4/5 overflow-auto flex flex-col flex-none max-w-full border-b border-zinc-700">
|
<div class="py-4" />
|
||||||
<!-- sticky top -->
|
<div class="flex items-center justify-center">
|
||||||
<div
|
<span
|
||||||
class="overflow-hidden sticky top-0 z-30 bg-zinc-800 border-b border-zinc-700 flex-none px-8"
|
>Mon <span class="items-center justify-center font-semibold"
|
||||||
>
|
>{Week.nThDay(week, 0).getDate()}</span
|
||||||
<div class="grid-cols-8 divide-x divide-zinc-700 grid">
|
></span
|
||||||
<div class="py-4" />
|
>
|
||||||
<div class="flex items-center justify-center">
|
</div>
|
||||||
<span
|
<div class="flex items-center justify-center">
|
||||||
>Mon <span
|
<span
|
||||||
class="items-center justify-center font-semibold"
|
>Tue <span class="items-center justify-center font-semibold"
|
||||||
>{Week.nThDay(week, 0).getDate()}</span
|
>{Week.nThDay(week, 1).getDate()}</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span
|
<span
|
||||||
>Tue <span
|
>Wed <span class="items-center justify-center font-semibold"
|
||||||
class="items-center justify-center font-semibold"
|
>{Week.nThDay(week, 2).getDate()}</span
|
||||||
>{Week.nThDay(week, 1).getDate()}</span
|
></span
|
||||||
></span
|
>
|
||||||
>
|
</div>
|
||||||
</div>
|
<div class="flex items-center justify-center">
|
||||||
<div class="flex items-center justify-center">
|
<span
|
||||||
<span
|
>Thu <span class="items-center justify-center font-semibold"
|
||||||
>Wed <span
|
>{Week.nThDay(week, 3).getDate()}</span
|
||||||
class="items-center justify-center font-semibold"
|
></span
|
||||||
>{Week.nThDay(week, 2).getDate()}</span
|
>
|
||||||
></span
|
</div>
|
||||||
>
|
<div class="flex items-center justify-center">
|
||||||
</div>
|
<span
|
||||||
<div class="flex items-center justify-center">
|
>Fri <span class="items-center justify-center font-semibold"
|
||||||
<span
|
>{Week.nThDay(week, 4).getDate()}</span
|
||||||
>Thu <span
|
></span
|
||||||
class="items-center justify-center font-semibold"
|
>
|
||||||
>{Week.nThDay(week, 3).getDate()}</span
|
</div>
|
||||||
></span
|
<div class="flex items-center justify-center">
|
||||||
>
|
<span
|
||||||
</div>
|
>Sat <span class="items-center justify-center font-semibold"
|
||||||
<div class="flex items-center justify-center">
|
>{Week.nThDay(week, 5).getDate()}</span
|
||||||
<span
|
></span
|
||||||
>Fri <span
|
>
|
||||||
class="items-center justify-center font-semibold"
|
</div>
|
||||||
>{Week.nThDay(week, 4).getDate()}</span
|
<div class="flex items-center justify-center">
|
||||||
></span
|
<span
|
||||||
>
|
>Sun <span class="items-center justify-center font-semibold"
|
||||||
</div>
|
>{Week.nThDay(week, 6).getDate()}</span
|
||||||
<div class="flex items-center justify-center">
|
></span
|
||||||
<span
|
>
|
||||||
>Sat <span
|
</div>
|
||||||
class="items-center justify-center font-semibold"
|
</div>
|
||||||
>{Week.nThDay(week, 5).getDate()}</span
|
</div>
|
||||||
></span
|
<div class="flex flex-auto ">
|
||||||
>
|
<div class="grid flex-auto grid-cols-1 grid-rows-1">
|
||||||
</div>
|
<!-- hours y lines-->
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<span
|
|
||||||
>Sun <span
|
|
||||||
class="items-center justify-center font-semibold"
|
|
||||||
>{Week.nThDay(week, 6).getDate()}</span
|
|
||||||
></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-auto ">
|
|
||||||
<div class="grid flex-auto grid-cols-1 grid-rows-1">
|
|
||||||
<!-- hours y lines-->
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="text-zinc-500 col-start-1 col-end-2 row-start-1 grid-rows-1 grid grid-cols-8 px-8"
|
class="text-zinc-500 col-start-1 col-end-2 row-start-1 grid-rows-1 grid grid-cols-8 px-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="col-start-1 col-end-2 row-start-1 grid justify-end"
|
class="col-start-1 col-end-2 row-start-1 grid justify-end"
|
||||||
style="grid-template-rows: repeat(24, minmax(1.5rem, 1fr));"
|
style="grid-template-rows: repeat(24, minmax(1.5rem, 1fr));"
|
||||||
>
|
>
|
||||||
<div class="row-end-1 h-7" />
|
<div class="row-end-1 h-7" />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
12 AM
|
||||||
>
|
</div>
|
||||||
12 AM
|
</div>
|
||||||
</div>
|
<div />
|
||||||
</div>
|
<div>
|
||||||
<div />
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
<div>
|
2 AM
|
||||||
<div
|
</div>
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
</div>
|
||||||
>
|
<div />
|
||||||
2 AM
|
<div>
|
||||||
</div>
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
</div>
|
4 AM
|
||||||
<div />
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<div
|
<div />
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
<div>
|
||||||
>
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
4 AM
|
6 AM
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
8 AM
|
||||||
>
|
</div>
|
||||||
6 AM
|
</div>
|
||||||
</div>
|
<div />
|
||||||
</div>
|
<div>
|
||||||
<div />
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
<div>
|
10 AM
|
||||||
<div
|
</div>
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
</div>
|
||||||
>
|
<div />
|
||||||
8 AM
|
<div>
|
||||||
</div>
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
</div>
|
12 PM
|
||||||
<div />
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<div
|
<div />
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
<div>
|
||||||
>
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
10 AM
|
2 PM
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
4 PM
|
||||||
>
|
</div>
|
||||||
12 PM
|
</div>
|
||||||
</div>
|
<div />
|
||||||
</div>
|
<div>
|
||||||
<div />
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
<div>
|
6 PM
|
||||||
<div
|
</div>
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
</div>
|
||||||
>
|
<div />
|
||||||
2 PM
|
<div>
|
||||||
</div>
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
</div>
|
8 PM
|
||||||
<div />
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<div
|
<div />
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
<div>
|
||||||
>
|
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
|
||||||
4 PM
|
10 PM
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
|
||||||
>
|
|
||||||
6 PM
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div />
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
|
||||||
>
|
|
||||||
8 PM
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div />
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500"
|
|
||||||
>
|
|
||||||
10 PM
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="text-zinc-500 col-start-1 col-end-2 row-start-1 grid-rows-1 grid grid-cols-8 ml-8 "
|
class="text-zinc-500 col-start-1 col-end-2 row-start-1 grid-rows-1 grid grid-cols-8 ml-8 "
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="col-start-2 col-end-9 row-start-1 grid divide-y divide-zinc-700/20"
|
class="col-start-2 col-end-9 row-start-1 grid divide-y divide-zinc-700/20"
|
||||||
style="grid-template-rows: repeat(24, minmax(1.5rem, 1fr));"
|
style="grid-template-rows: repeat(24, minmax(1.5rem, 1fr));"
|
||||||
>
|
>
|
||||||
<div class="row-end-1 h-7" />
|
<div class="row-end-1 h-7" />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- day x lines -->
|
<!-- day x lines -->
|
||||||
<div
|
<div
|
||||||
class="col-start-1 col-end-2 row-start-1 grid-rows-1 divide-x divide-zinc-700/50 grid grid-cols-8 px-8"
|
class="col-start-1 col-end-2 row-start-1 grid-rows-1 divide-x divide-zinc-700/50 grid grid-cols-8 px-8"
|
||||||
>
|
>
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- actual entries -->
|
<!-- actual entries -->
|
||||||
<ol
|
<ol
|
||||||
class="col-start-1 col-end-2 row-start-1 grid grid-cols-8 px-8"
|
class="col-start-1 col-end-2 row-start-1 grid grid-cols-8 px-8"
|
||||||
style="grid-template-rows: 1.75rem repeat(96, minmax(0px, 1fr)) auto;"
|
style="grid-template-rows: 1.75rem repeat(96, minmax(0px, 1fr)) auto;"
|
||||||
>
|
>
|
||||||
{#each $sessionsInWeek as session}
|
{#each $sessionsInWeek as session}
|
||||||
<WeekBlockEntry
|
<WeekBlockEntry
|
||||||
startTime={new Date(
|
startTime={new Date(session.meta.startTimestampMs)}
|
||||||
session.meta.startTimestampMs
|
endTime={new Date(session.meta.startTimestampMs)}
|
||||||
)}
|
label={session.meta.branch}
|
||||||
endTime={new Date(
|
href="/projects/{$project?.id}/sessions/{session.id}/"
|
||||||
session.meta.startTimestampMs
|
/>
|
||||||
)}
|
{/each}
|
||||||
label={session.meta.branch}
|
</ol>
|
||||||
href="/projects/{$project?.id}/sessions/{session.id}/"
|
</div>
|
||||||
/>
|
</div>
|
||||||
{/each}
|
</div>
|
||||||
</ol>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,224 +1,208 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Login } from "$lib/components";
|
import { Login } from '$lib/components';
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from './$types';
|
||||||
import MdAutorenew from "svelte-icons/md/MdAutorenew.svelte";
|
import MdAutorenew from 'svelte-icons/md/MdAutorenew.svelte';
|
||||||
import { log, toasts } from "$lib";
|
import { log, toasts } from '$lib';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { user, api } = data;
|
const { user, api } = data;
|
||||||
|
|
||||||
$: saving = false;
|
$: saving = false;
|
||||||
|
|
||||||
let userName = $user?.name;
|
let userName = $user?.name;
|
||||||
let userPicture = $user?.picture;
|
let userPicture = $user?.picture;
|
||||||
|
|
||||||
const fileTypes = ["image/jpeg", "image/png"];
|
const fileTypes = ['image/jpeg', 'image/png'];
|
||||||
|
|
||||||
const validFileType = (file: File) => {
|
const validFileType = (file: File) => {
|
||||||
return fileTypes.includes(file.type);
|
return fileTypes.includes(file.type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPictureChange = (e: Event) => {
|
const onPictureChange = (e: Event) => {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
const file = target.files?.[0];
|
const file = target.files?.[0];
|
||||||
|
|
||||||
if (file && validFileType(file)) {
|
if (file && validFileType(file)) {
|
||||||
userPicture = URL.createObjectURL(file);
|
userPicture = URL.createObjectURL(file);
|
||||||
} else {
|
} else {
|
||||||
userPicture = $user?.picture;
|
userPicture = $user?.picture;
|
||||||
toasts.error("Please use a valid image file");
|
toasts.error('Please use a valid image file');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (e: SubmitEvent) => {
|
const onSubmit = async (e: SubmitEvent) => {
|
||||||
if (!$user) return;
|
if (!$user) return;
|
||||||
saving = true;
|
saving = true;
|
||||||
|
|
||||||
const target = e.target as HTMLFormElement;
|
const target = e.target as HTMLFormElement;
|
||||||
const formData = new FormData(target);
|
const formData = new FormData(target);
|
||||||
const name = formData.get("name") as string | undefined;
|
const name = formData.get('name') as string | undefined;
|
||||||
const picture = formData.get("picture") as File | undefined;
|
const picture = formData.get('picture') as File | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$user = await api.user.update($user.access_token, {
|
$user = await api.user.update($user.access_token, {
|
||||||
name,
|
name,
|
||||||
picture: picture,
|
picture: picture
|
||||||
});
|
});
|
||||||
toasts.success("Profile updated");
|
toasts.success('Profile updated');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(e);
|
log.error(e);
|
||||||
toasts.error("Failed to update user");
|
toasts.error('Failed to update user');
|
||||||
}
|
}
|
||||||
|
|
||||||
saving = false;
|
saving = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4 mx-auto">
|
<div class="p-4 mx-auto">
|
||||||
<div class="max-w-xl mx-auto p-4">
|
<div class="max-w-xl mx-auto p-4">
|
||||||
{#if $user}
|
{#if $user}
|
||||||
<div class="flex flex-col gap-6 text-zinc-100">
|
<div class="flex flex-col gap-6 text-zinc-100">
|
||||||
<header class="flex items-center justify-between">
|
<header class="flex items-center justify-between">
|
||||||
<h2 class="text-2xl font-medium">
|
<h2 class="text-2xl font-medium">GitButler Cloud Account</h2>
|
||||||
GitButler Cloud Account
|
<Login {user} {api} />
|
||||||
</h2>
|
</header>
|
||||||
<Login {user} {api} />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<form
|
<form
|
||||||
on:submit={onSubmit}
|
on:submit={onSubmit}
|
||||||
class="flex flex-row gap-12 justify-between rounded-lg p-2 items-start"
|
class="flex flex-row gap-12 justify-between rounded-lg p-2 items-start"
|
||||||
>
|
>
|
||||||
<fields id="left" class="flex flex-1 flex-col gap-3">
|
<fields id="left" class="flex flex-1 flex-col gap-3">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="name" class="text-zinc-400">Name</label>
|
<label for="name" class="text-zinc-400">Name</label>
|
||||||
<input
|
<input
|
||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
bind:value={userName}
|
bind:value={userName}
|
||||||
type="text"
|
type="text"
|
||||||
class="px-2 py-1 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
class="px-2 py-1 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="email" class="text-zinc-400"
|
<label for="email" class="text-zinc-400">Email</label>
|
||||||
>Email</label
|
<input
|
||||||
>
|
disabled
|
||||||
<input
|
id="email"
|
||||||
disabled
|
name="email"
|
||||||
id="email"
|
bind:value={$user.email}
|
||||||
name="email"
|
type="text"
|
||||||
bind:value={$user.email}
|
class="px-2 py-1 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
||||||
type="text"
|
/>
|
||||||
class="px-2 py-1 text-zinc-300 bg-black border border-zinc-600 rounded-lg w-full"
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="pt-4">
|
<footer class="pt-4">
|
||||||
{#if saving}
|
{#if saving}
|
||||||
<div
|
<div
|
||||||
class="flex w-32 flex-row w-content items-center gap-1 justify-center py-1 px-3 rounded text-white bg-blue-400"
|
class="flex w-32 flex-row w-content items-center gap-1 justify-center py-1 px-3 rounded text-white bg-blue-400"
|
||||||
>
|
>
|
||||||
<div class="animate-spin w-5 h-5">
|
<div class="animate-spin w-5 h-5">
|
||||||
<MdAutorenew />
|
<MdAutorenew />
|
||||||
</div>
|
</div>
|
||||||
<span>Updating...</span>
|
<span>Updating...</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button type="submit" class="py-1 px-3 rounded text-white bg-blue-400"
|
||||||
type="submit"
|
>Update profile</button
|
||||||
class="py-1 px-3 rounded text-white bg-blue-400"
|
>
|
||||||
>Update profile</button
|
{/if}
|
||||||
>
|
</footer>
|
||||||
{/if}
|
</fields>
|
||||||
</footer>
|
|
||||||
</fields>
|
|
||||||
|
|
||||||
<fields id="right" class="flex flex-col gap-2 items-center">
|
<fields id="right" class="flex flex-col gap-2 items-center">
|
||||||
{#if $user.picture}
|
{#if $user.picture}
|
||||||
<img
|
<img
|
||||||
class="h-28 w-28 rounded-full border-zinc-300"
|
class="h-28 w-28 rounded-full border-zinc-300"
|
||||||
src={userPicture}
|
src={userPicture}
|
||||||
alt="Your avatar"
|
alt="Your avatar"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<label
|
<label
|
||||||
for="picture"
|
for="picture"
|
||||||
class="px-2 -mt-6 -ml-16 cursor-pointer text-center font-sm text-zinc-300 bg-zinc-800 border border-zinc-600 rounded-lg"
|
class="px-2 -mt-6 -ml-16 cursor-pointer text-center font-sm text-zinc-300 bg-zinc-800 border border-zinc-600 rounded-lg"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
<input
|
<input
|
||||||
on:change={onPictureChange}
|
on:change={onPictureChange}
|
||||||
type="file"
|
type="file"
|
||||||
id="picture"
|
id="picture"
|
||||||
name="picture"
|
name="picture"
|
||||||
accept={fileTypes.join("")}
|
accept={fileTypes.join('')}
|
||||||
class="hidden"
|
class="hidden"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</fields>
|
</fields>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div class="flex flex-col text-white space-y-6 items-center justify-items-center">
|
||||||
class="flex flex-col text-white space-y-6 items-center justify-items-center"
|
<div class="text-3xl font-bold text-white">Connect to GitButler Cloud</div>
|
||||||
>
|
<div>Sign up or log in to GitButler Cloud for more tools and features:</div>
|
||||||
<div class="text-3xl font-bold text-white">
|
<ul class="text-zinc-400 pb-4 space-y-2">
|
||||||
Connect to GitButler Cloud
|
<li class="flex flex-row space-x-3">
|
||||||
</div>
|
<svg
|
||||||
<div>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
Sign up or log in to GitButler Cloud for more tools and
|
fill="none"
|
||||||
features:
|
viewBox="0 0 24 24"
|
||||||
</div>
|
stroke-width="1.5"
|
||||||
<ul class="text-zinc-400 pb-4 space-y-2">
|
stroke="white"
|
||||||
<li class="flex flex-row space-x-3">
|
class="w-6 h-6"
|
||||||
<svg
|
>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
fill="none"
|
stroke-linecap="round"
|
||||||
viewBox="0 0 24 24"
|
stroke-linejoin="round"
|
||||||
stroke-width="1.5"
|
d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z"
|
||||||
stroke="white"
|
/>
|
||||||
class="w-6 h-6"
|
</svg>
|
||||||
>
|
<span>Backup everything you do in any of your projects</span>
|
||||||
<path
|
</li>
|
||||||
stroke-linecap="round"
|
<li class="flex flex-row space-x-3">
|
||||||
stroke-linejoin="round"
|
<svg
|
||||||
d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
/>
|
fill="none"
|
||||||
</svg>
|
viewBox="0 0 24 24"
|
||||||
<span
|
stroke-width="1.5"
|
||||||
>Backup everything you do in any of your projects</span
|
stroke="white"
|
||||||
>
|
class="w-6 h-6"
|
||||||
</li>
|
>
|
||||||
<li class="flex flex-row space-x-3">
|
<path
|
||||||
<svg
|
stroke-linecap="round"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
stroke-linejoin="round"
|
||||||
fill="none"
|
d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
stroke-width="1.5"
|
</svg>
|
||||||
stroke="white"
|
|
||||||
class="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<span>Sync your data across devices</span>
|
<span>Sync your data across devices</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex flex-row space-x-3">
|
<li class="flex flex-row space-x-3">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="white"
|
stroke="white"
|
||||||
class="w-6 h-6"
|
class="w-6 h-6"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>AI commit message automated suggestions</span>
|
<span>AI commit message automated suggestions</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="mt-8 text-center">
|
<div class="mt-8 text-center">
|
||||||
<Login {user} {api} />
|
<Login {user} {api} />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-zinc-300 text-center">
|
<div class="text-zinc-300 text-center">
|
||||||
You will still need to give us permission for each project
|
You will still need to give us permission for each project before we transfer any data to
|
||||||
before we transfer any data to our servers. You can revoke
|
our servers. You can revoke this permission at any time.
|
||||||
this permission at any time.
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,22 +1,15 @@
|
|||||||
import preprocess from "svelte-preprocess";
|
import staticAdapter from '@sveltejs/adapter-static';
|
||||||
import staticAdapter from "@sveltejs/adapter-static";
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
import { vitePreprocess } from "@sveltejs/kit/vite";
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
preprocess: [
|
preprocess: vitePreprocess(),
|
||||||
vitePreprocess(),
|
kit: {
|
||||||
preprocess({
|
adapter: staticAdapter({
|
||||||
postcss: true,
|
precompress: true,
|
||||||
typescript: true,
|
strict: false
|
||||||
}),
|
})
|
||||||
],
|
}
|
||||||
kit: {
|
|
||||||
adapter: staticAdapter({
|
|
||||||
precompress: true,
|
|
||||||
strict: false,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
const config = {
|
const config = {
|
||||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
darkMode: "class",
|
darkMode: 'class',
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["Inter", "SF Pro", "-apple-system", "system-ui"],
|
sans: ['Inter', 'SF Pro', '-apple-system', 'system-ui']
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
xs: "10px",
|
xs: '10px',
|
||||||
sm: "12px",
|
sm: '12px',
|
||||||
base: "13px",
|
base: '13px',
|
||||||
lg: "15px",
|
lg: '15px',
|
||||||
xl: "22px",
|
xl: '22px'
|
||||||
},
|
},
|
||||||
extend: {},
|
extend: {}
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [],
|
plugins: []
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
}
|
}
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
// 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
|
// 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
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from 'vite';
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
// prevent vite from obscuring rust errors
|
// prevent vite from obscuring rust errors
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
// tauri expects a fixed port, fail if that port is not available
|
// tauri expects a fixed port, fail if that port is not available
|
||||||
server: {
|
server: {
|
||||||
port: 1420,
|
port: 1420,
|
||||||
strictPort: true,
|
strictPort: true
|
||||||
},
|
},
|
||||||
// to make use of `TAURI_DEBUG` and other env variables
|
// to make use of `TAURI_DEBUG` and other env variables
|
||||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||||
envPrefix: ["VITE_", "TAURI_"],
|
envPrefix: ['VITE_', 'TAURI_'],
|
||||||
build: {
|
build: {
|
||||||
// Tauri supports es2021
|
// Tauri supports es2021
|
||||||
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
|
target: process.env.TAURI_PLATFORM == 'windows' ? 'chrome105' : 'safari13',
|
||||||
// don't minify for debug builds
|
// don't minify for debug builds
|
||||||
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
|
||||||
// produce sourcemaps for debug builds
|
// produce sourcemaps for debug builds
|
||||||
sourcemap: !!process.env.TAURI_DEBUG,
|
sourcemap: !!process.env.TAURI_DEBUG
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user