Skip to content

Commit

Permalink
banners now configuable in admin interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Hanke committed Jan 30, 2025
1 parent 8f4a8f2 commit e0db305
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 95 deletions.
11 changes: 8 additions & 3 deletions ckanext/matolabtheme/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ def serialize_row(row):
return {column: getattr(row, column) for column in row._mapping}


@toolkit.side_effect_free
def theme_stats(context: Context, data_dict: dict[str, Any]) -> dict[str, Any]:
package = table("package")
resource = table("resource")
session = model.Session
s = (
select(
package.c["owner_org"],
Expand All @@ -42,15 +44,18 @@ def theme_stats(context: Context, data_dict: dict[str, Any]) -> dict[str, Any]:
.order_by(func.count(package.c["id"]).desc())
# .limit(limit)
)
org_rows = model.Session.execute(s).fetchall()
conn: Any = model.Session.connection()
# cursor = conn.execute(q)

org_rows = conn.execute(s).fetchall()
s = select(
func.count(resource.c["id"]),
).where(
and_(
resource.c["state"] != "deleted",
)
)
res_rows = model.Session.execute(s).fetchall()
res_rows = conn.execute(s).fetchall()
orgs = [serialize_row(row) for row in org_rows]
org_count = len(orgs)
pkg_sum = sum([org.get("count", 0) for org in orgs])
Expand All @@ -65,5 +70,5 @@ def theme_stats(context: Context, data_dict: dict[str, Any]) -> dict[str, Any]:


def get_actions():
actions = {"theme_stats": theme_stats}
actions = {"matolabtheme_stats": theme_stats}
return actions
119 changes: 54 additions & 65 deletions ckanext/matolabtheme/assets/index.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,65 @@
ckan.module("matolabtheme-module", function ($, _) {
"use strict";
return {
options: {
debug: false,
},
initialize: function () {
console.log("Initialized Counter Animation for element: ", this.el);
'use strict';

const updateCounter = (counter, targetValue) => {
counter.innerText = "0"; // Start the counter from 0
counter.setAttribute("data-target", targetValue); // Update data-target attribute
ckan.module('matolabtheme-module', function ($) {
return {
initialize: function () {
console.log('matolabtheme-module initialized!');
console.log('Element:', this.el);

const duration = +counter.getAttribute("data-duration") * 1000;
const increment = targetValue / (duration / 10);
let count = 0;
const updateCounter = (counter, targetValue) => {
counter.innerText = '0'; // Start the counter from 0
counter.setAttribute('data-target', targetValue); // Update data-target attribute

const animate = () => {
count += increment;
const duration = +counter.getAttribute('data-duration') * 1000;
const increment = targetValue / (duration / 10);
let count = 0;

if (count >= targetValue) {
counter.innerText = targetValue; // Final value
} else {
counter.innerText = Math.ceil(count); // Animated value
requestAnimationFrame(animate);
}
};
const animate = () => {
count += increment;

requestAnimationFrame(animate);
};
if (count >= targetValue) {
counter.innerText = targetValue; // Final value
} else {
counter.innerText = Math.ceil(count); // Animated value
requestAnimationFrame(animate);
}
};

const fetchAndAnimateCounter = async () => {
const apiUrl = this.el.get(0).getAttribute("data-api-url"); // Fetch the API URL from the parent element
if (!apiUrl) {
console.error("No API URL specified for counter:", this.el);
return;
}
requestAnimationFrame(animate);
};

try {
const response = await $.getJSON(apiUrl);
let datasetCount = 0; // Count of datasets
let totalResources = 0; // Sum of all resources
const organizations = new Set(); // To store unique organizations
let orgCount = 0; // Sum of organisations
const fetchAndAnimateCounter = async () => {
const apiUrl = this.el.get(0).getAttribute('data-api-url');
if (!apiUrl) {
console.error('No API URL specified for counter:', this.el);
return;
}

// Check if the response is successful and contains results
if (response && typeof response === 'object'){
datasetCount = response.pkg_count; // Count of datasets
try {
const response = await $.getJSON(apiUrl);
let datasetCount = 0;
let totalResources = 0;
let orgCount = 0;

// Calculate total resources using num_resources property
totalResources=response.res_count;
orgCount=response.org_count;
} else {
console.error("Invalid API response structure:", response);
}
if (response && response.success && response.result) {
const result = response.result;
// Count datasets, resources, and organizations
datasetCount = result.pkg_count || 0; // `pkg_count` for datasets
totalResources = result.res_count || 0; // `res_count` for resources
orgCount = result.org_count || 0; // `org_count` for organizations
} else {
console.error('Invalid API response structure:', response);
}

// Update and animate the counters independently
updateCounter(document.getElementById("dataset_counter"), datasetCount); // For datasets
updateCounter(document.getElementById("resource_counter"), totalResources); // For resources
updateCounter(document.getElementById("orgs_counter"), orgCount); // For unique organizations
} catch (error) {
console.error(`API request to ${apiUrl} failed:`, error);
}
};
updateCounter(document.getElementById('dataset_counter'), datasetCount);
updateCounter(document.getElementById('resource_counter'), totalResources);
updateCounter(document.getElementById('orgs_counter'), orgCount);
} catch (error) {
console.error(`API request to ${apiUrl} failed:`, error);
}
};

// Fetch and animate the counters
fetchAndAnimateCounter(); // Call the function to fetch and animate counters

// Animate individual counters (if needed)
const counters = this.el.get(0).querySelectorAll(".theme-counter");
counters.forEach(counter => {
updateCounter(counter, 0); // Initialize counters to 0
});
},
};
});
fetchAndAnimateCounter();
},
};
});
28 changes: 21 additions & 7 deletions ckanext/matolabtheme/assets/script.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
$(document).ready(function () {
// Attach keypress listener to all forms
let hoveredForm = null;

// Track which form the mouse is currently over
$(document).on('mouseenter', 'form', function () {
hoveredForm = $(this);
});

$(document).on('mouseleave', 'form', function () {
hoveredForm = null;
});

// Listen for Shift + Enter keypress
$(document).on('keydown', function (event) {
if (event.shiftKey && event.key === 'Enter') {
event.preventDefault(); // Prevent any default behavior
// Find the submit button across the entire page or near the current active element
const button = $(event.target).closest('form').find('.form-actions button[type="submit"]').first();
event.preventDefault(); // Prevent default behavior

// Find the submit button in the hovered form (or the focused form)
const form = hoveredForm || $(event.target).closest('form');
const button = form.find('.form-actions button[type="submit"]').last();

if (button.length) {
button.click(); // Trigger the button's click event
}
button.click(); // Trigger the button's click event
}
}
});
});
});
54 changes: 53 additions & 1 deletion ckanext/matolabtheme/plugin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit

