mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Editor | Enhancement | Calculate decoration data for callout block and its children (#2383)
This commit is contained in:
parent
5fe33a8d72
commit
851e5c82bf
5 changed files with 339 additions and 17 deletions
|
@ -152,6 +152,12 @@ sealed class BlockView : ViewType {
|
|||
object Middle: Highlight()
|
||||
object End : Highlight()
|
||||
}
|
||||
sealed class Callout : Style() {
|
||||
object Start : Callout()
|
||||
object Middle: Callout()
|
||||
object End : Callout()
|
||||
object Full : Callout()
|
||||
}
|
||||
sealed class Header: Style() {
|
||||
object H1 : Header()
|
||||
object H2 : Header()
|
||||
|
|
|
@ -5,7 +5,6 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.presentation.BuildConfig
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.model.Indent
|
||||
|
||||
/**
|
||||
* Mapping indent level to gathered information about parents (ancestors) styles during document graph traversal.
|
||||
|
@ -28,17 +27,18 @@ data class DecorationData(
|
|||
* [end] value is calculated while graph traversal and depends on the depth / indent level.
|
||||
*/
|
||||
data class Highlight(val start: Id, val end: Id) : Style()
|
||||
/**
|
||||
* @property [start] id of the starting block for callout style
|
||||
* @property [end] id of the last block for callout style.
|
||||
* [end] value is calculated while graph traversal and depends on the depth / indent level.
|
||||
*/
|
||||
data class Callout(val start: Id, val end: Id) : Style()
|
||||
sealed class Header : Style() {
|
||||
object H1 : Header()
|
||||
object H2 : Header()
|
||||
object H3 : Header()
|
||||
}
|
||||
object Card : Style()
|
||||
/**
|
||||
* Add style for [Block.Content.Text.Style.CALLOUT] when it is supported by [DefaultBlockViewRenderer]
|
||||
*/
|
||||
//
|
||||
//data class Callout(val start: Id, val end: Id) : Style()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,20 @@ fun buildNestedDecorationData(
|
|||
add(holder)
|
||||
}
|
||||
}
|
||||
is DecorationData.Style.Callout -> {
|
||||
if (block.id == style.end) {
|
||||
// Block is a closing block for a callout style
|
||||
if (block.children.isEmpty()) {
|
||||
add(holder)
|
||||
} else {
|
||||
// But it has children, so its last child is supposed to be a closing block.
|
||||
add(holder.copy(style = style.copy(end = block.children.last())))
|
||||
}
|
||||
} else {
|
||||
// It is not a closing block for callout, no need to normalize it.
|
||||
add(holder)
|
||||
}
|
||||
}
|
||||
else -> add(holder)
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +109,29 @@ fun NestedDecorationData.toBlockViewDecoration(block: Block): List<BlockView.Dec
|
|||
)
|
||||
}
|
||||
}
|
||||
is DecorationData.Style.Callout -> {
|
||||
if (style.start == style.end) {
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.Full,
|
||||
background = holder.background
|
||||
)
|
||||
} else if (style.start == block.id) {
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.Start,
|
||||
background = holder.background
|
||||
)
|
||||
} else if (style.end == block.id) {
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.End,
|
||||
background = holder.background
|
||||
)
|
||||
} else {
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.Middle,
|
||||
background = holder.background
|
||||
)
|
||||
}
|
||||
}
|
||||
is DecorationData.Style.Header.H1 -> {
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Header.H1,
|
||||
|
@ -148,6 +185,19 @@ fun normalizeNestedDecorationData(
|
|||
holder
|
||||
}
|
||||
}
|
||||
is DecorationData.Style.Callout -> {
|
||||
if (block.id == style.end) {
|
||||
if (block.children.isEmpty()) {
|
||||
holder
|
||||
} else {
|
||||
holder.copy(
|
||||
style = style.copy(end = block.children.last())
|
||||
)
|
||||
}
|
||||
} else {
|
||||
holder
|
||||
}
|
||||
}
|
||||
else -> holder
|
||||
}
|
||||
}
|
||||
|
|
|
@ -518,8 +518,17 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
}
|
||||
Content.Text.Style.CALLOUT -> {
|
||||
mCounter = 0
|
||||
// TODO support nesting background
|
||||
val normalized = emptyList<DecorationData>()
|
||||
val blockDecorationScheme: NestedDecorationData = buildNestedDecorationData(
|
||||
block = block,
|
||||
parentScheme = parentScheme,
|
||||
currentDecoration = DecorationData(
|
||||
style = DecorationData.Style.Callout(
|
||||
start = block.id,
|
||||
end = block.children.lastOrNull() ?: block.id
|
||||
),
|
||||
background = block.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
result.add(
|
||||
callout(
|
||||
mode = mode,
|
||||
|
@ -529,7 +538,7 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
indent = indent,
|
||||
details = details,
|
||||
selection = selection,
|
||||
scheme = normalized
|
||||
scheme = blockDecorationScheme
|
||||
)
|
||||
)
|
||||
if (block.children.isNotEmpty()) {
|
||||
|
@ -546,8 +555,7 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
selection = selection,
|
||||
objectTypes = objectTypes,
|
||||
onRenderFlag = onRenderFlag,
|
||||
// TODO support nesting background
|
||||
parentScheme = emptyList()
|
||||
parentScheme = blockDecorationScheme
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1033,8 +1041,6 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
details = details,
|
||||
marks = marks
|
||||
)
|
||||
// TODO support nesting
|
||||
val current = emptyList<BlockView.Decoration>()
|
||||
val iconImage = content.iconImage
|
||||
val iconEmoji = content.iconEmoji
|
||||
val icon = when {
|
||||
|
@ -1057,7 +1063,7 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
block = block,
|
||||
selection = selection
|
||||
),
|
||||
decorations = scheme.toBlockViewDecoration(block) + current,
|
||||
decorations = scheme.toBlockViewDecoration(block),
|
||||
icon = icon
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.ObjectType
|
|||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.SmartBlockType
|
||||
import com.anytypeio.anytype.core_models.StubBookmark
|
||||
import com.anytypeio.anytype.core_models.StubCallout
|
||||
import com.anytypeio.anytype.core_models.StubParagraph
|
||||
import com.anytypeio.anytype.core_models.StubSmartBlock
|
||||
import com.anytypeio.anytype.core_models.ext.asMap
|
||||
|
@ -29,7 +30,6 @@ import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.Alignment
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.render.DecorationData
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.render.NestedDecorationData
|
||||
import com.anytypeio.anytype.presentation.editor.render.parseThemeBackgroundColor
|
||||
|
@ -4451,6 +4451,10 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
// Quote nesting
|
||||
|
||||
/**
|
||||
* Q with background
|
||||
* ...P with background
|
||||
*/
|
||||
@Test
|
||||
fun `should return blocks with expected decoration - when a quote contains a paragraph`() {
|
||||
val child = Block(
|
||||
|
@ -5388,4 +5392,258 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
//region Callout nesting
|
||||
|
||||
/**
|
||||
* C with background. No children.
|
||||
*/
|
||||
|
||||
@Test
|
||||
fun `should return blocks with expected decoration - when a callout without children has background`() {
|
||||
|
||||
val callout = StubCallout(backgroundColor = ThemeColor.BLUE.code)
|
||||
val page = StubSmartBlock(children = listOf(callout.id))
|
||||
val details = mapOf(page.id to Block.Fields.empty())
|
||||
val blocks = listOf(page, callout)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
wrapper = BlockViewRenderWrapper(
|
||||
blocks = map,
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
focus = Editor.Focus.empty(),
|
||||
indent = 0,
|
||||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Text.Callout(
|
||||
indent = 0,
|
||||
isFocused = false,
|
||||
id = callout.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = callout.backgroundColor,
|
||||
color = callout.content<Block.Content.Text>().color,
|
||||
text = callout.content<Block.Content.Text>().text,
|
||||
decorations = if (BuildConfig.NESTED_DECORATION_ENABLED) {
|
||||
listOf(
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.Full,
|
||||
background = callout.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
icon = ObjectIcon.Basic.Emoji("💡")
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* C with background
|
||||
* ...P with background
|
||||
*/
|
||||
@Test
|
||||
fun `should return blocks with expected decoration - when a callout contains a paragraph`() {
|
||||
|
||||
val child = StubParagraph(backgroundColor = ThemeColor.ORANGE.code)
|
||||
|
||||
val callout = StubCallout(
|
||||
children = listOf(child.id),
|
||||
backgroundColor = ThemeColor.BLUE.code
|
||||
)
|
||||
|
||||
val page = StubSmartBlock(children = listOf(callout.id))
|
||||
|
||||
val details = mapOf(page.id to Block.Fields.empty())
|
||||
|
||||
val blocks = listOf(page, callout, child)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
wrapper = BlockViewRenderWrapper(
|
||||
blocks = map,
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
focus = Editor.Focus.empty(),
|
||||
indent = 0,
|
||||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Text.Callout(
|
||||
indent = 0,
|
||||
isFocused = false,
|
||||
id = callout.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = callout.backgroundColor,
|
||||
color = callout.content<Block.Content.Text>().color,
|
||||
text = callout.content<Block.Content.Text>().text,
|
||||
decorations = if (BuildConfig.NESTED_DECORATION_ENABLED) {
|
||||
listOf(
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.Start,
|
||||
background = callout.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
icon = ObjectIcon.Basic.Emoji("💡")
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
indent = 1,
|
||||
isFocused = false,
|
||||
id = child.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = child.backgroundColor,
|
||||
color = child.content<Block.Content.Text>().color,
|
||||
text = child.content<Block.Content.Text>().text,
|
||||
decorations = if (BuildConfig.NESTED_DECORATION_ENABLED) {
|
||||
listOf(
|
||||
BlockView.Decoration(
|
||||
background = callout.parseThemeBackgroundColor(),
|
||||
style = BlockView.Decoration.Style.Callout.End
|
||||
),
|
||||
BlockView.Decoration(
|
||||
background = child.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* C with background
|
||||
* ...P with background
|
||||
* ...P with background
|
||||
*/
|
||||
@Test
|
||||
fun `should return blocks with expected decoration - when a callout with background contains two paragraphs, each paragraph has its own background`() {
|
||||
|
||||
val child1 = StubParagraph(backgroundColor = ThemeColor.ORANGE.code)
|
||||
val child2 = StubParagraph(backgroundColor = ThemeColor.ORANGE.code)
|
||||
|
||||
val callout = StubCallout(
|
||||
children = listOf(child1.id, child2.id),
|
||||
backgroundColor = ThemeColor.BLUE.code
|
||||
)
|
||||
|
||||
val page = StubSmartBlock(children = listOf(callout.id))
|
||||
|
||||
val details = mapOf(page.id to Block.Fields.empty())
|
||||
|
||||
val blocks = listOf(page, callout, child1, child2)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
wrapper = BlockViewRenderWrapper(
|
||||
blocks = map,
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
focus = Editor.Focus.empty(),
|
||||
indent = 0,
|
||||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Text.Callout(
|
||||
indent = 0,
|
||||
isFocused = false,
|
||||
id = callout.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = callout.backgroundColor,
|
||||
color = callout.content<Block.Content.Text>().color,
|
||||
text = callout.content<Block.Content.Text>().text,
|
||||
decorations = if (BuildConfig.NESTED_DECORATION_ENABLED) {
|
||||
listOf(
|
||||
BlockView.Decoration(
|
||||
style = BlockView.Decoration.Style.Callout.Start,
|
||||
background = callout.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
icon = ObjectIcon.Basic.Emoji("💡")
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
indent = 1,
|
||||
isFocused = false,
|
||||
id = child1.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = child1.backgroundColor,
|
||||
color = child1.content<Block.Content.Text>().color,
|
||||
text = child1.content<Block.Content.Text>().text,
|
||||
decorations = if (BuildConfig.NESTED_DECORATION_ENABLED) {
|
||||
listOf(
|
||||
BlockView.Decoration(
|
||||
background = callout.parseThemeBackgroundColor(),
|
||||
style = BlockView.Decoration.Style.Callout.Middle
|
||||
),
|
||||
BlockView.Decoration(
|
||||
background = child1.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
indent = 1,
|
||||
isFocused = false,
|
||||
id = child2.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = child2.backgroundColor,
|
||||
color = child2.content<Block.Content.Text>().color,
|
||||
text = child2.content<Block.Content.Text>().text,
|
||||
decorations = if (BuildConfig.NESTED_DECORATION_ENABLED) {
|
||||
listOf(
|
||||
BlockView.Decoration(
|
||||
background = callout.parseThemeBackgroundColor(),
|
||||
style = BlockView.Decoration.Style.Callout.End
|
||||
),
|
||||
BlockView.Decoration(
|
||||
background = child2.parseThemeBackgroundColor()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
|
@ -148,7 +148,8 @@ fun StubQuote(
|
|||
fun StubCallout(
|
||||
text: String = MockDataFactory.randomString(),
|
||||
children: List<Id> = emptyList(),
|
||||
marks: List<Block.Content.Text.Mark> = emptyList()
|
||||
marks: List<Block.Content.Text.Mark> = emptyList(),
|
||||
backgroundColor: String? = null
|
||||
): Block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = StubTextContent(
|
||||
|
@ -157,7 +158,8 @@ fun StubCallout(
|
|||
marks = marks
|
||||
),
|
||||
children = children,
|
||||
fields = Block.Fields.empty()
|
||||
fields = Block.Fields.empty(),
|
||||
backgroundColor = backgroundColor
|
||||
)
|
||||
|
||||
fun StubRelation(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue