Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a8a2166
Add forked from list item on package view
supersonicbyte Sep 9, 2024
287172f
Fix up tests
supersonicbyte Sep 9, 2024
489b423
Added Icon and style for `forked` metadata item.
daveverwer Sep 9, 2024
2e2cac4
Improve ForkedFrom to reflect more info in UI
supersonicbyte Sep 10, 2024
3cebb09
Add icon to css
supersonicbyte Sep 10, 2024
c60f68f
Fix up tests
supersonicbyte Sep 10, 2024
5f360f2
Add more tests
supersonicbyte Sep 10, 2024
e286cae
Fix lint error
supersonicbyte Sep 10, 2024
358e8cb
Resolve PR comments
supersonicbyte Sep 13, 2024
99d167e
Merge branch 'main' into add-forked-from-ui
supersonicbyte Sep 13, 2024
e63f8c4
Use relative URLs instead of absolute
supersonicbyte Sep 13, 2024
53e70ed
Fix tests
supersonicbyte Sep 13, 2024
a6c7883
Merge ForkedFromResult and ForkedFromInfo
finestructure Sep 15, 2024
0ec21b3
Merge pull request #3388 from SwiftPackageIndex/enum-rework
supersonicbyte Sep 15, 2024
c2bab32
NPM: Bump the npm-dependencies group with 2 updates
dependabot[bot] Sep 16, 2024
a5c5bb2
Remove SwiftUICharts (package removed)
finestructure Sep 16, 2024
e2d3a69
Add run 12 results
finestructure Sep 17, 2024
ee4032d
Update Fork to containt fallback url
supersonicbyte Sep 18, 2024
3a5172a
Fix tests
supersonicbyte Sep 18, 2024
0b40fce
Resolve PR comments
supersonicbyte Sep 18, 2024
57a0b21
Resort to fallback URL when package can't be found
supersonicbyte Sep 18, 2024
6e064b0
Fix typo
supersonicbyte Sep 18, 2024
5e26cd4
Fix url in test
supersonicbyte Sep 18, 2024
6a627d1
Merge branch 'main' into add-forked-from-ui
supersonicbyte Sep 20, 2024
ce07502
Merge branch 'main' into add-forked-from-ui
supersonicbyte Sep 23, 2024
c9fa38c
Merge branch 'main' into add-forked-from-ui
daveverwer Sep 24, 2024
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
2 changes: 2 additions & 0 deletions FrontEnd/styles/images.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
--image-download: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzJmMmYyZiI+PHBhdGggZD0ibTM3LjkzIDI1LjAxLTIuOTgtNC4wMS03LjQ1IDUuNTN2LTIzLjA5aC01djIzLjA5bC03LjQ1LTUuNTMtMi45OCA0LjAxIDEyLjkzIDkuNjF6Ii8+PHBhdGggZD0ibTQyLjMxIDMzLjA5djguNDdoLTM0LjYydi04LjQ3aC01djEzLjQ3aDQ0LjYydi0xMy40N3oiLz48L2c+PC9zdmc+');
--image-error: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bTIuNjYgMzYuMThjLS42NC41OS0xLjUzLjg5LTIuNjguODlzLTIuMDctLjMtMi43Mi0uODktLjk4LTEuNDItLjk4LTIuNDcuMzItMS45Ljk1LTIuNDkgMS41NS0uODkgMi43Ni0uODkgMi4xLjMgMi43Mi44OWMuNjEuNTkuOTIgMS40Mi45MiAyLjQ5cy0uMzIgMS44OC0uOTYgMi40N3ptLjI0LTguNjJoLTUuOGwtLjktMTYuNjNoNy41OWwtLjkgMTYuNjN6IiBmaWxsPSIjYzQ0Ii8+PC9zdmc+');
--image-executables: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bS0xMi45NCAzMy45NSA3LjM5LTkuOTUtNy4zOS05Ljk1IDQuMDEtMi45OCA5LjYxIDEyLjkzLTkuNjEgMTIuOTN6bTI0Ljg4IDMuODNoLTEyLjV2LTVoMTIuNXoiIGZpbGw9IiMyZjJmMmYiLz48L3N2Zz4=');
--image-fork: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQzLjUgMTEuODdjMC0zLjU5LTIuOTEtNi41LTYuNS02LjVzLTYuNSAyLjkxLTYuNSA2LjVjMCAyLjQgMS4zMSA0LjUgMy4yNSA1LjYydjExLjM3bC0yMC4xNiA1Ljg1IDEuODEgNi4yNCAyNC44NS03LjIxdi0xNi4yNWMxLjk0LTEuMTIgMy4yNS0zLjIyIDMuMjUtNS42MnoiIGZpbGw9IiMyYjJiMmIiLz48cGF0aCBkPSJtMjIuNSAzNy45M2MwLTMuMjYtMS45NS02LjA2LTQuNzUtNy4zMXYtMjkuNjJoLTYuNXYyOS42M2MtMi44IDEuMjUtNC43NSA0LjA1LTQuNzUgNy4zMXMxLjk1IDYuMDYgNC43NSA3LjMxdjMuNzZoNi41di0zLjc2YzIuOC0xLjI1IDQuNzUtNC4wNSA0Ljc1LTcuMzF6IiBmaWxsPSIjNmI2YjZiIi8+PC9zdmc+');
--image-ghcta-header: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDUwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0yNSAxLjU5Yy0xMy4yNSAwLTI0IDEwLjc1LTI0IDI0IDAgMTAuNiA2Ljg4IDE5LjYgMTYuNDEgMjIuNzcgMS4yLjIyIDEuNjQtLjUyIDEuNjQtMS4xNiAwLS41Ny0uMDItMi4wOC0uMDMtNC4wOC02LjY4IDEuNDUtOC4wOC0zLjIyLTguMDgtMy4yMi0xLjA5LTIuNzctMi42Ni0zLjUxLTIuNjYtMy41MS0yLjE4LTEuNDkuMTYtMS40Ni4xNi0xLjQ2IDIuNDEuMTcgMy42OCAyLjQ3IDMuNjggMi40NyAyLjE0IDMuNjcgNS42MiAyLjYxIDYuOTggMS45OS4yMi0xLjU1Ljg0LTIuNjEgMS41Mi0zLjIxLTUuMzMtLjYxLTEwLjkzLTIuNjYtMTAuOTMtMTEuODYgMC0yLjYyLjk0LTQuNzYgMi40Ny02LjQ0LS4yNS0uNjEtMS4wNy0zLjA1LjI0LTYuMzUgMCAwIDIuMDItLjY1IDYuNiAyLjQ2IDEuOTEtLjUzIDMuOTctLjggNi4wMS0uODEgMi4wNCAwIDQuMDkuMjggNi4wMS44MSA0LjU4LTMuMTEgNi41OS0yLjQ2IDYuNTktMi40NiAxLjMxIDMuMy40OSA1Ljc0LjI0IDYuMzUgMS41NCAxLjY4IDIuNDcgMy44MiAyLjQ3IDYuNDQgMCA5LjIyLTUuNjEgMTEuMjUtMTAuOTYgMTEuODQuODYuNzQgMS42MyAyLjIxIDEuNjMgNC40NCAwIDMuMjEtLjAzIDUuOC0uMDMgNi41OCAwIC42NC40MyAxLjM5IDEuNjUgMS4xNSA5LjUzLTMuMTggMTYuNC0xMi4xNyAxNi40LTIyLjc3IDAtMTMuMjYtMTAuNzUtMjQtMjQtMjR6IiBmaWxsPSIjMmYyZjJmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48Y2lyY2xlIGN4PSI5NSIgY3k9IjI1IiBmaWxsPSIjMmYyZjJmIiByPSIyNCIvPjxwYXRoIGQ9Im0xMDIuNjIgMTMuNTFjLTMuMTctLjItNS45OSAxLjMzLTcuNjIgMy43My0xLjY0LTIuNC00LjQ2LTMuOTMtNy42Mi0zLjczLTQuODEuMy04LjM0IDQuNzYtNy45NyA5LjU2LjE5IDIuNDcgMS41NCA3LjIzIDExLjE5IDEzLjQ3bDQuNCAyLjg1IDQuNC0yLjg1YzkuNjQtNi4yMyAxMS0xMSAxMS4xOS0xMy40Ny4zNy00LjgtMy4xNi05LjI2LTcuOTctOS41NnoiIGZpbGw9IiNmNDRlYjIiLz48cGF0aCBkPSJtNTQuNDMgMjMuN2g0LjI2di0zLjk1aDIuNjF2My45NWg0LjI3djIuNjJoLTQuMjd2My45NGgtMi42MXYtMy45NGgtNC4yNnoiIGZpbGw9IiMyZjJmMmYiLz48L3N2Zz4=');
--image-github: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDEuNTljLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjQgMCAxMC42IDYuODggMTkuNiAxNi40MSAyMi43NyAxLjIuMjIgMS42NC0uNTIgMS42NC0xLjE2IDAtLjU3LS4wMi0yLjA4LS4wMy00LjA4LTYuNjggMS40NS04LjA4LTMuMjItOC4wOC0zLjIyLTEuMDktMi43Ny0yLjY2LTMuNTEtMi42Ni0zLjUxLTIuMTgtMS40OS4xNi0xLjQ2LjE2LTEuNDYgMi40MS4xNyAzLjY4IDIuNDcgMy42OCAyLjQ3IDIuMTQgMy42NyA1LjYyIDIuNjEgNi45OCAxLjk5LjIyLTEuNTUuODQtMi42MSAxLjUyLTMuMjEtNS4zMy0uNjEtMTAuOTMtMi42Ni0xMC45My0xMS44NiAwLTIuNjIuOTQtNC43NiAyLjQ3LTYuNDQtLjI1LS42MS0xLjA3LTMuMDUuMjQtNi4zNSAwIDAgMi4wMi0uNjUgNi42IDIuNDYgMS45MS0uNTMgMy45Ny0uOCA2LjAxLS44MSAyLjA0IDAgNC4wOS4yOCA2LjAxLjgxIDQuNTgtMy4xMSA2LjU5LTIuNDYgNi41OS0yLjQ2IDEuMzEgMy4zLjQ5IDUuNzQuMjQgNi4zNSAxLjU0IDEuNjggMi40NyAzLjgyIDIuNDcgNi40NCAwIDkuMjItNS42MSAxMS4yNS0xMC45NiAxMS44NC44Ni43NCAxLjYzIDIuMjEgMS42MyA0LjQ0IDAgMy4yMS0uMDMgNS44LS4wMyA2LjU4IDAgLjY0LjQzIDEuMzkgMS42NSAxLjE1IDkuNTMtMy4xOCAxNi40LTEyLjE3IDE2LjQtMjIuNzcgMC0xMy4yNi0xMC43NS0yNC0yNC0yNHoiIGZpbGw9IiMyZjJmMmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==');
--image-heart: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTM2LjgyIDQuOTZjLTQuOTEtLjMxLTkuMjggMi4wNi0xMS44MiA1Ljc4LTIuNTQtMy43Mi02LjkxLTYuMDktMTEuODItNS43OC03LjQ1LjQ2LTEyLjkzIDcuMzctMTIuMzUgMTQuODIuMjkgMy44MyAyLjM5IDExLjIxIDE3LjM1IDIwLjg4bDYuODMgNC40MSA2LjgzLTQuNDFjMTQuOTUtOS42NiAxNy4wNS0xNy4wNSAxNy4zNS0yMC44OC41Ny03LjQ0LTQuOS0xNC4zNS0xMi4zNS0xNC44MnoiIGZpbGw9IiNmNDRlYjIiLz48L3N2Zz4=');
Expand Down Expand Up @@ -73,6 +74,7 @@
--image-download: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iI2YxZjFmMSI+PHBhdGggZD0ibTM3LjkzIDI1LjAxLTIuOTgtNC4wMS03LjQ1IDUuNTN2LTIzLjA5aC01djIzLjA5bC03LjQ1LTUuNTMtMi45OCA0LjAxIDEyLjkzIDkuNjF6Ii8+PHBhdGggZD0ibTQyLjMxIDMzLjA5djguNDdoLTM0LjYydi04LjQ3aC01djEzLjQ3aDQ0LjYydi0xMy40N3oiLz48L2c+PC9zdmc+');
--image-error: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bTIuNjYgMzYuMThjLS42NC41OS0xLjUzLjg5LTIuNjguODlzLTIuMDctLjMtMi43Mi0uODktLjk4LTEuNDItLjk4LTIuNDcuMzItMS45Ljk1LTIuNDkgMS41NS0uODkgMi43Ni0uODkgMi4xLjMgMi43Mi44OWMuNjEuNTkuOTIgMS40Mi45MiAyLjQ5cy0uMzIgMS44OC0uOTYgMi40N3ptLjI0LTguNjJoLTUuOGwtLjktMTYuNjNoNy41OWwtLjkgMTYuNjN6IiBmaWxsPSIjZmY0MzQzIi8+PC9zdmc+');
--image-executables: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bS0xMi45NCAzMy45NSA3LjM5LTkuOTUtNy4zOS05Ljk1IDQuMDEtMi45OCA5LjYxIDEyLjkzLTkuNjEgMTIuOTN6bTI0Ljg4IDMuODNoLTEyLjV2LTVoMTIuNXoiIGZpbGw9IiNmMWYxZjEiLz48L3N2Zz4=');
--image-fork: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQzLjUgMTEuODdjMC0zLjU5LTIuOTEtNi41LTYuNS02LjVzLTYuNSAyLjkxLTYuNSA2LjVjMCAyLjQgMS4zMSA0LjUgMy4yNSA1LjYydjExLjM3bC0yMC4xNiA1Ljg1IDEuODEgNi4yNCAyNC44NS03LjIxdi0xNi4yNWMxLjk0LTEuMTIgMy4yNS0zLjIyIDMuMjUtNS42MnoiIGZpbGw9IiNmMWYxZjEiLz48cGF0aCBkPSJtMjIuNSAzNy45M2MwLTMuMjYtMS45NS02LjA2LTQuNzUtNy4zMXYtMjkuNjJoLTYuNXYyOS42M2MtMi44IDEuMjUtNC43NSA0LjA1LTQuNzUgNy4zMXMxLjk1IDYuMDYgNC43NSA3LjMxdjMuNzZoNi41di0zLjc2YzIuOC0xLjI1IDQuNzUtNC4wNSA0Ljc1LTcuMzF6IiBmaWxsPSIjYjFiMWIxIi8+PC9zdmc+');
--image-ghcta-header: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDUwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0yNSAxLjU5Yy0xMy4yNSAwLTI0IDEwLjc1LTI0IDI0IDAgMTAuNiA2Ljg4IDE5LjYgMTYuNDEgMjIuNzcgMS4yLjIyIDEuNjQtLjUyIDEuNjQtMS4xNiAwLS41Ny0uMDItMi4wOC0uMDMtNC4wOC02LjY4IDEuNDUtOC4wOC0zLjIyLTguMDgtMy4yMi0xLjA5LTIuNzctMi42Ni0zLjUxLTIuNjYtMy41MS0yLjE4LTEuNDkuMTYtMS40Ni4xNi0xLjQ2IDIuNDEuMTcgMy42OCAyLjQ3IDMuNjggMi40NyAyLjE0IDMuNjcgNS42MiAyLjYxIDYuOTggMS45OS4yMi0xLjU1Ljg0LTIuNjEgMS41Mi0zLjIxLTUuMzMtLjYxLTEwLjkzLTIuNjYtMTAuOTMtMTEuODYgMC0yLjYyLjk0LTQuNzYgMi40Ny02LjQ0LS4yNS0uNjEtMS4wNy0zLjA1LjI0LTYuMzUgMCAwIDIuMDItLjY1IDYuNiAyLjQ2IDEuOTEtLjUzIDMuOTctLjggNi4wMS0uODEgMi4wNCAwIDQuMDkuMjggNi4wMS44MSA0LjU4LTMuMTEgNi41OS0yLjQ2IDYuNTktMi40NiAxLjMxIDMuMy40OSA1Ljc0LjI0IDYuMzUgMS41NCAxLjY4IDIuNDcgMy44MiAyLjQ3IDYuNDQgMCA5LjIyLTUuNjEgMTEuMjUtMTAuOTYgMTEuODQuODYuNzQgMS42MyAyLjIxIDEuNjMgNC40NCAwIDMuMjEtLjAzIDUuOC0uMDMgNi41OCAwIC42NC40MyAxLjM5IDEuNjUgMS4xNSA5LjUzLTMuMTggMTYuNC0xMi4xNyAxNi40LTIyLjc3IDAtMTMuMjYtMTAuNzUtMjQtMjQtMjR6IiBmaWxsPSIjZjFmMWYxIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48Y2lyY2xlIGN4PSI5NSIgY3k9IjI1IiBmaWxsPSIjZjFmMWYxIiByPSIyNCIvPjxwYXRoIGQ9Im0xMDIuNjIgMTMuNTFjLTMuMTctLjItNS45OSAxLjMzLTcuNjIgMy43My0xLjY0LTIuNC00LjQ2LTMuOTMtNy42Mi0zLjczLTQuODEuMy04LjM0IDQuNzYtNy45NyA5LjU2LjE5IDIuNDcgMS41NCA3LjIzIDExLjE5IDEzLjQ3bDQuNCAyLjg1IDQuNC0yLjg1YzkuNjQtNi4yMyAxMS0xMSAxMS4xOS0xMy40Ny4zNy00LjgtMy4xNi05LjI2LTcuOTctOS41NnoiIGZpbGw9IiNmNDRlYjIiLz48cGF0aCBkPSJtNTQuNDMgMjMuN2g0LjI2di0zLjk1aDIuNjF2My45NWg0LjI3djIuNjJoLTQuMjd2My45NGgtMi42MXYtMy45NGgtNC4yNnoiIGZpbGw9IiNmMWYxZjEiLz48L3N2Zz4=');
--image-github: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDEuNTljLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjQgMCAxMC42IDYuODggMTkuNiAxNi40MSAyMi43NyAxLjIuMjIgMS42NC0uNTIgMS42NC0xLjE2IDAtLjU3LS4wMi0yLjA4LS4wMy00LjA4LTYuNjggMS40NS04LjA4LTMuMjItOC4wOC0zLjIyLTEuMDktMi43Ny0yLjY2LTMuNTEtMi42Ni0zLjUxLTIuMTgtMS40OS4xNi0xLjQ2LjE2LTEuNDYgMi40MS4xNyAzLjY4IDIuNDcgMy42OCAyLjQ3IDIuMTQgMy42NyA1LjYyIDIuNjEgNi45OCAxLjk5LjIyLTEuNTUuODQtMi42MSAxLjUyLTMuMjEtNS4zMy0uNjEtMTAuOTMtMi42Ni0xMC45My0xMS44NiAwLTIuNjIuOTQtNC43NiAyLjQ3LTYuNDQtLjI1LS42MS0xLjA3LTMuMDUuMjQtNi4zNSAwIDAgMi4wMi0uNjUgNi42IDIuNDYgMS45MS0uNTMgMy45Ny0uOCA2LjAxLS44MSAyLjA0IDAgNC4wOS4yOCA2LjAxLjgxIDQuNTgtMy4xMSA2LjU5LTIuNDYgNi41OS0yLjQ2IDEuMzEgMy4zLjQ5IDUuNzQuMjQgNi4zNSAxLjU0IDEuNjggMi40NyAzLjgyIDIuNDcgNi40NCAwIDkuMjItNS42MSAxMS4yNS0xMC45NiAxMS44NC44Ni43NCAxLjYzIDIuMjEgMS42MyA0LjQ0IDAgMy4yMS0uMDMgNS44LS4wMyA2LjU4IDAgLjY0LjQzIDEuMzkgMS42NSAxLjE1IDkuNTMtMy4xOCAxNi40LTEyLjE3IDE2LjQtMjIuNzcgMC0xMy4yNi0xMC43NS0yNC0yNC0yNHoiIGZpbGw9IiNmMWYxZjEiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==');
--image-heart: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTM2LjgyIDQuOTZjLTQuOTEtLjMxLTkuMjggMi4wNi0xMS44MiA1Ljc4LTIuNTQtMy43Mi02LjkxLTYuMDktMTEuODItNS43OC03LjQ1LjQ2LTEyLjkzIDcuMzctMTIuMzUgMTQuODIuMjkgMy44MyAyLjM5IDExLjIxIDE3LjM1IDIwLjg4bDYuODMgNC40MSA2LjgzLTQuNDFjMTQuOTUtOS42NiAxNy4wNS0xNy4wNSAxNy4zNS0yMC44OC41Ny03LjQ0LTQuOS0xNC4zNS0xMi4zNS0xNC44MnoiIGZpbGw9IiNmNDRlYjIiLz48L3N2Zz4=');
Expand Down
7 changes: 6 additions & 1 deletion FrontEnd/styles/package.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,17 @@
display: block;
font-size: 11px;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's not add whitespace like this. The easiest way to fix this is to set these two options in Xcode's Editing settings.

