Skip to content

Commit

Permalink
View options dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
kochrt committed Feb 6, 2023
1 parent d2cb6b1 commit b9f9a6d
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 15 deletions.
2 changes: 2 additions & 0 deletions src/Dialogs/Dialogs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { computed } from "vue";
import JumpToRangeDialog from "@/Jump/JumpToRangeDialog.vue";
import { useJumpStore } from "@/Jump/jumpStore";
import ViewsDialog from "./ViewsDialog.vue";
const jumpStore = useJumpStore();
Expand All @@ -17,6 +18,7 @@ const showJumpToRange = computed({

<template>
<JumpToRangeDialog v-model="showJumpToRange" />
<ViewsDialog></ViewsDialog>
</template>

<style scoped></style>
68 changes: 68 additions & 0 deletions src/Dialogs/ViewOptionRow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
import type { ViewProvider } from "@/viewProvider";
const props = defineProps<{ isActive: boolean; vp: ViewProvider }>();
const emits = defineEmits<{ (event: "click"): void }>();
</script>

<template>
<button
:class="
isActive
? 'border-indigo-500 dark:border-indigo-400'
: 'border-transparent'
"
@click="emits('click')"
class="rounded px-2 py-1 mb-1 bg-white dark:bg-slate-900 font-bold grid border-2 vpButton"
>
<div
v-html="vp.iconSvg"
class="vpIcon flex w-full h-full justify-center items-center px-1"
></div>
<span class="vpName flex px-1"
><span>{{ vp.name }}</span></span
>
<span v-if="isActive" class="ml-auto vpCheckmark">
<svg
class="w-5 h-5 text-indigo-500 dark:text-indigo-400"
focusable="false"
aria-hidden="true"
viewBox="0 0 24 24"
fill="currentColor"
stroke-width="1"
stroke="currentColor"
>
<path
d="M9 16.17 5.53 12.7a.9959.9959 0 0 0-1.41 0c-.39.39-.39 1.02 0 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71c.39-.39.39-1.02 0-1.41a.9959.9959 0 0 0-1.41 0L9 16.17z"
></path></svg
></span>
<div class="vpUrl flex px-1 text-sm text-gray-500">
<span>{{ vp.url }}</span>
</div>
</button>
</template>

<style scoped>
.vpButton {
grid-template-areas:
"icon title checkmark"
". url .";
grid-template-columns: auto 1fr auto;
}
.vpIcon {
grid-area: icon;
}
.vpName {
grid-area: title;
}
.vpCheckmark {
grid-area: checkmark;
}
.vpUrl {
grid-area: url;
}
</style>
108 changes: 108 additions & 0 deletions src/Dialogs/ViewsDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import Dialog from "@/Dialog/Dialog.vue";
import { useViewStore } from "@/Views/viewStore";
import type { ViewProvider } from "@/viewProvider";
import ViewOptionRow from "./ViewOptionRow.vue";
const viewStore = useViewStore();
const viewUrl = ref("");
const addView = () => {
viewStore.views.unshift({
url: viewUrl.value,
name: "View",
capabilities: {
edit: true,
hoveringEvent: true,
jumpToEvent: true,
mobile: true,
},
iconSvg: `<svg xmlns="http:https://www.w3.org/2000/svg" class="w-5 h-5" width="40" height="40" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"></path>
<path d="M12 12l8 -4.5"></path>
<path d="M12 12l0 9"></path>
<path d="M12 12l-8 -4.5"></path>
</svg>`,
uses: {
jump: true,
pages: true,
sort: true,
tags: true,
},
});
viewStore.selectedViewIndex = 0;
viewStore.showingViewsDialog = false;
};
const isActiveView = (v: ViewProvider) => {
const index = viewStore.views.findIndex(
(vo) => vo.name === v.name && v.url === vo.url
);
return index;
};
const toggleView = (v: ViewProvider) => {
const index = isActiveView(v);
if (index < 0) {
viewStore.views.unshift(v);
} else if (viewStore.views.length > 1) {
viewStore.views.splice(index, 1);
}
};
</script>

<template>
<Dialog v-model="viewStore.showingViewsDialog">
<template #header>
<div
class="p-2 flex flex-row items-center dark:text-white text-gray-800 font-bold"
>
Views
</div></template
>
<div class="w-full flex px-2 mb-1 flex-col">
<ViewOptionRow
v-for="view in viewStore.viewOptions"
:vp="view"
:is-active="isActiveView(view) >= 0"
@click="toggleView(view)"
></ViewOptionRow>
</div>
<form class="w-full flex px-2 pb-2 flex-col" @submit.prevent="addView">
<div class="w-full flex flex-row gap-1">
<input
ref="input"
type="text"
placeholder="http:https://localhost:5000"
v-model="viewUrl"
class="w-full py-1 px-2 outline-none rounded-t dark:bg-gray-700 font-bold dark:text-white bg-gray-100"
required
/>
<button
role="button"
type="button"
class="rounded px-2 ml-auto dark:bg-gray-700 dark:text-white font-bold"
@click.prevent="addView"
>
Add
</button>
</div>
</form>
<div class="flex flex-row w-full pb-2 px-2">
<div class="flex flex-row ml-auto">
<button
role="button"
type="button"
class="rounded px-2 ml-auto dark:bg-gray-700 dark:text-white font-bold"
@click.prevent="viewStore.showingViewsDialog = false"
>
Done
</button>
</div>
</div>
</Dialog>
</template>

<style scoped></style>
16 changes: 16 additions & 0 deletions src/Drawer/ViewSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,26 @@ const isViewSelected = (view: ViewProvider, index: number) => {
const select = (i: number) => {
viewStore.setSelectedViewIndex(i);
};
const showAddView = () => {
viewStore.showingViewsDialog = true
}
</script>

<template>
<div class="flex flex-col rounded print-hidden items-center">
<ViewSwitcherButton :selected="false" title="Add view" @click="showAddView">
<svg
xmlns="http:https://www.w3.org/2000/svg"
viewBox="4 4 12 12"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M10.75 6.75a.75.75 0 00-1.5 0v2.5h-2.5a.75.75 0 000 1.5h2.5v2.5a.75.75 0 001.5 0v-2.5h2.5a.75.75 0 000-1.5h-2.5v-2.5z"
></path>
</svg>
</ViewSwitcherButton>
<ViewSwitcherButton
v-for="(view, i) in viewStore.views"
:key="view.name"
Expand Down
6 changes: 3 additions & 3 deletions src/Panels/Panels.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const currentViewComponent = computed(() => {
// if (viewStore.currentView.name === "Gantt") {
// return Timeline;
// }
return viewStore.currentView.component();
return viewStore.currentView.url;
});
const frames = ref<HTMLIFrameElement[]>();
Expand Down Expand Up @@ -69,8 +69,8 @@ onMounted(() => {
sandbox="allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts allow-top-navigation allow-top-navigation-by-user-activation allow-same-origin"
v-for="component in viewStore.framedViews"
class="w-full h-full"
v-show="currentViewComponent === component.component()"
:src="component.component()"
v-show="currentViewComponent === component.url"
:src="component.url"
:id="`view_${component.name}`"
></iframe>
<keep-alive>
Expand Down
1 change: 0 additions & 1 deletion src/Views/ViewOrchestrator/useLpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export function useLpc<ViewSpecificMessageTypes = {}>(
}

const data = e.data;
console.log(data)
if (data.response) {
calls.get(data.id)?.resolve(data);
calls.delete(data.id);
Expand Down
3 changes: 1 addition & 2 deletions src/Views/useViewProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ViewProvider } from "@/viewProvider";

export const useTimelineExternalProvider = () => ({
name: "Timeline",
component: () => "https://timeline.markwhen.com",
url: "https://timeline.markwhen.com",
iconSvg: `<svg xmlns="http:https://www.w3.org/2000/svg" viewBox="0 0 16 16" class="h-5 w-5"><path
fill="currentColor"
d="m 13 13 h -5 c -0.55 0 -1 -0.45 -1 -1 s 0.45 -1 1 -1 h 5 c 0.55 0 1 0.45 1 1 s -0.45 1 -1 1 z m -1 -4 h -7 c -0.55 0 -1 -0.45 -1 -1 s 0.45 -1 1 -1 h 7 c 0.55 0 1 0.45 1 1 s -0.45 1 -1 1 z m -9 -4 h 0 c -0.55 0 -1 -0.45 -1 -1 s 0.45 -1 1 -1 h 2 c 0.55 0 1 0.45 1 1 s -0.45 1 -1 1 z">
Expand All @@ -16,7 +16,6 @@ export const useTimelineExternalProvider = () => ({
pages: true,
jump: true,
},
framed: true,
});

export const useViewProviders: () => ViewProvider[] = () => {
Expand Down
66 changes: 59 additions & 7 deletions src/Views/viewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,78 @@ import { useViewProviders } from "@/Views/useViewProviders";
import type { ViewProvider } from "@/viewProvider";
import { useMediaQuery } from "@vueuse/core";
import { defineStore } from "pinia";
import { computed, ref, watchEffect } from "vue";
import { computed, ref, watch, watchEffect } from "vue";
import { useViewOrchestrator } from "./ViewOrchestrator/useViewOrchestrator";

export const useViewStore = defineStore("views", () => {
const selectedViewIndex = ref(-1);
const isMobile = useMediaQuery("(max-width: 1024px)");
// const timelineStore = useTimelineStore();
const activeFrame = ref<HTMLIFrameElement>();
const showingViewsDialog = ref(false);

const setActiveFrame = (frame?: HTMLIFrameElement) => {
activeFrame.value = frame;
};

const views = computed<ViewProvider[]>(() =>
isMobile.value ? useMobileViewProviders() : useViewProviders()
);
const framedViews = computed(() => views.value.filter((v) => v.framed));
const framedViews = computed(() => views.value.filter((v) => v.url));
const currentView = computed<ViewProvider>(
() => views.value[selectedViewIndex.value]
);

const getViewOptions = () => {
const defaultOptions = useViewProviders();
if (typeof localStorage !== "undefined") {
const options = localStorage.getItem("viewOptions");
if (options) {
try {
const parsed = JSON.parse(options);
return parsed as ViewProvider[];
} catch {
return defaultOptions;
}
}
return defaultOptions;
} else {
return defaultOptions;
}
};

const viewOptions = ref(getViewOptions());

const views = ref<ViewProvider[]>(
isMobile.value ? useMobileViewProviders() : useViewProviders()
);

watch(
viewOptions,
(v) => {
if (typeof localStorage !== "undefined") {
localStorage.setItem("viewOptions", JSON.stringify(v));
}
},
{ deep: true }
);

watch(
views,
(v) => {
for (const view of v) {
if (!viewOptions.value) {
viewOptions.value = v;
}
if (
viewOptions.value.findIndex(
(vo) => vo.name === view.name && vo.url === view.url
) < 0
) {
viewOptions.value.push(view);
}
}
},
{ deep: true }
);

watchEffect(() => {
if (selectedViewIndex.value >= views.value.length) {
selectedViewIndex.value = 0;
Expand All @@ -37,14 +88,15 @@ export const useViewStore = defineStore("views", () => {
selectedViewIndex.value = i;
};

const { jumpToRange, jumpToPath } =
useViewOrchestrator(activeFrame);
const { jumpToRange, jumpToPath } = useViewOrchestrator(activeFrame);

return {
// state
selectedViewIndex,
views,
isMobile,
showingViewsDialog,
viewOptions,

// getters
currentView,
Expand Down
4 changes: 2 additions & 2 deletions src/viewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export interface ViewSetting {
}

export interface ViewProvider {
component: () => any,
framed: boolean
url: string,
name: string,
iconSvg?: string,
settings?: (() => ViewSetting | any)[]
capabilities?: ViewCapabilities
uses?: ViewUses
active?: boolean
}

0 comments on commit b9f9a6d

Please sign in to comment.