quickdbd-cli 0.3.3__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/PKG-INFO +2 -2
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/README.md +1 -1
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/pyproject.toml +1 -1
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/scripts/build_gallery.py +2 -1
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/scripts/gen_thumbnails.py +17 -0
- quickdbd_cli-0.3.5/src/quickdbd_cli/__init__.py +1 -0
- quickdbd_cli-0.3.5/src/quickdbd_cli/assets/icon.svg +1 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/render.js +52 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/style.css +38 -2
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/viewer.js +200 -26
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/html.py +8 -0
- quickdbd_cli-0.3.5/tests/test_home_hook.py +52 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_pipeline.py +2 -0
- quickdbd_cli-0.3.5/tests/test_render.py +109 -0
- quickdbd_cli-0.3.5/tests/test_viewer_contract.py +22 -0
- quickdbd_cli-0.3.3/src/quickdbd_cli/__init__.py +0 -1
- quickdbd_cli-0.3.3/tests/test_render.py +0 -20
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/.gitignore +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/LICENSE +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/NOTICE +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/quickdbd-sample.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/synthetic/finance.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/synthetic/hr.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/synthetic/inventory.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/synthetic/marketing.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/synthetic/qdbd.layout.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/examples/synthetic/sales.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/scripts/gen_font_metrics.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/scripts/gen_schema.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/scripts/import_dbml_corpus.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/scripts/release.sh +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/__main__.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/agent.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/editor.js +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/font-metrics.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/assets/syntax-reference.md +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/cli.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/convert.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/dbml.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/dbt.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/diff.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/export.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/jsrt.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/layout.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/mcp.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/measure.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/model.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/parser.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/project.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/serve.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/share.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/tags.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/src/quickdbd_cli/targets.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml/team.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/LICENSE +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/README.md +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/alias_of_duplicated_names.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/alias_of_duplicated_names.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/dep.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/dep.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_columns.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_columns.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_enum_field.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_enum_field.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_name.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_name.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_names.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_names.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_table_partial_injections.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/duplicate_table_partial_injections.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/enum_as_default_column_value.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/enum_as_default_column_value.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/enum_name.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/enum_name.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/erroneous.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/erroneous.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/multiple_notes_in_table_group.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/multiple_notes_in_table_group.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index_table_partial.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index_table_partial.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table_partial.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table_partial.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/ref_validator.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/ref_validator.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/sticky_notes_validator.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/sticky_notes_validator.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/table_partial.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/table_partial.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/unknown_table_group_field.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/binder/unknown_table_group_field.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/array_type.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/array_type.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/checks.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/checks.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/circular_ref_1_inline_1_element.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/circular_ref_1_inline_1_element.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_elements.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_elements.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_inlines.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_inlines.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/column_caller_type.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/column_caller_type.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/comment.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/comment.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/composite_pk.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/composite_pk.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/default_tables.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/default_tables.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep_color.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep_color.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep_duplicate.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep_duplicate.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep_pipeline.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/dep_pipeline.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/enum_as_default_column_value.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/enum_as_default_column_value.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/enum_tables.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/enum_tables.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/erroneous.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/erroneous.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/general_schema.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/general_schema.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/header_color_tables.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/header_color_tables.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/index_table_partial.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/index_table_partial.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/index_tables.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/index_tables.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/metadata.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/metadata.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/metadata_errors.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/metadata_errors.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/multi_notes.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/multi_notes.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/multiline_string.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/multiline_string.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/negative_number.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/negative_number.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/note_normalize.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/note_normalize.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/note_normalize_with_top_empty_lines.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/note_normalize_with_top_empty_lines.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/old_undocumented_syntax.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/old_undocumented_syntax.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/primary_key.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/primary_key.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/project.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/project.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/ref_name_and_color_setting.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/ref_name_and_color_setting.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/ref_settings.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/ref_settings.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/referential_actions.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/referential_actions.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/self_referential_ref_in_tablepartial.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/self_referential_ref_in_tablepartial.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/sticky_notes.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/sticky_notes.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_element.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_element.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_group.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_group.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_group_element.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_group_element.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_group_settings.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_group_settings.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_partial.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_partial.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_reappear_tablegroup.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_reappear_tablegroup.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_settings.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/table_settings.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/tablepartial_causing_circular_ref.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/tablepartial_causing_circular_ref.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/two_inline_refs.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/interpreter/two_inline_refs.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/call_expression.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/call_expression.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/element_in_simple_body.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/element_in_simple_body.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/erroneous_setting.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/erroneous_setting.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/expression.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/expression.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/last_invalid_number.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/last_invalid_number.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/list_expression.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/list_expression.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/trailing_comments.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/trailing_comments.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/tuple_expression.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/parser/tuple_expression.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/checks.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/checks.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/complex_names.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/complex_names.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/dep.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/dep.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/enum.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/enum.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/invalid_args.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/invalid_args.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/public_schema.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/public_schema.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/redefined_note.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/redefined_note.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/ref_error_setting.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/ref_error_setting.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/ref_in_table.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/ref_in_table.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/schema_nested_tablegroup.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/schema_nested_tablegroup.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_group_settings.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_group_settings.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_partial_check.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_partial_check.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_partial_settings_general.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_partial_settings_general.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_settings_check.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_settings_check.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_settings_general.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/table_settings_general.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/wrong_sub_element_declarations.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/wrong_sub_element_declarations.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/wrong_table_partial_injection_syntax.dbml +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/dbml-ref/validator/wrong_table_partial_injection_syntax.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/jaffle/LICENSE +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/jaffle/README.md +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/jaffle/catalog.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/jaffle/manifest.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/fixtures/targets/cases.json +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_agent.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_convert.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_dbml.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_dbml_pipeline.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_dbt.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_diff.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_gallery.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_mcp.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_packaging.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_parser.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_path.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_select.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_serve.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_share.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_tags.py +0 -0
- {quickdbd_cli-0.3.3 → quickdbd_cli-0.3.5}/tests/test_targets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quickdbd-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
|
|
5
5
|
Project-URL: Homepage, https://qdbd.dev/
|
|
6
6
|
Project-URL: Documentation, https://qdbd.dev/guide/
|
|
@@ -186,7 +186,7 @@ jobs:
|
|
|
186
186
|
permissions: { contents: read, pull-requests: write }
|
|
187
187
|
steps:
|
|
188
188
|
- uses: actions/checkout@v4
|
|
189
|
-
- uses: MarkPhamm/quickdbd-cli@v0.3.
|
|
189
|
+
- uses: MarkPhamm/quickdbd-cli@v0.3.5
|
|
190
190
|
with:
|
|
191
191
|
path: erd/ # schema file(s) or folder(s)
|
|
192
192
|
output: site/index.html
|
|
@@ -148,7 +148,7 @@ jobs:
|
|
|
148
148
|
permissions: { contents: read, pull-requests: write }
|
|
149
149
|
steps:
|
|
150
150
|
- uses: actions/checkout@v4
|
|
151
|
-
- uses: MarkPhamm/quickdbd-cli@v0.3.
|
|
151
|
+
- uses: MarkPhamm/quickdbd-cli@v0.3.5
|
|
152
152
|
with:
|
|
153
153
|
path: erd/ # schema file(s) or folder(s)
|
|
154
154
|
output: site/index.html
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quickdbd-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.5"
|
|
8
8
|
description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from quickdbd_cli import __version__, diff
|
|
20
20
|
from quickdbd_cli.dbt import import_dbt
|
|
21
21
|
from quickdbd_cli.export import font_css
|
|
22
|
-
from quickdbd_cli.html import _escape, _json_for_script, render_page
|
|
22
|
+
from quickdbd_cli.html import _escape, _json_for_script, favicon, render_page
|
|
23
23
|
from quickdbd_cli.parser import compile_sources
|
|
24
24
|
from quickdbd_cli.project import Project
|
|
25
25
|
|
|
@@ -175,6 +175,7 @@ def gallery_page(catalog: Catalog, stats: dict[str, dict]) -> str:
|
|
|
175
175
|
html = TEMPLATE.read_text(encoding="utf-8")
|
|
176
176
|
return (
|
|
177
177
|
html.replace("/*FONT_CSS*/", font_css())
|
|
178
|
+
.replace("<!--FAVICON-->", favicon())
|
|
178
179
|
.replace("__VERSION__", _escape(__version__))
|
|
179
180
|
.replace("__DATA__", _json_for_script(data))
|
|
180
181
|
)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
python scripts/gen_thumbnails.py # every demo
|
|
4
4
|
python scripts/gen_thumbnails.py diff tags # just these
|
|
5
5
|
python scripts/gen_thumbnails.py hero # the README hero (docs/assets/img/hero.png)
|
|
6
|
+
python scripts/gen_thumbnails.py path-sql # the home page's path-to-SQL shot (docs/assets/img/path-sql.png)
|
|
6
7
|
|
|
7
8
|
Dev-only: needs Playwright (`uv pip install -p .venv playwright`) and drives the system Chrome, so no
|
|
8
9
|
browser download. Each demo is opened at its `view` + `shot` URL hash; the screenshot is framed in a card
|
|
@@ -24,6 +25,8 @@ from quickdbd_cli.html import _escape
|
|
|
24
25
|
|
|
25
26
|
W, H = 1200, 630
|
|
26
27
|
SHOT_W, SHOT_H = 1280, 820
|
|
28
|
+
# The home page's "Every relationship is a JOIN" section; hooks/home.py asks join_path for the same two tables.
|
|
29
|
+
PATH_SHOT = ("sakila", "t=payment&p=country")
|
|
27
30
|
# Hidden in the screenshot: the table list, toolbar, minimap and hint. The details panel and diff bar stay.
|
|
28
31
|
CLEAN = ".explorer, .bar, .minimap, .hint { display: none !important; }"
|
|
29
32
|
|
|
@@ -98,6 +101,20 @@ def main(argv: list[str]) -> int:
|
|
|
98
101
|
dst = ROOT / "docs" / "assets" / "img" / "hero.png"
|
|
99
102
|
page.screenshot(path=str(dst))
|
|
100
103
|
print(f"Wrote {dst.relative_to(ROOT)}")
|
|
104
|
+
if "path-sql" in (set(argv) or {"path-sql"}):
|
|
105
|
+
page.set_viewport_size({"width": 1440, "height": 860})
|
|
106
|
+
page.goto((out / PATH_SHOT[0] / "index.html").as_uri() + f"#{PATH_SHOT[1]}")
|
|
107
|
+
page.wait_for_timeout(800)
|
|
108
|
+
page.get_by_text("Show only this path").click()
|
|
109
|
+
# the home page shows the SQL beside the shot, so the panel goes and the whole path shows
|
|
110
|
+
page.add_style_tag(content=".details, .hint { display: none !important; }")
|
|
111
|
+
page.click("#zfit")
|
|
112
|
+
page.click("#zout") # fit counts tables only; the payment edge loops out to the right
|
|
113
|
+
page.mouse.move(700, 820)
|
|
114
|
+
page.wait_for_timeout(800)
|
|
115
|
+
dst = ROOT / "docs" / "assets" / "img" / "path-sql.png"
|
|
116
|
+
page.screenshot(path=str(dst))
|
|
117
|
+
print(f"Wrote {dst.relative_to(ROOT)}")
|
|
101
118
|
browser.close()
|
|
102
119
|
return 0
|
|
103
120
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.5"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><rect width="128" height="128" rx="28" fill="#2563eb"/><rect x="22" y="22" width="42" height="36" rx="9" fill="#fff"/><path d="M44 58v19a10 10 0 0 0 10 10h12" fill="none" stroke="#fff" stroke-width="8"/><rect x="64" y="70" width="42" height="33" rx="8" fill="#a8c1f7"/></svg>
|
|
@@ -547,6 +547,54 @@
|
|
|
547
547
|
return lines.join("\n");
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/* ------------------------------------------------------------------ lit relationships (viewer only, never exported) */
|
|
551
|
+
var CARD_LABEL = { one: "1", zeroOne: "0..1", many: "*", zeroMany: "0..*" };
|
|
552
|
+
var CARD_WORD = { one: "one", zeroOne: "zero or one", many: "many", zeroMany: "zero or many" };
|
|
553
|
+
|
|
554
|
+
/* Cardinality labels for a lit line: one just past each end, on the side the line leaves from. a end first. */
|
|
555
|
+
function edgeLabels(model, pos, mode, rel, geo) {
|
|
556
|
+
var e = edgeGeom(model, geo || geometry(model, mode), pos, rel);
|
|
557
|
+
if (!e) return null;
|
|
558
|
+
function spot(x, y, dir, card) {
|
|
559
|
+
return { x: n(x + dir * 8), y: n(y - 9), anchor: dir > 0 ? "start" : "end", text: CARD_LABEL[card] || "" };
|
|
560
|
+
}
|
|
561
|
+
return [spot(e.xa, e.ya, e.da, rel.a.card), spot(e.xb, e.yb, e.db, rel.b.card)];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/* Text of the relationship card for model.rels[i]: the child (FK / many) side first, a plain-English sentence and
|
|
565
|
+
* the JOIN, built like `qdbd path` so quoting and composite keys match. */
|
|
566
|
+
function relCard(model, i) {
|
|
567
|
+
var r = model.rels[i], byId = {};
|
|
568
|
+
model.tables.forEach(function (t) { byId[t.id] = t; });
|
|
569
|
+
var aMany = r.a.card === "many" || r.a.card === "zeroMany", bMany = r.b.card === "many" || r.b.card === "zeroMany";
|
|
570
|
+
var aIsChild = aMany || !bMany, child = aIsChild ? r.a : r.b, parent = aIsChild ? r.b : r.a;
|
|
571
|
+
var pairs = stepPairs(model, i, child.t);
|
|
572
|
+
function side(end, k) {
|
|
573
|
+
var t = byId[end.t], cols = pairs.map(function (p) {
|
|
574
|
+
var c = t.cols.filter(function (x) { return x.id === p[k]; })[0];
|
|
575
|
+
return c ? c.name : p[k];
|
|
576
|
+
});
|
|
577
|
+
return t.name + (cols.length > 1 ? ".(" + cols.join(", ") + ")" : cols.length ? "." + cols[0] : "");
|
|
578
|
+
}
|
|
579
|
+
var plural = child.card === "many" || child.card === "zeroMany";
|
|
580
|
+
return {
|
|
581
|
+
from: side(child, 0),
|
|
582
|
+
to: side(parent, 1),
|
|
583
|
+
cardFrom: CARD_LABEL[child.card] || "",
|
|
584
|
+
cardTo: CARD_LABEL[parent.card] || "",
|
|
585
|
+
sentence: "Each " + byId[parent.t].name + " row has " + CARD_WORD[child.card] + " " + byId[child.t].name + (plural ? " rows" : " row"),
|
|
586
|
+
sql: child.t === parent.t ? selfJoin(byId[child.t], pairs) : pathSql(model, child.t, [{ from: child, to: parent, pairs: pairs }]),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/* A table joined to itself needs an alias, or every column is ambiguous. */
|
|
590
|
+
function selfJoin(t, pairs) {
|
|
591
|
+
var name = sqlIdent(t.name), alias = sqlPart(t.name.split(".").pop() + "_2");
|
|
592
|
+
function col(c) { var x = t.cols.filter(function (k) { return k.id === c; })[0]; return sqlPart(x ? x.name : c); }
|
|
593
|
+
var on = pairs.map(function (p) { return alias + "." + col(p[1]) + " = " + name + "." + col(p[0]); });
|
|
594
|
+
return "FROM " + name + "\nJOIN " + name + " AS " + alias + " ON " +
|
|
595
|
+
(on.length ? on.join(" AND ") : "... -- table-level relationship: join columns not specified");
|
|
596
|
+
}
|
|
597
|
+
|
|
550
598
|
root.qdbd = {
|
|
551
599
|
findPath: findPath,
|
|
552
600
|
stepPairs: stepPairs,
|
|
@@ -563,8 +611,12 @@
|
|
|
563
611
|
renderSvg: renderSvg,
|
|
564
612
|
bounds: bounds,
|
|
565
613
|
neighbourhood: neighbourhood,
|
|
614
|
+
edgeLabels: edgeLabels,
|
|
615
|
+
relCard: relCard,
|
|
566
616
|
esc: esc,
|
|
567
617
|
};
|
|
618
|
+
root.qdbdEdgeLabels = edgeLabels;
|
|
619
|
+
root.qdbdRelCard = relCard;
|
|
568
620
|
root.qdbdRenderSvg = renderSvg;
|
|
569
621
|
root.qdbdSelect = select;
|
|
570
622
|
root.qdbdRenderDrawio = renderDrawio;
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
--t-edge: #b8bcc2;
|
|
35
35
|
--row-hl: #dbe6f6;
|
|
36
36
|
--row-hover: #eef3fa;
|
|
37
|
+
--lit: #4361ee;
|
|
38
|
+
--lit-deep: #2a3fb8;
|
|
37
39
|
}
|
|
38
40
|
/* Dark theme. viewer.js sets data-theme from the toggle, or from prefers-color-scheme when it's on auto. */
|
|
39
41
|
:root[data-theme="dark"] {
|
|
@@ -71,6 +73,8 @@
|
|
|
71
73
|
--t-edge: #5b6472;
|
|
72
74
|
--row-hl: #24344d;
|
|
73
75
|
--row-hover: #23282f;
|
|
76
|
+
--lit: #8aa0ff;
|
|
77
|
+
--lit-deep: #c3ceff;
|
|
74
78
|
}
|
|
75
79
|
::selection { background: var(--pick); color: var(--ink); }
|
|
76
80
|
:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
|
|
@@ -105,6 +109,36 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
105
109
|
.r.hl .rb { fill: var(--row-hl); fill-opacity: 1; }
|
|
106
110
|
.r:hover .rb { fill: var(--row-hover); fill-opacity: 1; }
|
|
107
111
|
.t.flash .tb { stroke: #f0a020; stroke-width: 3; }
|
|
112
|
+
.t .tb { transition: filter .18s; }
|
|
113
|
+
.t:hover .tb { filter: drop-shadow(0 6px 14px rgba(16, 24, 40, .16)); }
|
|
114
|
+
|
|
115
|
+
/* lit relationships: hovered or selected table, hovered or clicked line (viewer.js light()) */
|
|
116
|
+
.e.lit .ep { stroke: var(--lit); stroke-width: 2.2; }
|
|
117
|
+
.e.lit path, .e.lit .mc { stroke: var(--lit); }
|
|
118
|
+
.r.lit .rb { fill: var(--row-hl); fill-opacity: 1; }
|
|
119
|
+
.efocus .t:not(.on) { opacity: .18; }
|
|
120
|
+
.efocus .e:not(.lit) { opacity: .07; }
|
|
121
|
+
.fx { pointer-events: none; }
|
|
122
|
+
.fx .flow { fill: none; stroke: var(--lit-deep); stroke-width: 4; stroke-dasharray: 4 18; animation: qdbd-flow 1.1s linear infinite; }
|
|
123
|
+
.fx .flow.amber { stroke: #b86e00; }
|
|
124
|
+
@keyframes qdbd-flow { to { stroke-dashoffset: -22; } }
|
|
125
|
+
.fx .lbl { font: 600 11px Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; fill: var(--lit); paint-order: stroke;
|
|
126
|
+
stroke: var(--bg); stroke-width: 4px; stroke-linejoin: round; animation: qdbd-fade .2s ease-out; }
|
|
127
|
+
@keyframes qdbd-fade { from { opacity: 0; } }
|
|
128
|
+
.hit path { fill: none; stroke: transparent; stroke-width: 14; pointer-events: stroke; cursor: pointer; }
|
|
129
|
+
/* relationship card (a clicked line) */
|
|
130
|
+
.rcard { position: absolute; z-index: 6; display: none; min-width: 240px; max-width: min(460px, calc(100% - 16px)); padding: 10px 12px;
|
|
131
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); font-size: 12px;
|
|
132
|
+
color: var(--ink); transform-origin: top left; }
|
|
133
|
+
.rcard.open { display: block; animation: qdbd-cardin .16s ease-out; }
|
|
134
|
+
@keyframes qdbd-cardin { from { opacity: 0; transform: scale(.96) translateY(-4px); } }
|
|
135
|
+
.rcard .rt { font-weight: 600; margin-bottom: 2px; word-break: break-word; }
|
|
136
|
+
.rcard .rt b { color: var(--lit); font-weight: 600; padding: 0 6px; white-space: nowrap; }
|
|
137
|
+
.rcard .rk { color: var(--muted); margin-bottom: 8px; }
|
|
138
|
+
.rcard pre { margin: 0 0 8px; padding: 7px 8px; background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
|
|
139
|
+
font: 11px ui-monospace, Menlo, Consolas, monospace; white-space: pre-wrap; user-select: text; }
|
|
140
|
+
.rcard .acts a { color: var(--blue); cursor: pointer; margin-right: 12px; }
|
|
141
|
+
@media (prefers-reduced-motion: reduce) { .fx .flow, .fx .lbl, .rcard.open { animation: none; } }
|
|
108
142
|
.t { cursor: grab; }
|
|
109
143
|
#canvas.panning .t { cursor: grabbing; }
|
|
110
144
|
|
|
@@ -177,8 +211,10 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
177
211
|
/* details drawer (right) */
|
|
178
212
|
.details { position: absolute; top: 64px; right: 16px; bottom: 16px; width: 380px; background: var(--panel);
|
|
179
213
|
border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); overflow: auto;
|
|
180
|
-
|
|
181
|
-
.
|
|
214
|
+
z-index: 3; padding: 16px 18px; visibility: hidden; opacity: 0; transform: translateX(16px); pointer-events: none;
|
|
215
|
+
transition: opacity .2s, transform .2s, visibility 0s .2s; }
|
|
216
|
+
.details.open { visibility: visible; opacity: 1; transform: none; pointer-events: auto; transition: opacity .2s, transform .2s; }
|
|
217
|
+
@media (prefers-reduced-motion: reduce) { .details, .details.open { transition: none; } }
|
|
182
218
|
.has-details .minimap { right: 412px; }
|
|
183
219
|
.details .close { position: absolute; top: 10px; right: 10px; width: 28px; height: 28px; border: 0; border-radius: 6px;
|
|
184
220
|
background: transparent; display: flex; align-items: center; justify-content: center; color: var(--muted); cursor: pointer; }
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
shown: {},
|
|
17
17
|
regions: !!data.model.regions,
|
|
18
18
|
path: null, // {to: id, steps: [...]} from the selected table
|
|
19
|
+
edge: null, // the clicked (locked) relationship line: "a|ac|b|bc", like the .e data attributes
|
|
20
|
+
hover: null, // {t: table id} or {e: line key} under the mouse: lights lines without dimming anything
|
|
19
21
|
};
|
|
20
22
|
var svg = document.getElementById("canvas");
|
|
21
23
|
var vp = document.getElementById("vp");
|
|
@@ -29,7 +31,12 @@
|
|
|
29
31
|
|
|
30
32
|
/* ------------------------------------------------------------- render */
|
|
31
33
|
function render() {
|
|
32
|
-
vp.innerHTML = Q.renderInner(S.vm, S.pos, S.mode, { docMarks: true, regions: S.regions })
|
|
34
|
+
vp.innerHTML = Q.renderInner(S.vm, S.pos, S.mode, { docMarks: true, regions: S.regions }) +
|
|
35
|
+
'<g class="fx"></g><g class="hit"></g>';
|
|
36
|
+
vp.appendChild(vp.querySelector(".tables")); // lit overlay and line hit targets sit under the tables
|
|
37
|
+
relByKey = {};
|
|
38
|
+
S.vm.rels.forEach(function (r, i) { relByKey[[r.a.t, r.a.c, r.b.t, r.b.c].join("|")] = i; });
|
|
39
|
+
buildHits();
|
|
33
40
|
applyFocus();
|
|
34
41
|
drawMinimap();
|
|
35
42
|
}
|
|
@@ -37,9 +44,114 @@
|
|
|
37
44
|
function renderEdges() {
|
|
38
45
|
var g = vp.querySelector(".edges");
|
|
39
46
|
if (g) g.innerHTML = Q.renderEdges(S.vm, S.pos, S.mode);
|
|
47
|
+
buildHits();
|
|
40
48
|
applyFocus();
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
/* ------------------------------------------------------------- lit relationships
|
|
52
|
+
* A lit line gets the --lit colour, flowing dashes and cardinality labels (drawn in .fx), and its rows are
|
|
53
|
+
* highlighted. Lines light up under a hovered table or line, for the selected table, and for the clicked line. */
|
|
54
|
+
var relByKey = {};
|
|
55
|
+
function edgeKey(e) {
|
|
56
|
+
return [e.getAttribute("data-a"), e.getAttribute("data-ac"), e.getAttribute("data-b"), e.getAttribute("data-bc")].join("|");
|
|
57
|
+
}
|
|
58
|
+
/* Wide transparent copies of every line, so a 1px curve can be hovered and clicked. */
|
|
59
|
+
function buildHits() {
|
|
60
|
+
var hit = vp.querySelector(".hit");
|
|
61
|
+
if (!hit) return;
|
|
62
|
+
hit.innerHTML = [].map.call(vp.querySelectorAll(".e"), function (e) {
|
|
63
|
+
return '<path data-k="' + Q.esc(edgeKey(e)) + '" d="' + e.querySelector(".ep").getAttribute("d") + '"/>';
|
|
64
|
+
}).join("");
|
|
65
|
+
}
|
|
66
|
+
function light() {
|
|
67
|
+
var fx = vp.querySelector(".fx");
|
|
68
|
+
if (!fx) return;
|
|
69
|
+
if (S.edge && relByKey[S.edge] === undefined) S.edge = null; // the clicked line was filtered away or edited out
|
|
70
|
+
if (!S.edge) closeCard();
|
|
71
|
+
vp.querySelectorAll(".lit, .t.on").forEach(function (el) { el.classList.remove("lit", "on"); });
|
|
72
|
+
svg.classList.toggle("efocus", !!S.edge);
|
|
73
|
+
var focused = svg.classList.contains("focus"), h = S.hover || {}, geo = null, parts = [];
|
|
74
|
+
vp.querySelectorAll(".e").forEach(function (e) {
|
|
75
|
+
var k = edgeKey(e), a = e.getAttribute("data-a"), b = e.getAttribute("data-b"), d = e.querySelector(".ep").getAttribute("d");
|
|
76
|
+
var dk = ' data-k="' + Q.esc(k) + '"';
|
|
77
|
+
if (e.classList.contains("ph")) { parts.push('<path class="flow amber"' + dk + ' d="' + d + '"/>'); return; }
|
|
78
|
+
if (!(k === S.edge || h.e === k || h.t === a || h.t === b || e.classList.contains("hl") ||
|
|
79
|
+
(focused && !S.path && !e.classList.contains("dim")))) return;
|
|
80
|
+
var r = S.vm.rels[relByKey[k]];
|
|
81
|
+
if (!r) return;
|
|
82
|
+
e.classList.add("lit");
|
|
83
|
+
[[r.a.t, r.a.c], [r.b.t, r.b.c]].forEach(function (end) {
|
|
84
|
+
var t = vp.querySelector('.t[data-id="' + cssEsc(end[0]) + '"]');
|
|
85
|
+
if (!t) return;
|
|
86
|
+
if (k === S.edge) t.classList.add("on");
|
|
87
|
+
var row = t.querySelector('.r[data-c="' + cssEsc(end[1]) + '"]');
|
|
88
|
+
if (row) row.classList.add("lit");
|
|
89
|
+
});
|
|
90
|
+
parts.push('<path class="flow"' + dk + ' d="' + d + '"/>');
|
|
91
|
+
(Q.edgeLabels(S.vm, S.pos, S.mode, r, geo = geo || Q.geometry(S.vm, S.mode)) || []).forEach(function (l) {
|
|
92
|
+
parts.push('<text class="lbl"' + dk + ' x="' + l.x + '" y="' + l.y + '" text-anchor="' + l.anchor + '">' + Q.esc(l.text) + "</text>");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
fx.innerHTML = parts.join("");
|
|
96
|
+
}
|
|
97
|
+
var lightRaf = null;
|
|
98
|
+
function scheduleLight() {
|
|
99
|
+
if (!lightRaf) lightRaf = requestAnimationFrame(function () { lightRaf = null; light(); });
|
|
100
|
+
}
|
|
101
|
+
vp.addEventListener("mouseover", function (ev) {
|
|
102
|
+
var hp = ev.target.closest && ev.target.closest(".hit path"), t = ev.target.closest && ev.target.closest(".t");
|
|
103
|
+
var next = hp ? { e: hp.getAttribute("data-k") } : t ? { t: t.getAttribute("data-id") } : null;
|
|
104
|
+
if (JSON.stringify(next) !== JSON.stringify(S.hover)) { S.hover = next; scheduleLight(); }
|
|
105
|
+
});
|
|
106
|
+
/* A click on a line locks it: everything else dims. A second click on it, Esc or a click elsewhere clears it. */
|
|
107
|
+
function lockEdge(k, x, y) {
|
|
108
|
+
select(null);
|
|
109
|
+
S.edge = k;
|
|
110
|
+
light();
|
|
111
|
+
openCard(k, x, y);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* The relationship card: both ends with their cardinality, a plain-English sentence and the JOIN. */
|
|
115
|
+
var rcard = document.getElementById("rcard"), cardInfo = null;
|
|
116
|
+
function openCard(k, x, y) {
|
|
117
|
+
var r = S.vm.rels[relByKey[k]], c = Q.relCard(S.vm, relByKey[k]);
|
|
118
|
+
cardInfo = { sql: c.sql, tables: [r.a.t, r.b.t] };
|
|
119
|
+
rcard.innerHTML = '<div class="rt">' + Q.esc(c.from) + "<b>" + Q.esc(c.cardFrom) + " — " + Q.esc(c.cardTo) + "</b>" + Q.esc(c.to) +
|
|
120
|
+
'</div><div class="rk">' + Q.esc(c.sentence) + "</div><pre>" + Q.esc(c.sql) +
|
|
121
|
+
'</pre><div class="acts"><a id="rcopy">Copy JOIN</a><a id="rboth">Show both tables</a></div>';
|
|
122
|
+
rcard.classList.remove("open");
|
|
123
|
+
void rcard.offsetWidth; // restart the open animation
|
|
124
|
+
rcard.classList.add("open");
|
|
125
|
+
var st = document.getElementById("stage").getBoundingClientRect();
|
|
126
|
+
rcard.style.left = Math.max(8, Math.min(x - st.left + 12, st.width - rcard.offsetWidth - 8)) + "px";
|
|
127
|
+
rcard.style.top = Math.max(8, Math.min(y - st.top + 12, st.height - rcard.offsetHeight - 8)) + "px";
|
|
128
|
+
}
|
|
129
|
+
function closeCard() { rcard.classList.remove("open"); }
|
|
130
|
+
rcard.addEventListener("click", function (ev) {
|
|
131
|
+
if (ev.target.id === "rcopy" && cardInfo) copyText(cardInfo.sql, "JOIN copied", rcard.querySelector("pre"));
|
|
132
|
+
else if (ev.target.id === "rboth" && cardInfo) {
|
|
133
|
+
var names = cardInfo.tables.filter(function (t, i, all) { return all.indexOf(t) === i; }).map(function (t) { return byId[t].name; });
|
|
134
|
+
closeCard();
|
|
135
|
+
applyFilter(names.map(function (n) { return n.replace(/[\s,+!]/g, "?"); }).join(" "));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
document.addEventListener("pointerdown", function (ev) {
|
|
139
|
+
if (!rcard.contains(ev.target) && !(ev.target.closest && ev.target.closest(".hit path"))) closeCard();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/* The clipboard is blocked in some frames (the VS Code preview): select the text instead and say how to copy it. */
|
|
143
|
+
function copyText(text, done, pre) {
|
|
144
|
+
function fallback() {
|
|
145
|
+
var sel = window.getSelection(), range = document.createRange();
|
|
146
|
+
range.selectNodeContents(pre);
|
|
147
|
+
sel.removeAllRanges();
|
|
148
|
+
sel.addRange(range);
|
|
149
|
+
showToast("Press Ctrl/Cmd+C");
|
|
150
|
+
}
|
|
151
|
+
if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(text).then(function () { showToast(done); }, fallback);
|
|
152
|
+
else fallback();
|
|
153
|
+
}
|
|
154
|
+
|
|
43
155
|
// Zoomed out on a big diagram, table text is unreadable but repainting it made every zoom step take ~150 ms
|
|
44
156
|
// at 1000 tables, so it is hidden there. Smaller diagrams always show it.
|
|
45
157
|
var LOD_TABLES = 300, LOD_ZOOM = 0.3;
|
|
@@ -57,7 +169,28 @@
|
|
|
57
169
|
return [r.width || 800, r.height || 600];
|
|
58
170
|
}
|
|
59
171
|
|
|
60
|
-
|
|
172
|
+
/* Zoom, fit and jump glide instead of snapping. The next pointer press or wheel stops a glide. Instant with
|
|
173
|
+
* prefers-reduced-motion, on load and while a shared link is applied. */
|
|
174
|
+
var glide = null, glideTarget = null, reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
175
|
+
function glideTo(v, ms) {
|
|
176
|
+
cancelGlide();
|
|
177
|
+
if (!ms || reduceMotion.matches || applyingHash) { S.view = v; applyView(); return; }
|
|
178
|
+
var from = { x: S.view.x, y: S.view.y, k: S.view.k }, t0 = performance.now();
|
|
179
|
+
glideTarget = v;
|
|
180
|
+
(function step(now) {
|
|
181
|
+
var f = Math.min(1, (now - t0) / ms), e = 1 - Math.pow(1 - f, 3);
|
|
182
|
+
S.view = { x: from.x + (v.x - from.x) * e, y: from.y + (v.y - from.y) * e, k: from.k + (v.k - from.k) * e };
|
|
183
|
+
applyView();
|
|
184
|
+
glide = f < 1 ? requestAnimationFrame(step) : null;
|
|
185
|
+
if (!glide) glideTarget = null;
|
|
186
|
+
})(t0);
|
|
187
|
+
}
|
|
188
|
+
function cancelGlide() {
|
|
189
|
+
if (glide) cancelAnimationFrame(glide);
|
|
190
|
+
glide = glideTarget = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function fit(only, animate) {
|
|
61
194
|
var b = Q.bounds(only ? Q.subset(S.vm, only) : S.vm, S.pos, S.mode), sz = stageSize(), pad = 40;
|
|
62
195
|
if (S.regions && !only) b = [b[0] - 30, b[1] - 60, b[2] + 30, b[3] + 30]; // room for region borders and labels
|
|
63
196
|
if (only) sz = [sz[0] - (details.classList.contains("open") ? 400 : 0), sz[1]]; // keep clear of the details panel
|
|
@@ -65,24 +198,21 @@
|
|
|
65
198
|
var top = 70, bottom = sz[0] < 900 ? 70 : pad, availH = sz[1] - top - bottom;
|
|
66
199
|
var k = Math.min((sz[0] - 2 * pad) / (b[2] - b[0]), availH / (b[3] - b[1]), 1.5);
|
|
67
200
|
k = Math.max(k, 0.03); // same floor as manual zoom, so wide diagrams still fit a phone screen
|
|
68
|
-
|
|
69
|
-
applyView();
|
|
201
|
+
glideTo({ k: k, x: (sz[0] - (b[2] - b[0]) * k) / 2 - b[0] * k, y: top + (availH - (b[3] - b[1]) * k) / 2 - b[1] * k }, animate ? 350 : 0);
|
|
70
202
|
}
|
|
71
203
|
|
|
72
|
-
function zoomAt(factor, cx, cy) {
|
|
73
|
-
var v = S.view, k = Math.min(4, Math.max(0.03, v.k * factor));
|
|
74
|
-
|
|
75
|
-
applyView();
|
|
204
|
+
function zoomAt(factor, cx, cy, ms) {
|
|
205
|
+
var v = glideTarget || S.view, k = Math.min(4, Math.max(0.03, v.k * factor)); // quick clicks compound
|
|
206
|
+
glideTo({ k: k, x: cx - (cx - v.x) * (k / v.k), y: cy - (cy - v.y) * (k / v.k) }, ms);
|
|
76
207
|
}
|
|
77
208
|
|
|
78
|
-
function centerOn(id) {
|
|
209
|
+
function centerOn(id, animate) {
|
|
79
210
|
var t = byId[id], p = S.pos[id];
|
|
80
211
|
if (!t || !p) return;
|
|
81
212
|
var g = Q.geometry(S.vm, S.mode)[id], sz = stageSize();
|
|
82
213
|
if (!g) return;
|
|
83
214
|
var k = Math.max(S.view.k, 0.9);
|
|
84
|
-
|
|
85
|
-
applyView();
|
|
215
|
+
glideTo({ k: k, x: sz[0] / 2 - (p[0] + g.w / 2) * k, y: sz[1] / 2 - (p[1] + g.h / 2) * k }, animate ? 350 : 0);
|
|
86
216
|
var el = vp.querySelector('.t[data-id="' + cssEsc(id) + '"]');
|
|
87
217
|
if (el) {
|
|
88
218
|
el.classList.add("flash");
|
|
@@ -104,6 +234,10 @@
|
|
|
104
234
|
}
|
|
105
235
|
|
|
106
236
|
function applyFocus() {
|
|
237
|
+
applyFocusClasses();
|
|
238
|
+
light();
|
|
239
|
+
}
|
|
240
|
+
function applyFocusClasses() {
|
|
107
241
|
vp.querySelectorAll(".ph").forEach(function (el) { el.classList.remove("ph"); });
|
|
108
242
|
if (S.path && S.focus && S.path.steps) {
|
|
109
243
|
var on = {}, edges = [];
|
|
@@ -161,7 +295,7 @@
|
|
|
161
295
|
if (t) t.classList.add("hl");
|
|
162
296
|
});
|
|
163
297
|
});
|
|
164
|
-
vp.addEventListener("mouseleave", function () { clearHover(); hideTip(); });
|
|
298
|
+
vp.addEventListener("mouseleave", function () { clearHover(); hideTip(); S.hover = null; scheduleLight(); });
|
|
165
299
|
|
|
166
300
|
/* ------------------------------------------------------------- doc-comment tooltips */
|
|
167
301
|
var tip = document.getElementById("tip");
|
|
@@ -209,17 +343,21 @@
|
|
|
209
343
|
/* ------------------------------------------------------------- pointer: pan, click, drag */
|
|
210
344
|
var drag = null;
|
|
211
345
|
svg.addEventListener("pointerdown", function (ev) {
|
|
346
|
+
cancelGlide();
|
|
212
347
|
if (ev.button !== 0) return;
|
|
213
348
|
var tEl = ev.target.closest && ev.target.closest(".t");
|
|
214
|
-
|
|
349
|
+
var hp = !tEl && ev.target.closest && ev.target.closest(".hit path");
|
|
350
|
+
drag = { sx: ev.clientX, sy: ev.clientY, moved: false, table: tEl ? tEl.getAttribute("data-id") : null, el: tEl,
|
|
351
|
+
edge: hp ? hp.getAttribute("data-k") : null };
|
|
215
352
|
if (drag.table) { // tables are draggable everywhere; only `qdbd serve` saves the new position
|
|
216
353
|
drag.start = S.pos[drag.table].slice();
|
|
354
|
+
dragPrep(drag);
|
|
217
355
|
tEl.parentNode.appendChild(tEl); // bring the grabbed table to the front
|
|
218
356
|
} else {
|
|
219
357
|
drag.view = { x: S.view.x, y: S.view.y };
|
|
220
358
|
}
|
|
221
359
|
svg.classList.add("panning");
|
|
222
|
-
svg.setPointerCapture(ev.pointerId);
|
|
360
|
+
try { svg.setPointerCapture(ev.pointerId); } catch (e) { /* synthetic events (the qdbd.dev tour) have no pointer to capture */ }
|
|
223
361
|
});
|
|
224
362
|
var raf = null;
|
|
225
363
|
svg.addEventListener("pointermove", function (ev) {
|
|
@@ -231,19 +369,20 @@
|
|
|
231
369
|
var p = [Math.round(drag.start[0] + dx / S.view.k), Math.round(drag.start[1] + dy / S.view.k)];
|
|
232
370
|
S.pos[drag.table] = p;
|
|
233
371
|
drag.el.setAttribute("transform", "translate(" + p[0] + "," + p[1] + ")");
|
|
234
|
-
if (!raf) raf = requestAnimationFrame(function () { raf = null;
|
|
372
|
+
if (!raf) raf = requestAnimationFrame(function () { raf = null; if (drag) dragEdges(drag); });
|
|
235
373
|
} else if (drag.view) {
|
|
236
374
|
S.view.x = drag.view.x + dx;
|
|
237
375
|
S.view.y = drag.view.y + dy;
|
|
238
376
|
applyView();
|
|
239
377
|
}
|
|
240
378
|
});
|
|
241
|
-
svg.addEventListener("pointerup", function () {
|
|
379
|
+
svg.addEventListener("pointerup", function (ev) {
|
|
242
380
|
if (!drag) return;
|
|
243
381
|
svg.classList.remove("panning");
|
|
244
382
|
var d = drag;
|
|
245
383
|
drag = null;
|
|
246
384
|
if (!d.moved) {
|
|
385
|
+
if (d.edge) { if (d.edge === S.edge) select(null); else lockEdge(d.edge, ev.clientX, ev.clientY); return; }
|
|
247
386
|
// Clicking the selected table toggles it off, unless it was selected quietly: then show its details.
|
|
248
387
|
if (d.table && d.table === S.focus && !details.classList.contains("open")) showDetails(d.table);
|
|
249
388
|
else select(d.table && S.focus !== d.table ? d.table : null);
|
|
@@ -251,14 +390,45 @@
|
|
|
251
390
|
}
|
|
252
391
|
if (d.start) {
|
|
253
392
|
if (S.regions) render(); // regions are sized from table positions
|
|
393
|
+
else renderEdges();
|
|
254
394
|
drawMinimap();
|
|
255
395
|
var name = byId[d.table].name, body = {};
|
|
256
396
|
body[name] = S.pos[d.table];
|
|
257
397
|
if (window.qdbdServe) window.qdbdServe.saveLayout(body);
|
|
258
398
|
}
|
|
259
399
|
});
|
|
400
|
+
/* While a table is dragged only its own lines are redrawn (a full redraw per frame was slow at 200+ tables). */
|
|
401
|
+
function dragPrep(d) {
|
|
402
|
+
var on = function (k) { var p = k.split("|"); return p[0] === d.table || p[2] === d.table; };
|
|
403
|
+
d.geo = Q.geometry(S.vm, S.mode);
|
|
404
|
+
d.rels = S.vm.rels.filter(function (r) { return r.a.t === d.table || r.b.t === d.table; });
|
|
405
|
+
d.els = {};
|
|
406
|
+
d.hits = {};
|
|
407
|
+
vp.querySelectorAll(".edges .e").forEach(function (e) { var k = edgeKey(e); if (on(k)) d.els[k] = e; });
|
|
408
|
+
vp.querySelectorAll(".hit path").forEach(function (h) { var k = h.getAttribute("data-k"); if (on(k)) d.hits[k] = h; });
|
|
409
|
+
}
|
|
410
|
+
function dragEdges(d) {
|
|
411
|
+
var tmp = document.createElementNS("http://www.w3.org/2000/svg", "g"), fx = vp.querySelector(".fx");
|
|
412
|
+
tmp.innerHTML = Q.renderEdges({ style: S.vm.style, tables: S.vm.tables, rels: d.rels }, S.pos, S.mode, d.geo);
|
|
413
|
+
[].slice.call(tmp.children).forEach(function (e) {
|
|
414
|
+
var k = edgeKey(e), old = d.els[k], path = e.querySelector(".ep").getAttribute("d");
|
|
415
|
+
if (!old) return;
|
|
416
|
+
e.setAttribute("class", old.getAttribute("class")); // keep dim / lit / ph
|
|
417
|
+
old.replaceWith(e);
|
|
418
|
+
d.els[k] = e;
|
|
419
|
+
if (d.hits[k]) d.hits[k].setAttribute("d", path);
|
|
420
|
+
var items = fx ? fx.querySelectorAll('[data-k="' + cssEsc(k) + '"]') : [], labels = null, li = 0;
|
|
421
|
+
[].forEach.call(items, function (it) {
|
|
422
|
+
if (it.tagName === "path") { it.setAttribute("d", path); return; }
|
|
423
|
+
labels = labels || Q.edgeLabels(S.vm, S.pos, S.mode, S.vm.rels[relByKey[k]], d.geo) || [];
|
|
424
|
+
var l = labels[li++];
|
|
425
|
+
if (l) { it.setAttribute("x", l.x); it.setAttribute("y", l.y); it.setAttribute("text-anchor", l.anchor); }
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
}
|
|
260
429
|
svg.addEventListener("wheel", function (ev) {
|
|
261
430
|
ev.preventDefault();
|
|
431
|
+
cancelGlide();
|
|
262
432
|
var r = svg.getBoundingClientRect();
|
|
263
433
|
var factor = Math.exp(-ev.deltaY * (ev.ctrlKey ? 0.01 : 0.0015));
|
|
264
434
|
zoomAt(factor, ev.clientX - r.left, ev.clientY - r.top);
|
|
@@ -269,9 +439,9 @@
|
|
|
269
439
|
|
|
270
440
|
/* ------------------------------------------------------------- toolbar */
|
|
271
441
|
function center() { var s = stageSize(); return [s[0] / 2, s[1] / 2]; }
|
|
272
|
-
document.getElementById("zin").onclick = function () { var c = center(); zoomAt(1.25, c[0], c[1]); };
|
|
273
|
-
document.getElementById("zout").onclick = function () { var c = center(); zoomAt(0.8, c[0], c[1]); };
|
|
274
|
-
document.getElementById("zfit").onclick = function () { fit(); };
|
|
442
|
+
document.getElementById("zin").onclick = function () { var c = center(); zoomAt(1.25, c[0], c[1], 200); };
|
|
443
|
+
document.getElementById("zout").onclick = function () { var c = center(); zoomAt(0.8, c[0], c[1], 200); };
|
|
444
|
+
document.getElementById("zfit").onclick = function () { fit(null, true); };
|
|
275
445
|
document.querySelectorAll("#modes button").forEach(function (b) {
|
|
276
446
|
b.onclick = function () {
|
|
277
447
|
setMode(b.getAttribute("data-mode"));
|
|
@@ -325,13 +495,14 @@
|
|
|
325
495
|
/* opts.center: pan to it. opts.quiet: don't open the details panel (only refresh it if it's already open). */
|
|
326
496
|
function select(id, opts) {
|
|
327
497
|
if ((id || null) !== S.focus) S.path = null;
|
|
498
|
+
S.edge = null; // a table (or nothing) replaces the clicked line
|
|
328
499
|
S.focus = id && byId[id] ? id : null;
|
|
329
500
|
scheduleHash();
|
|
330
501
|
applyFocus();
|
|
331
502
|
if (S.focus && (!(opts && opts.quiet) || details.classList.contains("open"))) showDetails(S.focus);
|
|
332
503
|
else closeDetails();
|
|
333
504
|
markTree();
|
|
334
|
-
if (S.focus && opts && opts.center) centerOn(S.focus);
|
|
505
|
+
if (S.focus && opts && opts.center) centerOn(S.focus, true);
|
|
335
506
|
}
|
|
336
507
|
|
|
337
508
|
/* ------------------------------------------------------------- explorer: search + table tree */
|
|
@@ -465,7 +636,7 @@
|
|
|
465
636
|
drawTree();
|
|
466
637
|
if (S.focus) showDetails(S.focus);
|
|
467
638
|
else closeDetails();
|
|
468
|
-
if (!keepView) fit();
|
|
639
|
+
if (!keepView) fit(null, true);
|
|
469
640
|
scheduleHash();
|
|
470
641
|
}
|
|
471
642
|
finput.addEventListener("input", function () {
|
|
@@ -590,7 +761,7 @@
|
|
|
590
761
|
S.path = { to: target, steps: Q.findPath(S.vm, S.focus, target) };
|
|
591
762
|
showDetails(S.focus);
|
|
592
763
|
applyFocus();
|
|
593
|
-
if (S.path.steps && !applyingHash) fit([S.focus].concat(S.path.steps.map(function (st) { return st.to.t; })));
|
|
764
|
+
if (S.path.steps && !applyingHash) fit([S.focus].concat(S.path.steps.map(function (st) { return st.to.t; })), true);
|
|
594
765
|
scheduleHash();
|
|
595
766
|
return true;
|
|
596
767
|
}
|
|
@@ -606,9 +777,7 @@
|
|
|
606
777
|
// Selector syntax treats spaces, commas, + and a leading ! specially; match those characters with "?".
|
|
607
778
|
applyFilter(names.map(function (n) { return n.replace(/[\s,+!]/g, "?"); }).join(" "));
|
|
608
779
|
} else if (ev.target.id === "pathsql" && S.path && S.path.steps) {
|
|
609
|
-
|
|
610
|
-
if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(sql).then(function () { showToast("SQL copied"); });
|
|
611
|
-
else window.prompt("Copy SQL:", sql);
|
|
780
|
+
copyText(Q.pathSql(S.vm, S.focus, S.path.steps), "SQL copied", dbody.querySelector("#pathres pre"));
|
|
612
781
|
}
|
|
613
782
|
});
|
|
614
783
|
|
|
@@ -649,6 +818,7 @@
|
|
|
649
818
|
r.setAttribute("height", sz[1] / v.k);
|
|
650
819
|
}
|
|
651
820
|
function miniPan(ev) {
|
|
821
|
+
cancelGlide();
|
|
652
822
|
var pt = mini.createSVGPoint();
|
|
653
823
|
pt.x = ev.clientX;
|
|
654
824
|
pt.y = ev.clientY;
|
|
@@ -658,7 +828,11 @@
|
|
|
658
828
|
applyView();
|
|
659
829
|
}
|
|
660
830
|
var miniDrag = false;
|
|
661
|
-
mini.addEventListener("pointerdown", function (ev) {
|
|
831
|
+
mini.addEventListener("pointerdown", function (ev) {
|
|
832
|
+
miniDrag = true;
|
|
833
|
+
try { mini.setPointerCapture(ev.pointerId); } catch (e) { /* synthetic event */ }
|
|
834
|
+
miniPan(ev);
|
|
835
|
+
});
|
|
662
836
|
mini.addEventListener("pointermove", function (ev) { if (miniDrag) miniPan(ev); });
|
|
663
837
|
mini.addEventListener("pointerup", function () { miniDrag = false; });
|
|
664
838
|
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
from importlib import resources
|
|
7
|
+
from urllib.parse import quote
|
|
7
8
|
|
|
8
9
|
from .export import font_css
|
|
9
10
|
|
|
@@ -14,6 +15,11 @@ def _asset(name: str) -> str:
|
|
|
14
15
|
return resources.files("quickdbd_cli").joinpath(f"assets/{name}").read_text(encoding="utf-8")
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
def favicon() -> str:
|
|
19
|
+
"""The VS Code extension's icon as an inline SVG data URL, so pages stay single-file."""
|
|
20
|
+
return f'<link rel="icon" href="data:image/svg+xml,{quote(_asset("icon.svg").strip(), safe=" /=:")}">'
|
|
21
|
+
|
|
22
|
+
|
|
17
23
|
def _json_for_script(obj) -> str:
|
|
18
24
|
# Safe inside <script type="application/json">: no "</" sequences.
|
|
19
25
|
return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).replace("</", "<\\/")
|
|
@@ -90,6 +96,7 @@ BODY = """
|
|
|
90
96
|
<svg id="mini" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet"></svg>
|
|
91
97
|
</div>
|
|
92
98
|
<div class="tip" id="tip" role="tooltip"></div>
|
|
99
|
+
<div class="rcard" id="rcard" role="dialog" aria-label="Relationship"></div>
|
|
93
100
|
<div class="toast" id="toast" role="status"></div>
|
|
94
101
|
<div class="diffbar" id="diffbar"></div>
|
|
95
102
|
<div class="hint">{hint}</div>
|
|
@@ -170,6 +177,7 @@ def render_page(
|
|
|
170
177
|
'<!doctype html>\n<html lang="en"><head><meta charset="utf-8">'
|
|
171
178
|
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
172
179
|
'<meta name="generator" content="qdbd">'
|
|
180
|
+
f"{favicon()}"
|
|
173
181
|
f"<title>{_escape(title)}</title>"
|
|
174
182
|
f"<script>{THEME_JS}</script>"
|
|
175
183
|
# The font CSS lives in its own <style> so the viewer can reuse it for SVG/PNG export
|