Screenshot 2024-09-12 at 12 59 36@2x

li.archived {
grid-column-start: span 2;
background-image: var(--image-warning);
}

li.forked {
grid-column-start: span 2;
background-image: var(--image-fork);
}

li.authors {
grid-column-start: span 2;
background-image: var(--image-authors);
Expand Down
1 change: 1 addition & 0 deletions Resources/SVGs/fork~dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Resources/SVGs/fork~light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Fluent
import SQLKit
import Vapor

extension API.PackageController {
enum ForkedFromResult {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ForkedFromResult and ForkedFromInfo seem very similar. They have the same cases, almost the same associated values - could they be the same type? Is there a reason they're not? If so, perhaps the name could/should carry that concept.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are very similar. The reason I have two types is because I thought that it might be a good thing to separate the actual result and then to initialize the model (that's gonna be used in the view layer). Same thing if you look at PackageResult and API.PackageController.GetRoute.Model. I'm not really sure. Should I just merge it into one type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PackageResult (a db model reference type) and API.PackageController.GetRoute.Model (a view model value type) are great examples of why and when to add these types.

We could have avoided transforming data into the various types that make up GetRoute.Model but we wanted to have a plain value type view model that we can use to drive a page. That's a clearer separation but it also allows us to very easily assemble data for snapshot testing etc.

(It was also really helpful early on when Dave designed the pages and just specified in a struct what data he needed and I populated it from the db results independently. It made for a great interface.)

You'll see that some types don't get translated that way. For example we pass App.Reference and others 1:1 to GetRoute.Model - and that's because they're plain value types already. They're easy to construct for the tests and equally simple to pass around.

ForkedFromXXX seems to be the same: it's a value type and it really seems to be packaging all the same data. So it can just be the same type - we wouldn't need to transform it unless there's some other reason for it.

One such reason might be some computation that's happening. I can't think of a great example right now but we'd want to avoid having GetRoute.Model do a lot other than just package up data for a page.

Does that make sense?

I can take a closer look myself to see if the types can be collapsed if you prefer, just let me know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference FrokedFromInfo has from ForkedFromResult is this

      var url: String {
            switch self {
            case .fromSPI(_, let originalOwner, _, let originalRepo, _):
                return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).relativeURL()
            case .fromGitHub(let url):
                return url
            }
        }

        var ownerURL: String? {
            switch self {
            case .fromSPI(_, let owner, _, _, _):
                return SiteURL.author(.value(owner)).relativeURL()
            case .fromGitHub:
                return nil
            }
        }

it has some computed variables which come in handy in the view itself - so I think they are totally mergeable. But where should the resulting ForkedFrom be declared? And would be it be okay to make the fetchForkedFromResult method inside API+PackageController+GetRoute return the new ForkedFrom type and then pass it down to the Model through the initializer.

Could you please take a closer look when you find the time and propose the change, since I am not entirely sure what's the correct approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened a PR on top of your branch with the changes here: #3388

That should sort it all out!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot! The comments on the PR were really helpful! I've merged it into the branch.

case fromSPI(repository: String, owner: String, ownerName: String, packageName: String)
case fromGitHub(url: String)

static func query(on database: Database, packageId: Package.Id) async throws -> ForkedFromResult? {
let model = try await Joined3<Package, Repository, Version>.query(on: database, packageId: packageId).first()

guard let repoName = model?.repository.name,
let ownerName = model?.repository.ownerName,
let owner = model?.repository.owner,
let packageName = model?.version.packageName else {
return nil
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daveverwer @finestructure not entirely sure about this one. I saw that in some cases if the packageName is null we then use the repo name instead of it. Should we go with the same approach here?

Copy link
Member

@daveverwer daveverwer Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, falling back to repositori name is the approach we usually use. I'll review this properly shortly.

}

return ForkedFromResult.fromSPI(
repository: repoName,
owner: owner,
ownerName: ownerName,
packageName: packageName
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ extension API.PackageController.GetRoute {
var releaseReferences: [App.Version.Kind: App.Reference]
var fundingLinks: [FundingLink]
var swift6Readiness: Swift6Readiness?
var forkedFromInfo: ForkedFromInfo?

internal init(packageId: Package.Id,
repositoryOwner: String,
Expand Down Expand Up @@ -81,7 +82,8 @@ extension API.PackageController.GetRoute {
releaseReference: App.Reference?,
preReleaseReference: App.Reference?,
fundingLinks: [FundingLink] = [],
swift6Readiness: Swift6Readiness?
swift6Readiness: Swift6Readiness?,
forkedFromResult: API.PackageController.ForkedFromResult?
) {
self.packageId = packageId
self.repositoryOwner = repositoryOwner
Expand Down Expand Up @@ -123,6 +125,22 @@ extension API.PackageController.GetRoute {
}()
self.fundingLinks = fundingLinks
self.swift6Readiness = swift6Readiness
if let forkedFromResult {
switch forkedFromResult {
case .fromSPI(let repo, let owner, let ownerName, let packageName):
self.forkedFromInfo = ForkedFromInfo.fromSPI(
packageName: self.title,
originalOwner: owner,
originalOwnerName: ownerName,
originalRepo: repo,
originalPackageName: packageName
)
case .fromGitHub(let url):
self.forkedFromInfo = ForkedFromInfo.fromGitHub(url: url)
}
} else {
self.forkedFromInfo = nil
}
}

init?(result: API.PackageController.PackageResult,
Expand All @@ -132,7 +150,8 @@ extension API.PackageController.GetRoute {
swiftVersionBuildInfo: BuildInfo<CompatibilityMatrix.SwiftVersionCompatibility>?,
platformBuildInfo: BuildInfo<CompatibilityMatrix.PlatformCompatibility>?,
weightedKeywords: [WeightedKeyword] = [],
swift6Readiness: Swift6Readiness?) {
swift6Readiness: Swift6Readiness?,
forkedFromResult: API.PackageController.ForkedFromResult?) {
// we consider certain attributes as essential and return nil (raising .notFound)
let repository = result.repository
guard
Expand Down Expand Up @@ -177,7 +196,8 @@ extension API.PackageController.GetRoute {
releaseReference: result.releaseVersion?.reference,
preReleaseReference: result.preReleaseVersion?.reference,
fundingLinks: result.repository.fundingLinks,
swift6Readiness: swift6Readiness
swift6Readiness: swift6Readiness,
forkedFromResult: forkedFromResult
)

}
Expand Down Expand Up @@ -348,6 +368,26 @@ extension API.PackageController.GetRoute.Model {
}
}
}

enum ForkedFromInfo: Codable, Equatable {
case fromSPI(
packageName: String,
originalOwner: String,
originalOwnerName: String,
originalRepo: String,
originalPackageName: String
)
case fromGitHub(url: String)

var url: String {
switch self {
case .fromSPI(_, let originalOwner, _, let originalRepo, _):
return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).absoluteURL()
case .fromGitHub(let url):
return url
}
}
}

}

Expand Down
20 changes: 19 additions & 1 deletion Sources/App/Controllers/API/API+PackageController+GetRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ extension API.PackageController {
let packageResult = try await PackageResult.query(on: database,
owner: owner,
repository: repository)

let forkedFromResult = try? await self.fetchForkedFromResult(on: database,
repository: packageResult.repository)

async let weightedKeywords = WeightedKeyword.query(
on: database, keywords: packageResult.repository.keywords
)
Expand All @@ -55,7 +59,8 @@ extension API.PackageController {
swiftVersionBuildInfo: buildInfo.swiftVersion,
platformBuildInfo: buildInfo.platform,
weightedKeywords: weightedKeywords,
swift6Readiness: buildInfo.swift6Readiness
swift6Readiness: buildInfo.swift6Readiness,
forkedFromResult: forkedFromResult
),
let schema = API.PackageSchema(result: packageResult)
else {
Expand All @@ -64,6 +69,19 @@ extension API.PackageController {

return (model, schema)
}

static func fetchForkedFromResult(on database: Database, repository: Repository) async throws -> ForkedFromResult? {
if let forkedFrom = repository.forkedFrom {
switch forkedFrom {
case .parentId(let id):
let info = try await ForkedFromResult.query(on: database, packageId: id)
return info
case .parentURL(let url):
return .fromGitHub(url: url)
}
}
return nil
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/App/Controllers/API/Types+WithExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ extension API.PackageController.GetRoute.Model: WithExample {
defaultBranchReference: .branch("main"),
releaseReference: .tag(1, 2, 3, "1.2.3"),
preReleaseReference: nil,
swift6Readiness: nil)
swift6Readiness: nil,
forkedFromResult: nil)
}
}

Expand Down
8 changes: 8 additions & 0 deletions Sources/App/Core/Query+Support/Joined3+Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,12 @@ extension Joined3 where M == Package, R1 == Repository, R2 == Version {
.filter(Repository.self, \.$owner, .custom("ilike"), owner)
.filter(Repository.self, \.$name, .custom("ilike"), repository)
}

static func query(on database: Database, packageId: Package.Id) -> JoinedQueryBuilder<Self> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take @finestructure's view on this as he's the keeper of the Joined classes, but I wonder if this might need a more descriptive name. It makes sense to query with the packageID, but the fact that this is filtering to the default branch version too is a little hidden from whoever is calling this function.

Alternatively, we could move the filter to outside this query and let the calling code add that filter.

Don't make any changes based on what I say to this, though as I know @finestructure will have a view on it and we should go with his recommendation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Dave, we shouldn't be filtering inside the method. If you look above, there's a version related overload and I'd do the same here. Keep the parameter, that way it's obvious at the call site that versions are filtered:

Suggested change
static func query(on database: Database, packageId: Package.Id) -> JoinedQueryBuilder<Self> {
static func query(on database: Database,
packageId: Package.Id,
version: Version.Kind) -> JoinedQueryBuilder<Self> {
query(on: database, version: version)
.filter(Package.self, \Package.$id == packageId)
}

Call site:

            let model = try await Joined3<Package, Repository, Version>
                .query(on: database, packageId: packageId, version: .defaultBranch)
                .first()

query(on: database,
join: \Package.$id == \Repository.$package.$id, method: .inner,
join: \Version.$package.$id == \Package.$id, method: .inner)
.filter(Version.self, \Version.$latest == .defaultBranch)
.filter(Package.self, \Package.$id == packageId)
}
}
Loading
Loading