@nextcloud/vue
Version:
Nextcloud vue components
379 lines (378 loc) • 13.6 kB
JavaScript
import '../assets/NcAppContent-HF21N7dO.css';
import { getBuilder } from "@nextcloud/browser-storage";
import { getCapabilities } from "@nextcloud/capabilities";
import { emit } from "@nextcloud/event-bus";
import { useSwipe } from "@vueuse/core";
import { Splitpanes, Pane } from "splitpanes";
import { defineComponent, watch, onMounted, onBeforeUnmount, createBlock, openBlock, unref, normalizeClass, withCtx, createVNode, resolveComponent, createElementBlock, createCommentVNode, renderSlot, toDisplayString, Fragment, withDirectives, withModifiers, createElementVNode, vShow } from "vue";
import { m as mdiArrowRight } from "./mdi-XFJRiRqJ.mjs";
import { useIsMobile } from "../composables/useIsMobile/index.mjs";
import { r as register, a as t } from "./_l10n-DrTiip5c.mjs";
import { N as NcButton } from "./NcButton-Dc8V4Urj.mjs";
import { N as NcIconSvgWrapper } from "./NcIconSvgWrapper-BvLanNaW.mjs";
import { _ as _export_sfc } from "./_plugin-vue_export-helper-1tPrXgE0.mjs";
import { g as getLocalizedAppName, A as APP_NAME } from "./appName-DtnLUijR.mjs";
import { l as logger } from "./logger-D3RVzcfQ.mjs";
import { i as isRtl } from "./rtl-v0UOPAM7.mjs";
import "splitpanes/dist/splitpanes.css";
register();
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
__name: "NcAppContentDetailsToggle",
setup(__props) {
const isMobile = useIsMobile();
watch(isMobile, toggleAppNavigationButton);
onMounted(() => {
toggleAppNavigationButton(isMobile.value);
});
onBeforeUnmount(() => {
if (isMobile.value) {
toggleAppNavigationButton(false);
}
});
function toggleAppNavigationButton(hide = true) {
const appNavigationToggle = document.querySelector(".app-navigation .app-navigation-toggle");
if (appNavigationToggle) {
appNavigationToggle.style.display = hide ? "none" : "";
if (hide === true) {
emit("toggle-navigation", { open: false });
}
}
}
return (_ctx, _cache) => {
return openBlock(), createBlock(unref(NcButton), {
"aria-label": unref(t)("Go back to the list"),
class: normalizeClass(["app-details-toggle", { "app-details-toggle--mobile": unref(isMobile) }]),
title: unref(t)("Go back to the list"),
variant: "tertiary"
}, {
icon: withCtx(() => [
createVNode(unref(NcIconSvgWrapper), {
directional: "",
path: unref(mdiArrowRight)
}, null, 8, ["path"])
]),
_: 1
}, 8, ["aria-label", "class", "title"]);
};
}
});
const NcAppContentDetailsToggle = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-a28923a1"]]);
const browserStorage = getBuilder("nextcloud").persist().build();
const instanceName = getCapabilities().theming?.name ?? "Nextcloud";
const _sfc_main = {
name: "NcAppContent",
components: {
NcAppContentDetailsToggle,
Pane,
Splitpanes
},
props: {
/**
* Allows to disable the control by swipe of the app navigation open state.
*/
disableSwipe: {
type: Boolean,
default: false
},
/**
* Allows you to set the default width of the resizable list in % on vertical-split
* or respectively the default height on horizontal-split.
*
* Must be between `listMinWidth` and `listMaxWidth`.
*/
listSize: {
type: Number,
default: 20
},
/**
* Allows you to set the minimum width of the list column in % on vertical-split
* or respectively the minimum height on horizontal-split.
*/
listMinWidth: {
type: Number,
default: 15
},
/**
* Allows you to set the maximum width of the list column in % on vertical-split
* or respectively the maximum height on horizontal-split.
*/
listMaxWidth: {
type: Number,
default: 40
},
/**
* Specify the config key for the pane config sizes
* Default is the global var appName if you use the webpack-vue-config
*/
paneConfigKey: {
type: String,
default: ""
},
/**
* When in mobile view, only the list or the details are shown.
*
* If you provide a list, you need to provide a variable
* that will be set to true by the user when an element of
* the list gets selected. The details will then show a back
* arrow to return to the list that will update this prop to false.
*/
showDetails: {
type: Boolean,
default: true
},
/**
* Content layout used when there is a list together with content:
* - `vertical-split` - a 2-column layout with list and default content separated vertically
* - `no-split` - a single column layout; List is shown when `showDetails` is `false`, otherwise the default slot content is shown with a back button to return to the list.
* - 'horizontal-split' - a 2-column layout with list and default content separated horizontally
* On mobile screen `no-split` layout is forced.
*/
layout: {
type: String,
default: "vertical-split",
validator(value) {
return ["no-split", "vertical-split", "horizontal-split"].includes(value);
}
},
/**
* Specify the `<h1>` page heading
*/
pageHeading: {
type: String,
default: null
},
/**
* Allow setting the page's `<title>`
*
* If a page heading is set it defaults to `{pageHeading} - {appName} - {instanceName}` e.g. `Favorites - Files - MyPersonalCloud`.
* When the page heading and the app name is the same only one is used, e.g. `Files - Files - MyPersonalCloud` is shown as `Files - MyPersonalCloud`.
* When setting the prop then the following format will be used: `{pageTitle} - {instanceName}`
*/
pageTitle: {
type: String,
default: null
}
},
emits: [
"update:showDetails",
"resizeList"
],
setup() {
return {
isMobile: useIsMobile(),
isRtl
};
},
data() {
return {
contentHeight: 0,
swiping: {},
listPaneSize: this.restorePaneConfig()
};
},
computed: {
paneConfigID() {
if (this.paneConfigKey !== "") {
return `pane-list-size-${this.paneConfigKey}`;
}
try {
return `pane-list-size-${APP_NAME}`;
} catch {
logger.info("[NcAppContent]: falling back to global nextcloud pane config");
return "pane-list-size-nextcloud";
}
},
detailsPaneSize() {
if (this.listPaneSize) {
return 100 - this.listPaneSize;
}
return this.paneDefaults.details.size;
},
paneDefaults() {
return {
list: {
size: this.listSize,
min: this.listMinWidth,
max: this.listMaxWidth
},
// set the inverse values of the details column
// based on the provided (or default) values of the list column
details: {
size: 100 - this.listSize,
min: 100 - this.listMaxWidth,
max: 100 - this.listMinWidth
}
};
},
realPageTitle() {
const entries = /* @__PURE__ */ new Set();
if (this.pageTitle) {
for (const part of this.pageTitle.split(" - ")) {
entries.add(part);
}
} else if (this.pageHeading) {
for (const part of this.pageHeading.split(" - ")) {
entries.add(part);
}
if (entries.size > 0) {
entries.add(getLocalizedAppName());
}
} else {
return null;
}
entries.add(instanceName);
return [...entries.values()].join(" - ");
}
},
watch: {
realPageTitle: {
immediate: true,
handler() {
if (this.realPageTitle !== null) {
document.title = this.realPageTitle;
}
}
},
paneConfigKey: {
immediate: true,
handler() {
this.restorePaneConfig();
}
}
},
mounted() {
if (!this.disableSwipe) {
this.swiping = useSwipe(this.$el, {
onSwipeEnd: this.handleSwipe
});
}
this.restorePaneConfig();
},
methods: {
/**
* handle the swipe event
*
* @param {TouchEvent} e The touch event
* @param {import('@vueuse/core').SwipeDirection} direction The swipe direction of the event
*/
handleSwipe(e, direction) {
const minSwipeX = 70;
const touchZone = 300;
if (Math.abs(this.swiping.lengthX) > minSwipeX) {
if (this.swiping.coordsStart.x < touchZone / 2 && direction === "right") {
emit("toggle-navigation", {
open: true
});
} else if (this.swiping.coordsStart.x < touchZone * 1.5 && direction === "left") {
emit("toggle-navigation", {
open: false
});
}
}
},
handlePaneResize(event) {
const listPaneSize = parseInt(event.panes[0].size, 10);
browserStorage.setItem(this.paneConfigID, JSON.stringify(listPaneSize));
this.listPaneSize = listPaneSize;
this.$emit("resizeList", { size: listPaneSize });
logger.debug("[NcAppContent] pane config", { listPaneSize });
},
// browserStorage is not reactive, we need to update this manually
restorePaneConfig() {
const listPaneSize = parseInt(browserStorage.getItem(this.paneConfigID), 10);
if (!isNaN(listPaneSize) && listPaneSize !== this.listPaneSize) {
logger.debug("[NcAppContent] pane config", { listPaneSize });
this.listPaneSize = listPaneSize;
return listPaneSize;
}
},
/**
* The user clicked the back arrow from the details view
*/
hideDetails() {
this.$emit("update:showDetails", false);
}
}
};
const _hoisted_1 = {
key: 0,
class: "hidden-visually"
};
const _hoisted_2 = {
key: 1,
class: "app-content-wrapper"
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_NcAppContentDetailsToggle = resolveComponent("NcAppContentDetailsToggle");
const _component_Pane = resolveComponent("Pane");
const _component_Splitpanes = resolveComponent("Splitpanes");
return openBlock(), createElementBlock("main", {
id: "app-content-vue",
class: normalizeClass(["app-content no-snapper", { "app-content--has-list": !!_ctx.$slots.list }])
}, [
$props.pageHeading ? (openBlock(), createElementBlock("h1", _hoisted_1, toDisplayString($props.pageHeading), 1)) : createCommentVNode("", true),
!!_ctx.$slots.list ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
$setup.isMobile || $props.layout === "no-split" ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(["app-content-wrapper app-content-wrapper--no-split", {
"app-content-wrapper--show-details": $props.showDetails,
"app-content-wrapper--show-list": !$props.showDetails,
"app-content-wrapper--mobile": $setup.isMobile
}])
}, [
$props.showDetails ? (openBlock(), createBlock(_component_NcAppContentDetailsToggle, {
key: 0,
onClick: withModifiers($options.hideDetails, ["stop", "prevent"])
}, null, 8, ["onClick"])) : createCommentVNode("", true),
withDirectives(createElementVNode("div", null, [
renderSlot(_ctx.$slots, "list", {}, void 0, true)
], 512), [
[vShow, !$props.showDetails]
]),
$props.showDetails ? renderSlot(_ctx.$slots, "default", { key: 1 }, void 0, true) : createCommentVNode("", true)
], 2)) : $props.layout === "vertical-split" || $props.layout === "horizontal-split" ? (openBlock(), createElementBlock("div", _hoisted_2, [
createVNode(_component_Splitpanes, {
horizontal: $props.layout === "horizontal-split",
class: normalizeClass(["default-theme", {
"splitpanes--horizontal": $props.layout === "horizontal-split",
"splitpanes--vertical": $props.layout === "vertical-split"
}]),
rtl: $setup.isRtl,
onResized: $options.handlePaneResize
}, {
default: withCtx(() => [
createVNode(_component_Pane, {
class: "splitpanes__pane-list",
size: $data.listPaneSize || $options.paneDefaults.list.size,
"min-size": $options.paneDefaults.list.min,
"max-size": $options.paneDefaults.list.max
}, {
default: withCtx(() => [
renderSlot(_ctx.$slots, "list", {}, void 0, true)
]),
_: 3
}, 8, ["size", "min-size", "max-size"]),
createVNode(_component_Pane, {
class: "splitpanes__pane-details",
size: $options.detailsPaneSize,
"min-size": $options.paneDefaults.details.min,
"max-size": $options.paneDefaults.details.max
}, {
default: withCtx(() => [
renderSlot(_ctx.$slots, "default", {}, void 0, true)
]),
_: 3
}, 8, ["size", "min-size", "max-size"])
]),
_: 3
}, 8, ["horizontal", "class", "rtl", "onResized"])
])) : createCommentVNode("", true)
], 64)) : createCommentVNode("", true),
!_ctx.$slots.list ? renderSlot(_ctx.$slots, "default", { key: 2 }, void 0, true) : createCommentVNode("", true)
], 2);
}
const NcAppContent = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-a2641cc2"]]);
export {
NcAppContent as N
};
//# sourceMappingURL=NcAppContent-CTpYDkuT.mjs.map