feat: add new 'waterpark' theme, based on wuttaweb w/ vue2 + buefy
hoping to eventually replace the 'default' view with this one, if all goes well. definitely needs more testing and is not exposed as an option yet, unless configured
This commit is contained in:
parent
1ec1eba496
commit
59bd58aca7
23 changed files with 937 additions and 44 deletions
486
tailbone/templates/themes/waterpark/base.mako
Normal file
486
tailbone/templates/themes/waterpark/base.mako
Normal file
|
@ -0,0 +1,486 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="wuttaweb:templates/base.mako" />
|
||||
<%namespace name="base_meta" file="/base_meta.mako" />
|
||||
<%namespace file="/formposter.mako" import="declare_formposter_mixin" />
|
||||
<%namespace file="/grids/filter-components.mako" import="make_grid_filter_components" />
|
||||
<%namespace name="page_help" file="/page_help.mako" />
|
||||
|
||||
<%def name="base_styles()">
|
||||
${parent.base_styles()}
|
||||
<style>
|
||||
|
||||
.filters .filter-fieldname .field,
|
||||
.filters .filter-fieldname .field label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filters .filter-fieldname,
|
||||
.filters .filter-fieldname .field label,
|
||||
.filters .filter-fieldname .button {
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.filters .filter-verb .select,
|
||||
.filters .filter-verb .select select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
% if filter_fieldname_width is not Undefined:
|
||||
|
||||
.filters .filter-fieldname,
|
||||
.filters .filter-fieldname .button {
|
||||
min-width: ${filter_fieldname_width};
|
||||
}
|
||||
|
||||
.filters .filter-verb {
|
||||
min-width: ${filter_verb_width};
|
||||
}
|
||||
|
||||
% endif
|
||||
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="before_content()">
|
||||
## TODO: this must come before the self.body() call..but why?
|
||||
${declare_formposter_mixin()}
|
||||
</%def>
|
||||
|
||||
<%def name="render_navbar_brand()">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="${url('home')}"
|
||||
v-show="!menuSearchActive">
|
||||
${base_meta.header_logo()}
|
||||
<div id="global-header-title">
|
||||
${base_meta.global_title()}
|
||||
</div>
|
||||
</a>
|
||||
<div v-show="menuSearchActive"
|
||||
class="navbar-item">
|
||||
<b-autocomplete ref="menuSearchAutocomplete"
|
||||
v-model="menuSearchTerm"
|
||||
:data="menuSearchFilteredData"
|
||||
field="label"
|
||||
open-on-focus
|
||||
keep-first
|
||||
icon-pack="fas"
|
||||
clearable
|
||||
@keydown.native="menuSearchKeydown"
|
||||
@select="menuSearchSelect">
|
||||
</b-autocomplete>
|
||||
</div>
|
||||
<a role="button" class="navbar-burger" data-target="navbar-menu" aria-label="menu" aria-expanded="false">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="render_navbar_start()">
|
||||
<div class="navbar-start">
|
||||
|
||||
<div v-if="menuSearchData.length"
|
||||
class="navbar-item">
|
||||
<b-button type="is-primary"
|
||||
size="is-small"
|
||||
@click="menuSearchInit()">
|
||||
<span><i class="fa fa-search"></i></span>
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
% for topitem in menus:
|
||||
% if topitem['is_link']:
|
||||
${h.link_to(topitem['title'], topitem['url'], target=topitem['target'], class_='navbar-item')}
|
||||
% else:
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">${topitem['title']}</a>
|
||||
<div class="navbar-dropdown">
|
||||
% for item in topitem['items']:
|
||||
% if item['is_menu']:
|
||||
<% item_hash = id(item) %>
|
||||
<% toggle = 'menu_{}_shown'.format(item_hash) %>
|
||||
<div>
|
||||
<a class="navbar-link" @click.prevent="toggleNestedMenu('${item_hash}')">
|
||||
${item['title']}
|
||||
</a>
|
||||
</div>
|
||||
% for subitem in item['items']:
|
||||
% if subitem['is_sep']:
|
||||
<hr class="navbar-divider" v-show="${toggle}">
|
||||
% else:
|
||||
${h.link_to("{}".format(subitem['title']), subitem['url'], class_='navbar-item nested', target=subitem['target'], **{'v-show': toggle})}
|
||||
% endif
|
||||
% endfor
|
||||
% else:
|
||||
% if item['is_sep']:
|
||||
<hr class="navbar-divider">
|
||||
% else:
|
||||
${h.link_to(item['title'], item['url'], class_='navbar-item', target=item['target'])}
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="render_theme_picker()">
|
||||
% if expose_theme_picker and request.has_perm('common.change_app_theme'):
|
||||
<div class="level-item">
|
||||
${h.form(url('change_theme'), method="post", ref='themePickerForm')}
|
||||
${h.csrf_token(request)}
|
||||
<input type="hidden" name="referrer" :value="referrer" />
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span>Theme:</span>
|
||||
<b-select name="theme"
|
||||
v-model="globalTheme"
|
||||
@input="changeTheme()">
|
||||
% for option in theme_picker_options:
|
||||
<option value="${option.value}">
|
||||
${option.label}
|
||||
</option>
|
||||
% endfor
|
||||
</b-select>
|
||||
</div>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_feedback_button()">
|
||||
|
||||
<div class="level-item">
|
||||
<page-help
|
||||
% if can_edit_help:
|
||||
@configure-fields-help="configureFieldsHelp = true"
|
||||
% endif
|
||||
/>
|
||||
</div>
|
||||
|
||||
% if request.has_perm('common.feedback'):
|
||||
<feedback-form
|
||||
action="${url('feedback')}"
|
||||
:message="feedbackMessage">
|
||||
</feedback-form>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_this_page_component()">
|
||||
<this-page @change-content-title="changeContentTitle"
|
||||
% if can_edit_help:
|
||||
:configure-fields-help="configureFieldsHelp"
|
||||
% endif
|
||||
/>
|
||||
</%def>
|
||||
|
||||
<%def name="render_vue_templates()">
|
||||
${parent.render_vue_templates()}
|
||||
|
||||
${page_help.render_template()}
|
||||
${page_help.declare_vars()}
|
||||
|
||||
% if request.has_perm('common.feedback'):
|
||||
<script type="text/x-template" id="feedback-template">
|
||||
<div>
|
||||
|
||||
<div class="level-item">
|
||||
<b-button type="is-primary"
|
||||
@click="showFeedback()"
|
||||
icon-pack="fas"
|
||||
icon-left="comment">
|
||||
Feedback
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="showDialog">
|
||||
<div class="modal-card">
|
||||
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">User Feedback</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body">
|
||||
<p class="block">
|
||||
Questions, suggestions, comments, complaints, etc.
|
||||
<span class="red">regarding this website</span> are
|
||||
welcome and may be submitted below.
|
||||
</p>
|
||||
|
||||
<b-field label="User Name">
|
||||
<b-input v-model="userName"
|
||||
% if request.user:
|
||||
disabled
|
||||
% endif
|
||||
>
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Referring URL">
|
||||
<b-input
|
||||
v-model="referrer"
|
||||
disabled="true">
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Message">
|
||||
<b-input type="textarea"
|
||||
v-model="message"
|
||||
ref="textarea">
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<b-checkbox v-model="pleaseReply"
|
||||
@input="pleaseReplyChanged">
|
||||
Please email me back{{ pleaseReply ? " at: " : "" }}
|
||||
</b-checkbox>
|
||||
</div>
|
||||
<div class="level-item" v-show="pleaseReply">
|
||||
<b-input v-model="userEmail"
|
||||
ref="userEmail">
|
||||
</b-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<b-button @click="showDialog = false">
|
||||
Cancel
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="paper-plane"
|
||||
@click="sendFeedback()"
|
||||
:disabled="sendingFeedback || !message.trim()">
|
||||
{{ sendingFeedback ? "Working, please wait..." : "Send Message" }}
|
||||
</b-button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
<script>
|
||||
|
||||
const FeedbackForm = {
|
||||
template: '#feedback-template',
|
||||
mixins: [SimpleRequestMixin],
|
||||
props: [
|
||||
'action',
|
||||
'message',
|
||||
],
|
||||
methods: {
|
||||
|
||||
showFeedback() {
|
||||
this.referrer = location.href
|
||||
this.showDialog = true
|
||||
this.$nextTick(function() {
|
||||
this.$refs.textarea.focus()
|
||||
})
|
||||
},
|
||||
|
||||
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||
pleaseReplyChanged(value) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.userEmail.focus()
|
||||
})
|
||||
},
|
||||
% endif
|
||||
|
||||
sendFeedback() {
|
||||
this.sendingFeedback = true
|
||||
|
||||
const params = {
|
||||
referrer: this.referrer,
|
||||
user: this.userUUID,
|
||||
user_name: this.userName,
|
||||
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||
please_reply_to: this.pleaseReply ? this.userEmail : null,
|
||||
% endif
|
||||
message: this.message.trim(),
|
||||
}
|
||||
|
||||
this.simplePOST(this.action, params, response => {
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: "Message sent! Thank you for your feedback.",
|
||||
type: 'is-info',
|
||||
duration: 4000, // 4 seconds
|
||||
})
|
||||
|
||||
this.showDialog = false
|
||||
// clear out message, in case they need to send another
|
||||
this.message = ""
|
||||
this.sendingFeedback = false
|
||||
|
||||
}, response => { // failure
|
||||
this.sendingFeedback = false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const FeedbackFormData = {
|
||||
referrer: null,
|
||||
userUUID: null,
|
||||
userName: null,
|
||||
userEmail: null,
|
||||
% if config.get_bool('tailbone.feedback_allows_reply'):
|
||||
pleaseReply: false,
|
||||
% endif
|
||||
showDialog: false,
|
||||
sendingFeedback: false,
|
||||
}
|
||||
|
||||
</script>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="modify_vue_vars()">
|
||||
${parent.modify_vue_vars()}
|
||||
<script>
|
||||
|
||||
##############################
|
||||
## menu search
|
||||
##############################
|
||||
|
||||
WholePageData.menuSearchActive = false
|
||||
WholePageData.menuSearchTerm = ''
|
||||
WholePageData.menuSearchData = ${json.dumps(global_search_data or [])|n}
|
||||
|
||||
WholePage.computed.menuSearchFilteredData = function() {
|
||||
if (!this.menuSearchTerm.length) {
|
||||
return this.menuSearchData
|
||||
}
|
||||
|
||||
const terms = []
|
||||
for (let term of this.menuSearchTerm.toLowerCase().split(' ')) {
|
||||
term = term.trim()
|
||||
if (term) {
|
||||
terms.push(term)
|
||||
}
|
||||
}
|
||||
if (!terms.length) {
|
||||
return this.menuSearchData
|
||||
}
|
||||
|
||||
// all terms must match
|
||||
return this.menuSearchData.filter((option) => {
|
||||
const label = option.label.toLowerCase()
|
||||
for (const term of terms) {
|
||||
if (label.indexOf(term) < 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
WholePage.methods.globalKey = function(event) {
|
||||
|
||||
// Ctrl+8 opens menu search
|
||||
if (event.target.tagName == 'BODY') {
|
||||
if (event.ctrlKey && event.key == '8') {
|
||||
this.menuSearchInit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WholePage.mounted = function() {
|
||||
window.addEventListener('keydown', this.globalKey)
|
||||
for (let hook of this.mountedHooks) {
|
||||
hook(this)
|
||||
}
|
||||
}
|
||||
|
||||
WholePage.beforeDestroy = function() {
|
||||
window.removeEventListener('keydown', this.globalKey)
|
||||
}
|
||||
|
||||
WholePage.methods.menuSearchInit = function() {
|
||||
this.menuSearchTerm = ''
|
||||
this.menuSearchActive = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.menuSearchAutocomplete.focus()
|
||||
})
|
||||
}
|
||||
|
||||
WholePage.methods.menuSearchKeydown = function(event) {
|
||||
|
||||
// ESC will dismiss searchbox
|
||||
if (event.which == 27) {
|
||||
this.menuSearchActive = false
|
||||
}
|
||||
}
|
||||
|
||||
WholePage.methods.menuSearchSelect = function(option) {
|
||||
location.href = option.url
|
||||
}
|
||||
|
||||
##############################
|
||||
## theme picker
|
||||
##############################
|
||||
|
||||
% if expose_theme_picker and request.has_perm('common.change_app_theme'):
|
||||
|
||||
WholePageData.globalTheme = ${json.dumps(theme or None)|n}
|
||||
## WholePageData.referrer = location.href
|
||||
|
||||
WholePage.methods.changeTheme = function() {
|
||||
this.$refs.themePickerForm.submit()
|
||||
}
|
||||
|
||||
% endif
|
||||
|
||||
##############################
|
||||
## feedback
|
||||
##############################
|
||||
|
||||
% if request.has_perm('common.feedback'):
|
||||
|
||||
WholePageData.feedbackMessage = ""
|
||||
|
||||
% if request.user:
|
||||
FeedbackFormData.userUUID = ${json.dumps(request.user.uuid)|n}
|
||||
FeedbackFormData.userName = ${json.dumps(str(request.user))|n}
|
||||
% endif
|
||||
|
||||
% endif
|
||||
|
||||
##############################
|
||||
## edit fields help
|
||||
##############################
|
||||
|
||||
% if can_edit_help:
|
||||
WholePageData.configureFieldsHelp = false
|
||||
% endif
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_vue_components()">
|
||||
${parent.make_vue_components()}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.datepicker.js') + f'?ver={tailbone.__version__}')}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.numericinput.js') + f'?ver={tailbone.__version__}')}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.oncebutton.js') + f'?ver={tailbone.__version__}')}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.timepicker.js') + f'?ver={tailbone.__version__}')}
|
||||
${make_grid_filter_components()}
|
||||
${page_help.make_component()}
|
||||
% if request.has_perm('common.feedback'):
|
||||
<script>
|
||||
FeedbackForm.data = function() { return FeedbackFormData }
|
||||
Vue.component('feedback-form', FeedbackForm)
|
||||
</script>
|
||||
% endif
|
||||
</%def>
|
Loading…
Add table
Add a link
Reference in a new issue