from ckan.config.declaration import Declaration, Key
from ckanext.matolabtheme import helpers, views, action, auth


class MatolabthemePlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IConfigurer)
plugins.implements(plugins.IConfigDeclaration)
plugins.implements(plugins.ITemplateHelpers)
plugins.implements(plugins.IActions)
plugins.implements(plugins.IBlueprint)
Expand All @@ -18,6 +19,57 @@ def update_config(self, config_):
toolkit.add_public_directory(config_, "public")
toolkit.add_resource("assets", "matolabtheme")

def update_config_schema(self, schema):

ignore_missing = toolkit.get_validator("ignore_missing")
unicode_safe = toolkit.get_validator("unicode_safe")

schema.update(
{
# This is an existing CKAN core configuration option, we are just
# making it available to be editable at runtime
"ckanext.matolabtheme.banner_top": [
ignore_missing,
unicode_safe,
],
"ckanext.matolabtheme.banner_bottom": [
ignore_missing,
unicode_safe,
],
"ckanext.matolabtheme.banner_top_upload": [
ignore_missing,
unicode_safe,
],
"ckanext.matolabtheme.banner_bottom_upload": [
ignore_missing,
unicode_safe,
],
"ckanext.matolabtheme.clear_banner_top_upload": [
ignore_missing,
unicode_safe,
],
"ckanext.matolabtheme.clear_banner_bottom_upload": [
ignore_missing,
unicode_safe,
],
}
)

return schema

# IConfigDeclaration

def declare_config_options(self, declaration: Declaration, key: Key):

declaration.annotate("matolabtheme")
group = key.ckanext.matolabtheme
declaration.declare(group.banner_top, "/static/banner-top.png")
declaration.declare(group.banner_top_upload, "")
declaration.declare(group.clear_banner_top_upload, "")
declaration.declare(group.banner_bottom, "/static/banner-bottom.png")
declaration.declare(group.banner_bottom_upload, "")
declaration.declare(group.clear_banner_bottom_upload, "")

# ITemplateHelpers

