2828 */
2929class Visualizer_Render_Library extends Visualizer_Render {
3030
31+ /**
32+ * Cached result of _isListView() to avoid repeat DB reads per request.
33+ *
34+ * @var bool|null
35+ */
36+ private $ _list_view_cached = null ;
37+
3138 /**
3239 * Renders library page.
3340 *
@@ -78,6 +85,8 @@ private function getDisplayForm() {
7885 echo '<div class="visualizer-library-form">
7986 <form action=" ' . admin_url ( 'admin.php ' ) . '">
8087 <input type="hidden" name="page" value=" ' . Visualizer_Plugin::NAME . '"/>
88+ <input type="hidden" name="view" value=" ' . esc_attr ( $ this ->_isListView () ? 'list ' : 'grid ' ) . '"/>
89+ <span class="viz-view-toggle-group"> ' . $ this ->_getViewToggleHTML () . '</span>
8190 <select class="viz-filter" name="type">
8291 ' ;
8392
@@ -282,34 +291,58 @@ private function _renderLibrary() {
282291 echo '<div id="visualizer-content-wrapper"> ' ;
283292 echo '<div id="tsdk_banner" class="visualizer-banner"></div> ' ;
284293 if ( ! empty ( $ this ->charts ) ) {
285- echo '<div id="visualizer-library" class="visualizer-clearfix"> ' ;
286- $ count = 0 ;
287- foreach ( $ this ->charts as $ placeholder_id => $ chart ) {
288- // show the sidebar after the first 3 charts.
289- ++$ count ;
290- $ enable_controls = false ;
291- $ settings = isset ( $ chart ['settings ' ] ) ? $ chart ['settings ' ] : array ();
292- if ( ! empty ( $ settings ['controls ' ]['controlType ' ] ) ) {
293- $ column_index = $ settings ['controls ' ]['filterColumnIndex ' ];
294- $ column_label = $ settings ['controls ' ]['filterColumnLabel ' ];
295- if ( 'false ' !== $ column_index || 'false ' !== $ column_label ) {
296- $ enable_controls = true ;
294+ if ( $ this ->_isListView () ) {
295+ echo '<div id="visualizer-library" class="visualizer-clearfix view-list"> ' ;
296+ echo '<table class="wp-list-table widefat striped viz-charts-table"> ' ;
297+ echo '<thead><tr> ' ;
298+ echo '<th class="col-id"> ' . esc_html__ ( 'ID ' , 'visualizer ' ) . '</th> ' ;
299+ echo '<th class="col-title"> ' . esc_html__ ( 'Title ' , 'visualizer ' ) . '</th> ' ;
300+ echo '<th class="col-type"> ' . esc_html__ ( 'Type ' , 'visualizer ' ) . '</th> ' ;
301+ echo '<th class="col-shortcode"> ' . esc_html__ ( 'Shortcode ' , 'visualizer ' ) . '</th> ' ;
302+ echo '<th class="col-actions"> ' . esc_html__ ( 'Actions ' , 'visualizer ' ) . '</th> ' ;
303+ echo '</tr></thead><tbody> ' ;
304+ foreach ( $ this ->charts as $ placeholder_id => $ chart ) {
305+ $ enable_controls = false ;
306+ $ settings = isset ( $ chart ['settings ' ] ) ? $ chart ['settings ' ] : array ();
307+ if ( ! empty ( $ settings ['controls ' ]['controlType ' ] ) ) {
308+ $ column_index = $ settings ['controls ' ]['filterColumnIndex ' ];
309+ $ column_label = $ settings ['controls ' ]['filterColumnLabel ' ];
310+ if ( 'false ' !== $ column_index || 'false ' !== $ column_label ) {
311+ $ enable_controls = true ;
312+ }
297313 }
298- }
299- if ( 3 === $ count ) {
300- $ this ->_renderSidebar ();
301- $ this ->_renderChartBox ( $ placeholder_id , $ chart ['id ' ], $ enable_controls );
302- } else {
303314 $ this ->_renderChartBox ( $ placeholder_id , $ chart ['id ' ], $ enable_controls );
304315 }
305- }
306- // show the sidebar if there are less than 3 charts.
307- if ( $ count < 3 ) {
316+ echo '</tbody></table> ' ;
308317 $ this ->_renderSidebar ();
318+ echo '</div> ' ;
319+ } else {
320+ echo '<div id="visualizer-library" class="visualizer-clearfix view-grid"> ' ;
321+ $ count = 0 ;
322+ foreach ( $ this ->charts as $ placeholder_id => $ chart ) {
323+ // show the sidebar after the first 3 charts.
324+ ++$ count ;
325+ $ enable_controls = false ;
326+ $ settings = isset ( $ chart ['settings ' ] ) ? $ chart ['settings ' ] : array ();
327+ if ( ! empty ( $ settings ['controls ' ]['controlType ' ] ) ) {
328+ $ column_index = $ settings ['controls ' ]['filterColumnIndex ' ];
329+ $ column_label = $ settings ['controls ' ]['filterColumnLabel ' ];
330+ if ( 'false ' !== $ column_index || 'false ' !== $ column_label ) {
331+ $ enable_controls = true ;
332+ }
333+ }
334+ if ( 3 === $ count ) {
335+ $ this ->_renderSidebar ();
336+ }
337+ $ this ->_renderChartBox ( $ placeholder_id , $ chart ['id ' ], $ enable_controls );
338+ }
339+ if ( $ count < 3 ) {
340+ $ this ->_renderSidebar ();
341+ }
342+ echo '</div> ' ;
309343 }
310- echo '</div> ' ;
311344 } else {
312- echo '<div id="visualizer-library" class="visualizer-clearfix"> ' ;
345+ echo '<div id="visualizer-library" class="visualizer-clearfix view-grid "> ' ;
313346 echo '<div class="items"><div class="visualizer-chart"> ' ;
314347 echo '<div class="visualizer-chart-canvas visualizer-nochart-canvas"> ' ;
315348 echo '<div class="visualizer-notfound"> ' , esc_html__ ( 'No charts found ' , 'visualizer ' ), '<p><h2><a href="javascript:;" class="add-new-h2 add-new-chart"> ' , esc_html__ ( 'Add New ' , 'visualizer ' ), '</a></h2></p></div> ' ;
@@ -413,7 +446,28 @@ private function _renderChartBox( $placeholder_id, $chart_id, $with_filter = fal
413446 $ chart_status ['title ' ] = __ ( 'Click to view the error ' , 'visualizer ' );
414447 }
415448 $ shortcode = sprintf ( '[visualizer id="%s" class=""] ' , $ chart_id );
416- echo '<div class="items"><div class="visualizer-chart"><div class="visualizer-chart-title"> ' , esc_html ( $ title ), '</div> ' ;
449+
450+ if ( $ this ->_isListView () ) {
451+ // ── List view: table row ──
452+ echo '<tr class="viz-list-row"> ' ;
453+ echo '<td class="col-id"># ' . esc_html ( (string ) $ chart_id ) . '</td> ' ;
454+ echo '<td class="col-title"> ' . esc_html ( $ title ) . '</td> ' ;
455+ echo '<td class="col-type"> ' . ( ! empty ( $ chart_type ) ? '<span class="viz-chart-type-badge"> ' . esc_html ( $ chart_type ) . '</span> ' : '— ' ) . '</td> ' ;
456+ echo '<td class="col-shortcode"><code class="viz-shortcode-display"> ' . esc_html ( $ shortcode ) . '</code></td> ' ;
457+ echo '<td class="col-actions"><div class="visualizer-action-group"> ' ;
458+ echo '<a class="visualizer-chart-action visualizer-chart-delete" href=" ' . $ delete_url . '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text"> ' . esc_html__ ( 'Delete ' , 'visualizer ' ) . '</span></a> ' ;
459+ echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-clipboard-text=" ' . esc_attr ( $ shortcode ) . '"><span class="dashicons dashicons-shortcode ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Copy Shortcode ' , 'visualizer ' ) . '</span></a> ' ;
460+ echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' . $ export_link . '"><span class="dashicons dashicons-download ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Export CSV ' , 'visualizer ' ) . '</span></a> ' ;
461+ echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr ( $ pro_class ) . '" href=" ' . $ clone_url . '"><span class="dashicons dashicons-admin-page ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Duplicate ' , 'visualizer ' ) . '</span></a> ' ;
462+ echo '<a class="visualizer-chart-action visualizer-chart-edit ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' . esc_attr ( (string ) $ chart_id ) . '"><span class="dashicons dashicons-admin-generic ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Edit ' , 'visualizer ' ) . '</span></a> ' ;
463+ echo '</div></td> ' ;
464+ echo '</tr> ' ;
465+ return ;
466+ }
467+
468+ // ── Grid view: card ──
469+ $ type_badge = ! empty ( $ chart_type ) ? '<span class="viz-chart-type-badge"> ' . esc_html ( $ chart_type ) . '</span> ' : '' ;
470+ echo '<div class="items"><div class="visualizer-chart"><div class="visualizer-chart-title"><span> ' . esc_html ( $ title ) . '</span> ' . $ type_badge . '</div> ' ;
417471 if ( Visualizer_Module::is_pro () && $ with_filter ) {
418472 echo '<div id="chart_wrapper_ ' . $ placeholder_id . '"> ' ;
419473 echo '<div id="control_wrapper_ ' . $ placeholder_id . '" class="vz-library-chart-filter"></div> ' ;
@@ -426,51 +480,76 @@ private function _renderChartBox( $placeholder_id, $chart_id, $with_filter = fal
426480 }
427481 echo '<div class="visualizer-chart-footer visualizer-clearfix"> ' ;
428482 echo '<div class="visualizer-action-group"> ' ;
429- echo '<a class="visualizer-chart-action visualizer-chart-delete" href=" ' , $ delete_url , '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Delete ' , 'visualizer ' ) . '</span></a> ' ;
430- echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-clipboard-text=" ' , esc_attr ( $ shortcode ), '"><span class="dashicons dashicons-shortcode ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Copy Shortcode ' , 'visualizer ' ) . '</span></a> ' ;
483+ echo '<a class="visualizer-chart-action visualizer-chart-delete" href=" ' , $ delete_url , '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text"> ' . esc_html__ ( 'Delete ' , 'visualizer ' ) . '</span></a> ' ;
484+ echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-clipboard-text=" ' , esc_attr ( $ shortcode ), '"><span class="dashicons dashicons-shortcode ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Copy Shortcode ' , 'visualizer ' ) . '</span></a> ' ;
431485 if ( $ this ->can_chart_have_action ( 'image ' , $ chart_id ) ) {
432- echo '<a class="visualizer-chart-action visualizer-chart-image ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart="visualizer- ' , $ chart_id , '" data-chart-title=" ' , $ title , '"><span class="dashicons dashicons-format-image ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Download PNG ' , 'visualizer ' ) . '</span></a> ' ;
486+ echo '<a class="visualizer-chart-action visualizer-chart-image ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart="visualizer- ' , $ chart_id , '" data-chart-title=" ' , $ title , '"><span class="dashicons dashicons-format-image ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Download PNG ' , 'visualizer ' ) . '</span></a> ' ;
433487 }
434- echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ export_link , '"><span class="dashicons dashicons-download ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Export CSV ' , 'visualizer ' ) . '</span></a> ' ;
435- echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr ( $ pro_class ) . '" href=" ' , $ clone_url , '"><span class="dashicons dashicons-admin-page ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Duplicate ' , 'visualizer ' ) . '</span></a> ' ;
436- echo '<a class="visualizer-chart-action visualizer-chart-edit ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ chart_id , '"><span class="dashicons dashicons-admin-generic ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Edit ' , 'visualizer ' ) . '</span></a> ' ;
488+ echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ export_link , '"><span class="dashicons dashicons-download ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Export CSV ' , 'visualizer ' ) . '</span></a> ' ;
489+ echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr ( $ pro_class ) . '" href=" ' , $ clone_url , '"><span class="dashicons dashicons-admin-page ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Duplicate ' , 'visualizer ' ) . '</span></a> ' ;
490+ echo '<a class="visualizer-chart-action visualizer-chart-edit ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ chart_id , '"><span class="dashicons dashicons-admin-generic ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Edit ' , 'visualizer ' ) . '</span></a> ' ;
437491 echo '</div> ' ;
438492 do_action ( 'visualizer_chart_languages ' , $ chart_id );
439493 echo '<hr><div class="visualizer-chart-status"><span title=" ' . __ ( 'Chart ID ' , 'visualizer ' ) . '">( ' . $ chart_id . '):</span> <span class="visualizer-date" title=" ' . __ ( 'Last Updated ' , 'visualizer ' ) . '"> ' . $ chart_status ['date ' ] . '</span><span class="visualizer-error"><i class="dashicons ' . $ chart_status ['icon ' ] . '" data-viz-error=" ' . esc_attr ( str_replace ( '" ' , "' " , $ chart_status ['error ' ] ) ) . '" title=" ' . esc_attr ( $ chart_status ['title ' ] ) . '"></i></span></div> ' ;
440494 echo '</div> ' ;
441495 echo '</div></div> ' ;
442496 }
443497
498+ /**
499+ * Returns true when the library should render in list (no-preview) mode.
500+ *
501+ * Priority: ?view= URL param (saves to user meta) → saved user meta → grid default.
502+ *
503+ * No nonce needed: this is a bookmarkable UI preference URL. A nonce would expire
504+ * and break saved/shared links for zero real security gain — the value is allowlisted
505+ * to 'list'|'grid' before any write happens.
506+ */
507+ private function _isListView (): bool {
508+ if ( null !== $ this ->_list_view_cached ) {
509+ return $ this ->_list_view_cached ;
510+ }
511+ if ( isset ( $ _GET ['view ' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
512+ $ view = sanitize_text_field ( wp_unslash ( $ _GET ['view ' ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
513+ if ( in_array ( $ view , array ( 'list ' , 'grid ' ), true ) ) {
514+ update_user_meta ( get_current_user_id (), 'visualizer_library_view ' , $ view );
515+ }
516+ $ this ->_list_view_cached = ( 'list ' === $ view );
517+ } else {
518+ $ saved = get_user_meta ( get_current_user_id (), 'visualizer_library_view ' , true );
519+ $ this ->_list_view_cached = ( 'list ' === $ saved );
520+ }
521+ return $ this ->_list_view_cached ;
522+ }
523+
524+ /**
525+ * Returns the HTML for the grid/list view toggle links.
526+ */
527+ private function _getViewToggleHTML (): string {
528+ $ is_list = $ this ->_isListView ();
529+ $ grid_url = esc_url ( add_query_arg ( 'view ' , 'grid ' ) );
530+ $ list_url = esc_url ( add_query_arg ( 'view ' , 'list ' ) );
531+ return '<a href=" ' . $ grid_url . '" class="viz-view-toggle ' . ( ! $ is_list ? ' active ' : '' ) . '" title=" ' . esc_attr__ ( 'Grid View ' , 'visualizer ' ) . '"><span class="dashicons dashicons-screenoptions"></span></a> '
532+ . '<a href=" ' . $ list_url . '" class="viz-view-toggle ' . ( $ is_list ? ' active ' : '' ) . '" title=" ' . esc_attr__ ( 'List View ' , 'visualizer ' ) . '"><span class="dashicons dashicons-list-view"></span></a> ' ;
533+ }
534+
444535 /**
445536 * Render 2-col sidebar
446537 */
447538 private function _renderSidebar () {
448539 if ( ! Visualizer_Module::is_pro () ) {
449- echo '<div class="items"> ' ;
450- echo '<div class="viz-pro"> ' ;
451- echo '<div id="visualizer-sidebar" class="one-columns"> ' ;
452- echo '<div class="visualizer-sidebar-box"> ' ;
453- echo '<h3> ' . __ ( 'Discover the power of PRO! ' , 'visualizer ' ) . '</h3><ul> ' ;
454- if ( Visualizer_Module_Admin::proFeaturesLocked () ) {
455- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( '6 more chart types ' , 'visualizer ' );
456- } else {
457- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( '11 more chart types ' , 'visualizer ' ) . '</li> ' ;
458- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Synchronize Data Periodically ' , 'visualizer ' ) . '</li> ' ;
459- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'ChartJS Charts ' , 'visualizer ' ) . '</li> ' ;
460- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Table Google chart ' , 'visualizer ' ) . '</li> ' ;
461- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Frontend Actions(Print, Export, Copy, Download) ' , 'visualizer ' ) . '</li> ' ;
462- }
463- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Spreadsheet like editor ' , 'visualizer ' ) . '</li> ' ;
464- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Import from other charts ' , 'visualizer ' ) . '</li> ' ;
465- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Use database query to create charts ' , 'visualizer ' ) . '</li> ' ;
466- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Create charts from WordPress tables ' , 'visualizer ' ) . '</li> ' ;
467- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Frontend editor ' , 'visualizer ' ) . '</li> ' ;
468- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Private charts ' , 'visualizer ' ) . '</li> ' ;
469- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'WPML support for translating charts ' , 'visualizer ' ) . '</li> ' ;
470- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Integration with Woocommerce Data endpoints ' , 'visualizer ' ) . '</li> ' ;
471- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Auto-sync with online files ' , 'visualizer ' ) . '</li></ul> ' ;
472- echo '<p class="vz-sidebar-box-action"><a href=" ' . tsdk_utmify ( Visualizer_Plugin::PRO_TEASER_URL , 'sidebarMenuUpgrade ' , 'index ' ) . '#pro-features" target="_blank" class="button button-secondary"> ' . __ ( 'View more features ' , 'visualizer ' ) . '</a><a href=" ' . tsdk_utmify ( Visualizer_Plugin::PRO_TEASER_URL , 'sidebarMenuUpgrade ' , 'index ' ) . '#pricing" target="_blank" class="button button-primary"> ' . __ ( 'Upgrade Now ' , 'visualizer ' ) . '</a></p> ' ;
540+ $ upgrade_url = tsdk_utmify ( Visualizer_Plugin::PRO_TEASER_URL , 'sidebarMenuUpgrade ' , 'index ' );
541+ $ chart_types = Visualizer_Module_Admin::proFeaturesLocked () ? __ ( '6 more chart types ' , 'visualizer ' ) : __ ( '11 more chart types ' , 'visualizer ' );
542+ echo '<div class="items items--upsell"> ' ;
543+ echo '<div class="viz-upsell-banner"> ' ;
544+ echo '<span class="dashicons dashicons-star-filled viz-upsell-banner__icon"></span> ' ;
545+ echo '<div class="viz-upsell-banner__text"> ' ;
546+ echo '<strong> ' . esc_html__ ( 'Unlock the full power of Visualizer PRO! ' , 'visualizer ' ) . '</strong> ' ;
547+ /* translators: %s: number of additional chart types (e.g. "11 more chart types") */
548+ echo '<span> ' . sprintf ( esc_html__ ( '%s, periodic data sync, database queries, frontend editor, and more. ' , 'visualizer ' ), esc_html ( $ chart_types ) ) . '</span> ' ;
473549 echo '</div> ' ;
550+ echo '<div class="viz-upsell-banner__actions"> ' ;
551+ echo '<a href=" ' . esc_url ( $ upgrade_url . '#pro-features ' ) . '" target="_blank" class="button button-secondary"> ' . esc_html__ ( 'View Features ' , 'visualizer ' ) . '</a> ' ;
552+ echo '<a href=" ' . esc_url ( $ upgrade_url . '#pricing ' ) . '" target="_blank" class="button button-primary"> ' . esc_html__ ( 'Upgrade Now ' , 'visualizer ' ) . '</a> ' ;
474553 echo '</div> ' ;
475554 echo '</div> ' ;
476555 echo '</div> ' ;
0 commit comments