Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{#
Group Add Button - Shows add/status button for a single dataset.
#}

{% set dataset_id = dataset_id or '' %}

<div class="group-add-button"
x-data="groupAddButton('{{ dataset_id }}')"
x-init="init()">

<template x-if="enabled">
<div x-ref="statusContainer">
{# No group selected yet #}
<template x-if="!groupId">
<span class="badge bg-secondary">
<i class="fa fa-folder"></i> --
</span>
</template>

{# Loading #}
<template x-if="groupId && status === 'loading'">
<span class="badge bg-light text-muted">
<i class="fa fa-spinner fa-spin"></i>
</span>
</template>

{# Already in group #}
<template x-if="groupId && status === 'in-group'">
<span class="badge bg-success">
<i class="fa fa-check"></i> In group
</span>
</template>

{# Not in group - show add button #}
<template x-if="groupId && status === 'not-in-group'">
<button type="button"
class="btn btn-sm btn-outline-primary"
@click="addToGroup()"
:disabled="adding">
<span x-show="!adding"><i class="fa fa-plus"></i> Add</span>
<span x-show="adding"><i class="fa fa-spinner fa-spin"></i></span>
</button>
</template>

{# Error state #}
<template x-if="groupId && status === 'error'">
<span class="badge bg-warning">Error</span>
</template>
</div>
</template>
</div>

<script>
function groupAddButton(datasetId) {
return {
datasetId: datasetId,
enabled: false,
groupId: '',
status: 'idle', // idle, loading, in-group, not-in-group, error
adding: false,

init() {
// Listen for state changes from toolbar
window.addEventListener('groupAddStateChanged', (e) => {
this.enabled = e.detail.enabled;
const newGroupId = e.detail.selectedGroupId;

// Only fetch if group changed
if (newGroupId !== this.groupId) {
this.groupId = newGroupId;
this.checkStatus();
}
});

// Restore initial state from sessionStorage
const saved = sessionStorage.getItem('groupAddState');
if (saved) {
try {
const state = JSON.parse(saved);
this.enabled = state.enabled || false;
this.groupId = state.selectedGroupId || '';
if (this.enabled && this.groupId) {
this.checkStatus();
}
} catch (e) {
console.error('Failed to restore state:', e);
}
}
},

async checkStatus() {
if (!this.groupId || !this.datasetId) {
this.status = 'idle';
return;
}

this.status = 'loading';

try {
const response = await fetch(`/group-add/status/${this.groupId}/${this.datasetId}`);
const text = await response.text();

// Parse the response to determine status
if (text.includes('In group') || text.includes('fa-check')) {
this.status = 'in-group';
} else if (text.includes('Add') || text.includes('fa-plus')) {
this.status = 'not-in-group';
} else {
this.status = 'error';
}
} catch (e) {
console.error('Status check failed:', e);
this.status = 'error';
}
},

async addToGroup() {
if (!this.groupId || !this.datasetId || this.adding) return;

this.adding = true;

try {
const formData = new FormData();
formData.append('dataset_id', this.datasetId);
formData.append('group_id', this.groupId);

const response = await fetch('/group-add/add', {
method: 'POST',
body: formData
});

if (response.ok) {
this.status = 'in-group';
} else {
this.status = 'error';
}
} catch (e) {
console.error('Add to group failed:', e);
this.status = 'error';
} finally {
this.adding = false;
}
}
};
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{#
Group Add Toolbar - Alpine.js component for bulk adding datasets to groups.
#}

<div x-data="groupAddManager()"
x-init="init()"
class="group-add-toolbar d-inline-block ms-2">

{# Toggle Button #}
<button type="button"
class="btn btn-sm"
:class="enabled ? 'btn-warning' : 'btn-outline-secondary'"
@click="toggle()">
<i class="fa" :class="enabled ? 'fa-times' : 'fa-folder-plus'"></i>
<span x-text="enabled ? '{{ _('Disable Group Add') }}' : '{{ _('Enable Group Add') }}'"></span>
</button>

{# Group Dropdown - only visible when enabled #}
<template x-if="enabled">
<div class="d-inline-block ms-2">
<select class="form-select form-select-sm d-inline-block w-auto"
x-model="selectedGroupId"
@change="onGroupSelect()"
:disabled="loading">
<option value="">{{ _('-- Select a Group --') }}</option>
<template x-for="group in groups" :key="group.id">
<option :value="group.id" x-text="group.display_name"></option>
</template>
</select>
<span x-show="loading" class="ms-1">
<i class="fa fa-spinner fa-spin"></i>
</span>
</div>
</template>
</div>

<script>
function groupAddManager() {
return {
enabled: false,
groups: [],
selectedGroupId: '',
selectedGroupName: '',
loading: false,
_restoredGroupId: '', // Temporary storage for restored ID

init() {
const saved = sessionStorage.getItem('groupAddState');
if (saved) {
try {
const state = JSON.parse(saved);
this.enabled = state.enabled || false;
this._restoredGroupId = state.selectedGroupId || '';
this.selectedGroupName = state.selectedGroupName || '';

// If was enabled, fetch groups (this will restore selection after fetch)
if (this.enabled) {
this.fetchGroups();
}
} catch (e) {
console.error('Failed to restore group add state:', e);
}
}
},

saveState() {
const state = {
enabled: this.enabled,
selectedGroupId: this.selectedGroupId,
selectedGroupName: this.selectedGroupName
};
sessionStorage.setItem('groupAddState', JSON.stringify(state));

// Dispatch custom event so package items can react
window.dispatchEvent(new CustomEvent('groupAddStateChanged', { detail: state }));
},

toggle() {
this.enabled = !this.enabled;

if (this.enabled) {
this.fetchGroups();
} else {
// Reset state when disabling
this.selectedGroupId = '';
this.selectedGroupName = '';
this._restoredGroupId = '';
this.groups = [];
}

this.saveState();
},

async fetchGroups() {
this.loading = true;
try {
const response = await fetch('/group-add/groups');
const data = await response.json();

if (data.success) {
this.groups = data.groups;

// Restore previously selected group if it exists in the list
if (this._restoredGroupId) {
const stillExists = this.groups.find(g => g.id === this._restoredGroupId);
if (stillExists) {
this.selectedGroupId = this._restoredGroupId;
this.selectedGroupName = stillExists.display_name;
} else {
this.selectedGroupId = '';
this.selectedGroupName = '';
}
this._restoredGroupId = ''; // Clear after restore attempt
}
} else {
console.error('Failed to fetch groups:', data.error);
this.groups = [];
}
} catch (e) {
console.error('Error fetching groups:', e);
this.groups = [];
} finally {
this.loading = false;
// Dispatch event after groups loaded and selection restored
this.saveState();
}
},

onGroupSelect() {
const selected = this.groups.find(g => g.id === this.selectedGroupId);
this.selectedGroupName = selected ? selected.display_name : '';
this.saveState();
}
};
}
</script>

<style>
.group-add-toolbar {
vertical-align: middle;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{#
Package list with group add buttons.
Based on CKAN's snippets/package_list.html but adds group add button to each item.
#}

{% block package_list %}
{% if packages %}
<ul class="dataset-list list-unstyled">
{% for package in packages %}
<li class="dataset-item">
<div class="d-flex justify-content-between align-items-start">

{# Main dataset content #}
<div class="dataset-content flex-grow-1">
<h2 class="dataset-heading">
{% if package.private %}
<span class="dataset-private badge bg-secondary">
<i class="fa fa-lock"></i> {{ _('Private') }}
</span>
{% endif %}
<a href="{{ h.url_for('%s.read' % package.type, id=package.name) }}">
{{ (package.title or package.name)|truncate(80) }}
</a>
{% if package.get('state', '').startswith('draft') %}
<span class="badge bg-info">{{ _('Draft') }}</span>
{% elif package.get('state', '').startswith('deleted') %}
<span class="badge bg-danger">{{ _('Deleted') }}</span>
{% endif %}
</h2>

{% set notes = h.markdown_extract(package.notes, extract_length=180) %}
{% if notes %}
<div>{{ notes|urlize }}</div>
{% else %}
<p class="empty">{{ h.humanize_entity_type('package', package.type, 'no description') or _("There is no description for this dataset") }}</p>
{% endif %}

{% if package.resources %}
<ul class="dataset-resources list-unstyled">
{% for resource in h.dict_list_reduce(package.resources, 'format') %}
<li>
<a href="{{ h.url_for(package.type ~ '.read', id=package.name) }}"
class="badge badge-default"
data-format="{{ resource.lower() }}">{{ resource }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>

{# Group add button - only for authenticated users #}
{% if current_user.is_authenticated %}
<div class="ms-3">
{% snippet 'digitizationknowledge/snippets/group_add_button.html', dataset_id=package.id %}
</div>
{% endif %}

</div>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}
Loading