def get_helpers(self):
Expand Down
7 changes: 7 additions & 0 deletions ckanext/matolabtheme/templates/admin/config.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% ckan_extends %}
{% block admin_form_help %}
{{super()}}
{{data}}
<p><strong>Banner Config:</strong> You can change the banners for top and bottom here </p>
{% link_for _('Banner Config'), named_route='matolabtheme.banner_config', class_='btn btn-secondary', icon='upload' %}
{% endblock %}
2 changes: 1 addition & 1 deletion ckanext/matolabtheme/templates/footer.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<footer class="site-footer bsb-overlay" style="background-image: url('{{ h.url_for_static('/static/banner_bottom.png') }}');">
<footer class="site-footer bsb-overlay" style="background-image: url({{ h.url_for_static_or_external(config.get('ckanext.matolabtheme.banner_bottom')) }});">
<div class="container">
{% block footer_content %}
<div class="row">
Expand Down
14 changes: 7 additions & 7 deletions ckanext/matolabtheme/templates/home/snippets/promoted.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% set intro = g.site_intro_text %}

{% block home_image %}
<section class="bsb-hero-5 p-3 banner position-relative min-vh-25" style="background-image: url('{{ h.url_for_static('/static/banner_top.png') }}');">
<section class="bsb-hero-5 p-3 banner position-relative min-vh-25" style="background-image: url({{ h.url_for_static_or_external(config.get('ckanext.matolabtheme.banner_top')) }});">
<div class="row">
<div class="col-6 col-sm-2 order-1 order-sm-1 d-flex justify-content-start align-items-start">
<a class="" style="min-width: 100%;" href="{{ h.url_for('home.index') }}">
Expand All @@ -22,7 +22,7 @@
{% else %}
<h1 class="display-1 text-center fw-bold mb-4">{{ _("Welcome to CKAN") }}</h1>
<p class="lead text-center mb-3 d-flex justify-content-sm-center">
<span class="col-12 fw-bold fs-2">{% trans %}This is a nice introductory paragraph about CKAN
<span class="col-12 fw-bold fs-3">{% trans %}This is a nice introductory paragraph about CKAN
or the site
in general. We don't have any copy to go here yet but soon we will
{% endtrans %}
Expand All @@ -34,7 +34,7 @@ <h1 class="display-1 text-center fw-bold mb-4">{{ _("Welcome to CKAN") }}</h1>
<div class="input-group">
<input id="search" class="form-control" type="search" name="q"
placeholder="{{ _('Search our database...') }}" aria-label="{{ _('Search') }}" />
<button class="btn btn-secondary" type="submit" aria-label="{{ _('Submit') }}"><i
<button class="btn btn-primary" type="submit" aria-label="{{ _('Submit') }}"><i
class="fa fa-search"></i></button>
</div>
</form>
Expand All @@ -61,9 +61,9 @@ <h1 class="display-1 text-center fw-bold mb-4">{{ _("Welcome to CKAN") }}</h1>
</div>
</div>
</section>
<div class="counter-row m-0 row g-4 py-1 row-cols-1 row-cols-md-3" data-module="matolabtheme-module" data-api-url="{{ url_for('matolabtheme.stats') }}">
<div class="counter-row m-0 row g-4 py-1 row-cols-1 row-cols-md-3" data-module="matolabtheme-module" data-api-url="{{ h.url_for('api.action', ver=3, logic_function='matolabtheme_stats')}}">
<div class="col d-flex align-items-start justify-content-center border-light-5 ">
<a class="counter-link m-3" href="{{url_for('dataset.search')}}">
<a class="counter-link m-3" href="{{ h.url_for('dataset.search')}}">
<h1 class="fw-bold mb-0">
<i class="fas fa-database"></i>
<div id="dataset_counter" class="theme-counter" data-target="0" data-duration="0.5">0</div>
Expand All @@ -72,7 +72,7 @@ <h3>{{ _("Datasets") }}</h3>
</a>
</div>
<div class="col d-flex justify-content-center border-start border-light-5">
<a class="counter-link m-3" href="{{url_for('dataset.search')}}">
<a class="counter-link m-3" href="{{ h.url_for('dataset.search')}}">
<h1 class="fw-bold mb-0">
<i class="fas fa-file"></i>
<div id="resource_counter" class="theme-counter" data-target="0" data-duration="0.5">0</div>
Expand All @@ -81,7 +81,7 @@ <h3>{{ _("Resources") }}</h3>
</a>
</div>
<div class="col d-flex justify-content-center border-start border-light-5">
<a class="counter-link m-3" href="{{url_for('organization.index')}}">
<a class="counter-link m-3" href="{h.{url_for('organization.index')}}">
<h1 class="fw-bold mb-0">
<i class="fas fa-building"></i>
<div id="orgs_counter" class="theme-counter" data-target="0" data-duration="0.5">0</div>
Expand Down
Loading

0 comments on commit e0db305

Please sign in to comment.