diff --git a/package.json b/package.json index cf00920..4ed00ea 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build-only": "vite build", "type-check": "vue-tsc --build", "format": "prettier --write --experimental-cli src/", - "fetch-wasm": "curl -L -o src/assets/core.wasm https://github.com/git-calendar/core/releases/download/v0.1.5/core.wasm", + "fetch-wasm": "curl -L -o src/assets/core.wasm https://github.com/git-calendar/core/releases/download/v0.1.6/core.wasm", "fetch-wasm-latest": "curl -L -o src/assets/core.wasm https://github.com/git-calendar/core/releases/latest/download/core.wasm" }, "dependencies": { diff --git a/src/App.vue b/src/App.vue index 1c1afc8..0114d31 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,9 +5,10 @@ import { onBeforeMount, ref } from 'vue'; import AlertModal from '@/components/modals/AlertModal.vue'; import { createCalendarOnce } from '@/utils'; import { syncAllWrapper } from '@/services/gitSync'; -import { settings } from './services/settings'; -import { useAlertModal } from './composables/modals/useAlertModal'; -import { notifyEventsChanged } from './composables/useEventsRefresh'; +import { settings } from '@/services/settings'; +import { useAlertModal } from '@/composables/modals/useAlertModal'; +import { notifyEventsChanged } from '@/composables/useEventsRefresh'; +import { loadCalendars } from '@/services/calendarCache'; const { alert } = useAlertModal(); @@ -29,6 +30,8 @@ onBeforeMount(async () => { await CalendarCore.loadCalendars(); // load local first, they might change after with network pull sync(); + await loadCalendars(); + coreReady.value = true; } catch (err) { alert(String(err)); diff --git a/src/assets/locales/cs.json b/src/assets/locales/cs.json index f202942..aa697ac 100644 --- a/src/assets/locales/cs.json +++ b/src/assets/locales/cs.json @@ -23,6 +23,7 @@ "to": "Do", "entireDay": "Celý den", "calendar": "Kalendář", + "tag": "Štítek", "repeat": { "repeat": "Opakování", "end": "Konec", @@ -41,7 +42,13 @@ "username": "Uživatelské jméno", "password": "Heslo/Token", "encrypted": "Šifrování", - "decryptionKey": "Šifrovací heslo" + "encryptionKey": "Šifrovací heslo", + "readonly": "Pouze čtení" + }, + "tag": { + "name": "Jméno", + "color": "Barva", + "notag": "Bez štítku" }, "views": { "4days": "4 dny", @@ -85,5 +92,15 @@ "message": { "confirmCalendarDelete": "Chcete opravdu smazat celý kalenář?", "errorRemoteUrlEndDotGit": "Remote URL musí končit \".git\"." + }, + "colors": { + "blue": "Modrá", + "green": "Zelená", + "red": "Červená", + "purple": "Fialová", + "yellow": "Žlutá", + "orange": "Oranžová", + "pink": "Růžová", + "gray": "Šedá" } } diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index d2350c1..3d6ea86 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -23,6 +23,7 @@ "to": "Till", "entireDay": "Entire day", "calendar": "Calendar", + "tag": "Tag", "repeat": { "repeat": "Repeat", "end": "End", @@ -41,7 +42,13 @@ "username": "Username", "password": "Password/Token", "encrypted": "Encrypted", - "decryptionKey": "Encryption key/password" + "encryptionKey": "Encryption key/password", + "readonly": "Read-only" + }, + "tag": { + "name": "Name", + "color": "Color", + "notag": "Untagged" }, "views": { "4days": "4 days", @@ -85,5 +92,15 @@ "message": { "confirmCalendarDelete": "Do you really want to delete the whole calendar?", "errorRemoteUrlEndDotGit": "Remote URL must end with \".git\"." + }, + "colors": { + "blue": "Blue", + "green": "Green", + "red": "Red", + "purple": "Purple", + "yellow": "Yellow", + "orange": "Orange", + "pink": "Pink", + "gray": "Gray" } } diff --git a/src/assets/locales/git.json b/src/assets/locales/git.json index 92933e0..63d8048 100644 --- a/src/assets/locales/git.json +++ b/src/assets/locales/git.json @@ -19,5 +19,15 @@ "compiling": "Building...", "starting": "Running...", "done": "Ready!" + }, + "colors": { + "blue": "Commit Blue", + "green": "Addition Green (+)", + "red": "Deletion Red (-)", + "purple": "Merged Purple", + "yellow": "Stash Yellow", + "orange": "Rebase Orange", + "pink": "Fetch Pink", + "gray": "Docs Gray" } } diff --git a/src/assets/styles.css b/src/assets/styles.css index a773a18..293fb6a 100644 --- a/src/assets/styles.css +++ b/src/assets/styles.css @@ -120,7 +120,6 @@ input[type='checkbox'] { width: 1rem; height: 1rem; padding: 0; - margin-bottom: 2px; vertical-align: middle; cursor: pointer; } @@ -130,7 +129,7 @@ input[type='checkbox']::before { position: absolute; inset: 0; border-radius: var(--small-border-radius); - background-color: var(--btn-bg-color); + background-color: var(--sidebar-btn-bg-color); } input[type='checkbox']:checked::before { @@ -182,6 +181,33 @@ input.red, resize: vertical; min-height: 2rem; } + + fieldset { + display: contents; + border: 0; + padding: 0; + margin: 0; + + &:disabled label, + &:disabled .dates > span, + &:disabled > #git-credentials { + opacity: 0.5; + } + + &:disabled select { + opacity: 1; /* it was dimmed 2 times idk */ + } + } + + label:has(select) { + display: flex; + align-items: center; + gap: 0.8rem; + + select { + flex: 1 1 auto; + } + } } .bottom-btns { @@ -189,6 +215,15 @@ input.red, display: flex; gap: 1rem; + + .delete-btn { + border: 1px solid var(--git-color); + background-color: var(--btn-red-bg-color); + + &:hover:not(:focus):not(:disabled) { + background-color: var(--btn-red-bg-color-hover); + } + } } @media (max-width: 768px) { diff --git a/src/assets/themes.css b/src/assets/themes.css index 825a41a..02105d3 100644 --- a/src/assets/themes.css +++ b/src/assets/themes.css @@ -7,10 +7,11 @@ [data-theme='dark'] { --bg-color: #1a1a1a; --text-color: #c1c1c1; + --text-color-harder: #d9d9d9; --text-color-hard: #fff; --btn-bg-color: #2b2b2b; - --btn-bg-color-hover: color-mix(in srgb, var(--btn-bg-color), white 3%); + --btn-bg-color-hover: color-mix(in srgb, var(--btn-bg-color), white 5%); --btn-bg-color-checked: color-mix(in srgb, var(--btn-bg-color), white 10%); --btn-red-bg-color: color-mix(in srgb, var(--git-color), black 50%); --btn-red-bg-color-hover: color-mix(in srgb, var(--git-color), black 40%); @@ -19,14 +20,41 @@ --git-bg-color: color-mix(in srgb, var(--git-color-real), black 30%); --sidebar-color: #242424; + --sidebar-btn-bg-color: #333; + --sidebar-btn-bg-hover-color: color-mix(in srgb, var(--sidebar-btn-bg-color), white 5%); --sidebar-hover-color: color-mix(in srgb, transparent, white 10%); --grid-color: #262626; + + /* old: + --event-color-blue: #6495ed; + --event-color-green: #2ea043; + --event-color-red: #ee3a49; + --event-color-purple: #8250df; + --event-color-yellow: #d9a520; + --event-color-orange: #f66a0a; + --event-color-pink: #e84393; + --event-color-teal: #009688; + --event-color-gray: #9d9c9d;*/ + + --event-color-blue: #00579f; + --event-color-green: #0d7b00; + --event-color-red: #af0800; + --event-color-purple: #7a218f; + --event-color-yellow: #fdc700; + --event-color-orange: #e85d04; + --event-color-pink: #ff5ba1; + --event-color-teal: #00a8ad; + --event-color-gray: #777777; + --event-color-brown: #755020; + + --event-color-git: #f03c2e; } [data-theme='light'] { --bg-color: #f2f2f2; - --text-color: #101010; + --text-color: #1a1a1a; + --text-color-harder: #101010; --text-color-hard: #000; --btn-bg-color: #d5d5d5; @@ -39,6 +67,7 @@ --git-bg-color: color-mix(in srgb, var(--git-color-real), white 10%); --sidebar-color: #eaeaea; + --sidebar-btn-bg-color: #ddd; --sidebar-hover-color: color-mix(in srgb, transparent, black 10%); --grid-color: #d1d1d1; diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..f600e98 --- /dev/null +++ b/src/colors.ts @@ -0,0 +1,30 @@ +import { settings } from './services/settings'; + +export const colorsList = [ + 'blue', + 'green', + 'red', + 'purple', + 'yellow', + 'orange', + 'pink', + 'teal', + 'brown', + 'gray', +] as const; + +export type ColorId = (typeof colorsList)[number]; + +// Just returns the CSS variable name +export function getEventColorCSSVariable(colorId: ColorId): string { + return `var(--event-color-${colorId})`; +} + +export function getColorI18nKey(id: ColorId): string { + return `colors.${id}`; +} + +export function toColorId(color?: string): ColorId { + if (colorsList.includes(color as ColorId)) return color as ColorId; + else return settings.value.defaultEventColor; +} diff --git a/src/components/CalendarList.vue b/src/components/CalendarList.vue index 1188eb9..d929d2a 100644 --- a/src/components/CalendarList.vue +++ b/src/components/CalendarList.vue @@ -1,34 +1,35 @@ @@ -60,27 +90,46 @@ watch( diff --git a/src/components/ColorPicker.vue b/src/components/ColorPicker.vue new file mode 100644 index 0000000..be991af --- /dev/null +++ b/src/components/ColorPicker.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/components/MonthSideMap.vue b/src/components/MonthSideMap.vue index 1bb3a37..04ec96f 100644 --- a/src/components/MonthSideMap.vue +++ b/src/components/MonthSideMap.vue @@ -241,6 +241,10 @@ const hoverInterval = computed(() => { font-weight: 900; position: relative; + &:not(.in-range) { + color: var(--git-color); + } + /* the dot */ &::after { content: ''; diff --git a/src/components/SideBar.vue b/src/components/SideBar.vue index 30c302f..bf1bf92 100644 --- a/src/components/SideBar.vue +++ b/src/components/SideBar.vue @@ -34,6 +34,7 @@ async function syncAndReload() { +
+ +
@@ -364,24 +377,4 @@ onMounted(() => { } } } - -fieldset { - display: contents; - border: 0; - padding: 0; - margin: 0; - - &:disabled > #git-credentials { - opacity: 0.5; - } -} - -.delete-btn { - border: 1px solid var(--git-color); - background-color: var(--btn-red-bg-color); - - &:hover:not(:focus):not(:disabled) { - background-color: var(--btn-red-bg-color-hover); - } -} diff --git a/src/components/modals/EventModal.vue b/src/components/modals/EventModal.vue index 0b16ec0..ad3e2fc 100644 --- a/src/components/modals/EventModal.vue +++ b/src/components/modals/EventModal.vue @@ -10,6 +10,7 @@ import { useAlertModal } from '@/composables/modals/useAlertModal'; import { syncAllWrapper } from '@/services/gitSync'; import cloneDeep from 'lodash-es/cloneDeep'; import { notifyEventsChanged } from '@/composables/useEventsRefresh'; +import { cachedCalendars, getDefaultCalendar, loadCalendars } from '@/services/calendarCache'; const repeatEndOptions = [ { value: 'on', label: 'On' }, @@ -42,6 +43,7 @@ const form = reactive({ toTime: '', calendar: '', entireDay: false, + tagId: '', repeatFreq: Freq.Invalid, repeatEnd: 'after', @@ -49,31 +51,16 @@ const form = reactive({ repeatEndAfter: 5, }); const errors = reactive({ - missingName: false, + missingTitle: false, badToDate: false, badFromDate: false, badUntilDate: false, }); -const calendarNames = ref([]); -const calendarNamesLoaded = ref(false); - -void loadCalendarNames(); - -async function loadCalendarNames() { - try { - calendarNames.value = (await CalendarCore.listCalendars()).map((cal) => cal.name); - } catch (err) { - alert(String(err)); - } finally { - calendarNamesLoaded.value = true; - } -} - -let originalEvent: CalendarEvent | undefined = undefined; +let originalEvent: CalendarEvent | undefined; watch( - [() => thisModal.event.value, calendarNames], - ([newEvent]) => { + () => thisModal.event.value, + (newEvent) => { if (!newEvent) { originalEvent = undefined; return; @@ -83,16 +70,13 @@ watch( const eventForForm = cloneDeep(originalEvent); if (eventForForm.calendar === '') { - if (!calendarNamesLoaded.value) return; - console.log(calendarNames.value); - // calendar unset -> choose one - originalEvent.calendar = eventForForm.calendar = calendarNames.value.includes('default') - ? 'default' - : (calendarNames.value.at(0) ?? ''); + const defaultCal = getDefaultCalendar(); + originalEvent.calendar = eventForForm.calendar = defaultCal?.name ?? ''; } + updateFormFromEvent(originalEvent); }, - { immediate: true }, // fire right onMounted, not wait till first change + { immediate: true }, ); function updateFormFromEvent(event: CalendarEvent | undefined) { @@ -129,6 +113,7 @@ function updateFormFromEvent(event: CalendarEvent | undefined) { } form.calendar = event.calendar; + form.tagId = event.tagId ?? ''; } function reconstructEvent(): CalendarEvent { @@ -138,7 +123,7 @@ function reconstructEvent(): CalendarEvent { location: form.location, description: form.description, calendar: form.calendar, - tag: originalEvent!.tag, // TODO + tagId: form.tagId === '' ? undefined : form.tagId, from: form.entireDay ? DateTime.fromISO(`${form.fromDate}T00:00`, { zone: originalEvent?.from.zone }) : DateTime.fromISO(`${form.fromDate}T${form.fromTime}`, { zone: originalEvent?.from.zone }), @@ -267,7 +252,7 @@ async function deleteRepeatingEvent(strategy: UpdateStrategy) { function validate(event: CalendarEvent): boolean { if (event.title == '') { - errors.missingName = true; + errors.missingTitle = true; return false; } @@ -299,13 +284,19 @@ function validate(event: CalendarEvent): boolean { return true; // ok } -const nameInputField = useTemplateRef('name-input-field'); +const titleInputField = useTemplateRef('title-input-field'); onMounted(async () => { - nameInputField.value?.focus(); // focus name field -}); + titleInputField.value?.focus(); // focus title field + + await loadCalendars(); + + if (form.calendar === '') { + const calendar = getDefaultCalendar(); -// const exampleTags = ref(['School', 'Work', 'Birthday']); // TODO -// const selectedTags = ref([]); + form.calendar = calendar?.name ?? ''; + form.tagId = calendar?.tags[0]?.id ?? ''; + } +});